From 7829d708ea5f68662d75bf0d03ef10049bcf7fb3 Mon Sep 17 00:00:00 2001 From: Patrick Donelan Date: Fri, 15 May 2009 10:33:47 +0000 Subject: [PATCH] Survey test suite now functional. Tests are defined via a JSON-encoded spec, through Web Crud interface Test results are TAP encoded, and TAP parsed and prettified into HTML Added i18n, help and tests --- ...t_import_survey_default-test-results.wgpkg | Bin 0 -> 1450 bytes docs/upgrades/upgrade_7.7.5-7.7.6.pl | 11 + lib/WebGUI/Asset/Wobject/Survey.pm | 87 +++++ .../Asset/Wobject/Survey/ResponseJSON.pm | 43 +++ lib/WebGUI/Asset/Wobject/Survey/Test.pm | 326 +++++++++++++++++- lib/WebGUI/Help/Asset_Survey.pm | 61 +++- lib/WebGUI/i18n/English/Asset_Survey.pm | 18 +- t/Asset/Wobject/Survey/ResponseJSON.t | 21 +- t/Asset/Wobject/Survey/Test.t | 176 ++++++++++ 9 files changed, 720 insertions(+), 23 deletions(-) create mode 100644 docs/upgrades/packages-7.7.6/root_import_survey_default-test-results.wgpkg create mode 100644 t/Asset/Wobject/Survey/Test.t diff --git a/docs/upgrades/packages-7.7.6/root_import_survey_default-test-results.wgpkg b/docs/upgrades/packages-7.7.6/root_import_survey_default-test-results.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..a189079ba2ce99a90b69b510487ff23cb283bcef GIT binary patch literal 1450 zcmV;b1y%YViwFP!00000|Ls^^Z{j!<<#~RE#p9^un=&vg67$h*rB1~z?aae=gJ$8H#sa2l;YBUqX#)CDr{mUry$|g2H=FHtC%68ccBfq-|A~svwf?oAD&ez%eR~C~P}MAm ze5%=uYE9?MfQ4FB$$`){{#H8lTBpMh*@~P4vA0i<&^Ts9=#=dV* zFg%wGrt#jb5USd)rU7^gwZX4O@wLy@l6fu|vimPQ^u)cntKkA_6^cmlr}MA}E$IoP z-UVH#37y{p9L_jYdgU{sR}uT|3d(-a^|?}4)luz+a{t?0_(BdO^WHwzBC1p}u#k*} zIONmtpca7?0X?YAMSwyb)UM%H5yO`|CKVTu5{I{;xDS)tul<<4eH_-!+#|;xGMKzn zOrBbD896ri?`D1_uR_C~e8aDV*dhp4fdh{w0jtR+*s9W$`fD=wed6K?nH zv9Ax2)1hPvzAL9El*n|V<+S}Z`poq>SgyKn-jBIBX?Od7xLmF#QfPr2A-}1(I^mwj zLNnfUw=ATHqf_sr$F&W$Tx|USnZR(_CUbZY6l}4fh9_&rZ^)9W89X!BJ`2J>? zS}qR5*=;fXWs5z%)n-BBoX6%Ny!~(tsWELj9R% znB;ich$RW}gy0S+RHGQmMoK`@xsItn+fHsHp*-+*stIsu##(ryNJShk!4K1Sl${Be z09CgYv-F+RV~H+X?QGTVCBlVn-I1j`~aTYX{Xk2;i@J!#LdFOxm-nhQb1sB zgR+kMS205cMrdds+QbAEI)r~~eswSM{zsQ&tPVKILW{Ep@$%NDu>J&Q zyH|K(5_Z9CI2I_>8|D1gMndMS-2-$x#cg(G7wDc==0^~vZOksf#HSSLmW1|HY)|{L zi_3;HDPpvduqQ8jPYK(yi{aXq6p>rU_T-hdBSmo8_@0n^T9sV|?rBqi@W%P#4suNw z$*<+p{`nDI#LM8WcR2}fK8n@scDitxTuoOB4)=UNf&OmZ^%Qk@)5_KzGW)8>yaW1C zkAv0>W+|aJd1tz~@jK~9rXi+%FX4yptR7!IKHE#P7vL8KUKDsy;D4aNcSIHcZ~zdb->write("alter table Survey add column `testResultsTemplateId` char(22)"); + + print "Done\n" unless $quiet; + +} + #---------------------------------------------------------------------------- # Your sub here sub installCopySender { diff --git a/lib/WebGUI/Asset/Wobject/Survey.pm b/lib/WebGUI/Asset/Wobject/Survey.pm index a2b25c782..ae3c70566 100644 --- a/lib/WebGUI/Asset/Wobject/Survey.pm +++ b/lib/WebGUI/Asset/Wobject/Survey.pm @@ -192,6 +192,14 @@ sub definition { defaultValue => 'PBtmpl0000000000000062', namespace => 'Survey/Gradebook', }, + testResultsTemplateId => { + tab => 'display', + fieldType => 'template', + label => $i18n->get('Test Results Template'), + hoverHelp => $i18n->get('Test Results Template help'), + defaultValue => 'S3zpVitAmhy58CAioH359Q', + namespace => 'Survey/TestResults', + }, surveyJSON => { fieldType => 'text', defaultValue => '', @@ -2455,6 +2463,7 @@ sub www_editTestSuite { . $icon->edit( 'func=editTest;testId='.$id) . $icon->moveDown('func=demoteTest;testId='.$id) . $icon->moveUp( 'func=promoteTest;testId='.$id) + . qq{Run Test} . ''.$name.''; } $tests .= '
'; @@ -2621,6 +2630,84 @@ sub www_promoteTest { return $self->www_editTestSuite; } +#------------------------------------------------------------------- + +=head2 www_runTest ( ) + +Runs a test + +=cut + +sub www_runTest { + my $self = shift; + my $session = $self->session; + + return $self->session->privilege->insufficient() + unless $self->session->user->isInGroup( $self->get('groupToEditSurvey') ); + + my $id = $session->form->get("testId"); + + my $test = WebGUI::Asset::Wobject::Survey::Test->new($session, $id) + or return $self->www_editTestSuite('Unable to find test'); + + my $result = $test->run or return $self->www_editTestSuite('Unable to run test'); + + my $tap = $result->{tap} or return $self->www_editTestSuite('Unable to determine test result'); + + $self->session->log->debug("Got tap: [$tap]"); + use TAP::Parser; + my $parser = TAP::Parser->new( { tap => $tap } ); + + # Expose TAP::Parser and TAP::Parser::Result info as template variables + my $var = { + results => [], + }; + + while ( my $result = $parser->next ) { + my $rvar = {}; + for my $key (qw( + is_plan is_pragma is_test is_comment is_bailout is_version is_unknown + raw + type + as_string + is_ok + has_directive + has_todo + has_skip + )) { + $rvar->{$key} = $result->$key; + } + push @{$var->{results}}, $rvar; + } + + # add summary results + for my $key (qw( + passed + failed + actual_passed + actual_failed + todo + todo_passed + skipped + plan + tests_planned + tests_run + skip_all + has_problems + exit + wait + parse_errors + )) { + $var->{$key} = $parser->$key; + } + + my $ac = $self->getAdminConsole; + my $out = $self->processTemplate($var, $self->get('testResultsTemplateId')); + my $edit = WebGUI::International->new($self->session, "WebGUI")->get(575); + $ac->addSubmenuItem($self->session->url->page("func=editTest;testId=$id"), "$edit Test"); + return $ac->render($out, 'Test Results'); +} + ##------------------------------------------------------------------- # #=head2 www_settings ( ) diff --git a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm index 6952d57b9..c51e3cb02 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/ResponseJSON.pm @@ -332,6 +332,49 @@ sub surveyOrder { return $self->response->{surveyOrder}; } + +#------------------------------------------------------------------- + +=head2 surveyOrderIndexByVariableName + +Returns a lookup table of variable names to surveyOrder index + +Only questions with a defined variable name set are included. + +=cut + +sub surveyOrderIndexByVariableName { + my $self = shift; + + my %lookup; + + # Iterate over items in surveyOrder.. + my $i = 0; + for my $address ( @{ $self->surveyOrder } ) { + next if !$address; + + # Retreive the section and question for this address.. + my $section = $self->survey->section($address); + my $question = $self->survey->question($address); + + if (my $var = $section && $section->{variable} ) { + # Section variables appear for every question, only store lowest index + if (!exists $lookup{$var} || $lookup{$var} > $i) { + $lookup{$var} = $i; + } + } + + if (my $var = $question && $question->{variable} ) { + $lookup{$var} = $i; + } + + # Increment the item index counter + $i++; + } + + return \%lookup; +} + #------------------------------------------------------------------- =head2 nextResponse ([ $responseIndex ]) diff --git a/lib/WebGUI/Asset/Wobject/Survey/Test.pm b/lib/WebGUI/Asset/Wobject/Survey/Test.pm index 8c9f7a6bf..6aa0020bb 100644 --- a/lib/WebGUI/Asset/Wobject/Survey/Test.pm +++ b/lib/WebGUI/Asset/Wobject/Survey/Test.pm @@ -1,7 +1,12 @@ package WebGUI::Asset::Wobject::Survey::Test; +use strict; use base qw/WebGUI::Crud/; use WebGUI::International; +use Test::Deep::NoTest; +use JSON -support_by_pp; # so that we can still use allow_barekey etc.. +use Params::Validate qw(:all); +Params::Validate::validation_options( on_fail => sub { WebGUI::Error::InvalidParam->throw( error => shift ) } ); =head1 NAME @@ -31,10 +36,6 @@ Survey_test testId -=head3 assetId - -testId - =head3 sequenceKey assetId, e.g. each Survey instance has its own sequence of tests. @@ -49,9 +50,9 @@ Identifies the Survey instance. A name for the test -=head4 test +=head4 spec -The test code +The test spec =cut @@ -73,16 +74,315 @@ sub crud_definition { hoverHelp => $i18n->get( 'test name help', 'Asset_Survey' ), defaultValue => '', }; - $properties->{test} = { + $properties->{spec} = { fieldType => 'codearea', - label => $i18n->get( 'test code', 'Asset_Survey' ), - hoverHelp => $i18n->get( 'test code help', 'Asset_Survey' ), - syntax => 'perl', - defaultValue => 'test()', + label => $i18n->get( 'test spec', 'Asset_Survey' ), + hoverHelp => $i18n->get( 'test spec help', 'Asset_Survey' ), + syntax => 'js', + defaultValue => <session; + + if ( !$session->config->get('enableSurveyExpressionEngine') ) { + return { tap => 'Bail Out! enableSurveyExpressionEngine config option disabled' }; + } + + my $spec = $self->get('spec') + or return { tap => "Bail Out! Test spec undefined" }; + + eval { + $spec = from_json($spec, { relaxed => 1, allow_barekey => 1, allow_singlequote => 1, } ); + }; + + if ($@) { + my $error = $@; + $error =~ s/(.*?) at .*/$1/s; # don't reveal too much + return { tap => "Bail Out! Invalid test spec: $error" }; + } + + my $assetId = $self->get('assetId'); + my $survey = WebGUI::Asset::Wobject::Survey->new($session, $assetId); + if (!$survey || !$survey->isa('WebGUI::Asset::Wobject::Survey') ) { + return { tap => "Bail Out! Unable to instantiate Survey using assetId: $assetId" }; + } + + # Remove existing responses for current user + $self->session->db->write( 'delete from Survey_response where assetId = ? and userId = ?', + [ $self->getId, $self->session->user->userId() ] ); + + # disable cookies so that test code doesn't die + $survey->responseIdCookies(0); + + # Start a response as current user + my $responseId = $survey->responseId($self->session->user->userId) + or return { tap => "Bail Out! Unable to start survey response" }; + + # Prepare the ingredients.. + my $rJSON = $survey->responseJSON + or return { tap => "Bail Out! Unable to get responseJSON" }; + + my %validTargets = map { $_ => 1 } @{$survey->surveyJSON->getGotoTargets}; + my $surveyOrder = $rJSON->surveyOrder; + my $surveyOrderIndexByVariableName = $rJSON->surveyOrderIndexByVariableName; + + # Run the tests + my $testCount = 0; + my @tap; + for my $item (@$spec) { + $self->_resetResponses($rJSON); + $rJSON->lastResponse(-1); + if (my $args = $item->{test} ) { + push @tap, $self->_test( { + responseJSON => $rJSON, + surveyOrder => $surveyOrder, + surveyOrderIndexByVariableName => $surveyOrderIndexByVariableName, + args => $args, + testCount_ref => \$testCount, + } ); + } + elsif (my $args = $item->{test_mc} ) { + push @tap, $self->_test_mc( { + responseJSON => $rJSON, + surveyOrder => $surveyOrder, + surveyOrderIndexByVariableName => $surveyOrderIndexByVariableName, + args => $args, + testCount_ref => \$testCount, + } ); + } + else { + push @tap, "Bail Out!"; + } + } + + my $tap = "1..$testCount\n"; + $tap .= join "\n", @tap; + return { tap => "$tap" }; +} + +=head2 _resetResponses + +Private convenience sub to carry out the task of resetting a response between tests + +=cut + +sub _resetResponses { + my ($self, $rJSON) = @_; + $rJSON->responses( {} ); + $rJSON->lastResponse(-1); +} + +=head2 _test + +Private sub. Triggered when a test spec requests "test". + +In the test spec, keys without special meaning are assumed to be question/section vars. +The "next" key is special, indicating what section/question you expect the survey to +end up at after responses have been submitted. + +=cut + +sub _test { + my $self = shift; + my %opts = validate(@_, { + responseJSON => { isa => 'WebGUI::Asset::Wobject::Survey::ResponseJSON' }, + surveyOrder => { type => ARRAYREF }, + surveyOrderIndexByVariableName => { type => HASHREF }, + testCount_ref => { type => SCALARREF }, + args => { type => HASHREF }, + }); + + # assemble the top-level ingredients.. + my $rJSON = $opts{responseJSON}; + my $surveyOrder = $opts{surveyOrder}; + my $surveyOrderIndexByVariableName = $opts{surveyOrderIndexByVariableName}; + my $args = $opts{args}; + my $testCount = ++${$opts{testCount_ref}}; + + # ..and the test-specific arguments + my $next = $args->{next}; + delete $args->{next}; + # n.b. everything left in %args assumed to be var => answer text + + # get starting page + + # Record responses + my $responses = {}; + while ( my ( $variable, $answerText ) = each %$args ) { + my $index = $surveyOrderIndexByVariableName->{$variable}; + my $address = $surveyOrder->[$index]; + my $answerAddress; + + my $question = $rJSON->survey->question($address); + my $answer; + my $aIndex = 0; + for my $a (@{$question->{answers}}) { + if ($a->{text} =~ m/\Q$answerText\E/i) { + $answerAddress = "$address->[0]-$address->[1]-$aIndex"; + $answer = $a; + } + $aIndex++; + } + if (!$answer || $answerAddress !~ m/\d+-\d+-\d+/) { + return <{recordedAnswer}; + $responses->{$answerAddress} = $recordedAnswer; + $self->session->log->debug("Recording $variable ($answerAddress) => $recordedAnswer"); + } + + return $self->_recordResponses( { + responseJSON => $rJSON, + responses => $responses, + surveyOrder => $surveyOrder, + surveyOrderIndexByVariableName => $surveyOrderIndexByVariableName, + next => $next, + testCount => $testCount, + }); +} + +=head2 _test_mc + +Private sub. Triggered when a test spec requests "test_mc". + +In the test spec, the first item is a section/question, and all remaining items are definitions +of what you expect to happen next. + +=cut + +sub _test_mc { + my $self = shift; + my %opts = validate(@_, { + responseJSON => { isa => 'WebGUI::Asset::Wobject::Survey::ResponseJSON' }, + surveyOrder => { type => ARRAYREF }, + surveyOrderIndexByVariableName => { type => HASHREF }, + testCount_ref => { type => SCALARREF }, + args => { type => ARRAYREF }, + }); + + # assemble the top-level ingredients.. + my $rJSON = $opts{responseJSON}; + my $surveyOrder = $opts{surveyOrder}; + my $surveyOrderIndexByVariableName = $opts{surveyOrderIndexByVariableName}; + my $args = $opts{args}; + + # the first item is the section/question + my $variable = shift @$args; + # ..and all remaining items are the specs + my @specs = @$args; + + my $index = $surveyOrderIndexByVariableName->{$variable}; + my $address = $surveyOrder->[$index]; + my $question = $rJSON->survey->question($address); + my $answers = $question->{answers}; + + # Each spec is a sub-test, one per answer in the question + my @tap; + my $aIndex = 0; + for my $spec (@specs) { + $self->_resetResponses($rJSON); + my $responses = {}; + my $testCount = ++${$opts{testCount_ref}}; + + # assume spec can only be a target + my $next = $spec; + + my $answerAddress = "$address->[0]-$address->[1]-$aIndex"; + my $answer = $answers->[$aIndex]; + my $recordedAnswer = $answer->{recordedAnswer}; + $responses->{$answerAddress} = $recordedAnswer; + $self->session->log->debug("Recording answer for mc question $variable at index $aIndex ($answerAddress) => $recordedAnswer"); + push @tap, $self->_recordResponses( { + responseJSON => $rJSON, + responses => $responses, + surveyOrder => $surveyOrder, + surveyOrderIndexByVariableName => $surveyOrderIndexByVariableName, + next => $next, + testCount => $testCount, + }); + + $aIndex++; + } + return @tap; +} + +=head2 _recordResponses + +Private sub. Records responses and checks that you end up where you expect + +=cut + +sub _recordResponses { + my $self = shift; + my %opts = validate(@_, { + responseJSON => { isa => 'WebGUI::Asset::Wobject::Survey::ResponseJSON' }, + responses => { type => HASHREF }, + surveyOrder => { type => ARRAYREF }, + surveyOrderIndexByVariableName => { type => HASHREF }, + next => 1, + testCount => 1, + }); + + # assemble the top-level ingredients.. + my $rJSON = $opts{responseJSON}; + my $responses = $opts{responses}; + my $surveyOrder = $opts{surveyOrder}; + my $surveyOrderIndexByVariableName = $opts{surveyOrderIndexByVariableName}; + my $next = $opts{next}; + my $testCount = $opts{testCount}; + + $rJSON->recordResponses($responses); + + # Check where we end up + my $nextResponse = $rJSON->nextResponse; + my $nextAddress = $surveyOrder->[$nextResponse]; + my $nextSection = $rJSON->survey->section($nextAddress); + my $nextQuestion = $rJSON->survey->question($nextAddress); + + # Get the lowest section surveyOrderIndex from lookup + my $got; + my $svar = $nextSection->{variable}; + my $qvar = $nextQuestion->{variable}; + if ($surveyOrderIndexByVariableName->{$svar} == $nextResponse) { + $got = "$svar (<-- a section)"; + $got .= " and $qvar (<-- a question)" if $qvar; + } elsif ($qvar) { + $got = "$qvar (<-- a question)"; + } else { + $got = 'Unknown!'; + } + + my $expectedNextResponse = $surveyOrderIndexByVariableName->{$next}; + if ($nextResponse != $expectedNextResponse) { + chomp (my $tap = < 'survey feedback template', namespace => 'Asset_Survey' - }, + }, + { tag => 'survey test results template', + namespace => 'Asset_Survey' + }, ] }, @@ -102,7 +105,10 @@ our $HELP = { }, { tag => 'survey feedback template', namespace => 'Asset_Survey' - }, + }, + { tag => 'survey test results template', + namespace => 'Asset_Survey' + }, ] }, @@ -174,6 +180,9 @@ our $HELP = { }, { tag => 'survey feedback template', namespace => 'Asset_Survey' + }, + { tag => 'survey test results template', + namespace => 'Asset_Survey' }, ] }, @@ -221,7 +230,10 @@ our $HELP = { }, { tag => 'survey feedback template', namespace => 'Asset_Survey' - }, + }, + { tag => 'survey test results template', + namespace => 'Asset_Survey' + }, ] }, @@ -268,7 +280,10 @@ our $HELP = { }, { tag => 'survey feedback template', namespace => 'Asset_Survey' - }, + }, + { tag => 'survey test results template', + namespace => 'Asset_Survey' + }, ] }, @@ -310,7 +325,10 @@ our $HELP = { }, { tag => 'survey feedback template', namespace => 'Asset_Survey' - }, + }, + { tag => 'survey test results template', + namespace => 'Asset_Survey' + }, ] }, @@ -396,6 +414,39 @@ our $HELP = { { name => 'timeoutRestart', description => 'response timeout restart help' }, { name => 'endDate', description => 'response endDate help' }, ], + related => [ + { tag => 'survey template', + namespace => 'Asset_Survey' + }, + { tag => 'statistical overview report template', + namespace => 'Asset_Survey' + }, + { tag => 'gradebook report template', + namespace => 'Asset_Survey' + }, + { tag => 'survey section edit template', + namespace => 'Asset_Survey' + }, + { tag => 'survey question edit template', + namespace => 'Asset_Survey' + }, + { tag => 'survey answer edit template', + namespace => 'Asset_Survey' + }, + { tag => 'survey feedback template', + namespace => 'Asset_Survey' + }, + { tag => 'survey test results template', + namespace => 'Asset_Survey' + }, + ] + }, + + 'survey test results template' => { + title => 'survey test results template title', + body => 'survey test results template body', + isa => [], + fields => [], related => [ { tag => 'survey template', namespace => 'Asset_Survey' diff --git a/lib/WebGUI/i18n/English/Asset_Survey.pm b/lib/WebGUI/i18n/English/Asset_Survey.pm index d5bc883ce..01352633a 100644 --- a/lib/WebGUI/i18n/English/Asset_Survey.pm +++ b/lib/WebGUI/i18n/English/Asset_Survey.pm @@ -990,6 +990,16 @@ directly inside the answer_loop for other types of questions.|, message => q|All data tagged in survey expressions is also made available as template variables|, lastUpdated => 1242180657, }, + + 'survey test results template title' => { + message => q|Survey Test Results Template Variables|, + lastUpdated => 1242256111, + }, + + 'survey test results template body' => { + message => q|All TAP::Parser and TAP::Parser::Result fields are exposed as template variables|, + lastUpdated => 0, + }, 'maxResponsesSubmitted' => { message => q|A boolean indicating whether the current user has reached the maximum number of responses.|, @@ -1559,13 +1569,13 @@ section/answer.|, lastUpdated => 0, }, - 'test code' => { - message => q{Test Code}, + 'test spec' => { + message => q{Test Spec}, lastUpdated => 0, }, - 'test code help' => { - message => q{The test code}, + 'test spec help' => { + message => q{The JSON-encoded specification for your test(s)}, lastUpdated => 0, }, diff --git a/t/Asset/Wobject/Survey/ResponseJSON.t b/t/Asset/Wobject/Survey/ResponseJSON.t index 7345c1f32..1da0251a6 100644 --- a/t/Asset/Wobject/Survey/ResponseJSON.t +++ b/t/Asset/Wobject/Survey/ResponseJSON.t @@ -22,7 +22,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 96; +my $tests = 97; plan tests => $tests + 1; #---------------------------------------------------------------------------- @@ -321,6 +321,25 @@ is($rJSON->lastResponse(), 0, 'goto: works on existing question'); $rJSON->processGoto('goto 3-0'); is($rJSON->lastResponse(), 5, 'goto: finds first if there are duplicates'); +#################################################### +# +# surveyOrderIndexByVariableName +# +#################################################### +my $expect = { + 'goto 0' => 0, + 'goto 0-0' => 0, + 'goto 0-1' => 1, + 'goto 0-2' => 2, + 'goto 1' => 3, + 'goto 1-0' => 3, + 'goto 1-1' => 4, + 'goto 2' => 5, + 'goto 3-0' => 7, + 'goto 3-2' => 8, +}; +cmp_deeply($rJSON->surveyOrderIndexByVariableName(), $expect, 'surveyOrderIndexByVariableName'); + #################################################### # # responseScoresByVariableName diff --git a/t/Asset/Wobject/Survey/Test.t b/t/Asset/Wobject/Survey/Test.t new file mode 100644 index 000000000..06c8c1787 --- /dev/null +++ b/t/Asset/Wobject/Survey/Test.t @@ -0,0 +1,176 @@ +# Tests WebGUI::Asset::Wobject::Survey::SurveyJSON +# +# + +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../../../lib"; +use Test::More; +use Test::Deep; +use Test::Exception; +use Data::Dumper; +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use JSON; +use Clone qw/clone/; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; + +#---------------------------------------------------------------------------- +# Tests +plan tests => 16; + +my ( $s, $t1 ); + +#---------------------------------------------------------------------------- +# put your tests here +use_ok('WebGUI::Asset::Wobject::Survey::Test'); + +my $user = WebGUI::User->new( $session, 'new' ); +WebGUI::Test->usersToDelete($user); +my $import_node = WebGUI::Asset->getImportNode($session); + +# Create a Survey +$s = $import_node->addChild( { className => 'WebGUI::Asset::Wobject::Survey', } ); +isa_ok( $s, 'WebGUI::Asset::Wobject::Survey' ); + +$s->responseIdCookies(0); + +# Load bare-bones survey, containing a single section (S0) +$s->surveyJSON_update( [0], { variable => 'S0' } ); + +# Section 0 has a single question, S0Q0, which is a Yes/No muti-choice +$s->surveyJSON_newObject( [0] ); # S0Q0 +$s->surveyJSON_update( [ 0, 0 ], { variable => 'S0Q0' } ); +$s->surveyJSON->updateQuestionAnswers( [ 0, 0 ], 'Yes/No' ); + +# Add a new section (S1) +$s->surveyJSON_newObject( [] ); +$s->surveyJSON_update( [1], { variable => 'S1' } ); +$s->surveyJSON_newObject( [1] ); +$s->surveyJSON_newObject( [1] ); +$s->surveyJSON_update( [ 1, 0 ], { variable => 'S1Q0' } ); +$s->surveyJSON_update( [ 1, 1 ], { variable => 'S1Q1' } ); + +$s->persistSurveyJSON; + +cmp_deeply( + $s->responseJSON->surveyOrder, + [ [ 0, 0, [ 0, 1 ] ], [ 1, 0, [0] ], [ 1, 1, [0] ] ], + 'At this stage our surveyOrder has 3 items' +); + +cmp_deeply( + $s->responseJSON->surveyOrderIndexByVariableName, + { 'S0' => 0, + 'S0Q0' => 0, + 'S1' => 1, + 'S1Q0' => 1, + 'S1Q1' => 2, + }, + '..which corresponds to' +); + +$t1 = WebGUI::Asset::Wobject::Survey::Test->create( $session, { assetId => $s->getId } ); +my ($spec, $tap); + +$spec = < $tap }); + +# add a goto into the mix +$s->surveyJSON_update( [ 0, 0, 0 ], { goto => 'S1Q1' } ); +# deliberately pass in a spec that will fail +$spec = < $tap2, fail => 1 }); + +# try now with a spec that will pass +$spec = < $tap }); + +# Now use test_mc +$spec = q{ [ { test_mc : [ 'S0Q0', 'S1Q1', 'S1' ] } ] }; +try_it($t1, $spec, { tap => $tap }); + + + +use TAP::Parser; +sub try_it { + my ($test, $spec, $opts) = @_; + chomp($spec); + + $test->update( { spec => $spec } ); + my $result = $t1->run(); + ok( $result, 'Tests ran ok' ); + + if (my $tap = $opts->{tap}) { + chomp($tap); + is( $result->{tap}, $tap, 'TAP matches' ); + } + + my $parser = TAP::Parser->new( $result ); + while (my $r = $parser->next) { + # we could test extra stuff here, but mainly we just need to make the parser + # go all the way through so that we can access ->has_problems + } + ok(!$parser->has_problems == !$opts->{fail}, ($opts->{fail} ? "Fails" : "Passes") . ' as expected'); +} + +#---------------------------------------------------------------------------- +# Cleanup +END { + $s->purge() if $s; + $t1->delete(); +}