Skip to content

Commit 2f5d335

Browse files
authored
Merge pull request #3 from dlealv/README.md-review
Update README.md
2 parents 4edc6ef + 35faee9 commit 2f5d335

1 file changed

Lines changed: 102 additions & 50 deletions

File tree

README.md

Lines changed: 102 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Provides basic assertion capabilities and a structured test runner for easy test
1111
- **TestRunner**: Structured, hierarchical output with configurable verbosity levels (`OFF`, `HEADER`, `SECTION`, `SUBSECTION`).
1212
- **Compatible**: Runs on both Office Scripts and Node/TypeScript (for local or CI testing).
1313
- **Simple**: No dependencies, no decorators, no runtime imports.
14-
- **Extendable**: Add your own assertions or test conventions easily.
14+
- **Extensible**: Add your own assertions or test conventions easily.
1515

1616
---
1717

@@ -20,19 +20,19 @@ Provides basic assertion capabilities and a structured test runner for easy test
2020
### 1. Clone or copy this repo
2121

2222
Place `unit-test-framework.ts` in your project.
23-
(Optional: Use `main.ts` as a starting point for your test suite.)
23+
(Optional: Use `test/main.ts` as a starting point for your test suite.)
2424

2525
### 2. Write Tests
2626

2727
Define a `TestRunner` and create a test class with static methods, e.g.:
2828

2929
```typescript
30-
runner = new TestRunner(TestRunner.VERBOSITY.SECTION) // Define the test case runneer and verbosity level
31-
runner.title("Start Testing", 1) // Sending the title to console indicating the test started
32-
run.exec("Test Case for math", () => TestCase.math(), 1) // Executing math method from TestCase
33-
runner.title("End Testing", 1) // Sending the title to console indicating the test ended
30+
const runner = new TestRunner(TestRunner.VERBOSITY.SECTION) // Define the test case runner and verbosity level
31+
runner.title("Start Testing", 1) // Output title indicating the test started
32+
runner.exec("Test Case for math", () => TestCase.math(), 2) // Execute math method from TestCase with section indentation level
33+
runner.title("End Testing", 1) // Output title indicating the test ended
3434

35-
// Class where to organize all test cases
35+
// Class to organize all test cases
3636
class TestCase {
3737
public static math(): void {
3838
Assert.equals(2 + 2, 4, "Addition works")
@@ -41,7 +41,7 @@ class TestCase {
4141
}
4242
}
4343
```
44-
**Note:** `TestCase` class is not requied, just a way to organize all test cases to be executed via `TestRunner` class.
44+
**Note:** The `TestCase` class is not required, just a way to organize all test cases to be executed via the `TestRunner` class.
4545

4646
### 3. Run Tests
4747

@@ -64,9 +64,9 @@ Assert.equals(actual, expected, "optional message")
6464
- For arrays, each element is checked for both type and value. For objects/arrays of objects, a deep check (using JSON.stringify) is performed.
6565
- Example:
6666
```typescript
67-
Assert.equals([1, 2, 3], [1, 2, 3], "Arrays are equal"). // Passes: Arrays are equals. Using optional message
68-
Assert.equals([1, "2"], [1, 2]) // Fails: type mismatch at index 1
69-
Assert.equals([{x:1}], [{x:1}]) // Passes: objects are deeply equal
67+
Assert.equals([1, 2, 3], [1, 2, 3], "Arrays are equal") // Passes
68+
Assert.equals([1, "2"], [1, 2]) // Fails: type mismatch at index 1
69+
Assert.equals([{x:1}], [{x:1}]) // Passes: objects are deeply equal
7070
```
7171

