Skip to content

Commit 319f1d9

Browse files
committed
03/03: add exercise text
1 parent 0d0958c commit 319f1d9

File tree

14 files changed

+319
-10
lines changed

14 files changed

+319
-10
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Custom equality testers
2+
3+
Assertions in tests often come down to _value comparison_. You expect the result of <span style={{ whiteSpace: 'nowrap' }}>`sum(2, 3)`</span> to equal `5`; you expect `cart` to have a property `items` of type `Array`, and so on. And, sometimes, comparing things isn't as straightforward.
4+
5+
For example, imagine testing software that deals with measurements. Each measurement has a `value` (e.g. 1, 5, or 57) and a `unit` (inches, millimeters, centimeters, etc). It's quite easy to compare two measurements if both their values and units are the same:
6+
7+
```ts nonumber
8+
expect(new Measurement(1, 'in')).toEqual(new Measurement(1, 'in')) //
9+
```
10+
11+
It's a bit harder if those two are different.
12+
13+
```ts nonumber
14+
expect(new Measurement(1, 'in')).toEqual(new Measurement(2.54, 'cm')) //
15+
```
16+
17+
Semantically, these two measurements _are_ equal. 1 inch is, indeed, 2.54 centimeters. But syntanctically these two measurements produce different class instances that cannot be compared literally:
18+
19+
```ts nonumber
20+
// If you unwrap measurements, you can imagine them as plain objects.
21+
// Vitest will attempt to compare them as such, which will fail.
22+
expect({ value: 1, unit: 'in' }).toEqual({ value: 2.54, unit: 'cm' }) //
23+
```
24+
25+
Although you can, technically, solve this problem by introducing a custom matcher (something like `.toEqualMeasurement()`), that would be like using a fire hose to pour oneself a glass of water. It will work, but it's rather an overkill.
26+
27+
In situations like this, all you want is to _teach Vitest how to compare custom values_. That would still be equality comparison, but the one you fine-tune to compare measurements for what they stand for and not what objects they produce.
28+
29+
You need to extend `.toEqual()` itself.
30+
31+
## Your task
32+
33+
👨‍💼 In this exercise, your task is to implement a _custom equality checker_ for Vitest. Head to the <InlineFile file="vitest.setup.ts" /> file and follow the instructions to do that.
34+
35+
As always, run the tests (`npm test`) to verify your solution. The tests shall pass!
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"type": "module",
3+
"name": "exercises_03.assertions_03.problem.custom-quality-testers",
4+
"scripts": {
5+
"dev": "vite",
6+
"test": "vitest",
7+
"build": "vite build"
8+
},
9+
"devDependencies": {
10+
"vite": "^6.2.6",
11+
"vitest": "^3.1.1"
12+
}
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Measurement } from './measurement'
2+
3+
test('compares measurements', () => {
4+
expect(new Measurement(10, 'in')).toEqual(new Measurement(25.4, 'cm'))
5+
})
6+
7+
test('compares nested measurements', () => {
8+
expect({
9+
id: 1,
10+
name: 'fabric',
11+
length: new Measurement(10, 'in'),
12+
}).toEqual({
13+
id: 1,
14+
name: 'fabric',
15+
length: new Measurement(25.4, 'cm'),
16+
})
17+
})
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
type MeasurementUnit = 'in' | 'cm'
2+
3+
export class Measurement {
4+
constructor(
5+
public readonly value: number,
6+
public readonly unit: MeasurementUnit,
7+
) {}
8+
9+
public equals(other: Measurement): boolean {
10+
if (this.unit === other.unit) {
11+
return this.value === other.value
12+
}
13+
14+
const thisValue = this.unit === 'in' ? this.value * 2.54 : this.value
15+
const otherValue = other.unit === 'in' ? other.value * 2.54 : other.value
16+
17+
return thisValue === otherValue
18+
}
19+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "./tsconfig.base.json",
3+
"include": ["src/**/*"],
4+
"exclude": ["src/**/*.test.ts*"],
5+
"compilerOptions": {
6+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
7+
"jsx": "react-jsx"
8+
}
9+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ESNext",
4+
"module": "ESNext",
5+
"useDefineForClassFields": true,
6+
"skipLibCheck": true,
7+
8+
/* Bundler mode */
9+
"moduleResolution": "bundler",
10+
"allowImportingTsExtensions": true,
11+
"isolatedModules": true,
12+
"moduleDetection": "force",
13+
"noEmit": true,
14+
"verbatimModuleSyntax": true,
15+
16+
/* Linting */
17+
"strict": true,
18+
"noUnusedLocals": false,
19+
"noUnusedParameters": false,
20+
"noFallthroughCasesInSwitch": true,
21+
"noUncheckedSideEffectImports": true
22+
}
23+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"files": [],
3+
"references": [
4+
{ "path": "./tsconfig.app.json" },
5+
{ "path": "./tsconfig.node.json" },
6+
{ "path": "./tsconfig.test.json" }
7+
]
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "./tsconfig.base.json",
3+
"compilerOptions": {
4+
"lib": ["ES2023"]
5+
},
6+
"include": ["vitest.config.ts"]
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "./tsconfig.app.json",
3+
"include": ["vitest.setup.ts", "src/**/*", "src/**/*.test.ts*"],
4+
"exclude": [],
5+
"compilerOptions": {
6+
"types": ["vitest/globals"]
7+
}
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { defineConfig } from 'vitest/config'
2+
3+
export default defineConfig({
4+
server: {
5+
port: process.env.PORT ? Number(process.env.PORT) : undefined,
6+
},
7+
test: {
8+
globals: true,
9+
setupFiles: ['./vitest.setup.ts'],
10+
},
11+
})

0 commit comments

Comments
 (0)