SDWebCacheCategoriesTests.m 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. * (c) Matt Galloway
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. #import "SDTestCase.h"
  10. #import <KVOController/KVOController.h>
  11. @interface SDWebCacheCategoriesTests : SDTestCase
  12. @end
  13. @implementation SDWebCacheCategoriesTests
  14. - (void)testUIImageViewSetImageWithURL {
  15. XCTestExpectation *expectation = [self expectationWithDescription:@"UIImageView setImageWithURL"];
  16. UIImageView *imageView = [[UIImageView alloc] init];
  17. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  18. [imageView sd_setImageWithURL:originalImageURL
  19. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  20. expect(image).toNot.beNil();
  21. expect(error).to.beNil();
  22. expect(originalImageURL).to.equal(imageURL);
  23. expect(imageView.image).to.equal(image);
  24. [expectation fulfill];
  25. }];
  26. expect(imageView.sd_imageURL).equal(originalImageURL);
  27. [self waitForExpectationsWithCommonTimeout];
  28. }
  29. - (void)testUIImageViewSetImageWithURLDiskSync {
  30. NSData *imageData = [NSData dataWithContentsOfFile:[self testJPEGPath]];
  31. // Ensure the image is cached in disk but not memory
  32. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
  33. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
  34. [SDImageCache.sharedImageCache storeImageDataToDisk:imageData forKey:kTestJPEGURL];
  35. UIImageView *imageView = [[UIImageView alloc] init];
  36. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  37. [imageView sd_setImageWithURL:originalImageURL
  38. placeholderImage:nil
  39. options:SDWebImageQueryDiskDataSync
  40. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  41. expect(image).toNot.beNil();
  42. expect(error).to.beNil();
  43. expect(originalImageURL).to.equal(imageURL);
  44. expect(imageView.image).to.equal(image);
  45. }];
  46. expect(imageView.sd_imageURL).equal(originalImageURL);
  47. expect(imageView.image).toNot.beNil();
  48. }
  49. #if SD_UIKIT
  50. - (void)testUIImageViewSetHighlightedImageWithURL {
  51. XCTestExpectation *expectation = [self expectationWithDescription:@"UIImageView setHighlightedImageWithURL"];
  52. UIImageView *imageView = [[UIImageView alloc] init];
  53. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  54. [imageView sd_setHighlightedImageWithURL:originalImageURL
  55. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  56. expect(image).toNot.beNil();
  57. expect(error).to.beNil();
  58. expect(originalImageURL).to.equal(imageURL);
  59. expect(imageView.highlightedImage).to.equal(image);
  60. [expectation fulfill];
  61. }];
  62. [self waitForExpectationsWithCommonTimeout];
  63. }
  64. #endif
  65. - (void)testMKAnnotationViewSetImageWithURL {
  66. XCTestExpectation *expectation = [self expectationWithDescription:@"MKAnnotationView setImageWithURL"];
  67. MKAnnotationView *annotationView = [[MKAnnotationView alloc] init];
  68. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  69. [annotationView sd_setImageWithURL:originalImageURL
  70. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  71. expect(image).toNot.beNil();
  72. expect(error).to.beNil();
  73. expect(originalImageURL).to.equal(imageURL);
  74. expect(annotationView.image).to.equal(image);
  75. [expectation fulfill];
  76. }];
  77. [self waitForExpectationsWithCommonTimeout];
  78. }
  79. #if SD_UIKIT
  80. - (void)testUIButtonSetImageWithURLNormalState {
  81. XCTestExpectation *expectation = [self expectationWithDescription:@"UIButton setImageWithURL normalState"];
  82. UIButton *button = [[UIButton alloc] init];
  83. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  84. [button sd_setImageWithURL:originalImageURL
  85. forState:UIControlStateNormal
  86. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  87. expect(image).toNot.beNil();
  88. expect(error).to.beNil();
  89. expect(originalImageURL).to.equal(imageURL);
  90. expect([button imageForState:UIControlStateNormal]).to.equal(image);
  91. [expectation fulfill];
  92. }];
  93. [self waitForExpectationsWithCommonTimeout];
  94. }
  95. - (void)testUIButtonSetImageWithURLHighlightedState {
  96. XCTestExpectation *expectation = [self expectationWithDescription:@"UIButton setImageWithURL highlightedState"];
  97. UIButton *button = [[UIButton alloc] init];
  98. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  99. [button sd_setImageWithURL:originalImageURL
  100. forState:UIControlStateHighlighted
  101. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  102. expect(image).toNot.beNil();
  103. expect(error).to.beNil();
  104. expect(originalImageURL).to.equal(imageURL);
  105. expect([button imageForState:UIControlStateHighlighted]).to.equal(image);
  106. [expectation fulfill];
  107. }];
  108. [self waitForExpectationsWithCommonTimeout];
  109. }
  110. - (void)testUIButtonSetBackgroundImageWithURLNormalState {
  111. XCTestExpectation *expectation = [self expectationWithDescription:@"UIButton setBackgroundImageWithURL normalState"];
  112. UIButton *button = [[UIButton alloc] init];
  113. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  114. [button sd_setBackgroundImageWithURL:originalImageURL
  115. forState:UIControlStateNormal
  116. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  117. expect(image).toNot.beNil();
  118. expect(error).to.beNil();
  119. expect(originalImageURL).to.equal(imageURL);
  120. expect([button backgroundImageForState:UIControlStateNormal]).to.equal(image);
  121. [expectation fulfill];
  122. }];
  123. [self waitForExpectationsWithCommonTimeout];
  124. }
  125. - (void)testUIButtonBackgroundImageCancelCurrentImageLoad {
  126. UIButton *button = [[UIButton alloc] init];
  127. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  128. [button sd_setBackgroundImageWithURL:originalImageURL forState:UIControlStateNormal];
  129. [button sd_cancelBackgroundImageLoadForState:UIControlStateNormal];
  130. NSString *backgroundImageOperationKey = [self testBackgroundImageOperationKeyForState:UIControlStateNormal];
  131. expect([button sd_imageLoadOperationForKey:backgroundImageOperationKey]).beNil();
  132. }
  133. #endif
  134. #if SD_MAC
  135. - (void)testNSButtonSetImageWithURL {
  136. XCTestExpectation *expectation = [self expectationWithDescription:@"NSButton setImageWithURL"];
  137. NSButton *button = [[NSButton alloc] init];
  138. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  139. [button sd_setImageWithURL:originalImageURL
  140. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  141. expect(image).toNot.beNil();
  142. expect(error).to.beNil();
  143. expect(originalImageURL).to.equal(imageURL);
  144. expect(button.image).to.equal(image);
  145. [expectation fulfill];
  146. }];
  147. [self waitForExpectationsWithCommonTimeout];
  148. }
  149. - (void)testNSButtonSetAlternateImageWithURL {
  150. XCTestExpectation *expectation = [self expectationWithDescription:@"NSButton setAlternateImageWithURL"];
  151. NSButton *button = [[NSButton alloc] init];
  152. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  153. [button sd_setAlternateImageWithURL:originalImageURL
  154. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  155. expect(image).toNot.beNil();
  156. expect(error).to.beNil();
  157. expect(originalImageURL).to.equal(imageURL);
  158. expect(button.alternateImage).to.equal(image);
  159. [expectation fulfill];
  160. }];
  161. [self waitForExpectationsWithCommonTimeout];
  162. }
  163. #endif
  164. - (void)testUIViewInternalSetImageWithURL {
  165. XCTestExpectation *expectation = [self expectationWithDescription:@"UIView internalSetImageWithURL"];
  166. UIView *view = [[UIView alloc] init];
  167. #if SD_MAC
  168. view.wantsLayer = YES;
  169. #endif
  170. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  171. UIImage *placeholder = [[UIImage alloc] initWithContentsOfFile:[self testJPEGPath]];
  172. [view sd_internalSetImageWithURL:originalImageURL
  173. placeholderImage:placeholder
  174. options:0
  175. context:nil
  176. setImageBlock:^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  177. if (!imageData && cacheType == SDImageCacheTypeNone) {
  178. // placeholder
  179. expect(image).to.equal(placeholder);
  180. } else {
  181. // cache or download
  182. expect(image).toNot.beNil();
  183. }
  184. view.layer.contents = (__bridge id _Nullable)(image.CGImage);
  185. }
  186. progress:nil
  187. completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  188. expect(image).toNot.beNil();
  189. expect(error).to.beNil();
  190. expect(originalImageURL).to.equal(imageURL);
  191. expect((__bridge CGImageRef)view.layer.contents == image.CGImage).to.beTruthy();
  192. [expectation fulfill];
  193. }];
  194. [self waitForExpectationsWithCommonTimeout];
  195. }
  196. - (void)testUIViewCancelWithDelayPlaceholderShouldCallbackOnceBeforeSecond {
  197. XCTestExpectation *expectation1 = [self expectationWithDescription:@"UIView internalSetImageWithURL call 1"];
  198. XCTestExpectation *expectation2 = [self expectationWithDescription:@"UIView internalSetImageWithURL call 2"];
  199. UIView *imageView = [[UIView alloc] init];
  200. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  201. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
  202. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
  203. __block NSUInteger calledSetImageTimes = 0;
  204. __block NSUInteger calledSetImageTimes2 = 0;
  205. NSString *operationKey = NSUUID.UUID.UUIDString;
  206. UIImage *placeholder1 = UIImage.new;
  207. id<SDWebImageOperation> op1 = [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:placeholder1 options:SDWebImageDelayPlaceholder context:@{ SDWebImageContextSetImageOperationKey:operationKey} setImageBlock:^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  208. // Should called before second query (We changed the cache callback in sync when cancelled)
  209. expect(calledSetImageTimes2).equal(0);
  210. calledSetImageTimes++;
  211. } progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  212. if (calledSetImageTimes == 1) {
  213. [expectation1 fulfill];
  214. }
  215. }];
  216. [op1 cancel];
  217. UIImage *placeholder2 = UIImage.new;
  218. [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:placeholder2 options:0 context:@{ SDWebImageContextSetImageOperationKey:operationKey} setImageBlock:^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  219. if (calledSetImageTimes2 == 0) {
  220. expect(image).equal(placeholder2);
  221. } else {
  222. expect(image).notTo.beNil();
  223. }
  224. calledSetImageTimes2++;
  225. } progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  226. if (calledSetImageTimes2 == 2) {
  227. [expectation2 fulfill];
  228. }
  229. }];
  230. [self waitForExpectationsWithCommonTimeout];
  231. }
  232. - (void)testUIViewCancelWithoutDelayPlaceholderShouldCallbackOnceBeforeSecond {
  233. XCTestExpectation *expectation1 = [self expectationWithDescription:@"UIView internalSetImageWithURL call 1"];
  234. XCTestExpectation *expectation2 = [self expectationWithDescription:@"UIView internalSetImageWithURL call 2"];
  235. UIView *imageView = [[UIView alloc] init];
  236. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  237. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
  238. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
  239. __block NSUInteger calledSetImageTimes = 0;
  240. __block NSUInteger calledSetImageTimes2 = 0;
  241. NSString *operationKey = NSUUID.UUID.UUIDString;
  242. UIImage *placeholder1 = UIImage.new;
  243. id<SDWebImageOperation> op1 = [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:placeholder1 options:0 context:@{ SDWebImageContextSetImageOperationKey:operationKey} setImageBlock:^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  244. // Should called before second query (We changed the cache callback in sync when cancelled)
  245. expect(calledSetImageTimes2).equal(0);
  246. calledSetImageTimes++;
  247. } progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  248. if (calledSetImageTimes == 1) {
  249. [expectation1 fulfill];
  250. }
  251. }];
  252. [op1 cancel];
  253. UIImage *placeholder2 = UIImage.new;
  254. [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:placeholder2 options:0 context:@{ SDWebImageContextSetImageOperationKey:operationKey} setImageBlock:^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  255. if (calledSetImageTimes2 == 0) {
  256. expect(image).equal(placeholder2);
  257. } else {
  258. expect(image).notTo.beNil();
  259. }
  260. calledSetImageTimes2++;
  261. } progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  262. if (calledSetImageTimes2 == 2) {
  263. [expectation2 fulfill];
  264. }
  265. }];
  266. [self waitForExpectationsWithCommonTimeout];
  267. }
  268. - (void)testUIViewAutoCancelImage {
  269. UIView *imageView = [[UIView alloc] init];
  270. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  271. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
  272. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
  273. SDWebImageCombinedOperation *op1 = [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:0 context:nil setImageBlock:nil progress:nil completed:nil];
  274. SDWebImageCombinedOperation *op2 = [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:0 context:nil setImageBlock:nil progress:nil completed:nil];
  275. // op1 should be automatically cancelled
  276. expect(op1.isCancelled).beTruthy();
  277. expect(op2.isCancelled).beFalsy();
  278. }
  279. - (void)testUIViewAvoidAutoCancelImage {
  280. UIView *imageView = [[UIView alloc] init];
  281. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  282. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
  283. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
  284. SDWebImageCombinedOperation *op1 = [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:0 context:nil setImageBlock:nil progress:nil completed:nil];
  285. SDWebImageCombinedOperation *op2 = [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:SDWebImageAvoidAutoCancelImage context:nil setImageBlock:nil progress:nil completed:nil];
  286. // opt1 should not be automatically cancelled
  287. expect(op1.isCancelled).beFalsy();
  288. expect(op2.isCancelled).beFalsy();
  289. }
  290. - (void)testUIViewCancelCurrentImageLoad {
  291. UIView *imageView = [[UIView alloc] init];
  292. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  293. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
  294. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
  295. SDWebImageCombinedOperation *op1 = [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:0 context:nil setImageBlock:nil progress:nil completed:nil];
  296. [imageView sd_cancelLatestImageLoad];
  297. expect(op1.isCancelled).beTruthy();
  298. NSString *operationKey = NSStringFromClass(UIView.class);
  299. expect([imageView sd_imageLoadOperationForKey:operationKey]).beNil();
  300. }
  301. - (void)testUIViewCancelCurrentImageLoadWithTransition {
  302. UIView *imageView = [[UIView alloc] init];
  303. NSURL *firstImageUrl = [NSURL URLWithString:@"https://placehold.co/201x201.jpg"];
  304. NSURL *secondImageUrl = [NSURL URLWithString:@"https://placehold.co/201x201.png"];
  305. // First, reset our caches
  306. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:firstImageUrl.absoluteString];
  307. [SDImageCache.sharedImageCache removeImageFromDiskForKey:firstImageUrl.absoluteString];
  308. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:secondImageUrl.absoluteString];
  309. [SDImageCache.sharedImageCache removeImageFromDiskForKey:secondImageUrl.absoluteString];
  310. // Next, lets put our second image into memory, so that the next time
  311. // we load it, it will come from memory, and thus shouldUseTransition will be NO
  312. XCTestExpectation *firstLoadExpectation = [self expectationWithDescription:@"First image loaded"];
  313. [imageView sd_internalSetImageWithURL:secondImageUrl placeholderImage:nil options:0 context:nil setImageBlock:nil progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  314. [firstLoadExpectation fulfill];
  315. }];
  316. [self waitForExpectations:@[firstLoadExpectation]
  317. timeout:5.0];
  318. // Now, lets load a new image using a transition
  319. XCTestExpectation *secondLoadExpectation = [self expectationWithDescription:@"Second image loaded"];
  320. XCTestExpectation *transitionPreparesExpectation = [self expectationWithDescription:@"Transition prepares"];
  321. // Build a custom transition with a completion block that
  322. // we do not expect to be called, because we cancel in the
  323. // middle of a transition
  324. XCTestExpectation *transitionCompletionExpecation = [self expectationWithDescription:@"Transition completed"];
  325. transitionCompletionExpecation.inverted = YES;
  326. SDWebImageTransition *customTransition = [SDWebImageTransition new];
  327. customTransition.duration = 1.0;
  328. customTransition.prepares = ^(__kindof UIView * _Nonnull view, UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  329. [transitionPreparesExpectation fulfill];
  330. };
  331. customTransition.completion = ^(BOOL finished) {
  332. [transitionCompletionExpecation fulfill];
  333. };
  334. // Now, load our first image URL (maybe as part of a UICollectionView)
  335. // We use a custom context to ensure a unique ImageOperationKey for every load
  336. // that is requested
  337. NSMutableDictionary *context = [NSMutableDictionary new];
  338. context[SDWebImageContextSetImageOperationKey] = firstImageUrl.absoluteString;
  339. imageView.sd_imageTransition = customTransition;
  340. [imageView sd_internalSetImageWithURL:firstImageUrl placeholderImage:nil options:0 context:context setImageBlock:nil progress:nil completed:nil];
  341. [self waitForExpectations:@[transitionPreparesExpectation] timeout:5.0];
  342. // At this point, our transition has started, and so we cancel the load operation,
  343. // perhaps as a result of a call to `prepareForReuse` in a UICollectionViewCell
  344. [imageView sd_cancelLatestImageLoad];
  345. // Now, we update our context's imageOperationKey and URL, perhaps
  346. // because of a re-use of a UICollectionViewCell. In this case,
  347. // we are assigning an image URL that is already present in the
  348. // memory cache
  349. context[SDWebImageContextSetImageOperationKey] = secondImageUrl.absoluteString;
  350. [imageView sd_internalSetImageWithURL:secondImageUrl placeholderImage:nil options:0 context:context setImageBlock:nil progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  351. [secondLoadExpectation fulfill];
  352. }];
  353. // The original load operation's transitionCompletionExpecation should never
  354. // be called (it has been inverted, above)
  355. [self waitForExpectations:@[secondLoadExpectation, transitionCompletionExpecation]
  356. timeout:5.0];
  357. }
  358. - (void)testUIViewCancelCallbackWithError {
  359. XCTestExpectation *expectation = [self expectationWithDescription:@"UIView internalSetImageWithURL cancel callback error"];
  360. UIView *imageView = [[UIView alloc] init];
  361. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  362. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
  363. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
  364. [imageView sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:0 context:nil setImageBlock:nil progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  365. expect(error).notTo.beNil();
  366. expect(error.code).equal(SDWebImageErrorCancelled);
  367. [expectation fulfill];
  368. }];
  369. [imageView sd_cancelLatestImageLoad];
  370. [self waitForExpectationsWithCommonTimeout];
  371. }
  372. - (void)testUIViewImageProgressKVOWork {
  373. XCTestExpectation *expectation = [self expectationWithDescription:@"UIView imageProgressKVO failed"];
  374. UIView *view = [[UIView alloc] init];
  375. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  376. [self.KVOController observe:view.sd_imageProgress keyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
  377. NSProgress *progress = object;
  378. NSNumber *completedValue = change[NSKeyValueChangeNewKey];
  379. expect(progress.fractionCompleted).equal(completedValue.doubleValue);
  380. // mark that KVO is called
  381. [progress setUserInfoObject:@(YES) forKey:NSStringFromSelector(@selector(testUIViewImageProgressKVOWork))];
  382. }];
  383. // Clear the disk cache to force download from network
  384. [[SDImageCache sharedImageCache] removeImageForKey:kTestJPEGURL withCompletion:^{
  385. [view sd_internalSetImageWithURL:originalImageURL placeholderImage:nil options:0 context:nil setImageBlock:nil progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  386. expect(view.sd_imageProgress.fractionCompleted).equal(1.0);
  387. expect([view.sd_imageProgress.userInfo[NSStringFromSelector(_cmd)] boolValue]).equal(YES);
  388. [expectation fulfill];
  389. }];
  390. }];
  391. [self waitForExpectationsWithCommonTimeout];
  392. }
  393. - (void)testUIViewTransitionFromNetworkWork {
  394. XCTestExpectation *expectation = [self expectationWithDescription:@"UIView transition from network does not work"];
  395. // Attach a window, or CALayer will not submit drawing
  396. UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
  397. // Cover each convenience method
  398. imageView.sd_imageTransition = SDWebImageTransition.fadeTransition;
  399. imageView.sd_imageTransition = SDWebImageTransition.flipFromTopTransition;
  400. imageView.sd_imageTransition = SDWebImageTransition.flipFromLeftTransition;
  401. imageView.sd_imageTransition = SDWebImageTransition.flipFromBottomTransition;
  402. imageView.sd_imageTransition = SDWebImageTransition.flipFromRightTransition;
  403. imageView.sd_imageTransition = SDWebImageTransition.curlUpTransition;
  404. imageView.sd_imageTransition = SDWebImageTransition.curlDownTransition;
  405. imageView.sd_imageTransition.duration = 1;
  406. #if SD_UIKIT
  407. [self.window addSubview:imageView];
  408. #else
  409. imageView.wantsLayer = YES;
  410. [self.window.contentView addSubview:imageView];
  411. #endif
  412. UIImage *placeholder = [[UIImage alloc] initWithContentsOfFile:[self testJPEGPath]];
  413. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  414. __weak typeof(imageView) wimageView = imageView;
  415. [imageView sd_setImageWithURL:originalImageURL
  416. placeholderImage:placeholder
  417. options:SDWebImageForceTransition
  418. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  419. __strong typeof(wimageView) simageView = imageView;
  420. // Delay to let CALayer commit the transition in next runloop
  421. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMinDelayNanosecond), dispatch_get_main_queue(), ^{
  422. // Check current view contains layer animation
  423. NSArray *animationKeys = simageView.layer.animationKeys;
  424. expect(animationKeys.count).beGreaterThan(0);
  425. [expectation fulfill];
  426. });
  427. }];
  428. [self waitForExpectationsWithCommonTimeout];
  429. }
  430. - (void)testUIViewTransitionFromDiskWork {
  431. XCTestExpectation *expectation = [self expectationWithDescription:@"UIView transition from disk does not work"];
  432. // Attach a window, or CALayer will not submit drawing
  433. UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
  434. imageView.sd_imageTransition = SDWebImageTransition.fadeTransition;
  435. imageView.sd_imageTransition.duration = 1;
  436. #if SD_UIKIT
  437. [self.window addSubview:imageView];
  438. #else
  439. imageView.wantsLayer = YES;
  440. [self.window.contentView addSubview:imageView];
  441. #endif
  442. NSData *imageData = [NSData dataWithContentsOfFile:[self testJPEGPath]];
  443. UIImage *placeholder = [[UIImage alloc] initWithData:imageData];
  444. // Ensure the image is cached in disk but not memory
  445. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
  446. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
  447. [SDImageCache.sharedImageCache storeImageDataToDisk:imageData forKey:kTestJPEGURL];
  448. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  449. __weak typeof(imageView) wimageView = imageView;
  450. [imageView sd_setImageWithURL:originalImageURL
  451. placeholderImage:placeholder
  452. options:SDWebImageFromCacheOnly // Ensure we queired from disk cache
  453. completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  454. [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestJPEGURL];
  455. [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestJPEGURL];
  456. __strong typeof(wimageView) simageView = imageView;
  457. // Delay to let CALayer commit the transition in next runloop
  458. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMinDelayNanosecond), dispatch_get_main_queue(), ^{
  459. // Check current view contains layer animation
  460. NSArray *animationKeys = simageView.layer.animationKeys;
  461. expect(animationKeys.count).beGreaterThan(0);
  462. [expectation fulfill];
  463. });
  464. }];
  465. [self waitForExpectationsWithCommonTimeout];
  466. }
  467. - (void)testUIViewIndicatorWork {
  468. XCTestExpectation *expectation = [self expectationWithDescription:@"UIView indicator does not work"];
  469. UIImageView *imageView = [[UIImageView alloc] init];
  470. #if SD_IOS
  471. imageView.sd_imageIndicator = SDWebImageActivityIndicator.grayIndicator;
  472. // Cover each convience method, finally use progress indicator for test
  473. imageView.sd_imageIndicator = SDWebImageActivityIndicator.grayLargeIndicator;
  474. imageView.sd_imageIndicator = SDWebImageActivityIndicator.whiteIndicator;
  475. imageView.sd_imageIndicator = SDWebImageActivityIndicator.whiteLargeIndicator;
  476. imageView.sd_imageIndicator = SDWebImageProgressIndicator.barIndicator;
  477. #endif
  478. imageView.sd_imageIndicator = SDWebImageProgressIndicator.defaultIndicator;
  479. // Test setter trigger removeFromSuperView
  480. expect(imageView.subviews.count).equal(1);
  481. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  482. __weak typeof(imageView) wimageView = imageView;
  483. [imageView sd_setImageWithURL:originalImageURL
  484. placeholderImage:nil options:SDWebImageFromLoaderOnly progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
  485. dispatch_async(dispatch_get_main_queue(), ^{
  486. __strong typeof(wimageView) simageView = imageView;
  487. UIView *indicatorView = simageView.subviews.firstObject;
  488. expect(indicatorView).equal(simageView.sd_imageIndicator.indicatorView);
  489. if (receivedSize <= 0 || expectedSize <= 0) {
  490. return;
  491. }
  492. // Base on current implementation, since we dispatch the progressBlock to main queue, the indicator's progress state should be synchonized
  493. double progress = 0;
  494. double imageProgress = (double)receivedSize / (double)expectedSize;
  495. #if SD_UIKIT
  496. progress = ((UIProgressView *)simageView.sd_imageIndicator.indicatorView).progress;
  497. #else
  498. progress = ((NSProgressIndicator *)simageView.sd_imageIndicator.indicatorView).doubleValue / 100;
  499. #endif
  500. expect(progress).equal(imageProgress);
  501. });
  502. } completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  503. __strong typeof(wimageView) simageView = imageView;
  504. double progress = 0;
  505. #if SD_UIKIT
  506. progress = ((UIProgressView *)simageView.sd_imageIndicator.indicatorView).progress;
  507. #else
  508. progress = ((NSProgressIndicator *)simageView.sd_imageIndicator.indicatorView).doubleValue / 100;
  509. #endif
  510. // Finish progress is 1
  511. expect(progress).equal(1);
  512. [expectation fulfill];
  513. }];
  514. [self waitForExpectationsWithCommonTimeout];
  515. }
  516. - (void)testUIViewOperationKeyContextWorks {
  517. XCTestExpectation *expectation = [self expectationWithDescription:@"UIView operation key context should pass through"];
  518. UIView *view = [[UIView alloc] init];
  519. NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
  520. SDWebImageManager *customManager = [[SDWebImageManager alloc] initWithCache:SDImageCachesManager.sharedManager loader:SDImageLoadersManager.sharedManager];
  521. customManager.optionsProcessor = [SDWebImageOptionsProcessor optionsProcessorWithBlock:^SDWebImageOptionsResult * _Nullable(NSURL * _Nullable url, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
  522. // expect manager does not exist, avoid retain cycle
  523. expect(context[SDWebImageContextCustomManager]).beNil();
  524. // expect operation key to be the image view class
  525. expect(context[SDWebImageContextSetImageOperationKey]).equal(NSStringFromClass(view.class));
  526. return [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
  527. }];
  528. [view sd_internalSetImageWithURL:originalImageURL
  529. placeholderImage:nil
  530. options:0
  531. context:@{SDWebImageContextCustomManager: customManager}
  532. setImageBlock:nil
  533. progress:nil
  534. completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  535. [expectation fulfill];
  536. }];
  537. [self waitForExpectationsWithCommonTimeout];
  538. }
  539. // test url is nil
  540. - (void)testUIViewImageUrlForNilWorks {
  541. XCTestExpectation *expectation = [self expectationWithDescription:@"Completion is called with url is nil"];
  542. UIImageView *imageView = [[UIImageView alloc] init];
  543. SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"Test"];
  544. cache.config.shouldUseWeakMemoryCache = YES;
  545. SDWebImageManager *imageManager = [[SDWebImageManager alloc] initWithCache:cache loader:[SDWebImageDownloader sharedDownloader]];
  546. [imageView sd_setImageWithURL:nil placeholderImage:nil options:0 context:@{SDWebImageContextCustomManager:imageManager} progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  547. expect(image).to.beNil();
  548. [expectation fulfill];
  549. }];
  550. [self waitForExpectationsWithCommonTimeout];
  551. }
  552. // test url is NSString.
  553. - (void)testUIViewImageUrlForStringWorks {
  554. XCTestExpectation *expectation = [self expectationWithDescription:@"Completion is called with url is NSString"];
  555. UIImageView *imageView = [[UIImageView alloc] init];
  556. SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"Test"];
  557. cache.config.shouldUseWeakMemoryCache = YES;
  558. SDWebImageManager *imageManager = [[SDWebImageManager alloc] initWithCache:cache loader:[SDWebImageDownloader sharedDownloader]];
  559. [imageView sd_setImageWithURL:(NSURL *)kTestJPEGURL placeholderImage:nil options:0 context:@{SDWebImageContextCustomManager:imageManager} progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  560. expect(image).notTo.beNil();
  561. [expectation fulfill];
  562. }];
  563. [self waitForExpectationsWithCommonTimeout];
  564. }
  565. // test url is NSURL
  566. - (void)testUIViewImageUrlForNSURLWorks {
  567. XCTestExpectation *expectation = [self expectationWithDescription:@"Completion is called with url is NSURL"];
  568. UIImageView *imageView = [[UIImageView alloc] init];
  569. SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"Test"];
  570. cache.config.shouldUseWeakMemoryCache = YES;
  571. SDWebImageManager *imageManager = [[SDWebImageManager alloc] initWithCache:cache loader:[SDWebImageDownloader sharedDownloader]];
  572. [imageView sd_setImageWithURL:[NSURL URLWithString:kTestJPEGURL] placeholderImage:nil options:0 context:@{SDWebImageContextCustomManager:imageManager} progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  573. expect(image).notTo.beNil();
  574. [expectation fulfill];
  575. }];
  576. [self waitForExpectationsWithCommonTimeout];
  577. }
  578. #pragma mark - Helper
  579. - (NSString *)testJPEGPath {
  580. NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
  581. return [testBundle pathForResource:@"TestImage" ofType:@"jpg"];
  582. }
  583. #if SD_UIKIT
  584. - (NSString *)testBackgroundImageOperationKeyForState:(UIControlState)state {
  585. return [NSString stringWithFormat:@"UIButtonBackgroundImageOperation%lu", (unsigned long)state];
  586. }
  587. #endif
  588. @end