Skip to content

Commit 420b4c1

Browse files
Clarify exercise requirements (#5)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent af732d2 commit 420b4c1

29 files changed

Lines changed: 648 additions & 228 deletions

File tree

exercises/01.classes/01.problem.class-basics/README.mdx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,37 @@ system.
77

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

10-
1. Create a `Product` class with fields and methods
11-
2. Create a `ShoppingCart` class that holds products
12-
3. Instantiate and use these classes
10+
1. Create and export a `Product` class with:
11+
- Public fields: `name` (`string`), `price` (`number`)
12+
- A constructor that sets both from parameters `(name, price)`
13+
- A `getDescription()` method that returns a string in this exact form:
14+
`Product: {name} - ${price}` (include the dollar sign before the price)
15+
2. Create and export a `ShoppingCart` class with:
16+
- A public `items` field typed as `Array<Product>`
17+
- New carts start with `items` as an empty array
18+
- `addItem(product: Product)` that appends the product to `items`
19+
- `getTotal()` that returns the sum of every item's `price`
20+
3. Export both classes: `export { Product, ShoppingCart }`
21+
22+
## Fixtures and success criteria
23+
24+
With these fixtures:
25+
26+
```ts
27+
const laptop = new Product('Laptop', 999.99)
28+
const mouse = new Product('Mouse', 29.99)
29+
const cart = new ShoppingCart()
30+
cart.addItem(laptop)
31+
cart.addItem(mouse)
32+
```
33+
34+
you should observe:
35+
36+
- `laptop.name === 'Laptop'` and `laptop.price === 999.99`
37+
- `laptop.getDescription() === 'Product: Laptop - $999.99'`
38+
- A fresh cart starts with `items.length === 0`
39+
- After the two `addItem` calls above, `items` has length `2` in insertion order
40+
- `cart.getTotal() === 1029.98`
1341

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

exercises/01.classes/01.problem.class-basics/index.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,29 @@
22

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

8-
// Test Product
11+
// Optional smoke test:
912
// const laptop = new Product('Laptop', 999.99)
1013
// const mouse = new Product('Mouse', 29.99)
11-
// console.log(laptop)
12-
// console.log(mouse)
14+
// console.log(laptop.getDescription())
15+
// console.log(mouse.getDescription())
1316

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

20-
// Test ShoppingCart
23+
// Optional smoke test:
2124
// const cart = new ShoppingCart()
2225
// cart.addItem(laptop)
2326
// cart.addItem(mouse)
24-
// console.log(cart.getTotal())
25-
// console.log(cart)
27+
// console.log(cart.getTotal()) // 1029.98
2628

29+
// 🐨 Export both classes
2730
// export { Product, ShoppingCart }

exercises/01.classes/01.solution.class-basics/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ await test('Product getDescription should return formatted string', () => {
3535
assert.strictEqual(
3636
sampleLaptop.getDescription(),
3737
'Product: Laptop - $999.99',
38-
'🚨 getDescription() should return "Product: Laptop - $999.99" - check your method implementation and formatting',
38+
'🚨 getDescription() should return "Product: Laptop - $999.99"',
3939
)
4040
})
4141

@@ -89,6 +89,6 @@ await test('ShoppingCart getTotal should calculate sum of all item prices', () =
8989
assert.strictEqual(
9090
sampleCart.getTotal(),
9191
1029.98,
92-
'🚨 getTotal() should return 1029.98 (sum of 999.99 + 29.99) - check your method implementation and price calculation',
92+
'🚨 getTotal() should return 1029.98 for Laptop (999.99) + Mouse (29.99)',
9393
)
9494
})

