1 module dshould.contain;
2 
3 import dshould.ShouldType;
4 import dshould.basic : not, should;
5 import std.traits : isAssociativeArray;
6 
7 /**
8  * The word `.contain` takes one value, expected to appear in the range on the left hand side.
9  */
10 public void contain(Should, T)(Should should, T expected, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__)
11 if (isInstanceOf!(ShouldType, Should))
12 {
13     should.allowOnlyWords!("not", "only").before!"contain";
14 
15     should.addWord!"contain".checkContain(expected, file, line);
16 }
17 
18 ///
19 unittest
20 {
21     [2, 3, 4].should.contain(3);
22     [2, 3, 4].should.not.contain(5);
23 }
24 
25 public auto contain(Should)(Should should)
26 if (isInstanceOf!(ShouldType, Should))
27 {
28     should.allowOnlyWords!("not").before!"contain";
29 
30     return should.addWord!"contain";
31 }
32 
33 /**
34  * The phrase `.contain.only` or `.only.contain` takes a range, the elements of which are expected to be the only
35  * elements appearing in the range on the left hand side.
36  */
37 public void only(Should, T)(Should should, T expected, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__)
38 if (isInstanceOf!(ShouldType, Should))
39 {
40     should.requireWord!"contain".before!"only";
41     should.allowOnlyWords!("not", "contain").before!"only";
42 
43     should.addWord!"only".checkContain(expected, file, line);
44 }
45 
46 ///
47 unittest
48 {
49     [3, 4].should.only.contain([4, 3]);
50     [3, 4].should.only.contain([1, 2, 3, 4]);
51     [3, 4].should.contain.only([4, 3]);
52     [2, 3, 4].should.not.only.contain([4, 3]);
53 }
54 
55 public auto only(Should)(Should should)
56 if (isInstanceOf!(ShouldType, Should))
57 {
58     should.allowOnlyWords!("not").before!"only";
59 
60     return should.addWord!"only";
61 }
62 
63 /**
64  * The phrase `.contain.all` takes a range, all elements of which are expected to appear
65  * in the range on the left hand side.
66  */
67 public void all(Should, T)(Should should, T expected, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__)
68 if (isInstanceOf!(ShouldType, Should))
69 {
70     should.requireWord!"contain".before!"all";
71     should.allowOnlyWords!("not", "contain").before!"all";
72 
73     should.addWord!"all".checkContain(expected, file, line);
74 }
75 
76 ///
77 unittest
78 {
79     [2, 3, 4].should.contain.all([3]);
80     [2, 3, 4].should.contain.all([4, 3]);
81     [2, 3, 4].should.not.contain.all([3, 4, 5]);
82 }
83 
84 /**
85  * The phrase `.contain.any` takes a range, at least one element of which is expected to appear
86  * in the range on the left hand side.
87  */
88 public void any(Should, T)(Should should, T expected, Fence _ = Fence(), string file = __FILE__, size_t line = __LINE__)
89 if (isInstanceOf!(ShouldType, Should))
90 {
91     should.requireWord!"contain".before!"any";
92     should.allowOnlyWords!("not", "contain").before!"any";
93 
94     should.addWord!"any".checkContain(expected, file, line);
95 }
96 
97 ///
98 unittest
99 {
100     [2, 3, 4].should.contain.any([4, 5]);
101     [2, 3, 4].should.not.contain.any([5, 6]);
102 }
103 
104 unittest
105 {
106     const int[] constArray = [2, 3, 4];
107 
108     constArray.should.contain(4);
109 }
110 
111 unittest
112 {
113     string[string] assocArray = ["a": "b"];
114 
115     assocArray.should.contain.all(["a": "b"]);
116     assocArray.should.not.contain.any(["a": "y"]);
117     assocArray.should.not.contain.any(["x": "b"]);
118 }
119 
120 private void checkContain(Should, T)(Should should, T expected, string file, size_t line)
121 if (isInstanceOf!(ShouldType, Should) && isAssociativeArray!T && is(const typeof(should.got()) == const T))
122 {
123     import std.algorithm : any, all, canFind;
124     import std.format : format;
125 
126     alias pairEqual = (a, b) => a.key == b.key && a.value == b.value;
127 
128     with (should)
129     {
130         auto got = should.got();
131         alias inExpected = a => expected.byKeyValue.canFind!pairEqual(a);
132         alias inGot = a => got.byKeyValue.canFind!pairEqual(a);
133 
134         static if (hasWord!"only")
135         {
136             static if (hasWord!"not")
137             {
138                 check(
139                     !got.byKeyValue.all!inExpected,
140                     format("associative array containing pairs other than %s", expected),
141                     format("%s", got),
142                     file, line);
143             }
144             else
145             {
146                 check(
147                     got.byKeyValue.all!inExpected,
148                     format("associative array containing only the pairs %s", expected),
149                     format("%s", got),
150                     file, line);
151             }
152         }
153         else static if (hasWord!"all")
154         {
155             static if (hasWord!"not")
156             {
157                 check(
158                     !expected.byKeyValue.all!inGot,
159                     format("associative array not containing every pair in %s", expected),
160                     format("%s", got),
161                     file, line);
162             }
163             else
164             {
165                 check(
166                     expected.byKeyValue.all!inGot,
167                     format("associative array containing every pair in %s", expected),
168                     format("%s", got),
169                     file, line);
170             }
171         }
172         else static if (hasWord!"any")
173         {
174             static if (hasWord!"not")
175             {
176                 check(
177                     !expected.byKeyValue.any!inGot,
178                     format("associative array not containing any pair in %s", expected),
179                     format("%s", got),
180                     file, line);
181             }
182             else
183             {
184                 check(
185                     expected.byKeyValue.any!inGot,
186                     format("associative array containing any pair of %s", expected),
187                     format("%s", got),
188                     file, line);
189             }
190         }
191         else
192         {
193             static assert(false,
194                 `bad grammar: expected "contain all", "contain any", "contain only" (or "only contain")`);
195         }
196     }
197 }
198 
199 private void checkContain(Should, T)(Should should, T expected, string file, size_t line)
200 if (isInstanceOf!(ShouldType, Should) && !isAssociativeArray!T)
201 {
202     import std.algorithm : any, all, canFind;
203     import std.format : format;
204     import std.range : ElementType, save;
205 
206     with (should)
207     {
208         auto got = should.got();
209 
210         enum rhsIsValue = is(const T == const ElementType!(typeof(got)));
211 
212         static if (rhsIsValue)
213         {
214             allowOnlyWords!("not", "only", "contain").before!"contain";
215 
216             static if (hasWord!"only")
217             {
218                 static if (hasWord!"not")
219                 {
220                     check(
221                         got.any!(a => a != expected),
222                         format("array containing values other than %s", expected),
223                         format("%s", got),
224                         file, line);
225                 }
226                 else
227                 {
228                     check(
229                         got.all!(a => a == expected),
230                         format("array containing only the value %s", expected),
231                         format("%s", got),
232                         file, line);
233                 }
234             }
235             else
236             {
237                 static if (hasWord!"not")
238                 {
239                     check(
240                         !got.save.canFind(expected),
241                         format("array not containing %s", expected),
242                         format("%s", got),
243                         file, line);
244                 }
245                 else
246                 {
247                     check(
248                         got.save.canFind(expected),
249                         format("array containing %s", expected),
250                         format("%s", got),
251                         file, line);
252                 }
253             }
254         }
255         else
256         {
257             static if (hasWord!"only")
258             {
259                 static if (hasWord!"not")
260                 {
261                     check(
262                         !got.all!(a => expected.save.canFind(a)),
263                         format("array containing values other than %s", expected),
264                         format("%s", got),
265                         file, line);
266                 }
267                 else
268                 {
269                     check(
270                         got.all!(a => expected.save.canFind(a)),
271                         format("array containing only the values %s", expected),
272                         format("%s", got),
273                         file, line);
274                 }
275             }
276             else static if (hasWord!"all")
277             {
278                 static if (hasWord!"not")
279                 {
280                     check(
281                         !expected.all!(a => got.save.canFind(a)),
282                         format("array not containing every value in %s", expected),
283                         format("%s", got),
284                         file, line);
285                 }
286                 else
287                 {
288                     check(
289                         expected.all!(a => got.save.canFind(a)),
290                         format("array containing every value in %s", expected),
291                         format("%s", got),
292                         file, line);
293                 }
294             }
295             else static if (hasWord!"any")
296             {
297                 static if (hasWord!"not")
298                 {
299                     check(
300                         !expected.any!(a => got.save.canFind(a)),
301                         format("array not containing any value in %s", expected),
302                         format("%s", got),
303                         file, line);
304                 }
305                 else
306                 {
307                     check(
308                         expected.any!(a => got.save.canFind(a)),
309                         format("array containing any value of %s", expected),
310                         format("%s", got),
311                         file, line);
312                 }
313             }
314             else
315             {
316                 static assert(false,
317                     `bad grammar: expected "contain all", "contain any", "contain only" (or "only contain")`);
318             }
319         }
320     }
321 }
322 
323 unittest
324 {
325     const foo = ["foo": "bar"];
326 
327     foo.byKey.should.contain("foo");
328     foo.byValue.should.contain("bar");
329 }