Skip to content

Instantly share code, notes, and snippets.

@jbpros
Last active May 25, 2021 10:17
Show Gist options
  • Save jbpros/8414fc60f4fd072c12f3 to your computer and use it in GitHub Desktop.
Save jbpros/8414fc60f4fd072c12f3 to your computer and use it in GitHub Desktop.
Cucumber.js step definition transforms
# features/my.feature
Feature:
Scenario: cukes
Given I have 10 cucumbers in my BaG
Given I don't have 20 cucumbers in my bAg
Given I have 98775655 cucumbers in my bag
// features/step_definitions/stepdefs.js
var transform = require('./support/transform.js');
module.exports = function () {
this.Transform(/^(\d+)$/, function (matches) { return parseInt(matches[0]); });
this.Transform(/^(have|don't have)$/, function (matches) { return matches[0] == 'have'; });
this.Transform(/^([bB][aA][gG])$/, function () { return 'bag'; });
this.Given(/^I (have|don't have) (\d+) cucumbers in my (.*)$/, function (has, count, where) {
console.log('Given...', has, typeof count, where)
});
}
// features/support/transform.js
var transforms = [];
function cast(value) {
for (var i = 0; i < transforms.length; i++) {
var transform = transforms[i];
var matches = transform.pattern.exec(value);
if (matches) {
return transform.fn(matches);
}
}
return value;
}
function t(fn) {
var args = [];
var body = 'var args = [];';
for (var i = 0; i < fn.length; i++) {
args.push('arg' + i);
body += 'args.push(this.cast(arg' + i + '));\n';
}
body += 'return this.fn.apply(this, args);';
var transformedFn = Function.apply(null, args.concat([body])).bind({fn: fn, cast: cast});
return transformedFn;
}
module.exports = function () {
var _defineStep = this.defineStep;
this.defineStep = function (pattern, fn) {
_defineStep(pattern, t(fn));
};
this.Given = this.When = this.Then = this.defineStep;
this.Transform = function (pattern, fn) {
transforms.push({ pattern: pattern, fn: fn });
};
}
@robsquires
Copy link

Really helpful, thats for posting this 👍

I'm experiencing a bit of a strange error within my step definition callbacks...this is now scoped to {fn: fn, cast: cast}, so anything I've set in World is now longer accessible. I've had a little play but can't quite work out where I'm going wrong. I've tried:

Function.apply(null, args.concat([body])).bind(Object.assign(World, {fn: fn, cast: cast}), but I think this is not the World I am looking for.

I'm putting the snippet in /support/transform.js

Any thoughts?

@acontell
Copy link

acontell commented Jun 9, 2016

Hi there,

@robsquires you're right, the function loses the World context that cucumber associates to the callback of Givens, Thens... In order to make things work, I've changed a little bit the t function (I'm using lodash, but it is easy to adapt it to normal js). This way, the function doesn't lose context and everything keeps working as usual:

function t(fn) {
    return function() {
        return fn.apply(this, _.reduce(arguments, function(memo, arg) {
            return memo.concat(cast(arg));
        }, []));
    };
}

Hope this helps,

Kind regards.

@pittgoose
Copy link

I'm trying to implement this, but I keep getting an error which states: Error: TypeError: this.Transform is not a function.

Feature file:

Feature:
  Scenario: cukes
    Given i have 6 of one

Step file:

let transform = require('./support/transform.js');
module.exports = function () {

    this.Transform(/^(\d+)$/, function (matches) {
        return parseInt(matches[0]);
    });

    this.Given(/^i have (\d+) of one$/, function (arg) {
        console.log(arg)
    });
};

transform.js

let transforms = [];

function cast(value) {
    for (let i = 0; i < transforms.length; i++) {
        let transform = transforms[i];
        let matches = transform.pattern.exec(value);
        if (matches) {
            return transform.fn(matches);
        }
    }
    return value;
}

function t(fn) {
    return function() {
        return fn.apply(this, _.reduce(arguments, function(memo, arg) {
            return memo.concat(cast(arg));
        }, []));
    };
}

module.exports = function () {
    let _defineStep = this.defineStep;
    this.defineStep = function (pattern, fn) {
        _defineStep(pattern, t(fn));
    };
    this.Given = this.When = this.Then = this.defineStep;
    this.Transform = function (pattern, fn) {
        transforms.push({ pattern: pattern, fn: fn });
    };
}

@setthase
Copy link

setthase commented May 10, 2017

@robsquires - your solution was quite close, but you should do something like that:

Function.apply(null, args.concat([body])).bind(Object.assign({}, new this.World(), {fn: fn, cast: cast})

And just make sure that t function is called with the same scope from exported function. For example using ES6 you can do something like that:

  this.defineStep = (pattern, fn) => {
    _defineStep(pattern, t.call(this, fn));
  };

@pittgoose I also had same issue as you and to fix it I required transforms inside module.exports function, i.e.:

module.exports = function () {
  require('./support/transform').call(this);

  this.Transform(/^(\d+)$/, function (matches) {
    return parseInt(matches[0], 10);
  });
}

I know maybe it's late for you guys, but I got exactly same problem, so I would like to help others that will fall into same issues here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment