Skip to content

Commit 346ccf4

Browse files
committed
✨ add before and after hooks
1 parent c06d82b commit 346ccf4

File tree

13 files changed

+275
-4
lines changed

13 files changed

+275
-4
lines changed

NAMESPACE

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Generated by roxygen2: do not edit by hand
22

3+
export(after)
4+
export(before)
35
export(define_parameter_type)
46
export(given)
57
export(test)
@@ -13,6 +15,7 @@ importFrom(checkmate,assert_list)
1315
importFrom(checkmate,assert_string)
1416
importFrom(checkmate,assert_subset)
1517
importFrom(checkmate,test_character)
18+
importFrom(checkmate,test_subset)
1619
importFrom(dplyr,bind_cols)
1720
importFrom(fs,dir_ls)
1821
importFrom(glue,glue)
@@ -27,6 +30,7 @@ importFrom(purrr,set_names)
2730
importFrom(purrr,walk)
2831
importFrom(rlang,`:=`)
2932
importFrom(rlang,abort)
33+
importFrom(rlang,arg_match)
3034
importFrom(rlang,call2)
3135
importFrom(rlang,call_modify)
3236
importFrom(rlang,exec)

R/hooks.R

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#' @importFrom rlang abort
2+
make_hook <- function(name) {
3+
function(hook) {
4+
if (length(formals(hook)) != 2) {
5+
abort(
6+
message = "Hook must have two arguments.",
7+
body = "The first argument is the context and the second is the scenario name."
8+
)
9+
}
10+
register_hook(hook, name)
11+
invisible(hook)
12+
}
13+
}
14+
15+
#' Hooks
16+
#'
17+
#' Hooks are functions that are run before or after a scenario.
18+
#'
19+
#' You can define them alongside steps definitions.
20+
#'
21+
#' If you want to run a hook only before or after a specific scenario, use it's name to execute hook only for this scenario.
22+
#'
23+
#' @param hook A function that will be run. The function first argument is context and the scenario name is the second argument.
24+
25+
#' @examples
26+
#' \dontrun{
27+
#' before(function(context, scenario_name) {
28+
#' context$session <- selenider::selenider_session()
29+
#' })
30+
#'
31+
#' after(function(context, scenario_name) {
32+
#' selenider::close_session(context$session)
33+
#' })
34+
#'
35+
#' after(function(context, scenario_name) {
36+
#' if (scenario_name == "Playing one round of the game") {
37+
#' context$game$close()
38+
#' }
39+
#' })
40+
#' }
41+
#' @md
42+
#' @name hook
43+
NULL
44+
45+
#' @rdname hook
46+
#' @export
47+
before <- make_hook("before")
48+
49+
#' @rdname hook
50+
#' @export
51+
after <- make_hook("after")
52+
53+
#' @importFrom rlang list2
54+
.hooks <- function(...) {
55+
structure(list2(...), class = "hooks")
56+
}
57+
58+
#' @importFrom rlang `:=` arg_match abort
59+
#' @importFrom checkmate test_subset
60+
#' @importFrom glue glue
61+
register_hook <- function(
62+
hook,
63+
name = c("before", "after")
64+
) {
65+
arg_match(name)
66+
hooks <- getOption(".cucumber_hooks", default = .hooks())
67+
if (test_subset(name, names(hooks))) {
68+
abort(
69+
message = glue("Hook '{name}' already registered."),
70+
body = "A hook can only be registered once."
71+
)
72+
}
73+
hooks <- .hooks(!!name := hook)
74+
options(.cucumber_hooks = hooks)
75+
invisible(hooks)
76+
}
77+
78+
#' @importFrom purrr pluck
79+
get_hook <- function(hooks = get_hooks(), name) {
80+
pluck(hooks, name, .default = function(...) { })
81+
}
82+
83+
clear_hooks <- function() {
84+
options(.cucumber_hooks = .hooks())
85+
}
86+
87+
get_hooks <- function() {
88+
getOption(".cucumber_hooks", default = .hooks())
89+
}

R/parse_token.R

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
#' @importFrom glue glue
33
#' @importFrom purrr map walk
44
#' @importFrom testthat context_start_file test_that
5-
parse_token <- function(tokens, steps, parameters = get_parameters()) {
5+
parse_token <- function(
6+
tokens,
7+
steps,
8+
parameters = get_parameters(),
9+
hooks = get_hooks()
10+
) {
11+
force(hooks)
612
map(tokens, \(token) {
713
switch(
814
token$type,
@@ -13,17 +19,19 @@ parse_token <- function(tokens, steps, parameters = get_parameters()) {
1319
calls <- parse_token(token$children, steps, parameters) |>
1420
map(\(x) call_modify(x, context = context))
1521

22+
get_hook(hooks, "before")(context, token$value)
1623
# Use `for` for better error messages instead of purrr indexed ones
1724
for (call in calls) {
1825
eval(call)
1926
}
27+
get_hook(hooks, "after")(context, token$value)
2028
})
2129
}
2230
),
2331
"Feature" = call2(
2432
function(file_name = token$value) {
2533
context_start_file(glue("Feature: {file_name}"))
26-
parse_token(token$children, steps, parameters) |>
34+
parse_token(token$children, steps, parameters, hooks) |>
2735
walk(eval)
2836
}
2937
),

R/run.R

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
run <- function(
33
feature,
44
steps,
5-
parameters = get_parameters()
5+
parameters = get_parameters(),
6+
hooks = get_hooks()
67
) {
78
tokens <- tokenize(feature)
8-
call_queue <- parse_token(tokens, steps, parameters)
9+
call_queue <- parse_token(tokens, steps, parameters, hooks)
910
walk(call_queue, eval)
1011
}

R/test.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ test <- function(
3737
assert_function(steps_loader, nargs = 1)
3838
defer({
3939
clear_steps()
40+
clear_hooks()
4041
set_default_parameters()
4142
})
4243
steps <- steps_loader(steps_dir)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Feature: Hooks
2+
Scenario: Before hook is executed
3+
When I start the scenario
4+
Then the before hook was run
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
before(function(context, name) {
2+
context$before <- TRUE
3+
})
4+
5+
when("I start the scenario", function(context) {
6+
7+
})
8+
9+
then("the before hook was run", function(context) {
10+
expect_true(context$before)
11+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cucumber::test(".", "./steps")

man/hook.Rd

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/examples.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,17 @@
8888
== Results =====================================================================
8989
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 2 ]
9090

91+
# test: should run before hook
92+
93+
Code
94+
testthat::test_dir(tests_path, reporter = testthat::ProgressReporter$new(
95+
show_praise = FALSE), stop_on_failure = FALSE)
96+
Output
97+
v | F W S OK | Context
98+
v | 1 | Feature: Hooks
99+
== Results =====================================================================
100+
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 1 ]
101+
91102
# test: should run a Scenario with custom parameters
92103

93104
Code

0 commit comments

Comments
 (0)