Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions exercises/01.classes/01.problem.class-basics/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,37 @@ system.

🐨 Open <InlineFile file="index.ts" /> and:

1. Create a `Product` class with fields and methods
2. Create a `ShoppingCart` class that holds products
3. Instantiate and use these classes
1. Create and export a `Product` class with:
- Public fields: `name` (`string`), `price` (`number`)
- A constructor that sets both from parameters `(name, price)`
- A `getDescription()` method that returns a string in this exact form:
`Product: {name} - ${price}` (include the dollar sign before the price)
2. Create and export a `ShoppingCart` class with:
- A public `items` field typed as `Array<Product>`
- New carts start with `items` as an empty array
- `addItem(product: Product)` that appends the product to `items`
- `getTotal()` that returns the sum of every item's `price`
3. Export both classes: `export { Product, ShoppingCart }`

## Fixtures and success criteria

With these fixtures:

```ts
const laptop = new Product('Laptop', 999.99)
const mouse = new Product('Mouse', 29.99)
const cart = new ShoppingCart()
cart.addItem(laptop)
cart.addItem(mouse)
```

you should observe:

- `laptop.name === 'Laptop'` and `laptop.price === 999.99`
- `laptop.getDescription() === 'Product: Laptop - $999.99'`
- A fresh cart starts with `items.length === 0`
- After the two `addItem` calls above, `items` has length `2` in insertion order
- `cart.getTotal() === 1029.98`

💰 A class includes fields, a constructor, and methods.

Expand Down
27 changes: 15 additions & 12 deletions exercises/01.classes/01.problem.class-basics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@

// 🐨 Create a Product class with:
// - Fields: name (string), price (number)
// - Constructor to initialize both
// - Method: getDescription() returns "Product: {name} - ${price}"
// - Constructor(name, price) that initializes both
// - Method: getDescription() returns exactly:
// "Product: {name} - ${price}"
// Example: new Product('Laptop', 999.99).getDescription()
// → "Product: Laptop - $999.99"

// Test Product
// Optional smoke test:
// const laptop = new Product('Laptop', 999.99)
// const mouse = new Product('Mouse', 29.99)
// console.log(laptop)
// console.log(mouse)
// console.log(laptop.getDescription())
// console.log(mouse.getDescription())

// 🐨 Create a ShoppingCart class with:
// - Field: items (Array<Product>)
// - Constructor to initialize empty items array
// - Method: addItem(product: Product) adds to items
// - Method: getTotal() returns sum of all product prices
// - Field: items (Array<Product>), starts empty
// - Method: addItem(product: Product) appends to items
// - Method: getTotal() returns the sum of all item prices
// Example: laptop (999.99) + mouse (29.99) → 1029.98

// Test ShoppingCart
// Optional smoke test:
// const cart = new ShoppingCart()
// cart.addItem(laptop)
// cart.addItem(mouse)
// console.log(cart.getTotal())
// console.log(cart)
// console.log(cart.getTotal()) // 1029.98

// 🐨 Export both classes
// export { Product, ShoppingCart }
4 changes: 2 additions & 2 deletions exercises/01.classes/01.solution.class-basics/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ await test('Product getDescription should return formatted string', () => {
assert.strictEqual(
sampleLaptop.getDescription(),
'Product: Laptop - $999.99',
'🚨 getDescription() should return "Product: Laptop - $999.99" - check your method implementation and formatting',
'🚨 getDescription() should return "Product: Laptop - $999.99"',
)
})

