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
120 changes: 120 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# TestBox Documentation Conventions

This is the GitBook source for the official TestBox documentation, published at
<https://testbox.ortusbooks.com>. Each major version lives on its own branch (`v7.x`,
and so on); the version switcher in GitBook maps to those branches.

## Engine Preference

**BoxLang is the preferred engine.** When a feature exists on both BoxLang and CFML,
BoxLang comes first in every example, tab and list. Write BoxLang as the default voice
of the documentation and treat CFML as the companion, not the other way round.

## Code Examples

### Dual-engine features

When a feature works on **both** BoxLang and CFML, wrap the example in a GitBook tab
group with **BoxLang first, CFML second**:

````markdown
{% tabs %}
{% tab title="BoxLang" %}
{% code title="MyFirstSpec.bx" %}
```java
class extends="testbox.system.BaseSpec"{

function run(){
describe( "My First Test", () => {
it( "can add", () => {
expect( sum( 1, 2 ) ).toBe( 3 )
} )
} )
}

}
```
{% endcode %}
{% endtab %}

{% tab title="CFML" %}
{% code title="MyTest.cfc" %}
```cfscript
component extends="testbox.system.BaseSpec"{

function run(){
describe( "My First Test", function(){
it( "can add", function(){
expect( sum( 1, 2 ) ).toBe( 3 );
} );
} );
}

}
```
{% endcode %}
{% endtab %}
{% endtabs %}
````

Where a page separates BDD from xUnit style, the four-tab form is
`BDD - BoxLang`, `xUnit - BoxLang`, `BDD - CFML`, `xUnit - CFML` — again BoxLang first.

**Group by section, not by line.** One tabbed block covering a section's examples reads
far better than a tab group wrapped around every one-line snippet. A tab group whose two
sides differ only by a trailing semicolon is noise; fold those snippets into the nearest
substantive example instead.

### Engine differences to honor

| | BoxLang | CFML |
| --- | --- | --- |
| Fence language | ```` ```java ```` (or ```` ```groovy ```` for xUnit) | ```` ```cfscript ```` |
| File extension | `.bx` | `.cfc` |
| Class keyword | `class` | `component` |
| Statement terminator | no semicolons | semicolons |
| Closures | `() => {}` preferred | `function(){}` |

### BoxLang-only features

When a feature requires BoxLang and has no CFML equivalent, **do not use tabs** — there
is no second side. Mark it with a hint callout immediately after the heading, and write
the examples in BoxLang:

```markdown
{% hint style="info" %}
These matchers require BoxLang. On CFML engines they are guarded and report unsupported
behavior cleanly.
{% endhint %}
```

Say what actually happens on CFML rather than only that the feature is unavailable —
guarded, ignored, or throwing a named exception such as
`TestBox.BoxLangFeatureNotAvailable`.

Set expectations, Range expectations and the Data Navigator matchers are BoxLang-only.

## Callouts

Use GitBook hints, not plain `>` blockquotes:

- `{% hint style="info" %}` — engine requirements, cross-references, context
- `{% hint style="warning" %}` — behavioral changes and upgrade notes
- `{% hint style="success" %}` — tips worth acting on

Behavioral changes carry the version that introduced them, for example
**Changed in TestBox 7.1:**.

## Structure

- Register every new page in `SUMMARY.md`; an unregistered page will not appear in the book.
- Release notes live in `readme/release-history/whats-new-with-<version>.md`, newest first
in `SUMMARY.md`, with a `description:` frontmatter key holding the release date.
- Add a short paragraph to `readme/release-history/README.md` for each minor release.
- Keep the existing frontmatter on a page you edit, including `metaLinks` and `icon`.

## Before Committing

- Every `SUMMARY.md` target resolves to a file that exists.
- Every relative `.md` link resolves from its own directory.
- New APIs appear in the reference pages people actually browse, not only in the release notes.
4 changes: 4 additions & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* [Introduction](README.md)
* [Release History](readme/release-history/README.md)
* [What's New With 7.1.0](readme/release-history/whats-new-with-7.1.0.md)
* [What's New With 7.0.0](readme/release-history/whats-new-with-7.0.0.md)
* [About This Book](readme/about-this-book/README.md)
* [Author](readme/about-this-book/author.md)
Expand Down Expand Up @@ -64,6 +65,9 @@
* [Matchers](digging-deeper/expectations/matchers.md)
* [Not Operator](digging-deeper/expectations/not-operator.md)
* [Expecting Exceptions](digging-deeper/expectations/expecting-exceptions.md)
* [Set Expectations](digging-deeper/expectations/set-expectations.md)
* [Range Expectations](digging-deeper/expectations/range-expectations.md)
* [Data Navigator Expectations](digging-deeper/expectations/data-navigator.md)
* [Custom Matchers](digging-deeper/expectations/custom-matchers.md)
* [Output Utilities](digging-deeper/output-utilities.md)
* [Runner Listeners](digging-deeper/run-listeners.md)
Expand Down
118 changes: 118 additions & 0 deletions digging-deeper/assertions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,31 @@ assertCloseTo()
Here are some common assertion methods:

```javascript
all( closures, [heading] )
assert( expression, [message] )
between( actual, min, max, [message] )
closeTo(expected, actual, delta, [datePart], [message])
deepKey( target, key, [message] )
fail( [message] )
includes( target, needle, [message] )
includesAll( target, needles, [message] )
includesAny( target, needles, [message] )
includesNone( target, needles, [message] )
includesWithCase( target, needle, [message] )
instanceOf( actual, typeName, [message] )
isEmpty( target, [message] )
isEqual(expected, actual, [message])
isEqualWithCase(expected, actual, [message])
isFalse( actual, [message] )
isFalsy( actual, [message] )
isGT( actual, target, [message])
isGTE( actual, target, [message])
isLT( actual, target, [message])
isLTE( actual, target, [message])
isNotEmpty( target, [message] )
isNotEqual(expected, actual, [message])
isTrue( actual, [message] )
isTruthy( actual, [message] )
key( target, key, [message] )
lengthOf( target, length, [message] )
match( actual, regex, [message] )
Expand All @@ -70,3 +76,115 @@ skip( message, detail )
throws(target, [type], [regex], [message])
typeOf( type, actual, [message] )
```

### Truthiness

`isTrue()` and `isFalse()` require an actual boolean. `isTruthy()` and `isFalsy()` are looser, and are useful when the value under test is "something or nothing" rather than a strict boolean.

{% tabs %}
{% tab title="BoxLang" %}
```java
$assert.isTruthy( "hello" )
$assert.isTruthy( [ 1, 2 ] )

