1 /**
2  * Handels all BDD information concerning the Feature
3  *    Comments, description, function-names, scenarios and actions
4  */
5 module tagion.behaviour.BehaviourFeature;
6 
7 import std.format;
8 import std.meta : Alias, AliasSeq, ApplyLeft, ApplyRight, Erase, Filter, aliasSeqOf, aliasSeqOf, allSatisfy, anySatisfy;
9 import std.traits;
10 import std.typecons;
11 import tagion.basic.basic : isOneOf, staticSearchIndexOf;
12 import tagion.basic.traits : hasOneMemberUDA;
13 import tagion.hibon.Document;
14 import tagion.hibon.HiBONRecord;
15 
16 /* 
17  * Set the common property for
18  * Feature, Scenario and the Actions (Given,When,Then and But)
19  */
20 @safe:
21 mixin template Property() {
22     string description;
23     @label(VOID) @optional string[] comments;
24     mixin HiBONRecord!(q{
25             this(string description, string[] comments=null ) pure nothrow {
26                 this.description = description;
27                 this.comments = comments;
28             }
29             this(T)(T prop) pure nothrow {
30                 description = prop.description;
31                 comments = prop.comments;
32             }
33         });
34 }
35 
36 @recordType("Feature")
37 struct Feature {
38     mixin Property;
39 }
40 
41 struct Scenario {
42     mixin Property;
43 }
44 
45 /// Action property for Given
46 struct Given {
47     mixin Property;
48 }
49 
50 /// Action property for When
51 struct When {
52     mixin Property;
53 }
54 
55 /// Action property for Then 
56 struct Then {
57     mixin Property;
58 }
59 
60 /// Action property for But 
61 struct But {
62     mixin Property;
63 }
64 
65 enum isDescriptor(T) = hasMember!(T, "description");
66 
67 /* 
68  * Contains the information for the Protorty including the name id and the result of the Property
69 * The Property is as a general container for the Feature, Scenario and Actions
70  */
71 struct Info(alias Property) {
72     Property property; /// The property is a Feature, Scenario or an Action
73     string name; /// Name of the function member, scenario call or feature module
74     Document result; /// The result after execution of the property (See BehaviourResult)
75     mixin HiBONRecord!();
76 }
77 
78 /// Returns: true if I is a Info template
79 enum isInfo(alias I) = __traits(isSame, TemplateOf!I, Info);
80 
81 /**
82  * The Action group contains a list of acrion with the property defined which is a part of the 
83  * behaviour property
84  */
85 struct ActionGroup(Property) if (isOneOf!(Property, ActionProperties)) {
86     Info!Property[] infos;
87     mixin HiBONRecord!();
88 }
89 
90 /// Returns: true if I is a ActionGroup
91 enum isActionGroup(alias I) = __traits(isSame, TemplateOf!I, ActionGroup);
92 
93 /** 
94  * Contains all information of a scenario
95  * the class name of the scenario and the description
96  * it also contains all the action groups of the scenario
97  */
98 @safe
99 struct ScenarioGroup {
100     @("Scenario") Info!Scenario info;
101     ActionGroup!(Given) given; /// Given actions
102     @optional ActionGroup!(When) when; /// When actions
103     ActionGroup!(Then) then; /// Then actions
104     @optional ActionGroup!(But) but; /// But actions
105     mixin HiBONRecord!();
106 }
107 
108 /** 
109  * Contains all the sceanrio groups and information of the Feature
110  */
111 @safe
112 struct FeatureGroup {
113     @optional string alternative;
114     Info!Feature info; /// Information of the Feature
115     ScenarioGroup[] scenarios; /// This all the information of each Sceanrio
116     mixin HiBONRecord!();
117 }
118 
119 /// All action-properties of a Scenario
120 alias ActionProperties = AliasSeq!(Given, When, Then, But);
121 /// All mandatory actions of a Scenario (Given, Then)
122 alias MandatoryActionProperties = Erase!(When, Erase!(But, ActionProperties));
123 
124 /**
125  * Params:
126 * T = Scenario class
127 * name = the action member function
128  * Returns: method in a scenario class
129  *          void if no member has been found
130  */
131 template getMethod(alias T, string name) {
132     alias method = __traits(getOverloads, T, name);
133     static if (method.length > 0) {
134         alias getMethod = method[0];
135     }
136     else {
137         alias getMethod = void;
138     }
139 }
140 
141 // Check of the getMethod 
142 static unittest {
143     static assert(isCallable!(getMethod!(BehaviourUnittest.Some_awesome_feature, "is_debited")));
144     static assert(!isCallable!(getMethod!(BehaviourUnittest.Some_awesome_feature, "count")));
145 }
146 
147 /**
148  * Returns: an alias-sequency of all the callable members of an object/Scenario
149  */
150 template getAllCallables(T) if (is(T == class) || is(T == struct)) {
151     alias all_members = aliasSeqOf!([__traits(allMembers, T)]);
152     alias all_members_as_aliases = staticMap!(ApplyLeft!(getMethod, T), all_members);
153     alias getAllCallables = Filter!(isCallable, all_members_as_aliases);
154 }
155 
156 static unittest { // Test of getAllCallable
157     import tagion.behaviour.BehaviourUnittest;
158 
159     alias all_callables = getAllCallables!(Some_awesome_feature);
160     static assert(all_callables.length == 13);
161     static assert(allSatisfy!(isCallable, all_callables));
162 }
163 
164 /**
165  * Returns: true if the alias T is an Action
166  */
167 template hasActions(alias T) if (isCallable!T) {
168     alias hasProperty = ApplyLeft!(hasOneMemberUDA, T);
169     enum hasActions = anySatisfy!(hasProperty, ActionProperties);
170 }
171 
172 // Check if a function is an action or not
173 static unittest {
174     static assert(hasActions!(BehaviourUnittest.Some_awesome_feature.is_valid));
175     static assert(!hasActions!(BehaviourUnittest.Some_awesome_feature.helper_function));
176 }
177 
178 /**
179 Returns:
180 all the actions in a scenario
181 */
182 template getAllActions(T) if (is(T == class) || is(T == struct)) {
183     alias get_all_callable = getAllCallables!T;
184     alias getAllActions = Filter!(hasActions, get_all_callable);
185 }
186 
187 ///
188 static unittest { // Test of getAllActions
189     alias actions = getAllActions!(BehaviourUnittest.Some_awesome_feature);
190     static assert(actions.length == 7);
191     static assert(allSatisfy!(isCallable, actions));
192     static assert(allSatisfy!(hasActions, actions));
193 }
194 
195 /**
196    This template get the action with the behaviour-Property from a Behaviour object
197    Returns: The function with the behaviour-Property
198    The function fails if there is more than one behaviour with this behaviour
199    and returns void if no behaviour-Property has been found
200  */
201 template getActions(T, Property) if (is(T == class) || is(T == struct)) {
202     alias behaviours = getAllActions!T;
203     alias behaviour_with_property = Filter!(ApplyRight!(hasOneMemberUDA, Property), behaviours);
204     static if (behaviour_with_property.length > 0) {
205         alias getActions = behaviour_with_property;
206     }
207     else {
208         alias getActions = void;
209     }
210 
211 }
212 
213 ///
214 unittest {
215     alias behaviour_with_given = getActions!(BehaviourUnittest.Some_awesome_feature, Given);
216     static assert(allSatisfy!(isCallable, behaviour_with_given));
217 
218     static assert(allSatisfy!(ApplyRight!(hasOneMemberUDA, Given), behaviour_with_given));
219     static assert(is(getActions!(BehaviourUnittest.Some_awesome_feature_bad_format_missing_given, Given) == void));
220 
221     alias behaviour_with_when = getActions!(BehaviourUnittest.Some_awesome_feature, When);
222     static assert(isCallable!(behaviour_with_when));
223     static assert(hasOneMemberUDA!(behaviour_with_when, When));
224 
225 }
226 
227 /// Returns: true if T has the Property
228 enum hasProperty(alias T, Property) = !is(getActions!(T, Property) == void);
229 
230 ///
231 unittest {
232     static assert(hasProperty!(BehaviourUnittest.Some_awesome_feature, Then));
233     static assert(!hasProperty!(BehaviourUnittest.Some_awesome_feature_bad_format_missing_given, Given));
234 }
235 
236 /**
237 * Get the action propery of the alias T
238 * Returns: The behaviour property of T and void if T does not have a behaviour property
239 */
240 template getProperty(alias T) {
241     import tagion.basic.traits : getMemberUDAs;
242 
243     alias getUDAsProperty = ApplyLeft!(getMemberUDAs, T);
244     alias all_behaviour_properties = staticMap!(getUDAsProperty, ActionProperties);
245     static assert(all_behaviour_properties.length <= 1,
246             format!"The behaviour %s has more than one property %s"(T.strinof, all_behaviour_properties.stringof));
247     static if (all_behaviour_properties.length is 1) {
248         alias getProperty = all_behaviour_properties[0];
249     }
250     else {
251         alias getProperty = void;
252     }
253 }
254 
255 /// Examples: How get the behaviour property
256 unittest {
257     alias properties = getProperty!(BehaviourUnittest.Some_awesome_feature.request_cash);
258     static assert(is(typeof(properties) == When));
259     static assert(is(getProperty!(BehaviourUnittest.Some_awesome_feature.helper_function) == void));
260 }
261 
262 // Test of the getActions and check that the action-property is correct
263 @safe
264 unittest {
265     alias behaviour_of_given = getActions!(BehaviourUnittest.Some_awesome_feature, Given);
266     static assert(behaviour_of_given.length is 3);
267     static assert(getProperty!(behaviour_of_given[0]) == Given("the card is valid"));
268     static assert(getProperty!(behaviour_of_given[1]) == Given("the account is in credit"));
269     static assert(getProperty!(behaviour_of_given[2]) == Given("the dispenser contains cash"));
270 
271     alias behaviour_of_when = getActions!(BehaviourUnittest.Some_awesome_feature, When);
272     static assert(behaviour_of_when.length is 1);
273     static assert(getProperty!(behaviour_of_when[0]) == When("the Customer request cash"));
274 
275     alias behaviour_of_then = getActions!(BehaviourUnittest.Some_awesome_feature, Then);
276     static assert(behaviour_of_then.length is 2);
277     static assert(getProperty!(behaviour_of_then[0]) == Then("the account is debited"));
278     static assert(getProperty!(behaviour_of_then[1]) == Then("the cash is dispensed"));
279 
280     alias behaviour_of_but = getActions!(BehaviourUnittest.Some_awesome_feature, But);
281     static assert(behaviour_of_but.length is 1);
282     static assert(getProperty!(behaviour_of_but[0]) ==
283         But("if the Customer does not take his card, then the card must be swollowed"));
284 }
285 
286 ///Returns: true of T is a Scenario
287 enum isScenario(T) = (is(T == struct) || is(T == class)) && hasUDA!(T, Scenario);
288 
289 ///
290 static unittest {
291     static assert(isScenario!(BehaviourUnittest.Some_awesome_feature));
292     static assert(!isScenario!(BehaviourUnittest.This_is_not_a_scenario));
293 }
294 
295 enum feature_name = "feature"; /// Default enum name of an Feature module
296 
297 /** 
298  * Params:
299  *   M = the module
300 *	Returns: true if M is a feature module
301 */
302 template isFeature(alias M) if (__traits(isModule, M)) {
303     import std.algorithm.searching : any;
304 
305     enum feature_found = [__traits(allMembers, M)].any!(a => a == feature_name);
306     static if (feature_found) {
307         enum obtainFeature = __traits(getMember, M, feature_name);
308         enum isFeature = is(typeof(obtainFeature) == Feature);
309     }
310     else {
311         enum isFeature = false;
312     }
313 }
314 
315 ///
316 @safe
317 unittest {
318     import tagion.behaviour.BehaviourUnittest;
319 
320     static assert(isFeature!(tagion.behaviour.BehaviourUnittest));
321     static assert(!isFeature!(tagion.behaviour.BehaviourFeature));
322 }
323 
324 /**
325    Returns:
326    The Feature of a Module
327    If the Modules does not contain a feature then a false is returned
328  */
329 template obtainFeature(alias M) if (__traits(isModule, M)) {
330     static if (isFeature!M) {
331         enum obtainFeature = __traits(getMember, M, feature_name);
332     }
333     else {
334         enum obtainFeature = false;
335     }
336 }
337 
338 ///
339 @safe
340 unittest { // The obtainFeature of a module
341     import tagion.behaviour.BehaviourUnittest;
342 
343     static assert(obtainFeature!(tagion.behaviour.BehaviourUnittest) ==
344             Feature(
345                 "Some awesome feature should print some cash out of the blue", null));
346     static assert(!obtainFeature!(tagion.behaviour.BehaviourFeature));
347 }
348 
349 /// Helper template for Scenarios
350 protected template _Scenarios(alias M, string[] names) if (isFeature!M) {
351     static if (names.length is 0) {
352         alias _Scenarios = AliasSeq!();
353     }
354     else {
355         alias member = __traits(getMember, M, names[0]);
356         enum is_object = is(member == class) || is(member == struct);
357         static if (is_object) {
358             enum is_scenario = hasUDA!(member, Scenario);
359         }
360         else {
361             enum is_scenario = false;
362         }
363         static if (is_scenario) {
364             alias _Scenarios =
365                 AliasSeq!(
366                         member,
367                         _Scenarios!(M, names[1 .. $])
368             );
369         }
370         else {
371             alias _Scenarios = _Scenarios!(M, names[1 .. $]);
372         }
373     }
374 }
375 
376 /**
377 * Returns: An alias-sequency of all scenarios in the feature module M
378 */
379 template Scenarios(alias M) if (isFeature!M) {
380     alias Scenarios = _Scenarios!(M, [__traits(allMembers, M)]);
381 }
382 
383 ///
384 static unittest { //
385     import tagion.behaviour.BehaviourUnittest;
386 
387     alias scenarios = Scenarios!(tagion.behaviour.BehaviourUnittest);
388     alias expected_scenarios = AliasSeq!(
389             Some_awesome_feature,
390             Some_awesome_feature_bad_format_double_property,
391             Some_awesome_feature_bad_format_missing_given,
392             Some_awesome_feature_bad_format_missing_then);
393 
394     static assert(scenarios.length == expected_scenarios.length);
395     static assert(__traits(isSame, scenarios, expected_scenarios));
396 }
397 
398 /**
399 * Returns: The Scenario UDA of T and if T is not a Scenario then result is false 
400 */
401 template getScenario(T) if (is(T == class) || is(T == struct)) {
402     enum scenario_attr = getUDAs!(T, Scenario);
403     static assert(scenario_attr.length <= 1,
404             format!"%s is not a %s"(T.stringof, Scenario.stringof));
405     static if (scenario_attr.length is 1) {
406         enum getScenario = scenario_attr[0];
407     }
408     else {
409         enum getScenario = false;
410     }
411 }
412 
413 // Checks the getScenario
414 static unittest {
415     import tagion.behaviour.BehaviourUnittest;
416 
417     enum scenario = getScenario!(Some_awesome_feature);
418     static assert(is(typeof(scenario) == Scenario));
419     static assert(scenario is Scenario("Some awesome money printer", null));
420     enum not_a_scenario = getScenario!(This_is_not_a_scenario);
421     static assert(!not_a_scenario);
422 }
423 
424 version (unittest) {
425     import std.algorithm.comparison : equal;
426     import std.algorithm.iteration : joiner, map;
427     import std.array;
428     import std.range : only, zip;
429     import std.stdio;
430     import std.typecons;
431     import BehaviourUnittest = tagion.behaviour.BehaviourUnittest;
432 }