Skip to content

Commit 60364c0

Browse files
committed
04/05: add solution code
1 parent 631933e commit 60364c0

File tree

6 files changed

+110
-1
lines changed

6 files changed

+110
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# Soft assertions
22

33
- Use `expect.soft()` to allow multiple assertions to fail without short-circuiting. Useful when testing compound behaviors, where other assertion failures can help you uncover the reason of the failure.
4+
5+
- Soft assertions are useful when:
6+
- You have multiple expectations toward _the same thing_. Subsequent assertions may reveal more information about the failure.
7+
- You have multiple _independent_ expectations toward _different_ things. Do some action, check that (1) URL is correct; (2) Title is displayed; (3) Button is displayed. None should fail assertions that come after. You don't want to be fixing them one-by-one—that _slows down the iteration loop_.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { User } from './user'
2+
import { UnlimitedPlan } from './plans'
3+
4+
beforeAll(() => {
5+
vi.useFakeTimers()
6+
vi.setSystemTime(new Date('2025-12-02T00:00:00Z'))
7+
})
8+
9+
afterAll(() => {
10+
vi.useRealTimers()
11+
})
12+
13+
test('cancels the user subscription', () => {
14+
const user = new User()
15+
user.subscribe(new UnlimitedPlan())
16+
17+
expect.soft(user.subscription.name).toBe('Unlimited')
18+
expect.soft(user.subscription.kind).toBe('yearly')
19+
expect.soft(user.subscription.state).toBe('active')
20+
expect.soft(user.subscription.endsAt).toBeUndefined()
21+
22+
user.cancelSubscription()
23+
24+
expect.soft(user.subscription.state).toBe('cancelled')
25+
expect.soft(user.subscription.endsAt).toBe('2026-01-01T00:00:00.000Z')
26+
})
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
export type PlanKind = 'monthly' | 'yearly'
2+
3+
export class Plan {
4+
public name: string
5+
public kind: PlanKind
6+
public state: 'active' | 'cancelled'
7+
public chargeAmount: number
8+
public endsAt?: string
9+
10+
constructor(options: {
11+
name: string
12+
kind: PlanKind
13+
chargeAmount: number
14+
endsAt?: string
15+
}) {
16+
this.name = options.name
17+
this.kind = options.kind
18+
this.chargeAmount = options.chargeAmount
19+
this.state = 'active'
20+
this.endsAt = options.endsAt
21+
}
22+
23+
public cancel() {
24+
if (this.state !== 'active') {
25+
return
26+
}
27+
28+
this.state = 'cancelled'
29+
30+
const today = new Date()
31+
today.setUTCDate(1)
32+
today.setUTCMonth(
33+
today.getUTCMonth() + 1 > 11 ? 0 : today.getUTCMonth() + 1,
34+
)
35+
36+
if (today.getUTCMonth() === 0) {
37+
today.setUTCFullYear(today.getUTCFullYear() + 1)
38+
}
39+
40+
this.endsAt = today.toISOString()
41+
}
42+
}
43+
44+
export class TrialPlan extends Plan {
45+
constructor() {
46+
super({
47+
name: 'Trial',
48+
kind: 'monthly',
49+
chargeAmount: 0,
50+
})
51+
}
52+
}
53+
54+
export class UnlimitedPlan extends Plan {
55+
constructor() {
56+
super({
57+
name: 'Unlimited',
58+
kind: 'yearly',
59+
chargeAmount: 250,
60+
})
61+
}
62+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { TrialPlan, type Plan } from './plans'
2+
3+
export class User {
4+
public subscription: Plan
5+
6+
constructor() {
7+
this.subscription = new TrialPlan()
8+
}
9+
10+
public subscribe(subscription: Plan) {
11+
this.subscription = subscription
12+
}
13+
14+
public cancelSubscription() {
15+
this.subscription.cancel()
16+
}
17+
}

exercises/04.assertions/05.solution.soft-assertions/tsconfig.node.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"compilerOptions": {
44
"lib": ["ES2023"]
55
},
6-
"include": ["vite.config.ts"]
6+
"include": ["vitest.config.ts"]
77
}

0 commit comments

Comments
 (0)