$assert.isFalsy( "" )
$assert.isFalsy( 0 )
$assert.isFalsy( [] )
```
{% endtab %}

{% tab title="CFML" %}
```cfscript
$assert.isTruthy( "hello" );
$assert.isTruthy( [ 1, 2 ] );

$assert.isFalsy( "" );
$assert.isFalsy( 0 );
$assert.isFalsy( [] );
```
{% endtab %}
{% endtabs %}

### Multiple Inclusions

`includes()` checks for one needle. These three check for several at once:

{% tabs %}
{% tab title="BoxLang" %}
```java
$assert.includesAll( roles, [ "admin", "editor" ] )
$assert.includesAny( roles, [ "admin", "superuser" ] )
$assert.includesNone( serializedUser, [ "password", "salt", "apiToken" ] )
```
{% endtab %}

{% tab title="CFML" %}
```cfscript
$assert.includesAll( roles, [ "admin", "editor" ] );
$assert.includesAny( roles, [ "admin", "superuser" ] );
$assert.includesNone( serializedUser, [ "password", "salt", "apiToken" ] );
```
{% endtab %}
{% endtabs %}

### Grouped Assertions

By default a failing assertion aborts the test, so you only ever see the first failure and fix them one run at a time. `$assert.all()` runs a set of assertion closures and reports **every** failure at once.

{% tabs %}
{% tab title="BoxLang" %}
```java
$assert.all( [
() => $assert.isEqual( "Luis", user.getName() ),
() => $assert.isEqual( "luis@ortussolutions.com", user.getEmail() ),
() => $assert.isTrue( user.isActive() )
], "user profile" )
```
{% endtab %}

{% tab title="CFML" %}
```cfscript
$assert.all( [
function(){ return $assert.isEqual( "Luis", user.getName() ); },
function(){ return $assert.isEqual( "luis@ortussolutions.com", user.getEmail() ); },
function(){ return $assert.isTrue( user.isActive() ); }
], "user profile" );
```
{% endtab %}
{% endtabs %}

If the name and the active flag are both wrong, both are reported:

```
user profile: 1 of 3 assertions passed
[1] expected [Luis] but received [Alice]
[3] expected [true] but received [false]
```

The optional second argument is a heading prepended to the failure summary.

`assertAll()` is available as a spec-level shortcut for the same thing:

{% tabs %}
{% tab title="BoxLang" %}
```java
assertAll( [
() => $assert.isEqual( 200, response.status ),
() => $assert.key( response, "data" )
], "response envelope" )
```
{% endtab %}

{% tab title="CFML" %}
```cfscript
assertAll( [
function(){ return $assert.isEqual( 200, response.status ); },
function(){ return $assert.key( response, "data" ); }
], "response envelope" );
```
{% endtab %}
{% endtabs %}

{% hint style="info" %}
Grouped assertions are the assertion-style counterpart to [collection expectations](../expectations/#collection-expectations). Reach for them when several independent facts about one object should all be reported together.
{% endhint %}
14 changes: 9 additions & 5 deletions digging-deeper/code-coverage/configuring-code-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ Most of the coverage settings are devoted to helping TestBox know what files to

## Default Settings

Code coverage is enabled by default and set with a default configuration. You can control how it behaves with a series of `<CFParam>` tags in your `/tests/runner.cfm` file. If you created a fresh new ColdBox app from our app templates using `coldbox create app`, you'll see there are already configuration options ready for you to change. If you are working with an existing test suite runner, place the following lines PRIOR to the `<CFInclude>` in your runner.cfm.
Code coverage is **disabled by default** and set with a default configuration. You can control how it behaves with a series of `<CFParam>` tags in your `/tests/runner.cfm` file. If you created a fresh new ColdBox app from our app templates using `coldbox create app`, you'll see there are already configuration options ready for you to change. If you are working with an existing test suite runner, place the following lines PRIOR to the `<CFInclude>` in your runner.cfm.

```markup
<!--- Code Coverage requires FusionReactor --->
<cfparam name="url.coverageEnabled" default="true">
<cfparam name="url.coverageEnabled" default="false">
<cfparam name="url.coveragePathToCapture" default="#expandPath( '/' )#">
<cfparam name="url.coverageWhitelist" default="">
<cfparam name="url.coverageBlacklist" default="/testbox,/coldbox,/tests,/modules,Application.cfc,/index.cfm">
Expand All @@ -36,14 +36,18 @@ Let's go over the options above and what they do. Feel free to comment/uncomment