exercises/01.classes/02.problem.private-fields-and-defaults/README.mdx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,48 @@ const newCar = new Car('Toyota', 'Camry') // year defaults to 2024
5353
const oldCar = new Car('Ford', 'Mustang', 1965) // year explicitly set
5454
```
5555

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

58-
1. Use `#` prefix to create private fields
59-
2. Use default parameter values in constructors
60-
3. Create classes that encapsulate their internal state
58+
### `User`
59+
60+
- Public fields: `name`, `email`, `role` (all `string`)
61+
- Constructor `(name, email, role = 'user')`
62+
- Omitting `role` must leave it as `'user'`
63+
64+
### `BankAccount`
65+
66+
- Public field: `accountNumber` (`string`)
67+
- A private balance field (use `#` so it is inaccessible outside the class)
68+
- New accounts start with balance `0`
69+
- `deposit(amount: number)` increases the balance by `amount` (it accumulates)
70+
- `getBalance()` returns the current balance
71+
72+
### `Config`
73+
74+
- Public fields: `host` (`string`), `port` (`number`), `debug` (`boolean`)
75+
- Constructor defaults: `host = 'localhost'`, `port = 3000`, `debug = false`
76+
- `new Config()` must use all three defaults; custom args override them
77+
78+
Export all three: `export { User, BankAccount, Config }`
79+
80+
## Fixtures and success criteria
81+
82+
```ts
83+
const user = new User('Alice', 'alice@example.com')
84+
const admin = new User('Bob', 'bob@example.com', 'admin')
85+
const account = new BankAccount('12345')
86+
account.deposit(100)
87+
account.deposit(50)
88+
const config = new Config()
89+
const customConfig = new Config('example.com', 8080, true)
90+
```
91+
92+
- `user.role === 'user'` and `admin.role === 'admin'`
93+
- `account.accountNumber === '12345'`
94+
- A fresh account has `getBalance() === 0`
95+
- After the deposits above, `getBalance() === 150`
96+
- Default config: `host === 'localhost'`, `port === 3000`, `debug === false`
97+
- Custom config: `'example.com'`, `8080`, `true`
6198

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

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,39 @@
11
// Private Fields and Defaults
22

3-
// 🐨 Create a User class with these fields:
3+
// 🐨 Create a User class with:
44
// - name: string
55
// - email: string
6-
// - role: string (default: 'user')
6+
// - role: string (default: 'user' when omitted)
77
// Initialize fields in the constructor
88

9-
// Test User
9+
// Optional smoke test:
1010
// const user = new User('Alice', 'alice@example.com')
1111
// const admin = new User('Bob', 'bob@example.com', 'admin')
12-
// console.log(user)
13-
// console.log(admin)
12+
// console.log(user.role) // 'user'
13+
// console.log(admin.role) // 'admin'
1414

1515
// 🐨 Create a BankAccount class with:
1616
// - accountNumber: string
17-
// - #balance: number (private field, default: 0)
18-
// - Method: deposit(amount: number)
19-
// - Method: getBalance() returns the balance
20-
// 💰 #balance is a private field - can only be accessed inside the class
21-
22-
// Test BankAccount
17+
// - a private balance field using # (starts at 0)
18+
// - deposit(amount: number) increases the balance by amount
19+
// - getBalance() returns the current balance
20+
// 💰 Private fields can only be read/written inside the class
21+
//
22+
// Example:
2323
// const account = new BankAccount('12345')
24+
// account.getBalance() // 0
2425
// account.deposit(100)
25-
// console.log(account)
26-
// console.log(account.getBalance())
27-
28-
// 🐨 Create a Config class with optional default values:
29-
// - host: string (default: 'localhost')
30-
// - port: number (default: 3000)
31-
// - debug: boolean (default: false)
26+
// account.deposit(50)
27+
// account.getBalance() // 150
3228

33-
// Test Config
34-
// const config = new Config()
35-
// const customConfig = new Config('example.com', 8080, true)
36-
// console.log(config)
37-
// console.log(customConfig)
29+
// 🐨 Create a Config class with constructor defaults:
30+
// - host: string = 'localhost'
31+
// - port: number = 3000
32+
// - debug: boolean = false
33+
//
34+
// Example:
35+
// new Config() → localhost / 3000 / false
36+
// new Config('example.com', 8080, true) → those custom values
3837

38+
// 🐨 Export all three classes
3939
// export { User, BankAccount, Config }

exercises/01.classes/02.solution.private-fields-and-defaults/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@ await test('BankAccount deposit should increase balance', () => {
8484
assert.strictEqual(
8585
balanceAfterFirstDeposit,
8686
100,
87-
'🚨 After depositing 100, getBalance() should return 100 - check your deposit method implementation',
87+
'🚨 After deposit(100), getBalance() should return 100',
8888
)
8989
assert.strictEqual(
9090
sampleAccount.getBalance(),
9191
150,
92-
'🚨 After depositing another 50, getBalance() should return 150 - check your deposit method accumulates correctly',
92+
'🚨 After deposit(100) then deposit(50), getBalance() should return 150 (deposits accumulate)',
9393
)
9494
})
9595

exercises/02.interfaces-and-classes/01.problem.implementing-interfaces/README.mdx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,33 @@ interfaces to ensure all payment methods follow the same contract.
77

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

