HighlightsFeed.test.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. "use strict";
  2. import {actionTypes as at} from "common/Actions.jsm";
  3. import {Dedupe} from "common/Dedupe.jsm";
  4. import {GlobalOverrider} from "test/unit/utils";
  5. import injector from "inject!lib/HighlightsFeed.jsm";
  6. import {Screenshots} from "lib/Screenshots.jsm";
  7. const FAKE_LINKS = new Array(20).fill(null).map((v, i) => ({url: `http://www.site${i}.com`}));
  8. const FAKE_IMAGE = "data123";
  9. describe("Highlights Feed", () => {
  10. let HighlightsFeed;
  11. let SECTION_ID;
  12. let SYNC_BOOKMARKS_FINISHED_EVENT;
  13. let BOOKMARKS_RESTORE_SUCCESS_EVENT;
  14. let BOOKMARKS_RESTORE_FAILED_EVENT;
  15. let feed;
  16. let globals;
  17. let sandbox;
  18. let links;
  19. let fakeScreenshot;
  20. let fakeNewTabUtils;
  21. let filterAdultStub;
  22. let sectionsManagerStub;
  23. let downloadsManagerStub;
  24. let shortURLStub;
  25. let fakePageThumbs;
  26. beforeEach(() => {
  27. globals = new GlobalOverrider();
  28. sandbox = globals.sandbox;
  29. fakeNewTabUtils = {
  30. activityStreamLinks: {
  31. getHighlights: sandbox.spy(() => Promise.resolve(links)),
  32. deletePocketEntry: sandbox.spy(() => Promise.resolve({})),
  33. archivePocketEntry: sandbox.spy(() => Promise.resolve({})),
  34. },
  35. activityStreamProvider: {_processHighlights: sandbox.spy(l => l.slice(0, 1))},
  36. };
  37. sectionsManagerStub = {
  38. onceInitialized: sinon.stub().callsFake(callback => callback()),
  39. enableSection: sinon.spy(),
  40. disableSection: sinon.spy(),
  41. updateSection: sinon.spy(),
  42. updateSectionCard: sinon.spy(),
  43. sections: new Map([["highlights", {id: "highlights"}]]),
  44. };
  45. downloadsManagerStub = sinon.stub().returns({
  46. getDownloads: () => [{"url": "https://site.com/download"}],
  47. onAction: sinon.spy(),
  48. init: sinon.spy(),
  49. });
  50. fakeScreenshot = {
  51. getScreenshotForURL: sandbox.spy(() => Promise.resolve(FAKE_IMAGE)),
  52. maybeCacheScreenshot: Screenshots.maybeCacheScreenshot,
  53. _shouldGetScreenshots: sinon.stub().returns(true),
  54. };
  55. filterAdultStub = sinon.stub().returns([]);
  56. shortURLStub = sinon.stub().callsFake(site => site.url.match(/\/([^/]+)/)[1]);
  57. fakePageThumbs = {
  58. addExpirationFilter: sinon.stub(),
  59. removeExpirationFilter: sinon.stub(),
  60. };
  61. globals.set("NewTabUtils", fakeNewTabUtils);
  62. globals.set("PageThumbs", fakePageThumbs);
  63. ({HighlightsFeed, SECTION_ID, SYNC_BOOKMARKS_FINISHED_EVENT, BOOKMARKS_RESTORE_SUCCESS_EVENT, BOOKMARKS_RESTORE_FAILED_EVENT} = injector({
  64. "lib/FilterAdult.jsm": {filterAdult: filterAdultStub},
  65. "lib/ShortURL.jsm": {shortURL: shortURLStub},
  66. "lib/SectionsManager.jsm": {SectionsManager: sectionsManagerStub},
  67. "lib/Screenshots.jsm": {Screenshots: fakeScreenshot},
  68. "common/Dedupe.jsm": {Dedupe},
  69. "lib/DownloadsManager.jsm": {DownloadsManager: downloadsManagerStub},
  70. }));
  71. sandbox.spy(global.Services.obs, "addObserver");
  72. sandbox.spy(global.Services.obs, "removeObserver");
  73. feed = new HighlightsFeed();
  74. feed.store = {
  75. dispatch: sinon.spy(),
  76. getState() { return this.state; },
  77. state: {
  78. Prefs: {values: {"filterAdult": false, "section.highlights.includePocket": false, "section.highlights.includeDownloads": false}},
  79. TopSites: {
  80. initialized: true,
  81. rows: Array(12).fill(null).map((v, i) => ({url: `http://www.topsite${i}.com`})),
  82. },
  83. Sections: [{id: "highlights", initialized: false}],
  84. },
  85. subscribe: sinon.stub().callsFake(cb => { cb(); return () => {}; }),
  86. };
  87. links = FAKE_LINKS;
  88. });
  89. afterEach(() => {
  90. globals.restore();
  91. });
  92. describe("#init", () => {
  93. it("should create a HighlightsFeed", () => {
  94. assert.instanceOf(feed, HighlightsFeed);
  95. });
  96. it("should register a expiration filter", () => {
  97. assert.calledOnce(fakePageThumbs.addExpirationFilter);
  98. });
  99. it("should add the sync observer", () => {
  100. feed.onAction({type: at.INIT});
  101. assert.calledWith(global.Services.obs.addObserver, feed, SYNC_BOOKMARKS_FINISHED_EVENT);
  102. assert.calledWith(global.Services.obs.addObserver, feed, BOOKMARKS_RESTORE_SUCCESS_EVENT);
  103. assert.calledWith(global.Services.obs.addObserver, feed, BOOKMARKS_RESTORE_FAILED_EVENT);
  104. });
  105. it("should call SectionsManager.onceInitialized on INIT", () => {
  106. feed.onAction({type: at.INIT});
  107. assert.calledOnce(sectionsManagerStub.onceInitialized);
  108. });
  109. it("should enable its section", () => {
  110. feed.onAction({type: at.INIT});
  111. assert.calledOnce(sectionsManagerStub.enableSection);
  112. assert.calledWith(sectionsManagerStub.enableSection, SECTION_ID);
  113. });
  114. it("should fetch highlights on postInit", () => {
  115. feed.fetchHighlights = sinon.spy();
  116. feed.postInit();
  117. assert.calledOnce(feed.fetchHighlights);
  118. });
  119. it("should hook up the store for the DownloadsManager", () => {
  120. feed.onAction({type: at.INIT});
  121. assert.calledOnce(feed.downloadsManager.init);
  122. });
  123. });
  124. describe("#observe", () => {
  125. beforeEach(() => {
  126. feed.fetchHighlights = sinon.spy();
  127. });
  128. it("should fetch higlights when we are done a sync for bookmarks", () => {
  129. feed.observe(null, SYNC_BOOKMARKS_FINISHED_EVENT, "bookmarks");
  130. assert.calledWith(feed.fetchHighlights, {broadcast: true});
  131. });
  132. it("should fetch highlights after a successful import", () => {
  133. feed.observe(null, BOOKMARKS_RESTORE_SUCCESS_EVENT, "html");
  134. assert.calledWith(feed.fetchHighlights, {broadcast: true});
  135. });
  136. it("should fetch highlights after a failed import", () => {
  137. feed.observe(null, BOOKMARKS_RESTORE_FAILED_EVENT, "json");
  138. assert.calledWith(feed.fetchHighlights, {broadcast: true});
  139. });
  140. it("should not fetch higlights when we are doing a sync for something that is not bookmarks", () => {
  141. feed.observe(null, SYNC_BOOKMARKS_FINISHED_EVENT, "tabs");
  142. assert.notCalled(feed.fetchHighlights);
  143. });
  144. it("should not fetch higlights for other events", () => {
  145. feed.observe(null, "someotherevent", "bookmarks");
  146. assert.notCalled(feed.fetchHighlights);
  147. });
  148. });
  149. describe("#filterForThumbnailExpiration", () => {
  150. it("should pass rows.urls to the callback provided", () => {
  151. const rows = [{url: "foo.com"}, {"url": "bar.com"}];
  152. feed.store.state.Sections = [{id: "highlights", rows, initialized: true}];
  153. const stub = sinon.stub();
  154. feed.filterForThumbnailExpiration(stub);
  155. assert.calledOnce(stub);
  156. assert.calledWithExactly(stub, rows.map(r => r.url));
  157. });
  158. it("should include preview_image_url (if present) in the callback results", () => {
  159. const rows = [{url: "foo.com"}, {"url": "bar.com", "preview_image_url": "bar.jpg"}];
  160. feed.store.state.Sections = [{id: "highlights", rows, initialized: true}];
  161. const stub = sinon.stub();
  162. feed.filterForThumbnailExpiration(stub);
  163. assert.calledOnce(stub);
  164. assert.calledWithExactly(stub, ["foo.com", "bar.com", "bar.jpg"]);
  165. });
  166. it("should pass an empty array if not initialized", () => {
  167. const rows = [{url: "foo.com"}, {"url": "bar.com"}];
  168. feed.store.state.Sections = [{rows, initialized: false}];
  169. const stub = sinon.stub();
  170. feed.filterForThumbnailExpiration(stub);
  171. assert.calledOnce(stub);
  172. assert.calledWithExactly(stub, []);
  173. });
  174. });
  175. describe("#fetchHighlights", () => {
  176. const fetchHighlights = async options => {
  177. await feed.fetchHighlights(options);
  178. return sectionsManagerStub.updateSection.firstCall.args[1].rows;
  179. };
  180. it("should return early if TopSites are not initialised", async () => {
  181. sandbox.spy(feed.linksCache, "request");
  182. feed.store.state.TopSites.initialized = false;
  183. feed.store.state.Prefs.values["feeds.topsites"] = true;
  184. // Initially TopSites is uninitialised and fetchHighlights should return.
  185. await feed.fetchHighlights();
  186. assert.notCalled(fakeNewTabUtils.activityStreamLinks.getHighlights);
  187. assert.notCalled(feed.linksCache.request);
  188. });
  189. it("should return early if Sections are not initialised", async () => {
  190. sandbox.spy(feed.linksCache, "request");
  191. feed.store.state.TopSites.initialized = true;
  192. feed.store.state.Prefs.values["feeds.topsites"] = true;
  193. feed.store.state.Sections = [];
  194. await feed.fetchHighlights();
  195. assert.notCalled(fakeNewTabUtils.activityStreamLinks.getHighlights);
  196. assert.notCalled(feed.linksCache.request);
  197. });
  198. it("should fetch Highlights if TopSites are initialised", async () => {
  199. sandbox.spy(feed.linksCache, "request");
  200. // fetchHighlights should continue
  201. feed.store.state.TopSites.initialized = true;
  202. await feed.fetchHighlights();
  203. assert.calledOnce(feed.linksCache.request);
  204. assert.calledOnce(fakeNewTabUtils.activityStreamLinks.getHighlights);
  205. });
  206. it("should chronologically order highlight data types", async () => {
  207. links = [
  208. {url: "https://site0.com", type: "bookmark", bookmarkGuid: "1234", date_added: Date.now() - 80}, // 4th newest
  209. {url: "https://site1.com", type: "history", bookmarkGuid: "1234", date_added: Date.now() - 60}, // 3rd newest
  210. {url: "https://site2.com", type: "history", date_added: Date.now() - 160}, // append at the end
  211. {url: "https://site3.com", type: "history", date_added: Date.now() - 60}, // append at the end
  212. {url: "https://site4.com", type: "pocket", date_added: Date.now()}, // newest highlight
  213. {url: "https://site5.com", type: "pocket", date_added: Date.now() - 100}, // 5th newest
  214. {url: "https://site6.com", type: "bookmark", bookmarkGuid: "1234", date_added: Date.now() - 40}, // 2nd newest
  215. ];
  216. let highlights = await fetchHighlights();
  217. assert.equal(highlights[0].url, links[4].url);
  218. assert.equal(highlights[1].url, links[6].url);
  219. assert.equal(highlights[2].url, links[1].url);
  220. assert.equal(highlights[3].url, links[0].url);
  221. assert.equal(highlights[4].url, links[5].url);
  222. assert.equal(highlights[5].url, links[2].url);
  223. assert.equal(highlights[6].url, links[3].url);
  224. });
  225. it("should fetch Highlights if TopSites are not enabled", async () => {
  226. sandbox.spy(feed.linksCache, "request");
  227. feed.store.state.Prefs.values["feeds.topsites"] = false;
  228. await feed.fetchHighlights();
  229. assert.calledOnce(feed.linksCache.request);
  230. assert.calledOnce(fakeNewTabUtils.activityStreamLinks.getHighlights);
  231. });
  232. it("should add hostname and hasImage to each link", async () => {
  233. links = [{url: "https://mozilla.org"}];
  234. const highlights = await fetchHighlights();
  235. assert.equal(highlights[0].hostname, "mozilla.org");
  236. assert.equal(highlights[0].hasImage, true);
  237. });
  238. it("should add an existing image if it exists to the link without calling fetchImage", async () => {
  239. links = [{url: "https://mozilla.org", image: FAKE_IMAGE}];
  240. sinon.spy(feed, "fetchImage");
  241. const highlights = await fetchHighlights();
  242. assert.equal(highlights[0].image, FAKE_IMAGE);
  243. assert.notCalled(feed.fetchImage);
  244. });
  245. it("should call fetchImage with the correct arguments for new links", async () => {
  246. links = [{url: "https://mozilla.org", preview_image_url: "https://mozilla.org/preview.jog"}];
  247. sinon.spy(feed, "fetchImage");
  248. await feed.fetchHighlights();
  249. assert.calledOnce(feed.fetchImage);
  250. const [arg] = feed.fetchImage.firstCall.args;
  251. assert.propertyVal(arg, "url", links[0].url);
  252. assert.propertyVal(arg, "preview_image_url", links[0].preview_image_url);
  253. });
  254. it("should not include any links already in Top Sites", async () => {
  255. links = [
  256. {url: "https://mozilla.org"},
  257. {url: "http://www.topsite0.com"},
  258. {url: "http://www.topsite1.com"},
  259. {url: "http://www.topsite2.com"},
  260. ];
  261. const highlights = await fetchHighlights();
  262. assert.equal(highlights.length, 1);
  263. assert.equal(highlights[0].url, links[0].url);
  264. });
  265. it("should include bookmark but not history already in Top Sites", async () => {
  266. links = [
  267. {url: "http://www.topsite0.com", type: "bookmark"},
  268. {url: "http://www.topsite1.com", type: "history"},
  269. ];
  270. const highlights = await fetchHighlights();
  271. assert.equal(highlights.length, 1);
  272. assert.equal(highlights[0].url, links[0].url);
  273. });
  274. it("should not include history of same hostname as a bookmark", async () => {
  275. links = [
  276. {url: "https://site.com/bookmark", type: "bookmark"},
  277. {url: "https://site.com/history", type: "history"},
  278. ];
  279. const highlights = await fetchHighlights();
  280. assert.equal(highlights.length, 1);
  281. assert.equal(highlights[0].url, links[0].url);
  282. });
  283. it("should take the first history of a hostname", async () => {
  284. links = [
  285. {url: "https://site.com/first", type: "history"},
  286. {url: "https://site.com/second", type: "history"},
  287. {url: "https://other", type: "history"},
  288. ];
  289. const highlights = await fetchHighlights();
  290. assert.equal(highlights.length, 2);
  291. assert.equal(highlights[0].url, links[0].url);
  292. assert.equal(highlights[1].url, links[2].url);
  293. });
  294. it("should take a bookmark, a pocket, and downloaded item of the same hostname", async () => {
  295. links = [
  296. {url: "https://site.com/bookmark", type: "bookmark"},
  297. {url: "https://site.com/pocket", type: "pocket"},
  298. {url: "https://site.com/download", type: "download"},
  299. ];
  300. const highlights = await fetchHighlights();
  301. assert.equal(highlights.length, 3);
  302. assert.equal(highlights[0].url, links[0].url);
  303. assert.equal(highlights[1].url, links[1].url);
  304. assert.equal(highlights[2].url, links[2].url);
  305. });
  306. it("should includePocket pocket items when pref is true", async () => {
  307. feed.store.state.Prefs.values["section.highlights.includePocket"] = true;
  308. sandbox.spy(feed.linksCache, "request");
  309. await feed.fetchHighlights();
  310. assert.propertyVal(feed.linksCache.request.firstCall.args[0], "excludePocket", false);
  311. });
  312. it("should not includePocket pocket items when pref is false", async () => {
  313. sandbox.spy(feed.linksCache, "request");
  314. await feed.fetchHighlights();
  315. assert.propertyVal(feed.linksCache.request.firstCall.args[0], "excludePocket", true);
  316. });
  317. it("should not include downloads when includeDownloads pref is false", async () => {
  318. links = [
  319. {url: "https://site.com/bookmark", type: "bookmark"},
  320. {url: "https://site.com/pocket", type: "pocket"},
  321. ];
  322. // Check that we don't have the downloaded item in highlights
  323. const highlights = await fetchHighlights();
  324. assert.equal(highlights.length, 2);
  325. assert.equal(highlights[0].url, links[0].url);
  326. assert.equal(highlights[1].url, links[1].url);
  327. });
  328. it("should include downloads when includeDownloads pref is true", async () => {
  329. feed.store.state.Prefs.values["section.highlights.includeDownloads"] = true;
  330. links = [
  331. {url: "https://site.com/bookmark", type: "bookmark"},
  332. {url: "https://site.com/pocket", type: "pocket"},
  333. ];
  334. // Check that we did get the downloaded item in highlights
  335. const highlights = await fetchHighlights();
  336. assert.equal(highlights.length, 3);
  337. assert.equal(highlights[0].url, links[0].url);
  338. assert.equal(highlights[1].url, links[1].url);
  339. assert.equal(highlights[2].url, "https://site.com/download");
  340. assert.propertyVal(highlights[2], "type", "download");
  341. });
  342. it("should only take 1 download", async () => {
  343. feed.store.state.Prefs.values["section.highlights.includeDownloads"] = true;
  344. feed.downloadsManager.getDownloads = () => [
  345. {"url": "https://site1.com/download"},
  346. {"url": "https://site2.com/download"},
  347. ];
  348. links = [{url: "https://site.com/bookmark", type: "bookmark"}];
  349. // Check that we did get the most single recent downloaded item in highlights
  350. const highlights = await fetchHighlights();
  351. assert.equal(highlights.length, 2);
  352. assert.equal(highlights[0].url, links[0].url);
  353. assert.equal(highlights[1].url, "https://site1.com/download");
  354. });
  355. it("should sort bookmarks, pocket, and downloads chronologically", async () => {
  356. feed.store.state.Prefs.values["section.highlights.includeDownloads"] = true;
  357. feed.downloadsManager.getDownloads = () => [
  358. {url: "https://site1.com/download", type: "download", date_added: Date.now()},
  359. ];
  360. links = [
  361. {url: "https://site.com/bookmark", type: "bookmark", date_added: Date.now() - 10000},
  362. {url: "https://site2.com/pocket", type: "pocket", date_added: Date.now() - 5000},
  363. {url: "https://site3.com/visited", type: "history", date_added: Date.now()},
  364. ];
  365. // Check that the higlights are ordered chronologically by their 'date_added'
  366. const highlights = await fetchHighlights();
  367. assert.equal(highlights.length, 4);
  368. assert.equal(highlights[0].url, "https://site1.com/download");
  369. assert.equal(highlights[1].url, links[1].url);
  370. assert.equal(highlights[2].url, links[0].url);
  371. assert.equal(highlights[3].url, links[2].url); // history item goes last
  372. });
  373. it("should set type to bookmark if there is a bookmarkGuid", async () => {
  374. feed.store.state.Prefs.values["section.highlights.includeBookmarks"] = true;
  375. links = [{url: "https://mozilla.org", type: "history", bookmarkGuid: "1234567890"}];
  376. const highlights = await fetchHighlights();
  377. assert.equal(highlights[0].type, "bookmark");
  378. });
  379. it("should keep history type if there is a bookmarkGuid but don't include bookmarks", async () => {
  380. feed.store.state.Prefs.values["section.highlights.includeBookmarks"] = false;
  381. links = [{url: "https://mozilla.org", type: "history", bookmarkGuid: "1234567890"}];
  382. const highlights = await fetchHighlights();
  383. assert.propertyVal(highlights[0], "type", "history");
  384. });
  385. it("should not filter out adult pages when pref is false", async () => {
  386. await feed.fetchHighlights();
  387. assert.notCalled(filterAdultStub);
  388. });
  389. it("should filter out adult pages when pref is true", async () => {
  390. feed.store.state.Prefs.values.filterAdult = true;
  391. const highlights = await fetchHighlights();
  392. // The stub filters out everything
  393. assert.calledOnce(filterAdultStub);
  394. assert.equal(highlights.length, 0);
  395. });
  396. it("should not expose internal link properties", async () => {
  397. const highlights = await fetchHighlights();
  398. const internal = Object.keys(highlights[0]).filter(key => key.startsWith("__"));
  399. assert.equal(internal.join(""), "");
  400. });
  401. it("should broadcast if feed is not initialized", async () => {
  402. links = [];
  403. await fetchHighlights();
  404. assert.calledOnce(sectionsManagerStub.updateSection);
  405. assert.calledWithExactly(sectionsManagerStub.updateSection, SECTION_ID, {rows: []}, true);
  406. });
  407. it("should broadcast if options.broadcast is true", async () => {
  408. links = [];
  409. feed.store.state.Sections[0].initialized = true;
  410. await fetchHighlights({broadcast: true});
  411. assert.calledOnce(sectionsManagerStub.updateSection);
  412. assert.calledWithExactly(sectionsManagerStub.updateSection, SECTION_ID, {rows: []}, true);
  413. });
  414. it("should not broadcast if options.broadcast is false and initialized is true", async () => {
  415. links = [];
  416. feed.store.state.Sections[0].initialized = true;
  417. await fetchHighlights({broadcast: false});
  418. assert.calledOnce(sectionsManagerStub.updateSection);
  419. assert.calledWithExactly(sectionsManagerStub.updateSection, SECTION_ID, {rows: []}, false);
  420. });
  421. });
  422. describe("#fetchImage", () => {
  423. const FAKE_URL = "https://mozilla.org";
  424. const FAKE_IMAGE_URL = "https://mozilla.org/preview.jpg";
  425. function fetchImage(page) {
  426. return feed.fetchImage(Object.assign({__sharedCache: {updateLink() {}}},
  427. page));
  428. }
  429. it("should capture the image, if available", async () => {
  430. await fetchImage({
  431. preview_image_url: FAKE_IMAGE_URL,
  432. url: FAKE_URL,
  433. });
  434. assert.calledOnce(fakeScreenshot.getScreenshotForURL);
  435. assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_IMAGE_URL);
  436. });
  437. it("should fall back to capturing a screenshot", async () => {
  438. await fetchImage({url: FAKE_URL});
  439. assert.calledOnce(fakeScreenshot.getScreenshotForURL);
  440. assert.calledWith(fakeScreenshot.getScreenshotForURL, FAKE_URL);
  441. });
  442. it("should call SectionsManager.updateSectionCard with the right arguments", async () => {
  443. await fetchImage({
  444. preview_image_url: FAKE_IMAGE_URL,
  445. url: FAKE_URL,
  446. });
  447. assert.calledOnce(sectionsManagerStub.updateSectionCard);
  448. assert.calledWith(sectionsManagerStub.updateSectionCard, "highlights", FAKE_URL, {image: FAKE_IMAGE}, true);
  449. });
  450. it("should not update the card with the image", async () => {
  451. const card = {
  452. preview_image_url: FAKE_IMAGE_URL,
  453. url: FAKE_URL,
  454. };
  455. await fetchImage(card);
  456. assert.notProperty(card, "image");
  457. });
  458. });
  459. describe("#uninit", () => {
  460. it("should disable its section", () => {
  461. feed.onAction({type: at.UNINIT});
  462. assert.calledOnce(sectionsManagerStub.disableSection);
  463. assert.calledWith(sectionsManagerStub.disableSection, SECTION_ID);
  464. });
  465. it("should remove the expiration filter", () => {
  466. feed.onAction({type: at.UNINIT});
  467. assert.calledOnce(fakePageThumbs.removeExpirationFilter);
  468. });
  469. it("should remove the sync and Places observers", () => {
  470. feed.onAction({type: at.UNINIT});
  471. assert.calledWith(global.Services.obs.removeObserver, feed, SYNC_BOOKMARKS_FINISHED_EVENT);
  472. assert.calledWith(global.Services.obs.removeObserver, feed, BOOKMARKS_RESTORE_SUCCESS_EVENT);
  473. assert.calledWith(global.Services.obs.removeObserver, feed, BOOKMARKS_RESTORE_FAILED_EVENT);
  474. });
  475. });
  476. describe("#onAction", () => {
  477. it("should relay all actions to DownloadsManager.onAction", () => {
  478. let action = {type: at.COPY_DOWNLOAD_LINK, data: {url: "foo.png"}, _target: {}};
  479. feed.onAction(action);
  480. assert.calledWith(feed.downloadsManager.onAction, action);
  481. });
  482. it("should fetch highlights on SYSTEM_TICK", async () => {
  483. await feed.fetchHighlights();
  484. feed.fetchHighlights = sinon.spy();
  485. feed.onAction({type: at.SYSTEM_TICK});
  486. assert.calledOnce(feed.fetchHighlights);
  487. assert.calledWithExactly(feed.fetchHighlights, {broadcast: false});
  488. });
  489. it("should fetch highlights on PREF_CHANGED for include prefs", async () => {
  490. feed.fetchHighlights = sinon.spy();
  491. feed.onAction({type: at.PREF_CHANGED, data: {name: "section.highlights.includeBookmarks"}});
  492. assert.calledOnce(feed.fetchHighlights);
  493. assert.calledWith(feed.fetchHighlights, {broadcast: true});
  494. });
  495. it("should not fetch highlights on PREF_CHANGED for other prefs", async () => {
  496. feed.fetchHighlights = sinon.spy();
  497. feed.onAction({type: at.PREF_CHANGED, data: {name: "section.topstories.pocketCta"}});
  498. assert.notCalled(feed.fetchHighlights);
  499. });
  500. it("should fetch highlights on PLACES_HISTORY_CLEARED", async () => {
  501. await feed.fetchHighlights();
  502. feed.fetchHighlights = sinon.spy();
  503. feed.onAction({type: at.PLACES_HISTORY_CLEARED});
  504. assert.calledOnce(feed.fetchHighlights);
  505. assert.calledWith(feed.fetchHighlights, {broadcast: true});
  506. });
  507. it("should fetch highlights on DOWNLOAD_CHANGED", async () => {
  508. await feed.fetchHighlights();
  509. feed.fetchHighlights = sinon.spy();
  510. feed.onAction({type: at.DOWNLOAD_CHANGED});
  511. assert.calledOnce(feed.fetchHighlights);
  512. assert.calledWith(feed.fetchHighlights, {broadcast: true});
  513. });
  514. it("should fetch highlights on PLACES_LINKS_CHANGED", async () => {
  515. await feed.fetchHighlights();
  516. feed.fetchHighlights = sinon.spy();
  517. sandbox.stub(feed.linksCache, "expire");
  518. feed.onAction({type: at.PLACES_LINKS_CHANGED});
  519. assert.calledOnce(feed.fetchHighlights);
  520. assert.calledWith(feed.fetchHighlights, {broadcast: false});
  521. assert.calledOnce(feed.linksCache.expire);
  522. });
  523. it("should fetch highlights on PLACES_LINK_BLOCKED", async () => {
  524. await feed.fetchHighlights();
  525. feed.fetchHighlights = sinon.spy();
  526. feed.onAction({type: at.PLACES_LINK_BLOCKED});
  527. assert.calledOnce(feed.fetchHighlights);
  528. assert.calledWith(feed.fetchHighlights, {broadcast: true});
  529. });
  530. it("should fetch highlights and expire the cache on PLACES_SAVED_TO_POCKET", async () => {
  531. await feed.fetchHighlights();
  532. feed.fetchHighlights = sinon.spy();
  533. sandbox.stub(feed.linksCache, "expire");
  534. feed.onAction({type: at.PLACES_SAVED_TO_POCKET});
  535. assert.calledOnce(feed.fetchHighlights);
  536. assert.calledWith(feed.fetchHighlights, {broadcast: false});
  537. assert.calledOnce(feed.linksCache.expire);
  538. });
  539. it("should call fetchHighlights with broadcast false on TOP_SITES_UPDATED", () => {
  540. sandbox.stub(feed, "fetchHighlights");
  541. feed.onAction({type: at.TOP_SITES_UPDATED});
  542. assert.calledOnce(feed.fetchHighlights);
  543. assert.calledWithExactly(feed.fetchHighlights, {broadcast: false});
  544. });
  545. it("should call fetchHighlights when deleting or archiving from Pocket", async () => {
  546. feed.fetchHighlights = sinon.spy();
  547. feed.onAction({type: at.POCKET_LINK_DELETED_OR_ARCHIVED, data: {pocket_id: 12345}});
  548. assert.calledOnce(feed.fetchHighlights);
  549. assert.calledWithExactly(feed.fetchHighlights, {broadcast: true});
  550. });
  551. });
  552. });