7272
#### Inequality
@@ -164,6 +164,7 @@ Assert.doesNotThrow(
164164
**Note:**
165165
`Assert.throws` requires **the throwing code to be passed as a function reference** (using `() => ...` or `function() { ... }`).
166166
This allows the assertion method to execute your function and catch any exceptions inside its own logic.
167+
167168
#### Fail Manually
168169

169170
```typescript
@@ -182,13 +183,13 @@ const runner = new TestRunner(TestRunner.VERBOSITY.SECTION) // or HEADER, OFF, S
182183

183184
#### Verbosity Levels
184185

185-
- `OFF` (0): No output.
186-
- `HEADER` (1): Only top-level section headers.
187-
- `SECTION` (2): Section and higher.
188-
- `SUBSECTION` (3): All titles, including subsections.
186+
- `OFF` (`0`): No output.
187+
- `HEADER` (`1`): Only top-level section headers.
188+
- `SECTION` (`2`): Section and higher.
189+
- `SUBSECTION` (`3`): All titles, including subsections.
189190

190191
**How verbosity and indent work:**
191-
- Each call to `runner.title("Title", indent)` prints the message with `indent` number of `*` as prefix and suffix (e.g., `** title **` for indent=2).
192+
- Each call to `runner.title("Title", indent)` prints the message with `indent` number of `*` as prefix and suffix (e.g., `** title **` for `indent=2`).
192193
- A title is only printed if its `indent` is **less than or equal to** the current verbosity.
193194
- This lets you control granularity of test output: higher verbosity shows more detail.
194195

@@ -220,49 +221,97 @@ runner.getVerbosityLabel() // returns "HEADER", etc
220221
## Example: Full Test Suite
221222

222223
```typescript
224+
// main test file for the unit test framework
225+
223226
function main(workbook: ExcelScript.Workbook) {
224227
const runner = new TestRunner(TestRunner.VERBOSITY.SECTION)
225228
let success = false
226229
try {
227230
runner.title("Running All Tests", 1)
228-
runner.exec("Math Test", () => {
229-
Assert.equals(2 + 3, 5)
230-
Assert.notEquals(2 * 2, 5)
231-
Assert.equals([1, 2], [1, 2], "Array equality")
232-
Assert.throws(() => { throw new TypeError("fail") }, TypeError, "fail", "Throws test")
233-
}, 2)
234-
runner.exec("Null/Undefined Test", () => {
235-
Assert.isNull(null)
236-
Assert.isNotNull(0)
237-
Assert.isUndefined(undefined)
238-
Assert.isNotUndefined("")
239-
Assert.isDefined(123)
240-
}, 2)
241-
runner.exec("Instance Test", () => {
242-
class Animal {}
243-
class Dog extends Animal {}
244-
const d = new Dog()
245-
Assert.isInstanceOf(d, Dog)
246-
Assert.isInstanceOf(d, Animal)
247-
Assert.throws(() => Assert.isInstanceOf({}, Dog), AssertionError, undefined, "Throws if not instance")
248-
Assert.isNotInstanceOf({}, Dog)
249-
}, 2)
250-
runner.exec("Throws/DoesNotThrow Test", () => {
251-
const thrower = () => { throw new Error("fail") }
252-
const nonThrower = () => { return 42 }
253-
Assert.throws(thrower, Error, "fail", "Should throw error")
254-
Assert.doesNotThrow(nonThrower, "Should not throw")
255-
}, 2)
256-
runner.exec("Type Test", () => {
257-
Assert.assertType("abc", "string")
258-
Assert.assertType(123, "number")
259-
Assert.throws(() => Assert.assertType(123, "string"))
260-
}, 3) // indent=3 (SUBSECTION)
231+
runner.exec("Math Test", () => TestCase.math(), 2)
232+
runner.exec("Null/Undefined Test", () => TestCase.nullUndefined(), 2)
233+
runner.exec("Instance Test", () => TestCase.instance(), 2)
234+
runner.exec("Throws/DoesNotThrow Test", () => TestCase.throwsDoesNotThrow(), 2)
235+
runner.exec("Type Test", () => TestCase.type(), 2)
261236
success = true
262237
} finally {
263238
runner.title(success ? "All Tests Passed" : "Test Failure", 1)
264239
}
265240
}
241+
242+
// Class to organize all test cases as static methods
243+
class TestCase {
244+
public static math() {
245+
Assert.equals(2 + 3, 5, "Addition works")
246+
Assert.notEquals(2 * 2, 5, "Multiplication does not equal 5")
247+
Assert.equals([1, 2], [1, 2], "Array equality")
248+
}
249+
250+
public static nullUndefined() {
251+
Assert.isNull(null, "Should be null")
252+
Assert.isNotNull(0, "Zero is not null")
253+
Assert.isUndefined(undefined, "Should be undefined")
254+
Assert.isNotUndefined("", "Empty string is defined")
255+
Assert.isDefined(123, "Number is defined")
256+
}
257+
258+
public static instance() {
259+
class Animal {}
260+
class Dog extends Animal {}
261+
const d = new Dog()
262+
Assert.isInstanceOf(d, Dog, "Dog instance of Dog")
263+
Assert.isInstanceOf(d, Animal, "Dog instance of Animal")
264+
Assert.throws(() => Assert.isInstanceOf({}, Dog), AssertionError, undefined, "Throws if not instance")
265+
Assert.isNotInstanceOf({}, Dog, "Plain object is not instance of Dog")
266+
}
267+
268+
public static throwsDoesNotThrow() {
269+
// --- All throws cases ---
270+
// 1. Throws an Error with specific message
271+
Assert.throws(() => { throw new Error("fail") }, Error, "fail", "Should throw Error")
272+
273+
// 2. Throws a TypeError
274+
Assert.throws(() => { throw new TypeError("bad type") }, TypeError, "bad type", "Should throw TypeError")
275+
276+
// 3. Throws any error (not checking error type or message)
277+
Assert.throws(() => { throw "custom error string" }, undefined, undefined, "Should throw any error (string)")
278+
279+
// 4. Throws AssertionError when an assertion fails inside
280+
Assert.throws(() => Assert.isTrue(false, "Forced fail"), AssertionError, undefined, "Should throw AssertionError when assertion fails")
281+
282+
// 5. Using a function variable that throws
283+
const failFunc = () => { throw new RangeError("range fail") }
284+
Assert.throws(failFunc, RangeError, "range fail", "Should throw RangeError")
285+
286+
// --- All doesNotThrow cases ---
287+
// 1. Does not throw (simple value)
288+
Assert.doesNotThrow(() => 42, "Should not throw on returning 42")
289+
290+
// 2. Does not throw (returns undefined)
291+
Assert.doesNotThrow(() => undefined, "Should not throw on returning undefined")
292+
293+
// 3. Does not throw (assertion that passes)
294+
Assert.doesNotThrow(() => Assert.isTrue(true, "Should pass"), "Should not throw if assertion passes")
295+
296+
// 4. Using a function variable that does not throw
297+
const safeFunc = () => "hello"
298+
Assert.doesNotThrow(safeFunc, "Should not throw with safeFunc")
299+
}
300+
301+
public static type() {
302+
Assert.isType("abc", "string", "abc is string")
303+
Assert.isType(123, "number", "123 is number")
304+
Assert.throws(() => Assert.isType(123, "string"), undefined, undefined, "Throws if type mismatch")
305+
Assert.isNotType("hello", "number", "String is not number")
306+
Assert.isNotType(42, "string", "Number is not string")
307+
}
308+
}
309+
310+
// Make main available globally for Node/ts-node test environments
311+
if (typeof globalThis !== "undefined" && typeof main !== "undefined") {
312+
// @ts-ignore
313+
globalThis.main = main
314+
}
266315
```
267316

268317
---
@@ -279,12 +328,14 @@ function main(workbook: ExcelScript.Workbook) {
279328
** END Instance Test **
280329
** START Throws/DoesNotThrow Test **
281330
** END Throws/DoesNotThrow Test **
331+
** START Type Test **
332+
** END Type Test **
282333
* All Tests Passed *
283334
```
284335

285336
- Each title uses `*` characters as prefix/suffix, repeated according to the `indent` parameter.
286337
- A title prints only if its `indent` is less than or equal to the runner's verbosity.
287-
- Example above shows only indent 1 and 2 titles, because verbosity is set to `SECTION` (2).
338+
- Example above shows only indent `1` and `2` titles, because verbosity is set to `SECTION` (`2`).
288339

289340
If verbosity level is `HEADER` the output will be:
290341
```
@@ -303,6 +354,7 @@ If verbosity level is `HEADER` the output will be:
303354
---
304355

305356
## Additional Information
357+
306358
- TypeDoc documentation: [TYPEDOC](docs/typedoc/index.html)
307359

308360
## License

0 commit comments

Comments
 (0)