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 }