10-
1. Create a `PaymentMethod` interface with a `pay(amount: number)` method
11-
2. Create a `CreditCard` class that implements `PaymentMethod`
12-
3. Create a `PayPal` class that implements `PaymentMethod`
13-
4. Each class should have its own implementation of `pay()`
10+
1. Create a `PaymentMethod` interface with:
11+
- `pay(amount: number): string`
12+
2. Create and export a `CreditCard` class that `implements PaymentMethod`:
13+
- Public field: `cardNumber` (`string`)
14+
- Constructor `(cardNumber: string)`
15+
- `pay(amount)` returns exactly:
16+
`Paid $${amount} with credit card ${cardNumber}`
17+
3. Create and export a `PayPal` class that `implements PaymentMethod`:
18+
- Public field: `email` (`string`)
19+
- Constructor `(email: string)`
20+
- `pay(amount)` returns exactly:
21+
`Paid $${amount} with PayPal ${email}`
22+
4. Export the classes: `export { CreditCard, PayPal }`
23+
(`PaymentMethod` itself does not need to be exported)
24+
25+
## Fixtures and success criteria
26+
27+
```ts
28+
const creditCard = new CreditCard('1234-5678-9012-3456')
29+
const paypal = new PayPal('user@example.com')
30+
```
31+
32+
- `creditCard.cardNumber === '1234-5678-9012-3456'`
33+
- `creditCard.pay(100) === 'Paid $100 with credit card 1234-5678-9012-3456'`
34+
- `paypal.email === 'user@example.com'`
35+
- `paypal.pay(50) === 'Paid $50 with PayPal user@example.com'`
36+
- Both instances expose a callable `pay` method
1437

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
// Implementing Interfaces
22

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

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

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

1618
// 🐨 Create a PayPal class that implements PaymentMethod:
1719
// - Field: email (string)
18-
// - Constructor that takes email
19-
// - Implement pay(amount: number) returns "Paid $${amount} with PayPal ${email}"
20+
// - Constructor(email)
21+
// - pay(amount) returns exactly:
22+
// "Paid $${amount} with PayPal ${email}"
23+
// Example: new PayPal('user@example.com').pay(50)
24+
// → "Paid $50 with PayPal user@example.com"
2025

21-
// Test PayPal
26+
// Optional smoke test:
2227
// const paypal = new PayPal('user@example.com')
2328
// console.log(paypal.pay(50))
24-
// console.log(paypal)
2529

30+
// 🐨 Export the classes (PaymentMethod does not need to be exported)
2631
// export { CreditCard, PayPal }

exercises/02.interfaces-and-classes/01.solution.implementing-interfaces/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ await test('CreditCard should implement PaymentMethod interface', () => {
2626
assert.strictEqual(
2727
creditCard.pay(100),
2828
'Paid $100 with credit card 1234-5678-9012-3456',
29-
'🚨 pay() should return "Paid $100 with credit card 1234-5678-9012-3456" - check your PaymentMethod interface implementation',
29+
'🚨 CreditCard.pay(100) should return "Paid $100 with credit card 1234-5678-9012-3456"',
3030
)
3131
})
3232

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

exercises/02.interfaces-and-classes/02.problem.programming-to-abstractions/README.mdx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,30 @@
66
This is called "programming to abstractions"—using interfaces instead of
77
concrete classes.
88

9+
The `PaymentMethod` interface plus `CreditCard` and `PayPal` are already
10+
defined in <InlineFile file="index.ts" />. You only need to add
11+
`processPayment`.
12+
913
🐨 Open <InlineFile file="index.ts" /> and:
1014

11-
1. Create a `processPayment` function that accepts a `PaymentMethod` parameter
12-
2. The function should call `method.pay(amount)` and return the result
13-
3. Test it with both `CreditCard` and `PayPal` instances
15+
1. Create a `processPayment` function with this contract:
16+
- Parameters: `method: PaymentMethod`, `amount: number`
17+
- Return type: `string`
18+
- Behavior: return the payment result string for that method and amount
19+
2. Type the first parameter as `PaymentMethod` (not `CreditCard` or `PayPal`)
20+
3. Export it with the existing classes:
21+
`export { CreditCard, PayPal, processPayment }`
22+
23+
## Fixtures and success criteria
24+
25+
```ts
26+
const creditCard = new CreditCard('1234-5678-9012-3456')
27+
const paypal = new PayPal('user@example.com')
28+
```
29+
30+
- `processPayment(creditCard, 100) === 'Paid $100 with credit card 1234-5678-9012-3456'`
31+
- `processPayment(paypal, 50) === 'Paid $50 with PayPal user@example.com'`
32+
- The same function works for both implementations without special-casing
1433

1534
💰 Use the interface type (`PaymentMethod`) for the parameter instead of a
1635
concrete class type. This way, the function works with any class that

0 commit comments

Comments
 (0)