The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl

use strict;
use warnings;

use Test::More;
use Test::BDD::Cucumber::Harness::Data;
use Test::BDD::Cucumber::Executor;
use Test::BDD::Cucumber::Parser;
use Test::BDD::Cucumber::StepFile;
use Method::Signatures;
use List::Util qw/sum/;

my $template = join '', (<DATA>);

my $step_mappings = {
	passing => sub { pass("Step passed") },
	failing => sub { fail("Step passed") },
	pending => sub { TODO: { local $TODO = 'Todo Step'; ok(0, "Todo Step") } }
};

Given 'the following feature', func ($c) {
	# Save a new executor
	$c->stash->{'scenario'}->{'executor'} = Test::BDD::Cucumber::Executor->new();

	# Create a feature object
	$c->stash->{'scenario'}->{'feature'} =
		Test::BDD::Cucumber::Parser->parse_string( $c->data );
};

Given qr/a scenario ("([^"]+)" )?with:/, func ($c) {
	my $scenario_name = $1 || '';

	# Save a new executor
	$c->stash->{'scenario'}->{'executor'} = Test::BDD::Cucumber::Executor->new();

	# Create a feature object with just that scenario inside it
	$c->stash->{'scenario'}->{'feature'} =
		Test::BDD::Cucumber::Parser->parse_string(
			$template . "\n\nScenario: $scenario_name\n" . $c->data
		);
};

Given qr/the step "([^"]+)" has a (passing|failing|pending) mapping/, func ($c) {
	# Add the step to our 'Step' list in the executor
	$c->stash->{'scenario'}->{'executor'}->add_steps(
		[ 'Step', $1, $step_mappings->{$2} ]
	);
};

When 'Cucumber runs the feature', func ($c) {
	$c->stash->{'scenario'}->{'harness'} =
		Test::BDD::Cucumber::Harness::Data->new({});

	$c->stash->{'scenario'}->{'executor'}->execute(
		$c->stash->{'scenario'}->{'feature'},
		$c->stash->{'scenario'}->{'harness'}
	);
};

When 'Cucumber runs the scenario with steps for a calculator', func ($c) {
	# FFS. "runs the scenario with steps for a calculator"?!. Cads. Lucky we're
	# using Perl here...
	$c->stash->{'scenario'}->{'executor'}->add_steps(
		[ 'Given', 'a calculator', func ($cc) {
			$cc->stash->{'scenario'}->{'calculator'} = 0;
			$cc->stash->{'scenario'}->{'constants'}->{'PI'} = 3.14159265;
		} ],
		[ 'When', 'the calculator computes PI', func ($cc) {
			$cc->stash->{'scenario'}->{'calculator'} =
				$cc->stash->{'scenario'}->{'constants'}->{'PI'};
		} ],
		[ 'Then', qr/the calculator returns "?(PI|[\d\.]+)"?/, func ($cc) {
			my $value = $cc->stash->{'scenario'}->{'constants'}->{$1} || $1;
			is( $cc->stash->{'scenario'}->{'calculator'}, $value,
				"Correctly returned $value" );
		}],
		[ 'Then', qr/the calculator does not return "?(PI|[\d\.]+)"?/, func ($cc) {
			my $value = $cc->stash->{'scenario'}->{'constants'}->{$1} || $1;
			isnt( $cc->stash->{'scenario'}->{'calculator'}, $value,
				"Correctly did not return $value" );
		}],
		[ 'When', qr/the calculator adds up (.+)/, func ($cc) {
			my $numbers = $1;
			my @numbers = $numbers =~ m/([\d\.]+)/g;

			$cc->stash->{'scenario'}->{'calculator'} = sum( @numbers );
		}]
	);

	$c->stash->{'scenario'}->{'harness'} =
		Test::BDD::Cucumber::Harness::Data->new({});

	$c->stash->{'scenario'}->{'executor'}->execute_scenario({
		scenario => $c->stash->{'scenario'}->{'feature'}->scenarios->[0],
		feature  => $c->stash->{'scenario'}->{'feature'},
		feature_stash => {},
		harness => $c->stash->{'scenario'}->{'harness'}
	});
	$c->stash->{'scenario'}->{'scenario'} =
		$c->stash->{'scenario'}->{'harness'}->current_feature->{'scenarios'}->[0];
};

When qr/Cucumber executes the scenario( "([^"]+)")?/, func ($c) {
	$c->stash->{'scenario'}->{'harness'} =
		Test::BDD::Cucumber::Harness::Data->new({});

	$c->stash->{'scenario'}->{'executor'}->execute_scenario({
		scenario => $c->stash->{'scenario'}->{'feature'}->scenarios->[0],
		feature  => $c->stash->{'scenario'}->{'feature'},
		feature_stash => {},
		harness => $c->stash->{'scenario'}->{'harness'}
	});
	$c->stash->{'scenario'}->{'scenario'} =
		$c->stash->{'scenario'}->{'harness'}->current_feature->{'scenarios'}->[0];
};

Then 'the feature passes', func ($c) {
	my $harness = $c->stash->{'scenario'}->{'harness'};
	my $result = $harness->feature_status( $harness->current_feature );

	is( $result->result, 'passing', "Feature passes" ) ||
		diag( $result->output );
};

Then qr/the scenario (passes|fails)/, func ($c) {
	my $wanted   = $1;
	my $harness  = $c->stash->{'scenario'}->{'harness'};
	my $scenario = $c->stash->{'scenario'}->{'scenario'};

	my $result = $harness->scenario_status( $scenario );

	my $expected_result = {
		passes => 'passing',
		fails  => 'failing'
	}->{$wanted};

	is ( $result->result, $expected_result, "Step success return $expected_result" ) ||
		diag $result->output;
};

Then qr/the step "(.+)" is skipped/, func ($c) {
	my $harness  = $c->stash->{'scenario'}->{'harness'};
	my $scenario = $c->stash->{'scenario'}->{'scenario'};

	my $step = $harness->find_scenario_step_by_name( $scenario, $1 );
	my $result = $harness->step_status( $step );

	is ( $result->result, 'pending', "Step success return 'undefined'" ) ||
		diag $result->output;
};

Then qr/the scenario is (pending|undefined)/, func ($c) {
	my $harness  = $c->stash->{'scenario'}->{'harness'};
	my $scenario = $c->stash->{'scenario'}->{'scenario'};
	my $expected = $1;

	my $result = $harness->scenario_status( $scenario );

	is( $result->result, $expected, "Scenario status is $expected" ) ||
		diag $result->output;
}

__DATA__
Feature: Sample Blank Feature
  When interpretting the "Given a Scenario" steps, we'll use this as the base
  to which to add those scenarios.