Expand Down Expand Up @@ -89,6 +89,6 @@ await test('ShoppingCart getTotal should calculate sum of all item prices', () =
assert.strictEqual(
sampleCart.getTotal(),
1029.98,
'🚨 getTotal() should return 1029.98 (sum of 999.99 + 29.99) - check your method implementation and price calculation',
'🚨 getTotal() should return 1029.98 for Laptop (999.99) + Mouse (29.99)',
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,48 @@ const newCar = new Car('Toyota', 'Camry') // year defaults to 2024
const oldCar = new Car('Ford', 'Mustang', 1965) // year explicitly set
```

🐨 Open <InlineFile file="index.ts" /> and:
🐨 Open <InlineFile file="index.ts" /> and create/export three classes:

1. Use `#` prefix to create private fields
2. Use default parameter values in constructors
3. Create classes that encapsulate their internal state
### `User`

- Public fields: `name`, `email`, `role` (all `string`)
- Constructor `(name, email, role = 'user')`
- Omitting `role` must leave it as `'user'`

### `BankAccount`

- Public field: `accountNumber` (`string`)
- A private balance field (use `#` so it is inaccessible outside the class)
- New accounts start with balance `0`
- `deposit(amount: number)` increases the balance by `amount` (it accumulates)
- `getBalance()` returns the current balance

### `Config`

- Public fields: `host` (`string`), `port` (`number`), `debug` (`boolean`)
- Constructor defaults: `host = 'localhost'`, `port = 3000`, `debug = false`
- `new Config()` must use all three defaults; custom args override them

Export all three: `export { User, BankAccount, Config }`

## Fixtures and success criteria

```ts
const user = new User('Alice', 'alice@example.com')
const admin = new User('Bob', 'bob@example.com', 'admin')
const account = new BankAccount('12345')
account.deposit(100)
account.deposit(50)
const config = new Config()
const customConfig = new Config('example.com', 8080, true)
```

- `user.role === 'user'` and `admin.role === 'admin'`
- `account.accountNumber === '12345'`
- A fresh account has `getBalance() === 0`
- After the deposits above, `getBalance() === 150`
- Default config: `host === 'localhost'`, `port === 3000`, `debug === false`
- Custom config: `'example.com'`, `8080`, `true`

💰 Use `#` to declare a private field—it's truly private, not just a convention.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
// Private Fields and Defaults

// 🐨 Create a User class with these fields:
// 🐨 Create a User class with:
// - name: string
// - email: string
// - role: string (default: 'user')
// - role: string (default: 'user' when omitted)
// Initialize fields in the constructor

// Test User
// Optional smoke test:
// const user = new User('Alice', 'alice@example.com')
// const admin = new User('Bob', 'bob@example.com', 'admin')
// console.log(user)
// console.log(admin)
// console.log(user.role) // 'user'
// console.log(admin.role) // 'admin'

// 🐨 Create a BankAccount class with:
// - accountNumber: string
// - #balance: number (private field, default: 0)
// - Method: deposit(amount: number)
// - Method: getBalance() returns the balance
// 💰 #balance is a private field - can only be accessed inside the class

// Test BankAccount
// - a private balance field using # (starts at 0)
// - deposit(amount: number) increases the balance by amount
// - getBalance() returns the current balance
// 💰 Private fields can only be read/written inside the class
//
// Example:
// const account = new BankAccount('12345')
// account.getBalance() // 0
// account.deposit(100)
// console.log(account)
// console.log(account.getBalance())

// 🐨 Create a Config class with optional default values:
// - host: string (default: 'localhost')
// - port: number (default: 3000)
// - debug: boolean (default: false)
// account.deposit(50)
// account.getBalance() // 150

// Test Config
// const config = new Config()
// const customConfig = new Config('example.com', 8080, true)
// console.log(config)
// console.log(customConfig)
// 🐨 Create a Config class with constructor defaults:
// - host: string = 'localhost'
// - port: number = 3000
// - debug: boolean = false
//
// Example:
// new Config() → localhost / 3000 / false
// new Config('example.com', 8080, true) → those custom values

// 🐨 Export all three classes
// export { User, BankAccount, Config }
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ await test('BankAccount deposit should increase balance', () => {
assert.strictEqual(
balanceAfterFirstDeposit,
100,
'🚨 After depositing 100, getBalance() should return 100 - check your deposit method implementation',
'🚨 After deposit(100), getBalance() should return 100',
)
assert.strictEqual(
sampleAccount.getBalance(),
150,
'🚨 After depositing another 50, getBalance() should return 150 - check your deposit method accumulates correctly',
'🚨 After deposit(100) then deposit(50), getBalance() should return 150 (deposits accumulate)',
)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,33 @@ interfaces to ensure all payment methods follow the same contract.

🐨 Open <InlineFile file="index.ts" /> and:

1. Create a `PaymentMethod` interface with a `pay(amount: number)` method
2. Create a `CreditCard` class that implements `PaymentMethod`
3. Create a `PayPal` class that implements `PaymentMethod`
4. Each class should have its own implementation of `pay()`
1. Create a `PaymentMethod` interface with:
- `pay(amount: number): string`
2. Create and export a `CreditCard` class that `implements PaymentMethod`:
- Public field: `cardNumber` (`string`)
- Constructor `(cardNumber: string)`
- `pay(amount)` returns exactly:
`Paid $${amount} with credit card ${cardNumber}`
3. Create and export a `PayPal` class that `implements PaymentMethod`:
- Public field: `email` (`string`)
- Constructor `(email: string)`
- `pay(amount)` returns exactly:
`Paid $${amount} with PayPal ${email}`
4. Export the classes: `export { CreditCard, PayPal }`
(`PaymentMethod` itself does not need to be exported)

## Fixtures and success criteria

```ts
const creditCard = new CreditCard('1234-5678-9012-3456')
const paypal = new PayPal('user@example.com')
```

- `creditCard.cardNumber === '1234-5678-9012-3456'`
- `creditCard.pay(100) === 'Paid $100 with credit card 1234-5678-9012-3456'`
- `paypal.email === 'user@example.com'`
- `paypal.pay(50) === 'Paid $50 with PayPal user@example.com'`
- Both instances expose a callable `pay` method

💰 Classes that implement an interface must define all its methods.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
// Implementing Interfaces

// 🐨 Create a PaymentMethod interface with:
// - Method: pay(amount: number) returns string
// - pay(amount: number): string

// 🐨 Create a CreditCard class that implements PaymentMethod:
// - Field: cardNumber (string)
// - Constructor that takes cardNumber
// - Implement pay(amount: number) returns "Paid $${amount} with credit card ${cardNumber}"
// - Constructor(cardNumber)
// - pay(amount) returns exactly:
// "Paid $${amount} with credit card ${cardNumber}"
// Example: new CreditCard('1234-5678-9012-3456').pay(100)
// → "Paid $100 with credit card 1234-5678-9012-3456"

// Test CreditCard
// Optional smoke test:
// const creditCard = new CreditCard('1234-5678-9012-3456')
// console.log(creditCard.pay(100))
// console.log(creditCard)

// 🐨 Create a PayPal class that implements PaymentMethod:
// - Field: email (string)
// - Constructor that takes email
// - Implement pay(amount: number) returns "Paid $${amount} with PayPal ${email}"
// - Constructor(email)
// - pay(amount) returns exactly:
// "Paid $${amount} with PayPal ${email}"
// Example: new PayPal('user@example.com').pay(50)
// → "Paid $50 with PayPal user@example.com"

// Test PayPal
// Optional smoke test:
// const paypal = new PayPal('user@example.com')
// console.log(paypal.pay(50))
// console.log(paypal)

// 🐨 Export the classes (PaymentMethod does not need to be exported)
// export { CreditCard, PayPal }
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ await test('CreditCard should implement PaymentMethod interface', () => {
assert.strictEqual(
creditCard.pay(100),
'Paid $100 with credit card 1234-5678-9012-3456',
'🚨 pay() should return "Paid $100 with credit card 1234-5678-9012-3456" - check your PaymentMethod interface implementation',
'🚨 CreditCard.pay(100) should return "Paid $100 with credit card 1234-5678-9012-3456"',
)
})

Expand All @@ -40,7 +40,7 @@ await test('PayPal should implement PaymentMethod interface', () => {
assert.strictEqual(
paypal.pay(50),
'Paid $50 with PayPal user@example.com',
'🚨 pay() should return "Paid $50 with PayPal user@example.com" - check your PaymentMethod interface implementation',
'🚨 PayPal.pay(50) should return "Paid $50 with PayPal user@example.com"',
)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,30 @@
This is called "programming to abstractions"—using interfaces instead of
concrete classes.

The `PaymentMethod` interface plus `CreditCard` and `PayPal` are already
defined in <InlineFile file="index.ts" />. You only need to add
`processPayment`.

🐨 Open <InlineFile file="index.ts" /> and:

1. Create a `processPayment` function that accepts a `PaymentMethod` parameter
2. The function should call `method.pay(amount)` and return the result
3. Test it with both `CreditCard` and `PayPal` instances
1. Create a `processPayment` function with this contract:
- Parameters: `method: PaymentMethod`, `amount: number`
- Return type: `string`
- Behavior: return the payment result string for that method and amount
2. Type the first parameter as `PaymentMethod` (not `CreditCard` or `PayPal`)
3. Export it with the existing classes:
`export { CreditCard, PayPal, processPayment }`

## Fixtures and success criteria

```ts
const creditCard = new CreditCard('1234-5678-9012-3456')
const paypal = new PayPal('user@example.com')
```

- `processPayment(creditCard, 100) === 'Paid $100 with credit card 1234-5678-9012-3456'`
- `processPayment(paypal, 50) === 'Paid $50 with PayPal user@example.com'`
- The same function works for both implementations without special-casing

💰 Use the interface type (`PaymentMethod`) for the parameter instead of a
concrete class type. This way, the function works with any class that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@ class PayPal implements PaymentMethod {
}

// 🐨 Create a processPayment function:
// - Parameters: method (PaymentMethod), amount (number)
// - Returns: string
// - Calls method.pay(amount)

// Test processPayment
// - Parameters: method: PaymentMethod, amount: number
// - Returns: the payment result string for that method and amount
// - Type the first parameter as PaymentMethod, not a concrete class
//
// Expected results (using the classes above):
// processPayment(new CreditCard('1234-5678-9012-3456'), 100)
// → "Paid $100 with credit card 1234-5678-9012-3456"
// processPayment(new PayPal('user@example.com'), 50)
// → "Paid $50 with PayPal user@example.com"

// Optional smoke test:
// const creditCard = new CreditCard('1234-5678-9012-3456')
// const paypal = new PayPal('user@example.com')
// console.log(processPayment(creditCard, 100))
Expand Down
Loading
Loading