# gocuke API reference

## Contents

- [Runner](#runner)
- [Suite struct](#suite-struct)
- [Step discovery](#step-discovery)
- [Manual step registration](#manual-step-registration)
- [Injected types](#injected-types)
- [Data tables](#data-tables)
- [Doc strings](#doc-strings)
- [Hooks](#hooks)
- [Property-based testing with rapid](#property-based-testing-with-rapid)
- [Tag filtering](#tag-filtering)
- [Running tests](#running-tests)

## Runner

Create a runner in a standard `TestXxx` function:

```go
func TestFeatureName(t *testing.T) {
    gocuke.NewRunner(t, &mySuite{}).Run()
}
```

The runner is fluent — chain options before `.Run()`:

```go
gocuke.NewRunner(t, &mySuite{}).
    Path("features/vcs/**/*.feature"). // custom glob (default: features/**/*.feature)
    Tags("@smoke"). // tag filter expression
    ShortTags("not @slow"). // applied with go test -short
    NonParallel(). // disable t.Parallel()
    Step(`^custom regex$`, (*mySuite).CustomMethod). // manual step
    Before(func() { /* setup */ }). // runner-level hook
    After(func() { /* teardown */ }).
    Run()
```

## Suite struct

Holds state for one scenario. A new instance is created per scenario — no cross-contamination. Embed `gocuke.TestingT` to get assertion methods.

```go
type mySuite struct {
    gocuke.TestingT // auto-injected — provides Fatal, Log, etc.
    // per-scenario state
    result string
    err    error
}
```

Any assertion library accepting a `testing.T`-like interface works because the suite itself satisfies that interface.

## Step discovery

gocuke converts step text to a method name via reflection:

1. Split step text on spaces
2. Skip numeric and quoted tokens (they become parameters)
3. PascalCase remaining words, strip punctuation
4. Concatenate

| Step text                     | Method signature                       |
| ----------------------------- | -------------------------------------- |
| `I have 5 cukes`              | `IHaveCukes(a int64)`                  |
| `I spend <amount>`            | `ISpend(a int64)`                      |
| `I load a "red" one`          | `ILoadAOne(a string)`                  |
| `the result is 1.5`           | `TheResultIs(a *apd.Decimal)`          |
| `I see the following` + table | `ISeeTheFollowing(t gocuke.DataTable)` |

When a step has no matching method, gocuke prints a copy-pasteable suggestion with the correct signature.

## Manual step registration

For complex patterns or when auto-discovery doesn't fit:

```go
gocuke.NewRunner(t, &suite{}).
    Step(`^I send "(GET|POST|PUT|DELETE)" to "([^"]*)"$`, (*suite).ISendTo).
    Step(regexp.MustCompile(`the response code is (\d+)`), (*suite).TheResponseCodeIs).
    Run()
```

Manual registrations take priority over auto-discovered methods.

## Injected types

Exported fields of these types on the suite struct are auto-populated:

| Type              | What you get                                   |
| ----------------- | ---------------------------------------------- |
| `gocuke.TestingT` | Current `*testing.T` (or rapid wrapper)        |
| `gocuke.Scenario` | Scenario metadata: `Name()`, `Tags()`, `URI()` |
| `gocuke.Step`     | Current step metadata (nil in hooks)           |
| `*rapid.T`        | Rapid handle (triggers property-based mode)    |

## Data tables

```go
func (s *suite) TheFollowingUsersExist(table gocuke.DataTable) {
    // Raw access
    table.NumRows() // total rows including header
    table.NumCols()
    table.Cell(0, 1) // 0-indexed → *Cell

    // Header-based access
    ht := table.HeaderTable() // first row = column names
    ht.NumRows() // rows excluding header
    ht.Get(0, "name") // row offset + column name → *Cell
}
```

Cell methods: `.String()`, `.Int64()`, `.BigInt()`, `.Decimal()`.

## Doc strings

```go
func (s *suite) TheConfigContains(doc gocuke.DocString) {
    doc.Content // raw string content
    doc.MediaType // "json", "yaml", etc. (empty if unannotated)
}
```

Must be the last parameter.

## Hooks

Define methods on the suite struct:

```go
func (s *suite) Before(scenario gocuke.Scenario) { /* per-scenario setup */ }
func (s *suite) After() { /* always runs, even on failure */ }
func (s *suite) BeforeStep() { /* before each step */ }
func (s *suite) AfterStep(step gocuke.Step) { /* after each step */ }
```

Hooks can also be registered on the runner for shared setup:

```go
gocuke.NewRunner(t, &suite{}).
    Before(func() { /* runner-level before */ }).
    After(func() { /* runner-level after */ }).
    Run()
```

## Property-based testing with rapid

If any step method takes `*rapid.T` as its first parameter (after the receiver), the entire scenario is wrapped in `rapid.Check()`:

```go
func (s *suite) AnyValidInput(t *rapid.T) {
    s.input = rapid.String().Draw(t, "input")
}
```

- The scenario runs hundreds of times with different random inputs
- On failure, rapid shrinks to a minimal reproducing case
- `*rapid.T` can also be an exported field on the suite for universal access

Tag these scenarios `@property` and use `ShortTags("not @property")` to skip them during quick iterations.

## Tag filtering

```go
// In test code
gocuke.NewRunner(t, &suite{}).
    Tags("@smoke"). // always applied
    Tags("(@smoke or @unit) and not @slow"). // complex expression
    ShortTags("not @property"). // applied with -short
    Run()
```

```bash
# From command line (global)
go test ./... -gocuke.tags "not @integration"
go test ./... -short
```

## Running tests

```bash
go test ./... # all tests, parallel by default
go test ./... -v # verbose step-by-step output
go test ./... -run TestVCSDetection # single test function
go test ./... -gocuke.tags "@wip" # tag filter
go test ./... -short # trigger ShortTags
go test ./... -count=1 # disable test caching
```

Each Feature becomes a `t.Run(featureName)` subtest. Each Scenario (including each Outline row) becomes a nested `t.Run`. Tests run in parallel by default — use `NonParallel()` when scenarios share external state.
