1 module fluentasserts.core.operations.contain;
2 
3 import std.algorithm;
4 import std.array;
5 import std.conv;
6 
7 import fluentasserts.core.array;
8 import fluentasserts.core.results;
9 import fluentasserts.core.evaluation;
10 import fluentasserts.core.serializers;
11 
12 import fluentasserts.core.lifecycle;
13 
14 version(unittest) {
15   import fluentasserts.core.expect;
16 }
17 
18 ///
19 IResult[] contain(ref Evaluation evaluation) @safe nothrow {
20   evaluation.message.addText(".");
21 
22   IResult[] results = [];
23 
24   auto expectedPieces = evaluation.expectedValue.strValue.parseList.cleanString;
25   auto testData = evaluation.currentValue.strValue.cleanString;
26 
27   if(!evaluation.isNegated) {
28     auto missingValues = expectedPieces.filter!(a => !testData.canFind(a)).array;
29 
30     if(missingValues.length > 0) {
31       addLifecycleMessage(evaluation, missingValues);
32       try results ~= new ExpectedActualResult(createResultMessage(evaluation.expectedValue, expectedPieces), testData);
33       catch(Exception e) {
34         results ~= e.toResults;
35         return results;
36       }
37     }
38   } else {
39     auto presentValues = expectedPieces.filter!(a => testData.canFind(a)).array;
40 
41     if(presentValues.length > 0) {
42       string message = "to not contain ";
43 
44       if(presentValues.length > 1) {
45         message ~= "any ";
46       }
47 
48       message ~= evaluation.expectedValue.strValue;
49 
50       evaluation.message.addText(" ");
51 
52       if(presentValues.length == 1) {
53         try evaluation.message.addValue(presentValues[0]); catch(Exception e) {
54           evaluation.message.addText(" some value ");
55         }
56 
57         evaluation.message.addText(" is present in ");
58       } else {
59         try evaluation.message.addValue(presentValues.to!string); catch(Exception e) {
60           evaluation.message.addText(" some values ");
61         }
62 
63         evaluation.message.addText(" are present in ");
64       }
65 
66       evaluation.message.addValue(evaluation.currentValue.strValue);
67       evaluation.message.addText(".");
68 
69       try results ~= new ExpectedActualResult(message, testData);
70       catch(Exception e) {
71         results ~= e.toResults;
72         return results;
73       }
74     }
75   }
76 
77   return results;
78 }
79 
80 ///
81 IResult[] arrayContain(ref Evaluation evaluation) @trusted nothrow {
82   evaluation.message.addText(".");
83 
84   IResult[] results = [];
85 
86   auto expectedPieces = evaluation.expectedValue.proxyValue.toArray;
87   auto testData = evaluation.currentValue.proxyValue.toArray;
88 
89   if(!evaluation.isNegated) {
90     auto missingValues = expectedPieces.filter!(a => testData.filter!(b => b.isEqualTo(a)).empty).array;
91 
92     if(missingValues.length > 0) {
93       addLifecycleMessage(evaluation, missingValues);
94       try results ~= new ExpectedActualResult(createResultMessage(evaluation.expectedValue, expectedPieces), evaluation.currentValue.strValue);
95       catch(Exception e) {
96         results ~= e.toResults;
97         return results;
98       }
99     }
100   } else {
101     auto presentValues = expectedPieces.filter!(a => !testData.filter!(b => b.isEqualTo(a)).empty).array;
102 
103     if(presentValues.length > 0) {
104       addNegatedLifecycleMessage(evaluation, presentValues);
105       try results ~= new ExpectedActualResult(createNegatedResultMessage(evaluation.expectedValue, expectedPieces), evaluation.currentValue.strValue);
106       catch(Exception e) {
107         results ~= e.toResults;
108         return results;
109       }
110     }
111   }
112 
113   return results;
114 }
115 
116 ///
117 IResult[] arrayContainOnly(ref Evaluation evaluation) @safe nothrow {
118   evaluation.message.addText(".");
119 
120   IResult[] results = [];
121 
122   auto expectedPieces = evaluation.expectedValue.proxyValue.toArray;
123   auto testData = evaluation.currentValue.proxyValue.toArray;
124 
125   auto comparison = ListComparison!EquableValue(testData, expectedPieces);
126 
127   EquableValue[] missing;
128   EquableValue[] extra;
129   EquableValue[] common;
130 
131   try {
132     missing = comparison.missing;
133     extra = comparison.extra;
134     common = comparison.common;
135   } catch(Exception e) {
136     results ~= e.toResults;
137 
138     return results;
139   }
140 
141   string strExtra = "";
142   string strMissing = "";
143 
144   if(extra.length > 0) {
145     strExtra = extra.niceJoin(evaluation.currentValue.typeName);
146   }
147 
148   if(missing.length > 0) {
149     strMissing = missing.niceJoin(evaluation.currentValue.typeName);
150   }
151 
152   if(!evaluation.isNegated) {
153     auto isSuccess = missing.length == 0 && extra.length == 0 && common.length == testData.length;
154 
155     if(!isSuccess) {
156       try results ~= new ExpectedActualResult("", testData.niceJoin(evaluation.currentValue.typeName));
157       catch(Exception e) {
158         results ~= e.toResults;
159         return results;
160       }
161 
162       try results ~= new ExtraMissingResult(strExtra, strMissing);
163       catch(Exception e) {
164         results ~= e.toResults;
165         return results;
166       }
167     }
168   } else {
169     auto isSuccess = (missing.length != 0 || extra.length != 0) || common.length != testData.length;
170 
171     if(!isSuccess) {
172       try results ~= new ExpectedActualResult("to not contain " ~ expectedPieces.niceJoin(evaluation.currentValue.typeName), testData.niceJoin(evaluation.currentValue.typeName));
173       catch(Exception e) {
174         results ~= e.toResults;
175         return results;
176       }
177     }
178   }
179 
180   return results;
181 }
182 
183 ///
184 void addLifecycleMessage(ref Evaluation evaluation, string[] missingValues) @safe nothrow {
185   evaluation.message.addText(" ");
186 
187   if(missingValues.length == 1) {
188     try evaluation.message.addValue(missingValues[0]); catch(Exception) {
189       evaluation.message.addText(" some value ");
190     }
191 
192     evaluation.message.addText(" is missing from ");
193   } else {
194     try {
195       evaluation.message.addValue(missingValues.niceJoin(evaluation.currentValue.typeName));
196     } catch(Exception) {
197       evaluation.message.addText(" some values ");
198     }
199 
200     evaluation.message.addText(" are missing from ");
201   }
202 
203   evaluation.message.addValue(evaluation.currentValue.strValue);
204   evaluation.message.addText(".");
205 }
206 
207 ///
208 void addLifecycleMessage(ref Evaluation evaluation, EquableValue[] missingValues) @safe nothrow {
209   auto missing = missingValues.map!(a => a.getSerialized.cleanString).array;
210 
211   addLifecycleMessage(evaluation, missing);
212 }
213 
214 ///
215 void addNegatedLifecycleMessage(ref Evaluation evaluation, string[] presentValues) @safe nothrow {
216   evaluation.message.addText(" ");
217 
218   if(presentValues.length == 1) {
219     try evaluation.message.addValue(presentValues[0]); catch(Exception e) {
220       evaluation.message.addText(" some value ");
221     }
222 
223     evaluation.message.addText(" is present in ");
224   } else {
225     try evaluation.message.addValue(presentValues.niceJoin(evaluation.currentValue.typeName));
226     catch(Exception e) {
227       evaluation.message.addText(" some values ");
228     }
229 
230     evaluation.message.addText(" are present in ");
231   }
232 
233   evaluation.message.addValue(evaluation.currentValue.strValue);
234   evaluation.message.addText(".");
235 }
236 
237 ///
238 void addNegatedLifecycleMessage(ref Evaluation evaluation, EquableValue[] missingValues) @safe nothrow {
239   auto missing = missingValues.map!(a => a.getSerialized).array;
240 
241   addNegatedLifecycleMessage(evaluation, missing);
242 }
243 
244 string createResultMessage(ValueEvaluation expectedValue, string[] expectedPieces) @safe nothrow {
245   string message = "to contain ";
246 
247   if(expectedPieces.length > 1) {
248     message ~= "all ";
249   }
250 
251   message ~= expectedValue.strValue;
252 
253   return message;
254 }
255 
256 ///
257 string createResultMessage(ValueEvaluation expectedValue, EquableValue[] missingValues) @safe nothrow {
258   auto missing = missingValues.map!(a => a.getSerialized).array;
259 
260   return createResultMessage(expectedValue, missing);
261 }
262 
263 string createNegatedResultMessage(ValueEvaluation expectedValue, string[] expectedPieces) @safe nothrow {
264   string message = "to not contain ";
265 
266   if(expectedPieces.length > 1) {
267     message ~= "any ";
268   }
269 
270   message ~= expectedValue.strValue;
271 
272   return message;
273 }
274 
275 ///
276 string createNegatedResultMessage(ValueEvaluation expectedValue, EquableValue[] missingValues) @safe nothrow {
277   auto missing = missingValues.map!(a => a.getSerialized).array;
278 
279   return createNegatedResultMessage(expectedValue, missing);
280 }
281 
282 string niceJoin(string[] values, string typeName = "") @safe nothrow {
283   string result = "";
284 
285   try {
286     result = values.to!string;
287 
288     if(!typeName.canFind("string")) {
289       result = result.replace(`"`, "");
290     }
291   } catch(Exception) {}
292 
293   return result;
294 }
295 
296 string niceJoin(EquableValue[] values, string typeName = "") @safe nothrow {
297   return values.map!(a => a.getSerialized.cleanString).array.niceJoin(typeName);
298 }
299