## coverageEnabled

Set this to `true` or `false` to enable the code coverage feature of TestBox. This setting will default to `true` if TestBox detects that you have FusionReactor installed, `false` otherwise. Setting this to `true` without FusionReactor installed will be ignored.
Set this to `true` or `false` to enable the code coverage feature of TestBox. Setting this to `true` without FusionReactor installed will be ignored.

The following setting would turn off code coverage:
The following setting would turn on code coverage:

```markup
<cfparam name="url.coverageEnabled" default="false">
<cfparam name="url.coverageEnabled" default="true">
```

{% hint style="warning" %}
**Changed in TestBox 7.1:** this setting now defaults to `false`. Previously it defaulted to `true`, so every runner hit attempted to start coverage even on installs with no FusionReactor. Coverage is now opt-in: set the param to `true`, or pass `?coverageEnabled=true` on the URL.
{% endhint %}

## coveragePathToCapture

Use this to point to the root folder that contains code you wish to gather coverage data from. This must be an absolute path and feel free to use any CF Mappings defined in your `/tests/Application.cfc` to make the path dynamic. This is especially useful if the app being tested is in a subfolder of the actual web root. There is nominal overhead in gathering the coverage data from files, so set this to the correct folder and instead of using the whitelist to filter down from your web root if possible.
Expand Down
2 changes: 1 addition & 1 deletion digging-deeper/code-coverage/running-code-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ coldbox create app
server start
```

Inside your directory will be a folder called `/tests` which has our test runner `/tests/runner.cfm`. You will need to open your runner.cfm and default code coverage enabled to true.
Inside your directory will be a folder called `/tests` which has our test runner `/tests/runner.cfm`. Code coverage is opt-in, so you will need to open your `runner.cfm` and default code coverage enabled to true.

```
<!--- Code Coverage requires FusionReactor --->
Expand Down
Loading