Merge pull request #3975 from sarracini/bug_1432657
Fix Bug 1432657 - Add pocket card type to highlight types
This commit is contained in:
Коммит
b033185d8e
|
@ -33,6 +33,7 @@ type_label_visited=Visited
|
|||
type_label_bookmarked=Bookmarked
|
||||
type_label_synced=Synced from another device
|
||||
type_label_recommended=Trending
|
||||
type_label_pocket=Saved to Pocket
|
||||
# LOCALIZATION NOTE(type_label_open): Open is an adjective, as in "page is open"
|
||||
type_label_open=Open
|
||||
type_label_topic=Topic
|
||||
|
@ -58,6 +59,8 @@ confirm_history_delete_p1=Are you sure you want to delete every instance of this
|
|||
# page from history.
|
||||
confirm_history_delete_notice_p2=This action cannot be undone.
|
||||
menu_action_save_to_pocket=Save to Pocket
|
||||
menu_action_delete_pocket=Delete from Pocket
|
||||
menu_action_archive_pocket=Archive in Pocket
|
||||
|
||||
# LOCALIZATION NOTE (search_for_something_with): {search_term} is a placeholder
|
||||
# for what the user has typed in the search input field, e.g. 'Search for ' +
|
||||
|
|
|
@ -25,9 +25,11 @@ this.globalImportContext = globalImportContext;
|
|||
// }
|
||||
const actionTypes = {};
|
||||
for (const type of [
|
||||
"ARCHIVE_FROM_POCKET",
|
||||
"BLOCK_URL",
|
||||
"BOOKMARK_URL",
|
||||
"DELETE_BOOKMARK_BY_ID",
|
||||
"DELETE_FROM_POCKET",
|
||||
"DELETE_HISTORY_URL",
|
||||
"DELETE_HISTORY_URL_CONFIRM",
|
||||
"DIALOG_CANCEL",
|
||||
|
@ -53,6 +55,7 @@ for (const type of [
|
|||
"PLACES_HISTORY_CLEARED",
|
||||
"PLACES_LINKS_DELETED",
|
||||
"PLACES_LINK_BLOCKED",
|
||||
"PLACES_SAVED_TO_POCKET",
|
||||
"PREFS_INITIAL_VALUES",
|
||||
"PREF_CHANGED",
|
||||
"RICH_ICON_MISSING",
|
||||
|
|
|
@ -265,6 +265,22 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
return item;
|
||||
})
|
||||
}));
|
||||
case at.PLACES_SAVED_TO_POCKET:
|
||||
if (!action.data) {
|
||||
return prevState;
|
||||
}
|
||||
return prevState.map(section => Object.assign({}, section, {
|
||||
rows: section.rows.map(item => {
|
||||
if (item.url === action.data.url) {
|
||||
return Object.assign({}, item, {
|
||||
pocket_id: action.data.pocket_id,
|
||||
title: action.data.title,
|
||||
type: "pocket"
|
||||
});
|
||||
}
|
||||
return item;
|
||||
})
|
||||
}));
|
||||
case at.PLACES_BOOKMARK_REMOVED:
|
||||
if (!action.data) {
|
||||
return prevState;
|
||||
|
@ -291,6 +307,10 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
|
|||
case at.PLACES_LINK_BLOCKED:
|
||||
return prevState.map(section =>
|
||||
Object.assign({}, section, {rows: section.rows.filter(site => site.url !== action.data.url)}));
|
||||
case at.DELETE_FROM_POCKET:
|
||||
case at.ARCHIVE_FROM_POCKET:
|
||||
return prevState.map(section =>
|
||||
Object.assign({}, section, {rows: section.rows.filter(site => site.pocket_id !== action.data.pocket_id)}));
|
||||
default:
|
||||
return prevState;
|
||||
}
|
||||
|
|
|
@ -14,5 +14,9 @@ export const cardContextTypes = {
|
|||
now: {
|
||||
intlID: "type_label_now",
|
||||
icon: "now"
|
||||
},
|
||||
pocket: {
|
||||
intlID: "type_label_pocket",
|
||||
icon: "pocket-small"
|
||||
}
|
||||
};
|
||||
|
|
|
@ -34,7 +34,7 @@ export class ContextMenu extends React.PureComponent {
|
|||
<ul role="menu" className="context-menu-list">
|
||||
{this.props.options.map((option, i) => (option.type === "separator" ?
|
||||
(<li key={i} className="separator" />) :
|
||||
(<ContextMenuItem key={i} option={option} hideContext={this.hideContext} />)
|
||||
(option.type !== "empty" && <ContextMenuItem key={i} option={option} hideContext={this.hideContext} />)
|
||||
))}
|
||||
</ul>
|
||||
</span>);
|
||||
|
|
|
@ -7,6 +7,7 @@ import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
|
|||
*/
|
||||
export const LinkMenuOptions = {
|
||||
Separator: () => ({type: "separator"}),
|
||||
EmptyItem: () => ({type: "empty"}),
|
||||
RemoveBookmark: site => ({
|
||||
id: "menu_action_remove_bookmark",
|
||||
icon: "bookmark-added",
|
||||
|
@ -48,7 +49,7 @@ export const LinkMenuOptions = {
|
|||
icon: "dismiss",
|
||||
action: ac.AlsoToMain({
|
||||
type: at.BLOCK_URL,
|
||||
data: site.url
|
||||
data: {url: site.url, pocket_id: site.pocket_id}
|
||||
}),
|
||||
impression: ac.ImpressionStats({
|
||||
source: eventSource,
|
||||
|
@ -77,7 +78,7 @@ export const LinkMenuOptions = {
|
|||
type: at.DIALOG_OPEN,
|
||||
data: {
|
||||
onConfirm: [
|
||||
ac.AlsoToMain({type: at.DELETE_HISTORY_URL, data: {url: site.url, forceBlock: site.bookmarkGuid}}),
|
||||
ac.AlsoToMain({type: at.DELETE_HISTORY_URL, data: {url: site.url, pocket_id: site.pocket_id, forceBlock: site.bookmarkGuid}}),
|
||||
ac.UserEvent({event: "DELETE", source: eventSource, action_position: index})
|
||||
],
|
||||
eventSource,
|
||||
|
@ -121,6 +122,24 @@ export const LinkMenuOptions = {
|
|||
}),
|
||||
userEvent: "SAVE_TO_POCKET"
|
||||
}),
|
||||
DeleteFromPocket: site => ({
|
||||
id: "menu_action_delete_pocket",
|
||||
icon: "delete",
|
||||
action: ac.AlsoToMain({
|
||||
type: at.DELETE_FROM_POCKET,
|
||||
data: {pocket_id: site.pocket_id}
|
||||
}),
|
||||
userEvent: "DELETE_FROM_POCKET"
|
||||
}),
|
||||
ArchiveFromPocket: site => ({
|
||||
id: "menu_action_archive_pocket",
|
||||
icon: "check",
|
||||
action: ac.AlsoToMain({
|
||||
type: at.ARCHIVE_FROM_POCKET,
|
||||
data: {pocket_id: site.pocket_id}
|
||||
}),
|
||||
userEvent: "ARCHIVE_FROM_POCKET"
|
||||
}),
|
||||
EditTopSite: (site, index) => ({
|
||||
id: "edit_topsites_button_text",
|
||||
icon: "edit",
|
||||
|
@ -130,5 +149,8 @@ export const LinkMenuOptions = {
|
|||
}
|
||||
}),
|
||||
CheckBookmark: site => (site.bookmarkGuid ? LinkMenuOptions.RemoveBookmark(site) : LinkMenuOptions.AddBookmark(site)),
|
||||
CheckPinTopSite: (site, index) => (site.isPinned ? LinkMenuOptions.UnpinTopSite(site) : LinkMenuOptions.PinTopSite(site, index))
|
||||
CheckPinTopSite: (site, index) => (site.isPinned ? LinkMenuOptions.UnpinTopSite(site) : LinkMenuOptions.PinTopSite(site, index)),
|
||||
CheckSavedToPocket: (site, index) => (site.pocket_id ? LinkMenuOptions.DeleteFromPocket(site) : LinkMenuOptions.SaveToPocket(site, index)),
|
||||
CheckBookmarkOrArchive: site => (site.pocket_id ? LinkMenuOptions.ArchiveFromPocket(site) : LinkMenuOptions.CheckBookmark(site)),
|
||||
CheckDeleteHistoryOrEmpty: (site, index, eventSource) => (site.pocket_id ? LinkMenuOptions.EmptyItem() : LinkMenuOptions.DeleteUrl(site, index, eventSource))
|
||||
};
|
||||
|
|
|
@ -84,6 +84,11 @@
|
|||
background-image: url('#{$image-path}glyph-pocket-16.svg');
|
||||
}
|
||||
|
||||
&.icon-pocket-small {
|
||||
background-image: url('#{$image-path}glyph-pocket-16.svg');
|
||||
background-size: $smaller-icon-size;
|
||||
}
|
||||
|
||||
&.icon-historyItem { // sass-lint:disable-line class-name-format
|
||||
background-image: url('#{$image-path}glyph-historyItem-16.svg');
|
||||
}
|
||||
|
|
|
@ -121,6 +121,10 @@ const PREFS_CONFIG = new Map([
|
|||
title: "Collapse the Highlights section",
|
||||
value: false
|
||||
}],
|
||||
["section.highlights.includePocket", {
|
||||
title: "Boolean flag that decides whether or not to show saved Pocket stories in highlights.",
|
||||
value: true
|
||||
}],
|
||||
["section.topstories.collapsed", {
|
||||
title: "Collapse the Top Stories section",
|
||||
value: false
|
||||
|
|
|
@ -36,8 +36,8 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
}
|
||||
|
||||
_dedupeKey(site) {
|
||||
// Treat bookmarks as un-dedupable, otherwise show one of a url
|
||||
return site && (site.type === "bookmark" ? {} : site.url);
|
||||
// Treat bookmarks and pocket items as un-dedupable, otherwise show one of a url
|
||||
return site && ((site.pocket_id || site.type === "bookmark") ? {} : site.url);
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -86,8 +86,10 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
|
||||
// Request more than the expected length to allow for items being removed by
|
||||
// deduping against Top Sites or multiple history from the same domain, etc.
|
||||
// Until bug 1425496 lands, do not include saved Pocket items in highlights
|
||||
const manyPages = await this.linksCache.request({numItems: MANY_EXTRA_LENGTH, excludePocket: true});
|
||||
const manyPages = await this.linksCache.request({
|
||||
numItems: MANY_EXTRA_LENGTH,
|
||||
excludePocket: !this.store.getState().Prefs.values["section.highlights.includePocket"]
|
||||
});
|
||||
|
||||
// Remove adult highlights if we need to
|
||||
const checkedAdult = this.store.getState().Prefs.values.filterAdult ?
|
||||
|
@ -112,14 +114,20 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
this.fetchImage(page);
|
||||
}
|
||||
|
||||
// Adjust the type for 'history' items that are also 'bookmarked'
|
||||
if (page.type === "history" && page.bookmarkGuid) {
|
||||
page.type = "bookmark";
|
||||
}
|
||||
|
||||
// We want the page, so update various fields for UI
|
||||
Object.assign(page, {
|
||||
hasImage: true, // We always have an image - fall back to a screenshot
|
||||
hostname,
|
||||
type: page.bookmarkGuid ? "bookmark" : page.type
|
||||
type: page.type,
|
||||
pocket_id: page.pocket_id
|
||||
});
|
||||
|
||||
// Add the "bookmark" or not-skipped "history"
|
||||
// Add the "bookmark", "pocket", or not-skipped "history"
|
||||
highlights.push(page);
|
||||
hosts.add(hostname);
|
||||
|
||||
|
@ -152,6 +160,34 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an item from a user's saved to Pocket feed and then refreshes highlights
|
||||
* @param {int} itemID
|
||||
* The unique ID given by Pocket for that item; used to look the item up when deleting
|
||||
*/
|
||||
async deleteFromPocket(itemID) {
|
||||
try {
|
||||
await NewTabUtils.activityStreamLinks.deletePocketEntry(itemID);
|
||||
this.fetchHighlights({broadcast: true});
|
||||
} catch (err) {
|
||||
Cu.reportError(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Archives an item from a user's saved to Pocket feed and then refreshes highlights
|
||||
* @param {int} itemID
|
||||
* The unique ID given by Pocket for that item; used to look the item up when archiving
|
||||
*/
|
||||
async archiveFromPocket(itemID) {
|
||||
try {
|
||||
await NewTabUtils.activityStreamLinks.archivePocketEntry(itemID);
|
||||
this.fetchHighlights({broadcast: true});
|
||||
} catch (err) {
|
||||
Cu.reportError(err);
|
||||
}
|
||||
}
|
||||
|
||||
onAction(action) {
|
||||
switch (action.type) {
|
||||
case at.INIT:
|
||||
|
@ -166,8 +202,15 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
case at.PLACES_LINK_BLOCKED:
|
||||
this.fetchHighlights({broadcast: true});
|
||||
break;
|
||||
case at.DELETE_FROM_POCKET:
|
||||
this.deleteFromPocket(action.data.pocket_id);
|
||||
break;
|
||||
case at.ARCHIVE_FROM_POCKET:
|
||||
this.archiveFromPocket(action.data.pocket_id);
|
||||
break;
|
||||
case at.PLACES_BOOKMARK_ADDED:
|
||||
case at.PLACES_BOOKMARK_REMOVED:
|
||||
case at.PLACES_SAVED_TO_POCKET:
|
||||
this.linksCache.expire();
|
||||
this.fetchHighlights({broadcast: false});
|
||||
break;
|
||||
|
@ -181,4 +224,4 @@ this.HighlightsFeed = class HighlightsFeed {
|
|||
}
|
||||
};
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["HighlightsFeed", "SECTION_ID"];
|
||||
this.EXPORTED_SYMBOLS = ["HighlightsFeed", "SECTION_ID", "MANY_EXTRA_LENGTH"];
|
||||
|
|
|
@ -12,8 +12,6 @@ ChromeUtils.defineModuleGetter(this, "NewTabUtils",
|
|||
"resource://gre/modules/NewTabUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "Pocket",
|
||||
"chrome://pocket/content/Pocket.jsm");
|
||||
|
||||
const LINK_BLOCKED_EVENT = "newtab-linkBlocked";
|
||||
|
||||
|
@ -262,6 +260,21 @@ class PlacesFeed {
|
|||
win.openLinkIn(action.data.url, where || win.whereToOpenLink(event), params);
|
||||
}
|
||||
|
||||
async saveToPocket(site, browser) {
|
||||
const {url, title} = site;
|
||||
try {
|
||||
let data = await NewTabUtils.activityStreamLinks.addPocketEntry(url, title, browser);
|
||||
if (data) {
|
||||
this.store.dispatch(ac.BroadcastToContent({
|
||||
type: at.PLACES_SAVED_TO_POCKET,
|
||||
data: {url, title, pocket_id: data.item.item_id}
|
||||
}));
|
||||
}
|
||||
} catch (err) {
|
||||
Cu.reportError(err);
|
||||
}
|
||||
}
|
||||
|
||||
onAction(action) {
|
||||
switch (action.type) {
|
||||
case at.INIT:
|
||||
|
@ -271,9 +284,11 @@ class PlacesFeed {
|
|||
case at.UNINIT:
|
||||
this.removeObservers();
|
||||
break;
|
||||
case at.BLOCK_URL:
|
||||
NewTabUtils.activityStreamLinks.blockURL({url: action.data});
|
||||
case at.BLOCK_URL: {
|
||||
const {url, pocket_id} = action.data;
|
||||
NewTabUtils.activityStreamLinks.blockURL({url, pocket_id});
|
||||
break;
|
||||
}
|
||||
case at.BOOKMARK_URL:
|
||||
NewTabUtils.activityStreamLinks.addBookmark(action.data, action._target.browser);
|
||||
break;
|
||||
|
@ -281,10 +296,10 @@ class PlacesFeed {
|
|||
NewTabUtils.activityStreamLinks.deleteBookmark(action.data);
|
||||
break;
|
||||
case at.DELETE_HISTORY_URL: {
|
||||
const {url, forceBlock} = action.data;
|
||||
const {url, forceBlock, pocket_id} = action.data;
|
||||
NewTabUtils.activityStreamLinks.deleteHistoryEntry(url);
|
||||
if (forceBlock) {
|
||||
NewTabUtils.activityStreamLinks.blockURL({url});
|
||||
NewTabUtils.activityStreamLinks.blockURL({url, pocket_id});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -295,7 +310,7 @@ class PlacesFeed {
|
|||
this.openLink(action, "window", true);
|
||||
break;
|
||||
case at.SAVE_TO_POCKET:
|
||||
Pocket.savePage(action._target.browser, action.data.site.url, action.data.site.title);
|
||||
this.saveToPocket(action.data.site, action._target.browser);
|
||||
break;
|
||||
case at.OPEN_LINK: {
|
||||
this.openLink(action);
|
||||
|
|
|
@ -42,7 +42,7 @@ const BUILT_IN_SECTIONS = {
|
|||
},
|
||||
privacyNoticeURL: options.privacy_notice_link || "https://www.mozilla.org/privacy/firefox/#suggest-relevant-content",
|
||||
maxRows: 1,
|
||||
availableLinkMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
|
||||
availableLinkMenuOptions: ["CheckBookmarkOrArchive", "CheckSavedToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"],
|
||||
emptyState: {
|
||||
message: {id: "topstories_empty_state", values: {provider: options.provider_name}},
|
||||
icon: "check"
|
||||
|
@ -62,7 +62,7 @@ const BUILT_IN_SECTIONS = {
|
|||
icon: "highlights",
|
||||
title: {id: "header_highlights"},
|
||||
maxRows: 3,
|
||||
availableLinkMenuOptions: ["CheckBookmark", "SaveToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "DeleteUrl"],
|
||||
availableLinkMenuOptions: ["CheckBookmarkOrArchive", "CheckSavedToPocket", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", "CheckDeleteHistoryOrEmpty"],
|
||||
emptyState: {
|
||||
message: {id: "highlights_empty_state"},
|
||||
icon: "highlights"
|
||||
|
|
|
@ -83,7 +83,9 @@ export const UserEventAction = Joi.object().keys({
|
|||
"SECTION_MENU_EXPAND",
|
||||
"SECTION_MENU_MANAGE",
|
||||
"SECTION_MENU_ADD_TOPSITE",
|
||||
"SECTION_MENU_PRIVACY_NOTICE"
|
||||
"SECTION_MENU_PRIVACY_NOTICE",
|
||||
"DELETE_FROM_POCKET",
|
||||
"ARCHIVE_FROM_POCKET"
|
||||
]).required(),
|
||||
source: Joi.valid(["TOP_SITES", "TOP_STORIES", "HIGHLIGHTS"]),
|
||||
action_position: Joi.number().integer()
|
||||
|
|
|
@ -181,7 +181,7 @@ describe("Reducers", () => {
|
|||
id: `foo_bar_${i}`,
|
||||
title: `Foo Bar ${i}`,
|
||||
initialized: false,
|
||||
rows: [{url: "www.foo.bar"}, {url: "www.other.url"}],
|
||||
rows: [{url: "www.foo.bar", pocket_id: 123}, {url: "www.other.url"}],
|
||||
order: i,
|
||||
type: "history"
|
||||
}));
|
||||
|
@ -353,6 +353,20 @@ describe("Reducers", () => {
|
|||
assert.lengthOf(section.rows, 0);
|
||||
});
|
||||
});
|
||||
it("should remove all removed pocket urls", () => {
|
||||
const removeAction = {type: at.DELETE_FROM_POCKET, data: {pocket_id: 123}};
|
||||
const newBlockState = Sections(oldState, removeAction);
|
||||
newBlockState.forEach(section => {
|
||||
assert.deepEqual(section.rows, [{url: "www.other.url"}]);
|
||||
});
|
||||
});
|
||||
it("should archive all archived pocket urls", () => {
|
||||
const removeAction = {type: at.ARCHIVE_FROM_POCKET, data: {pocket_id: 123}};
|
||||
const newBlockState = Sections(oldState, removeAction);
|
||||
newBlockState.forEach(section => {
|
||||
assert.deepEqual(section.rows, [{url: "www.other.url"}]);
|
||||
});
|
||||
});
|
||||
it("should not update state for empty action.data on PLACES_BOOKMARK_ADDED", () => {
|
||||
const nextState = Sections(undefined, {type: at.PLACES_BOOKMARK_ADDED});
|
||||
assert.equal(nextState, INITIAL_STATE.Sections);
|
||||
|
@ -411,6 +425,32 @@ describe("Reducers", () => {
|
|||
assert.isUndefined(newRow.bookmarkTitle);
|
||||
assert.isUndefined(newRow.bookmarkDateCreated);
|
||||
|
||||
// old row is unchanged
|
||||
assert.equal(oldRow, oldState[0].rows[1]);
|
||||
});
|
||||
it("should not update state for empty action.data on PLACES_SAVED_TO_POCKET", () => {
|
||||
const nextState = Sections(undefined, {type: at.PLACES_SAVED_TO_POCKET});
|
||||
assert.equal(nextState, INITIAL_STATE.Sections);
|
||||
});
|
||||
it("should add a pocked item on PLACES_SAVED_TO_POCKET", () => {
|
||||
const action = {
|
||||
type: at.PLACES_SAVED_TO_POCKET,
|
||||
data: {
|
||||
url: "www.foo.bar",
|
||||
pocket_id: 1234,
|
||||
title: "Title for bar.com"
|
||||
}
|
||||
};
|
||||
const nextState = Sections(oldState, action);
|
||||
// check a section to ensure the correct url was saved to pocket
|
||||
const [newRow, oldRow] = nextState[0].rows;
|
||||
|
||||
// new row has pocket data
|
||||
assert.equal(newRow.url, action.data.url);
|
||||
assert.equal(newRow.type, "pocket");
|
||||
assert.equal(newRow.pocket_id, action.data.pocket_id);
|
||||
assert.equal(newRow.title, action.data.title);
|
||||
|
||||
// old row is unchanged
|
||||
assert.equal(oldRow, oldState[0].rows[1]);
|
||||
});
|
||||
|
|
|
@ -75,6 +75,41 @@ describe("<LinkMenu>", () => {
|
|||
const {options} = wrapper.find(ContextMenu).props();
|
||||
assert.isDefined(options.find(o => (o.id && o.id === "menu_action_bookmark")));
|
||||
});
|
||||
it("should show Save to Pocket option for an unsaved Pocket item if CheckSavedToPocket in options list", () => {
|
||||
wrapper = shallowWithIntl(<LinkMenu site={{url: "", bookmarkGuid: 0}} source={"HIGHLIGHTS"} options={["CheckSavedToPocket"]} dispatch={() => {}} />);
|
||||
const {options} = wrapper.find(ContextMenu).props();
|
||||
assert.isDefined(options.find(o => (o.id && o.id === "menu_action_save_to_pocket")));
|
||||
});
|
||||
it("should show Delete from Pocket option for a saved Pocket item if CheckSavedToPocket in options list", () => {
|
||||
wrapper = shallowWithIntl(<LinkMenu site={{url: "", pocket_id: 1234}} source={"HIGHLIGHTS"} options={["CheckSavedToPocket"]} dispatch={() => {}} />);
|
||||
const {options} = wrapper.find(ContextMenu).props();
|
||||
assert.isDefined(options.find(o => (o.id && o.id === "menu_action_delete_pocket")));
|
||||
});
|
||||
it("should show Archive from Pocket option for a saved Pocket item if CheckBookmarkOrArchive", () => {
|
||||
wrapper = shallowWithIntl(<LinkMenu site={{url: "", pocket_id: 1234}} source={"HIGHLIGHTS"} options={["CheckBookmarkOrArchive"]} dispatch={() => {}} />);
|
||||
const {options} = wrapper.find(ContextMenu).props();
|
||||
assert.isDefined(options.find(o => (o.id && o.id === "menu_action_archive_pocket")));
|
||||
});
|
||||
it("should show Bookmark option for an unbookmarked site if CheckBookmarkOrArchive in options list and no pocket_id", () => {
|
||||
wrapper = shallowWithIntl(<LinkMenu site={{url: ""}} source={"HIGHLIGHTS"} options={["CheckBookmarkOrArchive"]} dispatch={() => {}} />);
|
||||
const {options} = wrapper.find(ContextMenu).props();
|
||||
assert.isDefined(options.find(o => (o.id && o.id === "menu_action_bookmark")));
|
||||
});
|
||||
it("should show Unbookmark option for a bookmarked site if CheckBookmarkOrArchive in options list and no pocket_id", () => {
|
||||
wrapper = shallowWithIntl(<LinkMenu site={{url: "", bookmarkGuid: 1234}} source={"HIGHLIGHTS"} options={["CheckBookmarkOrArchive"]} dispatch={() => {}} />);
|
||||
const {options} = wrapper.find(ContextMenu).props();
|
||||
assert.isDefined(options.find(o => (o.id && o.id === "menu_action_remove_bookmark")));
|
||||
});
|
||||
it("should show Delete from History option for a site that is visited but not from Pocket if CheckDeleteHistoryOrEmpty", () => {
|
||||
wrapper = shallowWithIntl(<LinkMenu site={{url: "", type: history}} source={"HIGHLIGHTS"} options={["CheckDeleteHistoryOrEmpty"]} dispatch={() => {}} />);
|
||||
const {options} = wrapper.find(ContextMenu).props();
|
||||
assert.isDefined(options.find(o => (o.id && o.id === "menu_action_delete")));
|
||||
});
|
||||
it("should not show a Delete from History option for a site that is Pocket'ed if CheckDeleteHistoryOrEmpty", () => {
|
||||
wrapper = shallowWithIntl(<LinkMenu site={{url: "", pocket_id: 1234}} source={"HIGHLIGHTS"} options={["CheckDeleteHistoryOrEmpty"]} dispatch={() => {}} />);
|
||||
const {options} = wrapper.find(ContextMenu).props();
|
||||
assert.isUndefined(options.find(o => (o.id && o.id === "menu_action_delete")));
|
||||
});
|
||||
it("should show Edit option", () => {
|
||||
const props = {url: "foo", label: "label"};
|
||||
const index = 5;
|
||||
|
@ -104,20 +139,22 @@ describe("<LinkMenu>", () => {
|
|||
describe(".onClick", () => {
|
||||
const FAKE_INDEX = 3;
|
||||
const FAKE_SOURCE = "TOP_SITES";
|
||||
const FAKE_SITE = {url: "https://foo.com", referrer: "https://foo.com/ref", title: "bar", bookmarkGuid: 1234, hostname: "foo", type: "history"};
|
||||
const FAKE_SITE = {url: "https://foo.com", pocket_id: "1234", referrer: "https://foo.com/ref", title: "bar", bookmarkGuid: 1234, hostname: "foo", type: "history"};
|
||||
const dispatch = sinon.stub();
|
||||
const propOptions = ["Separator", "RemoveBookmark", "AddBookmark", "OpenInNewWindow", "OpenInPrivateWindow", "BlockUrl", "DeleteUrl", "PinTopSite", "UnpinTopSite", "SaveToPocket", "WebExtDismiss"];
|
||||
const propOptions = ["Separator", "RemoveBookmark", "AddBookmark", "OpenInNewWindow", "OpenInPrivateWindow", "BlockUrl", "DeleteUrl", "PinTopSite", "UnpinTopSite", "SaveToPocket", "DeleteFromPocket", "ArchiveFromPocket", "WebExtDismiss"];
|
||||
const expectedActionData = {
|
||||
menu_action_remove_bookmark: FAKE_SITE.bookmarkGuid,
|
||||
menu_action_bookmark: {url: FAKE_SITE.url, title: FAKE_SITE.title, type: FAKE_SITE.type},
|
||||
menu_action_open_new_window: {url: FAKE_SITE.url, referrer: FAKE_SITE.referrer},
|
||||
menu_action_open_private_window: {url: FAKE_SITE.url, referrer: FAKE_SITE.referrer},
|
||||
menu_action_dismiss: FAKE_SITE.url,
|
||||
menu_action_dismiss: {url: FAKE_SITE.url, pocket_id: FAKE_SITE.pocket_id},
|
||||
menu_action_webext_dismiss: {source: "TOP_SITES", url: FAKE_SITE.url, action_position: 3},
|
||||
menu_action_delete: {url: FAKE_SITE.url, forceBlock: FAKE_SITE.bookmarkGuid},
|
||||
menu_action_delete: {url: FAKE_SITE.url, pocket_id: FAKE_SITE.pocket_id, forceBlock: FAKE_SITE.bookmarkGuid},
|
||||
menu_action_pin: {site: {url: FAKE_SITE.url}, index: FAKE_INDEX},
|
||||
menu_action_unpin: {site: {url: FAKE_SITE.url}},
|
||||
menu_action_save_to_pocket: {site: {url: FAKE_SITE.url, title: FAKE_SITE.title}}
|
||||
menu_action_save_to_pocket: {site: {url: FAKE_SITE.url, title: FAKE_SITE.title}},
|
||||
menu_action_delete_pocket: {pocket_id: "1234"},
|
||||
menu_action_archive_pocket: {pocket_id: "1234"}
|
||||
};
|
||||
|
||||
const {options} = shallowWithIntl(<LinkMenu site={FAKE_SITE} dispatch={dispatch} index={FAKE_INDEX} options={propOptions} source={FAKE_SOURCE} shouldSendImpressionStats={true} />)
|
||||
|
|
|
@ -12,6 +12,7 @@ const FAKE_IMAGE = "data123";
|
|||
describe("Highlights Feed", () => {
|
||||
let HighlightsFeed;
|
||||
let SECTION_ID;
|
||||
let MANY_EXTRA_LENGTH;
|
||||
let feed;
|
||||
let globals;
|
||||
let sandbox;
|
||||
|
@ -26,7 +27,13 @@ describe("Highlights Feed", () => {
|
|||
beforeEach(() => {
|
||||
globals = new GlobalOverrider();
|
||||
sandbox = globals.sandbox;
|
||||
fakeNewTabUtils = {activityStreamLinks: {getHighlights: sandbox.spy(() => Promise.resolve(links))}};
|
||||
fakeNewTabUtils = {
|
||||
activityStreamLinks: {
|
||||
getHighlights: sandbox.spy(() => Promise.resolve(links)),
|
||||
deletePocketEntry: sandbox.spy(() => Promise.resolve({})),
|
||||
archivePocketEntry: sandbox.spy(() => Promise.resolve({}))
|
||||
}
|
||||
};
|
||||
sectionsManagerStub = {
|
||||
onceInitialized: sinon.stub().callsFake(callback => callback()),
|
||||
enableSection: sinon.spy(),
|
||||
|
@ -49,7 +56,7 @@ describe("Highlights Feed", () => {
|
|||
|
||||
globals.set("NewTabUtils", fakeNewTabUtils);
|
||||
globals.set("PageThumbs", fakePageThumbs);
|
||||
({HighlightsFeed, SECTION_ID} = injector({
|
||||
({HighlightsFeed, SECTION_ID, MANY_EXTRA_LENGTH} = injector({
|
||||
"lib/FilterAdult.jsm": {filterAdult: filterAdultStub},
|
||||
"lib/ShortURL.jsm": {shortURL: shortURLStub},
|
||||
"lib/SectionsManager.jsm": {SectionsManager: sectionsManagerStub},
|
||||
|
@ -61,7 +68,7 @@ describe("Highlights Feed", () => {
|
|||
dispatch: sinon.spy(),
|
||||
getState() { return this.state; },
|
||||
state: {
|
||||
Prefs: {values: {filterAdult: false}},
|
||||
Prefs: {values: {"filterAdult": false, "section.highlights.includePocket": false}},
|
||||
TopSites: {
|
||||
initialized: true,
|
||||
rows: Array(12).fill(null).map((v, i) => ({url: `http://www.topsite${i}.com`}))
|
||||
|
@ -232,6 +239,31 @@ describe("Highlights Feed", () => {
|
|||
assert.equal(highlights[0].url, links[0].url);
|
||||
assert.equal(highlights[1].url, links[2].url);
|
||||
});
|
||||
it("should take both a bookmark and a pocket of the same hostname", async () => {
|
||||
links = [
|
||||
{url: "https://site.com/bookmark", type: "bookmark"},
|
||||
{url: "https://site.com/pocket", type: "pocket"}
|
||||
];
|
||||
|
||||
const highlights = await fetchHighlights();
|
||||
|
||||
assert.equal(highlights.length, 2);
|
||||
assert.equal(highlights[0].url, links[0].url);
|
||||
assert.equal(highlights[1].url, links[1].url);
|
||||
});
|
||||
it("should includePocket pocket items when pref is true", async () => {
|
||||
feed.store.state.Prefs.values["section.highlights.includePocket"] = true;
|
||||
sandbox.spy(feed.linksCache, "request");
|
||||
await feed.fetchHighlights();
|
||||
|
||||
assert.calledWith(feed.linksCache.request, {numItems: MANY_EXTRA_LENGTH, excludePocket: false});
|
||||
});
|
||||
it("should not includePocket pocket items when pref is false", async () => {
|
||||
sandbox.spy(feed.linksCache, "request");
|
||||
await feed.fetchHighlights();
|
||||
|
||||
assert.calledWith(feed.linksCache.request, {numItems: MANY_EXTRA_LENGTH, excludePocket: true});
|
||||
});
|
||||
it("should set type to bookmark if there is a bookmarkGuid", async () => {
|
||||
links = [{url: "https://mozilla.org", type: "history", bookmarkGuid: "1234567890"}];
|
||||
|
||||
|
@ -394,6 +426,16 @@ describe("Highlights Feed", () => {
|
|||
|
||||
assert.calledOnce(feed.linksCache.expire);
|
||||
});
|
||||
it("should fetch highlights and expire the cache on PLACES_SAVED_TO_POCKET", async () => {
|
||||
await feed.fetchHighlights();
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
sandbox.stub(feed.linksCache, "expire");
|
||||
|
||||
feed.onAction({type: at.PLACES_SAVED_TO_POCKET});
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWith(feed.fetchHighlights, {broadcast: false});
|
||||
assert.calledOnce(feed.linksCache.expire);
|
||||
});
|
||||
it("should call fetchHighlights with broadcast false on TOP_SITES_UPDATED", () => {
|
||||
sandbox.stub(feed, "fetchHighlights");
|
||||
feed.onAction({type: at.TOP_SITES_UPDATED});
|
||||
|
@ -401,5 +443,59 @@ describe("Highlights Feed", () => {
|
|||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWithExactly(feed.fetchHighlights, {broadcast: false});
|
||||
});
|
||||
it("should call deleteFromPocket on DELETE_FROM_POCKET", () => {
|
||||
sandbox.stub(feed, "deleteFromPocket");
|
||||
feed.onAction({type: at.DELETE_FROM_POCKET, data: {pocket_id: 12345}});
|
||||
|
||||
assert.calledOnce(feed.deleteFromPocket);
|
||||
assert.calledWithExactly(feed.deleteFromPocket, 12345);
|
||||
});
|
||||
it("should call fetchHighlights when deleting from Pocket", async () => {
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
await feed.deleteFromPocket(12345);
|
||||
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWithExactly(feed.fetchHighlights, {broadcast: true});
|
||||
});
|
||||
it("should catch if deletePocketEntry throws", async () => {
|
||||
sandbox.spy(global.Cu, "reportError");
|
||||
fakeNewTabUtils.activityStreamLinks.deletePocketEntry = sandbox.stub().rejects("not ok");
|
||||
await feed.deleteFromPocket(12345);
|
||||
|
||||
assert.calledOnce(global.Cu.reportError);
|
||||
});
|
||||
it("should call NewTabUtils.deletePocketEntry when deleting from Pocket", async () => {
|
||||
await feed.deleteFromPocket(12345);
|
||||
|
||||
assert.calledOnce(global.NewTabUtils.activityStreamLinks.deletePocketEntry);
|
||||
assert.calledWith(global.NewTabUtils.activityStreamLinks.deletePocketEntry, 12345);
|
||||
});
|
||||
it("should call archiveFromPocket on ARCHIVE_FROM_POCKET", () => {
|
||||
sandbox.stub(feed, "archiveFromPocket");
|
||||
feed.onAction({type: at.ARCHIVE_FROM_POCKET, data: {pocket_id: 12345}});
|
||||
|
||||
assert.calledOnce(feed.archiveFromPocket);
|
||||
assert.calledWithExactly(feed.archiveFromPocket, 12345);
|
||||
});
|
||||
it("should call fetchHighlights when archiving from Pocket", async () => {
|
||||
feed.fetchHighlights = sinon.spy();
|
||||
await feed.archiveFromPocket(12345);
|
||||
|
||||
assert.calledOnce(feed.fetchHighlights);
|
||||
assert.calledWithExactly(feed.fetchHighlights, {broadcast: true});
|
||||
});
|
||||
it("should catch if archiveFromPocket throws", async () => {
|
||||
sandbox.spy(global.Cu, "reportError");
|
||||
fakeNewTabUtils.activityStreamLinks.archivePocketEntry = sandbox.stub().rejects("not ok");
|
||||
await feed.archiveFromPocket(12345);
|
||||
|
||||
assert.calledOnce(global.Cu.reportError);
|
||||
});
|
||||
it("should call NewTabUtils.archivePocketEntry when deleting from Pocket", async () => {
|
||||
await feed.archiveFromPocket(12345);
|
||||
|
||||
assert.calledOnce(global.NewTabUtils.activityStreamLinks.archivePocketEntry);
|
||||
assert.calledWith(global.NewTabUtils.activityStreamLinks.archivePocketEntry, 12345);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,7 +25,8 @@ describe("PlacesFeed", () => {
|
|||
addBookmark: sandbox.spy(),
|
||||
deleteBookmark: sandbox.spy(),
|
||||
deleteHistoryEntry: sandbox.spy(),
|
||||
blockURL: sandbox.spy()
|
||||
blockURL: sandbox.spy(),
|
||||
addPocketEntry: sandbox.spy(() => Promise.resolve())
|
||||
}
|
||||
});
|
||||
globals.set("PlacesUtils", {
|
||||
|
@ -41,7 +42,6 @@ describe("PlacesFeed", () => {
|
|||
SOURCES
|
||||
}
|
||||
});
|
||||
globals.set("Pocket", {savePage: sandbox.spy()});
|
||||
global.Cc["@mozilla.org/browser/nav-history-service;1"] = {
|
||||
getService() {
|
||||
return global.PlacesUtils.history;
|
||||
|
@ -96,8 +96,8 @@ describe("PlacesFeed", () => {
|
|||
assert.calledWith(global.Services.obs.removeObserver, feed, BLOCKED_EVENT);
|
||||
});
|
||||
it("should block a url on BLOCK_URL", () => {
|
||||
feed.onAction({type: at.BLOCK_URL, data: "apple.com"});
|
||||
assert.calledWith(global.NewTabUtils.activityStreamLinks.blockURL, {url: "apple.com"});
|
||||
feed.onAction({type: at.BLOCK_URL, data: {url: "apple.com", pocket_id: 1234}});
|
||||
assert.calledWith(global.NewTabUtils.activityStreamLinks.blockURL, {url: "apple.com", pocket_id: 1234});
|
||||
});
|
||||
it("should bookmark a url on BOOKMARK_URL", () => {
|
||||
const data = {url: "pear.com", title: "A pear"};
|
||||
|
@ -117,7 +117,7 @@ describe("PlacesFeed", () => {
|
|||
it("should delete a history entry on DELETE_HISTORY_URL and force a site to be blocked if specified", () => {
|
||||
feed.onAction({type: at.DELETE_HISTORY_URL, data: {url: "guava.com", forceBlock: "g123kd"}});
|
||||
assert.calledWith(global.NewTabUtils.activityStreamLinks.deleteHistoryEntry, "guava.com");
|
||||
assert.calledWith(global.NewTabUtils.activityStreamLinks.blockURL, {url: "guava.com"});
|
||||
assert.calledWith(global.NewTabUtils.activityStreamLinks.blockURL, {url: "guava.com", pocket_id: undefined});
|
||||
});
|
||||
it("should call openLinkIn with the correct url and where on OPEN_NEW_WINDOW", () => {
|
||||
const openLinkIn = sinon.stub();
|
||||
|
@ -182,9 +182,58 @@ describe("PlacesFeed", () => {
|
|||
assert.propertyVal(params, "referrerPolicy", 5);
|
||||
assert.propertyVal(params.referrerURI, "spec", "foo.com/ref");
|
||||
});
|
||||
it("should save to Pocket on SAVE_TO_POCKET", () => {
|
||||
feed.onAction({type: at.SAVE_TO_POCKET, data: {site: {url: "raspberry.com", title: "raspberry"}}, _target: {browser: {}}});
|
||||
assert.calledWith(global.Pocket.savePage, {}, "raspberry.com", "raspberry");
|
||||
it("should call saveToPocket on SAVE_TO_POCKET", () => {
|
||||
const action = {
|
||||
type: at.SAVE_TO_POCKET,
|
||||
data: {site: {url: "raspberry.com", title: "raspberry"}},
|
||||
_target: {browser: {}}
|
||||
};
|
||||
sinon.stub(feed, "saveToPocket");
|
||||
feed.onAction(action);
|
||||
assert.calledWithExactly(feed.saveToPocket, action.data.site, action._target.browser);
|
||||
});
|
||||
it("should call NewTabUtils.activityStreamLinks.addPocketEntry if we are saving a pocket story", async () => {
|
||||
const action = {
|
||||
data: {site: {url: "raspberry.com", title: "raspberry"}},
|
||||
_target: {browser: {}}
|
||||
};
|
||||
await feed.saveToPocket(action.data.site, action._target.browser);
|
||||
assert.calledOnce(global.NewTabUtils.activityStreamLinks.addPocketEntry);
|
||||
assert.calledWithExactly(global.NewTabUtils.activityStreamLinks.addPocketEntry, action.data.site.url, action.data.site.title, action._target.browser);
|
||||
});
|
||||
it("should reject the promise if NewTabUtils.activityStreamLinks.addPocketEntry rejects", async () => {
|
||||
const e = new Error("Error");
|
||||
const action = {
|
||||
data: {site: {url: "raspberry.com", title: "raspberry"}},
|
||||
_target: {browser: {}}
|
||||
};
|
||||
global.NewTabUtils.activityStreamLinks.addPocketEntry = sandbox.stub().rejects(e);
|
||||
await feed.saveToPocket(action.data.site, action._target.browser);
|
||||
assert.calledWith(global.Cu.reportError, e);
|
||||
});
|
||||
it("should broadcast to content if we successfully added a link to Pocket", async () => {
|
||||
// test in the form that the API returns data based on: https://getpocket.com/developer/docs/v3/add
|
||||
global.NewTabUtils.activityStreamLinks.addPocketEntry = sandbox.stub().resolves({item: {item_id: 1234}});
|
||||
const action = {
|
||||
data: {site: {url: "raspberry.com", title: "raspberry"}},
|
||||
_target: {browser: {}}
|
||||
};
|
||||
await feed.saveToPocket(action.data.site, action._target.browser);
|
||||
assert.equal(feed.store.dispatch.firstCall.args[0].type, at.PLACES_SAVED_TO_POCKET);
|
||||
assert.deepEqual(feed.store.dispatch.firstCall.args[0].data, {
|
||||
url: "raspberry.com",
|
||||
title: "raspberry",
|
||||
pocket_id: 1234
|
||||
});
|
||||
});
|
||||
it("should only broadcast if we got some data back from addPocketEntry", async () => {
|
||||
global.NewTabUtils.activityStreamLinks.addPocketEntry = sandbox.stub().resolves(null);
|
||||
const action = {
|
||||
data: {site: {url: "raspberry.com", title: "raspberry"}},
|
||||
_target: {browser: {}}
|
||||
};
|
||||
await feed.saveToPocket(action.data.site, action._target.browser);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче