summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/test/unit/common
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /browser/components/newtab/test/unit/common
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/newtab/test/unit/common')
-rw-r--r--browser/components/newtab/test/unit/common/Actions.test.js241
-rw-r--r--browser/components/newtab/test/unit/common/Dedupe.test.js38
-rw-r--r--browser/components/newtab/test/unit/common/Reducers.test.js1566
3 files changed, 1845 insertions, 0 deletions
diff --git a/browser/components/newtab/test/unit/common/Actions.test.js b/browser/components/newtab/test/unit/common/Actions.test.js
new file mode 100644
index 0000000000..32d7b446e3
--- /dev/null
+++ b/browser/components/newtab/test/unit/common/Actions.test.js
@@ -0,0 +1,241 @@
+import {
+ actionCreators as ac,
+ actionTypes as at,
+ actionUtils as au,
+ BACKGROUND_PROCESS,
+ CONTENT_MESSAGE_TYPE,
+ globalImportContext,
+ MAIN_MESSAGE_TYPE,
+ PRELOAD_MESSAGE_TYPE,
+ UI_CODE,
+} from "common/Actions.sys.mjs";
+
+describe("Actions", () => {
+ it("should set globalImportContext to UI_CODE", () => {
+ assert.equal(globalImportContext, UI_CODE);
+ });
+});
+
+describe("ActionTypes", () => {
+ it("should be in alpha order", () => {
+ assert.equal(
+ Object.keys(at).join(", "),
+ Object.keys(at)
+ .sort()
+ .join(", ")
+ );
+ });
+});
+
+describe("ActionCreators", () => {
+ describe("_RouteMessage", () => {
+ it("should throw if options are not passed as the second param", () => {
+ assert.throws(() => {
+ au._RouteMessage({ type: "FOO" });
+ });
+ });
+ it("should set all defined options on the .meta property of the new action", () => {
+ assert.deepEqual(
+ au._RouteMessage(
+ { type: "FOO", meta: { hello: "world" } },
+ { from: "foo", to: "bar" }
+ ),
+ { type: "FOO", meta: { hello: "world", from: "foo", to: "bar" } }
+ );
+ });
+ it("should remove any undefined options related to message routing", () => {
+ const action = au._RouteMessage(
+ { type: "FOO", meta: { fromTarget: "bar" } },
+ { from: "foo", to: "bar" }
+ );
+ assert.isUndefined(action.meta.fromTarget);
+ });
+ });
+ describe("AlsoToMain", () => {
+ it("should create the right action", () => {
+ const action = { type: "FOO", data: "BAR" };
+ const newAction = ac.AlsoToMain(action);
+ assert.deepEqual(newAction, {
+ type: "FOO",
+ data: "BAR",
+ meta: { from: CONTENT_MESSAGE_TYPE, to: MAIN_MESSAGE_TYPE },
+ });
+ });
+ it("should add the fromTarget if it was supplied", () => {
+ const action = { type: "FOO", data: "BAR" };
+ const newAction = ac.AlsoToMain(action, "port123");
+ assert.equal(newAction.meta.fromTarget, "port123");
+ });
+ describe("isSendToMain", () => {
+ it("should return true if action is AlsoToMain", () => {
+ const newAction = ac.AlsoToMain({ type: "FOO" });
+ assert.isTrue(au.isSendToMain(newAction));
+ });
+ it("should return false if action is not AlsoToMain", () => {
+ assert.isFalse(au.isSendToMain({ type: "FOO" }));
+ });
+ });
+ });
+ describe("AlsoToOneContent", () => {
+ it("should create the right action", () => {
+ const action = { type: "FOO", data: "BAR" };
+ const targetId = "abc123";
+ const newAction = ac.AlsoToOneContent(action, targetId);
+ assert.deepEqual(newAction, {
+ type: "FOO",
+ data: "BAR",
+ meta: {
+ from: MAIN_MESSAGE_TYPE,
+ to: CONTENT_MESSAGE_TYPE,
+ toTarget: targetId,
+ },
+ });
+ });
+ it("should throw if no targetId is provided", () => {
+ assert.throws(() => {
+ ac.AlsoToOneContent({ type: "FOO" });
+ });
+ });
+ describe("isSendToOneContent", () => {
+ it("should return true if action is AlsoToOneContent", () => {
+ const newAction = ac.AlsoToOneContent({ type: "FOO" }, "foo123");
+ assert.isTrue(au.isSendToOneContent(newAction));
+ });
+ it("should return false if action is not AlsoToMain", () => {
+ assert.isFalse(au.isSendToOneContent({ type: "FOO" }));
+ assert.isFalse(
+ au.isSendToOneContent(ac.BroadcastToContent({ type: "FOO" }))
+ );
+ });
+ });
+ describe("isFromMain", () => {
+ it("should return true if action is AlsoToOneContent", () => {
+ const newAction = ac.AlsoToOneContent({ type: "FOO" }, "foo123");
+ assert.isTrue(au.isFromMain(newAction));
+ });
+ it("should return true if action is BroadcastToContent", () => {
+ const newAction = ac.BroadcastToContent({ type: "FOO" });
+ assert.isTrue(au.isFromMain(newAction));
+ });
+ it("should return false if action is AlsoToMain", () => {
+ const newAction = ac.AlsoToMain({ type: "FOO" });
+ assert.isFalse(au.isFromMain(newAction));
+ });
+ });
+ });
+ describe("BroadcastToContent", () => {
+ it("should create the right action", () => {
+ const action = { type: "FOO", data: "BAR" };
+ const newAction = ac.BroadcastToContent(action);
+ assert.deepEqual(newAction, {
+ type: "FOO",
+ data: "BAR",
+ meta: { from: MAIN_MESSAGE_TYPE, to: CONTENT_MESSAGE_TYPE },
+ });
+ });
+ describe("isBroadcastToContent", () => {
+ it("should return true if action is BroadcastToContent", () => {
+ assert.isTrue(
+ au.isBroadcastToContent(ac.BroadcastToContent({ type: "FOO" }))
+ );
+ });
+ it("should return false if action is not BroadcastToContent", () => {
+ assert.isFalse(au.isBroadcastToContent({ type: "FOO" }));
+ assert.isFalse(
+ au.isBroadcastToContent(
+ ac.AlsoToOneContent({ type: "FOO" }, "foo123")
+ )
+ );
+ });
+ });
+ });
+ describe("AlsoToPreloaded", () => {
+ it("should create the right action", () => {
+ const action = { type: "FOO", data: "BAR" };
+ const newAction = ac.AlsoToPreloaded(action);
+ assert.deepEqual(newAction, {
+ type: "FOO",
+ data: "BAR",
+ meta: { from: MAIN_MESSAGE_TYPE, to: PRELOAD_MESSAGE_TYPE },
+ });
+ });
+ });
+ describe("isSendToPreloaded", () => {
+ it("should return true if action is AlsoToPreloaded", () => {
+ assert.isTrue(au.isSendToPreloaded(ac.AlsoToPreloaded({ type: "FOO" })));
+ });
+ it("should return false if action is not AlsoToPreloaded", () => {
+ assert.isFalse(au.isSendToPreloaded({ type: "FOO" }));
+ assert.isFalse(
+ au.isSendToPreloaded(ac.BroadcastToContent({ type: "FOO" }))
+ );
+ });
+ });
+ describe("UserEvent", () => {
+ it("should include the given data", () => {
+ const data = { action: "foo" };
+ assert.equal(ac.UserEvent(data).data, data);
+ });
+ it("should wrap with AlsoToMain", () => {
+ const action = ac.UserEvent({ action: "foo" });
+ assert.isTrue(au.isSendToMain(action), "isSendToMain");
+ });
+ });
+ describe("ASRouterUserEvent", () => {
+ it("should include the given data", () => {
+ const data = { action: "foo" };
+ assert.equal(ac.ASRouterUserEvent(data).data, data);
+ });
+ it("should wrap with AlsoToMain", () => {
+ const action = ac.ASRouterUserEvent({ action: "foo" });
+ assert.isTrue(au.isSendToMain(action), "isSendToMain");
+ });
+ });
+ describe("ImpressionStats", () => {
+ it("should include the right data", () => {
+ const data = { action: "foo" };
+ assert.equal(ac.ImpressionStats(data).data, data);
+ });
+ it("should wrap with AlsoToMain if in UI code", () => {
+ assert.isTrue(
+ au.isSendToMain(ac.ImpressionStats({ action: "foo" })),
+ "isSendToMain"
+ );
+ });
+ it("should not wrap with AlsoToMain if not in UI code", () => {
+ const action = ac.ImpressionStats({ action: "foo" }, BACKGROUND_PROCESS);
+ assert.isFalse(au.isSendToMain(action), "isSendToMain");
+ });
+ });
+ describe("WebExtEvent", () => {
+ it("should set the provided type", () => {
+ const action = ac.WebExtEvent(at.WEBEXT_CLICK, {
+ source: "MyExtension",
+ url: "foo.com",
+ });
+ assert.equal(action.type, at.WEBEXT_CLICK);
+ });
+ it("should set the provided data", () => {
+ const data = { source: "MyExtension", url: "foo.com" };
+ const action = ac.WebExtEvent(at.WEBEXT_CLICK, data);
+ assert.equal(action.data, data);
+ });
+ it("should throw if the 'source' property is missing", () => {
+ assert.throws(() => {
+ ac.WebExtEvent(at.WEBEXT_CLICK, {});
+ });
+ });
+ });
+});
+
+describe("ActionUtils", () => {
+ describe("getPortIdOfSender", () => {
+ it("should return the PortID from a AlsoToMain action", () => {
+ const portID = "foo123";
+ const result = au.getPortIdOfSender(
+ ac.AlsoToMain({ type: "FOO" }, portID)
+ );
+ assert.equal(result, portID);
+ });
+ });
+});
diff --git a/browser/components/newtab/test/unit/common/Dedupe.test.js b/browser/components/newtab/test/unit/common/Dedupe.test.js
new file mode 100644
index 0000000000..1c85eafa50
--- /dev/null
+++ b/browser/components/newtab/test/unit/common/Dedupe.test.js
@@ -0,0 +1,38 @@
+import { Dedupe } from "common/Dedupe.sys.mjs";
+
+describe("Dedupe", () => {
+ let instance;
+ beforeEach(() => {
+ instance = new Dedupe();
+ });
+ describe("group", () => {
+ it("should remove duplicates inside the groups", () => {
+ const beforeItems = [
+ [1, 1, 1],
+ [2, 2, 2],
+ [3, 3, 3],
+ ];
+ const afterItems = [[1], [2], [3]];
+ assert.deepEqual(instance.group(...beforeItems), afterItems);
+ });
+ it("should remove duplicates between groups, favouring earlier groups", () => {
+ const beforeItems = [
+ [1, 2, 3],
+ [2, 3, 4],
+ [3, 4, 5],
+ ];
+ const afterItems = [[1, 2, 3], [4], [5]];
+ assert.deepEqual(instance.group(...beforeItems), afterItems);
+ });
+ it("should remove duplicates from groups of objects", () => {
+ instance = new Dedupe(item => item.id);
+ const beforeItems = [
+ [{ id: 1 }, { id: 1 }, { id: 2 }],
+ [{ id: 1 }, { id: 3 }, { id: 2 }],
+ [{ id: 1 }, { id: 2 }, { id: 5 }],
+ ];
+ const afterItems = [[{ id: 1 }, { id: 2 }], [{ id: 3 }], [{ id: 5 }]];
+ assert.deepEqual(instance.group(...beforeItems), afterItems);
+ });
+ });
+});
diff --git a/browser/components/newtab/test/unit/common/Reducers.test.js b/browser/components/newtab/test/unit/common/Reducers.test.js
new file mode 100644
index 0000000000..9248990c58
--- /dev/null
+++ b/browser/components/newtab/test/unit/common/Reducers.test.js
@@ -0,0 +1,1566 @@
+import { INITIAL_STATE, insertPinned, reducers } from "common/Reducers.jsm";
+const {
+ TopSites,
+ App,
+ Snippets,
+ Prefs,
+ Dialog,
+ Sections,
+ Pocket,
+ Personalization,
+ DiscoveryStream,
+ Search,
+ ASRouter,
+} = reducers;
+import { actionTypes as at } from "common/Actions.sys.mjs";
+
+describe("Reducers", () => {
+ describe("App", () => {
+ it("should return the initial state", () => {
+ const nextState = App(undefined, { type: "FOO" });
+ assert.equal(nextState, INITIAL_STATE.App);
+ });
+ it("should set initialized to true on INIT", () => {
+ const nextState = App(undefined, { type: "INIT" });
+
+ assert.propertyVal(nextState, "initialized", true);
+ });
+ });
+ describe("TopSites", () => {
+ it("should return the initial state", () => {
+ const nextState = TopSites(undefined, { type: "FOO" });
+ assert.equal(nextState, INITIAL_STATE.TopSites);
+ });
+ it("should add top sites on TOP_SITES_UPDATED", () => {
+ const newRows = [{ url: "foo.com" }, { url: "bar.com" }];
+ const nextState = TopSites(undefined, {
+ type: at.TOP_SITES_UPDATED,
+ data: { links: newRows },
+ });
+ assert.equal(nextState.rows, newRows);
+ });
+ it("should not update state for empty action.data on TOP_SITES_UPDATED", () => {
+ const nextState = TopSites(undefined, { type: at.TOP_SITES_UPDATED });
+ assert.equal(nextState, INITIAL_STATE.TopSites);
+ });
+ it("should initialize prefs on TOP_SITES_UPDATED", () => {
+ const nextState = TopSites(undefined, {
+ type: at.TOP_SITES_UPDATED,
+ data: { links: [], pref: "foo" },
+ });
+
+ assert.equal(nextState.pref, "foo");
+ });
+ it("should pass prevState.prefs if not present in TOP_SITES_UPDATED", () => {
+ const nextState = TopSites(
+ { prefs: "foo" },
+ { type: at.TOP_SITES_UPDATED, data: { links: [] } }
+ );
+
+ assert.equal(nextState.prefs, "foo");
+ });
+ it("should set editForm.site to action.data on TOP_SITES_EDIT", () => {
+ const data = { index: 7 };
+ const nextState = TopSites(undefined, { type: at.TOP_SITES_EDIT, data });
+ assert.equal(nextState.editForm.index, data.index);
+ });
+ it("should set editForm to null on TOP_SITES_CANCEL_EDIT", () => {
+ const nextState = TopSites(undefined, { type: at.TOP_SITES_CANCEL_EDIT });
+ assert.isNull(nextState.editForm);
+ });
+ it("should preserve the editForm.index", () => {
+ const actionTypes = [
+ at.PREVIEW_RESPONSE,
+ at.PREVIEW_REQUEST,
+ at.PREVIEW_REQUEST_CANCEL,
+ ];
+ actionTypes.forEach(type => {
+ const oldState = { editForm: { index: 0, previewUrl: "foo" } };
+ const action = { type, data: { url: "foo" } };
+ const nextState = TopSites(oldState, action);
+ assert.equal(nextState.editForm.index, 0);
+ });
+ });
+ it("should set previewResponse on PREVIEW_RESPONSE", () => {
+ const oldState = { editForm: { previewUrl: "url" } };
+ const action = {
+ type: at.PREVIEW_RESPONSE,
+ data: { preview: "data:123", url: "url" },
+ };
+ const nextState = TopSites(oldState, action);
+ assert.propertyVal(nextState.editForm, "previewResponse", "data:123");
+ });
+ it("should return previous state if action url does not match expected", () => {
+ const oldState = { editForm: { previewUrl: "foo" } };
+ const action = { type: at.PREVIEW_RESPONSE, data: { url: "bar" } };
+ const nextState = TopSites(oldState, action);
+ assert.equal(nextState, oldState);
+ });
+ it("should return previous state if editForm is not set", () => {
+ const actionTypes = [
+ at.PREVIEW_RESPONSE,
+ at.PREVIEW_REQUEST,
+ at.PREVIEW_REQUEST_CANCEL,
+ ];
+ actionTypes.forEach(type => {
+ const oldState = { editForm: null };
+ const action = { type, data: { url: "bar" } };
+ const nextState = TopSites(oldState, action);
+ assert.equal(nextState, oldState, type);
+ });
+ });
+ it("should set previewResponse to null on PREVIEW_REQUEST", () => {
+ const oldState = { editForm: { previewResponse: "foo" } };
+ const action = { type: at.PREVIEW_REQUEST, data: {} };
+ const nextState = TopSites(oldState, action);
+ assert.propertyVal(nextState.editForm, "previewResponse", null);
+ });
+ it("should set previewUrl on PREVIEW_REQUEST", () => {
+ const oldState = { editForm: {} };
+ const action = { type: at.PREVIEW_REQUEST, data: { url: "bar" } };
+ const nextState = TopSites(oldState, action);
+ assert.propertyVal(nextState.editForm, "previewUrl", "bar");
+ });
+ it("should add screenshots for SCREENSHOT_UPDATED", () => {
+ const oldState = { rows: [{ url: "foo.com" }, { url: "bar.com" }] };
+ const action = {
+ type: at.SCREENSHOT_UPDATED,
+ data: { url: "bar.com", screenshot: "data:123" },
+ };
+ const nextState = TopSites(oldState, action);
+ assert.deepEqual(nextState.rows, [
+ { url: "foo.com" },
+ { url: "bar.com", screenshot: "data:123" },
+ ]);
+ });
+ it("should not modify rows if nothing matches the url for SCREENSHOT_UPDATED", () => {
+ const oldState = { rows: [{ url: "foo.com" }, { url: "bar.com" }] };
+ const action = {
+ type: at.SCREENSHOT_UPDATED,
+ data: { url: "baz.com", screenshot: "data:123" },
+ };
+ const nextState = TopSites(oldState, action);
+ assert.deepEqual(nextState, oldState);
+ });
+ it("should bookmark an item on PLACES_BOOKMARK_ADDED", () => {
+ const oldState = { rows: [{ url: "foo.com" }, { url: "bar.com" }] };
+ const action = {
+ type: at.PLACES_BOOKMARK_ADDED,
+ data: {
+ url: "bar.com",
+ bookmarkGuid: "bookmark123",
+ bookmarkTitle: "Title for bar.com",
+ dateAdded: 1234567,
+ },
+ };
+ const nextState = TopSites(oldState, action);
+ const [, newRow] = nextState.rows;
+ // new row has bookmark data
+ assert.equal(newRow.url, action.data.url);
+ assert.equal(newRow.bookmarkGuid, action.data.bookmarkGuid);
+ assert.equal(newRow.bookmarkTitle, action.data.bookmarkTitle);
+ assert.equal(newRow.bookmarkDateCreated, action.data.dateAdded);
+
+ // old row is unchanged
+ assert.equal(nextState.rows[0], oldState.rows[0]);
+ });
+ it("should not update state for empty action.data on PLACES_BOOKMARK_ADDED", () => {
+ const nextState = TopSites(undefined, { type: at.PLACES_BOOKMARK_ADDED });
+ assert.equal(nextState, INITIAL_STATE.TopSites);
+ });
+ it("should remove a bookmark on PLACES_BOOKMARKS_REMOVED", () => {
+ const oldState = {
+ rows: [
+ { url: "foo.com" },
+ {
+ url: "bar.com",
+ bookmarkGuid: "bookmark123",
+ bookmarkTitle: "Title for bar.com",
+ dateAdded: 123456,
+ },
+ ],
+ };
+ const action = {
+ type: at.PLACES_BOOKMARKS_REMOVED,
+ data: { urls: ["bar.com"] },
+ };
+ const nextState = TopSites(oldState, action);
+ const [, newRow] = nextState.rows;
+ // new row no longer has bookmark data
+ assert.equal(newRow.url, oldState.rows[1].url);
+ assert.isUndefined(newRow.bookmarkGuid);
+ assert.isUndefined(newRow.bookmarkTitle);
+ assert.isUndefined(newRow.bookmarkDateCreated);
+
+ // old row is unchanged
+ assert.deepEqual(nextState.rows[0], oldState.rows[0]);
+ });
+ it("should not update state for empty action.data on PLACES_BOOKMARKS_REMOVED", () => {
+ const nextState = TopSites(undefined, {
+ type: at.PLACES_BOOKMARKS_REMOVED,
+ });
+ assert.equal(nextState, INITIAL_STATE.TopSites);
+ });
+ it("should update prefs on TOP_SITES_PREFS_UPDATED", () => {
+ const state = TopSites(
+ {},
+ { type: at.TOP_SITES_PREFS_UPDATED, data: { pref: "foo" } }
+ );
+
+ assert.equal(state.pref, "foo");
+ });
+ it("should not update state for empty action.data on PLACES_LINKS_DELETED", () => {
+ const nextState = TopSites(undefined, { type: at.PLACES_LINKS_DELETED });
+ assert.equal(nextState, INITIAL_STATE.TopSites);
+ });
+ it("should remove the site on PLACES_LINKS_DELETED", () => {
+ const oldState = { rows: [{ url: "foo.com" }, { url: "bar.com" }] };
+ const deleteAction = {
+ type: at.PLACES_LINKS_DELETED,
+ data: { urls: ["foo.com"] },
+ };
+ const nextState = TopSites(oldState, deleteAction);
+ assert.deepEqual(nextState.rows, [{ url: "bar.com" }]);
+ });
+ it("should set showSearchShortcutsForm to true on TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", () => {
+ const data = { index: 7 };
+ const nextState = TopSites(undefined, {
+ type: at.TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL,
+ data,
+ });
+ assert.isTrue(nextState.showSearchShortcutsForm);
+ });
+ it("should set showSearchShortcutsForm to false on TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL", () => {
+ const nextState = TopSites(undefined, {
+ type: at.TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL,
+ });
+ assert.isFalse(nextState.showSearchShortcutsForm);
+ });
+ it("should update searchShortcuts on UPDATE_SEARCH_SHORTCUTS", () => {
+ const shortcuts = [
+ {
+ keyword: "@google",
+ shortURL: "google",
+ url: "https://google.com",
+ searchIdentifier: /^google/,
+ },
+ {
+ keyword: "@baidu",
+ shortURL: "baidu",
+ url: "https://baidu.com",
+ searchIdentifier: /^baidu/,
+ },
+ ];
+ const nextState = TopSites(undefined, {
+ type: at.UPDATE_SEARCH_SHORTCUTS,
+ data: { searchShortcuts: shortcuts },
+ });
+ assert.deepEqual(shortcuts, nextState.searchShortcuts);
+ });
+ it("should remove all content on SNIPPETS_PREVIEW_MODE", () => {
+ const oldState = { rows: [{ url: "foo.com" }, { url: "bar.com" }] };
+ const nextState = TopSites(oldState, { type: at.SNIPPETS_PREVIEW_MODE });
+ assert.lengthOf(nextState.rows, 0);
+ });
+ });
+ describe("Prefs", () => {
+ function prevState(custom = {}) {
+ return Object.assign({}, INITIAL_STATE.Prefs, custom);
+ }
+ it("should have the correct initial state", () => {
+ const state = Prefs(undefined, {});
+ assert.deepEqual(state, INITIAL_STATE.Prefs);
+ });
+ describe("PREFS_INITIAL_VALUES", () => {
+ it("should return a new object", () => {
+ const state = Prefs(undefined, {
+ type: at.PREFS_INITIAL_VALUES,
+ data: {},
+ });
+ assert.notEqual(
+ INITIAL_STATE.Prefs,
+ state,
+ "should not modify INITIAL_STATE"
+ );
+ });
+ it("should set initalized to true", () => {
+ const state = Prefs(undefined, {
+ type: at.PREFS_INITIAL_VALUES,
+ data: {},
+ });
+ assert.isTrue(state.initialized);
+ });
+ it("should set .values", () => {
+ const newValues = { foo: 1, bar: 2 };
+ const state = Prefs(undefined, {
+ type: at.PREFS_INITIAL_VALUES,
+ data: newValues,
+ });
+ assert.equal(state.values, newValues);
+ });
+ });
+ describe("PREF_CHANGED", () => {
+ it("should return a new Prefs object", () => {
+ const state = Prefs(undefined, {
+ type: at.PREF_CHANGED,
+ data: { name: "foo", value: 2 },
+ });
+ assert.notEqual(
+ INITIAL_STATE.Prefs,
+ state,
+ "should not modify INITIAL_STATE"
+ );
+ });
+ it("should set the changed pref", () => {
+ const state = Prefs(prevState({ foo: 1 }), {
+ type: at.PREF_CHANGED,
+ data: { name: "foo", value: 2 },
+ });
+ assert.equal(state.values.foo, 2);
+ });
+ it("should return a new .pref object instead of mutating", () => {
+ const oldState = prevState({ foo: 1 });
+ const state = Prefs(oldState, {
+ type: at.PREF_CHANGED,
+ data: { name: "foo", value: 2 },
+ });
+ assert.notEqual(oldState.values, state.values);
+ });
+ });
+ });
+ describe("Dialog", () => {
+ it("should return INITIAL_STATE by default", () => {
+ assert.equal(
+ INITIAL_STATE.Dialog,
+ Dialog(undefined, { type: "non_existent" })
+ );
+ });
+ it("should toggle visible to true on DIALOG_OPEN", () => {
+ const action = { type: at.DIALOG_OPEN };
+ const nextState = Dialog(INITIAL_STATE.Dialog, action);
+ assert.isTrue(nextState.visible);
+ });
+ it("should pass url data on DIALOG_OPEN", () => {
+ const action = { type: at.DIALOG_OPEN, data: "some url" };
+ const nextState = Dialog(INITIAL_STATE.Dialog, action);
+ assert.equal(nextState.data, action.data);
+ });
+ it("should toggle visible to false on DIALOG_CANCEL", () => {
+ const action = { type: at.DIALOG_CANCEL, data: "some url" };
+ const nextState = Dialog(INITIAL_STATE.Dialog, action);
+ assert.isFalse(nextState.visible);
+ });
+ it("should return inital state on DELETE_HISTORY_URL", () => {
+ const action = { type: at.DELETE_HISTORY_URL };
+ const nextState = Dialog(INITIAL_STATE.Dialog, action);
+
+ assert.deepEqual(INITIAL_STATE.Dialog, nextState);
+ });
+ });
+ describe("Sections", () => {
+ let oldState;
+
+ beforeEach(() => {
+ oldState = new Array(5).fill(null).map((v, i) => ({
+ id: `foo_bar_${i}`,
+ title: `Foo Bar ${i}`,
+ initialized: false,
+ rows: [
+ { url: "www.foo.bar", pocket_id: 123 },
+ { url: "www.other.url" },
+ ],
+ order: i,
+ type: "history",
+ }));
+ });
+
+ it("should return INITIAL_STATE by default", () => {
+ assert.equal(
+ INITIAL_STATE.Sections,
+ Sections(undefined, { type: "non_existent" })
+ );
+ });
+ it("should remove the correct section on SECTION_DEREGISTER", () => {
+ const newState = Sections(oldState, {
+ type: at.SECTION_DEREGISTER,
+ data: "foo_bar_2",
+ });
+ assert.lengthOf(newState, 4);
+ const expectedNewState = oldState.splice(2, 1) && oldState;
+ assert.deepEqual(newState, expectedNewState);
+ });
+ it("should add a section on SECTION_REGISTER if it doesn't already exist", () => {
+ const action = {
+ type: at.SECTION_REGISTER,
+ data: { id: "foo_bar_5", title: "Foo Bar 5" },
+ };
+ const newState = Sections(oldState, action);
+ assert.lengthOf(newState, 6);
+ const insertedSection = newState.find(
+ section => section.id === "foo_bar_5"
+ );
+ assert.propertyVal(insertedSection, "title", action.data.title);
+ });
+ it("should set newSection.rows === [] if no rows are provided on SECTION_REGISTER", () => {
+ const action = {
+ type: at.SECTION_REGISTER,
+ data: { id: "foo_bar_5", title: "Foo Bar 5" },
+ };
+ const newState = Sections(oldState, action);
+ const insertedSection = newState.find(
+ section => section.id === "foo_bar_5"
+ );
+ assert.deepEqual(insertedSection.rows, []);
+ });
+ it("should update a section on SECTION_REGISTER if it already exists", () => {
+ const NEW_TITLE = "New Title";
+ const action = {
+ type: at.SECTION_REGISTER,
+ data: { id: "foo_bar_2", title: NEW_TITLE },
+ };
+ const newState = Sections(oldState, action);
+ assert.lengthOf(newState, 5);
+ const updatedSection = newState.find(
+ section => section.id === "foo_bar_2"
+ );
+ assert.ok(updatedSection && updatedSection.title === NEW_TITLE);
+ });
+ it("should set initialized to false on SECTION_REGISTER if there are no rows", () => {
+ const NEW_TITLE = "New Title";
+ const action = {
+ type: at.SECTION_REGISTER,
+ data: { id: "bloop", title: NEW_TITLE },
+ };
+ const newState = Sections(oldState, action);
+ const updatedSection = newState.find(section => section.id === "bloop");
+ assert.propertyVal(updatedSection, "initialized", false);
+ });
+ it("should set initialized to true on SECTION_REGISTER if there are rows", () => {
+ const NEW_TITLE = "New Title";
+ const action = {
+ type: at.SECTION_REGISTER,
+ data: { id: "bloop", title: NEW_TITLE, rows: [{}, {}] },
+ };
+ const newState = Sections(oldState, action);
+ const updatedSection = newState.find(section => section.id === "bloop");
+ assert.propertyVal(updatedSection, "initialized", true);
+ });
+ it("should have no effect on SECTION_UPDATE if the id doesn't exist", () => {
+ const action = {
+ type: at.SECTION_UPDATE,
+ data: { id: "fake_id", data: "fake_data" },
+ };
+ const newState = Sections(oldState, action);
+ assert.deepEqual(oldState, newState);
+ });
+ it("should update the section with the correct data on SECTION_UPDATE", () => {
+ const FAKE_DATA = { rows: ["some", "fake", "data"], foo: "bar" };
+ const action = {
+ type: at.SECTION_UPDATE,
+ data: Object.assign(FAKE_DATA, { id: "foo_bar_2" }),
+ };
+ const newState = Sections(oldState, action);
+ const updatedSection = newState.find(
+ section => section.id === "foo_bar_2"
+ );
+ assert.include(updatedSection, FAKE_DATA);
+ });
+ it("should set initialized to true on SECTION_UPDATE if rows is defined on action.data", () => {
+ const data = { rows: [], id: "foo_bar_2" };
+ const action = { type: at.SECTION_UPDATE, data };
+ const newState = Sections(oldState, action);
+ const updatedSection = newState.find(
+ section => section.id === "foo_bar_2"
+ );
+ assert.propertyVal(updatedSection, "initialized", true);
+ });
+ it("should retain pinned cards on SECTION_UPDATE", () => {
+ const ROW = { id: "row" };
+ let newState = Sections(oldState, {
+ type: at.SECTION_UPDATE,
+ data: Object.assign({ rows: [ROW] }, { id: "foo_bar_2" }),
+ });
+ let updatedSection = newState.find(section => section.id === "foo_bar_2");
+ assert.deepEqual(updatedSection.rows, [ROW]);
+
+ const PINNED_ROW = { id: "pinned", pinned: true, guid: "pinned" };
+ newState = Sections(newState, {
+ type: at.SECTION_UPDATE,
+ data: Object.assign({ rows: [PINNED_ROW] }, { id: "foo_bar_2" }),
+ });
+ updatedSection = newState.find(section => section.id === "foo_bar_2");
+ assert.deepEqual(updatedSection.rows, [PINNED_ROW]);
+
+ // Updating the section again should not duplicate pinned cards
+ newState = Sections(newState, {
+ type: at.SECTION_UPDATE,
+ data: Object.assign({ rows: [PINNED_ROW] }, { id: "foo_bar_2" }),
+ });
+ updatedSection = newState.find(section => section.id === "foo_bar_2");
+ assert.deepEqual(updatedSection.rows, [PINNED_ROW]);
+
+ // Updating the section should retain pinned card at its index
+ newState = Sections(newState, {
+ type: at.SECTION_UPDATE,
+ data: Object.assign({ rows: [ROW] }, { id: "foo_bar_2" }),
+ });
+ updatedSection = newState.find(section => section.id === "foo_bar_2");
+ assert.deepEqual(updatedSection.rows, [PINNED_ROW, ROW]);
+
+ // Clearing/Resetting the section should clear pinned cards
+ newState = Sections(newState, {
+ type: at.SECTION_UPDATE,
+ data: Object.assign({ rows: [] }, { id: "foo_bar_2" }),
+ });
+ updatedSection = newState.find(section => section.id === "foo_bar_2");
+ assert.deepEqual(updatedSection.rows, []);
+ });
+ it("should have no effect on SECTION_UPDATE_CARD if the id or url doesn't exist", () => {
+ const noIdAction = {
+ type: at.SECTION_UPDATE_CARD,
+ data: {
+ id: "non-existent",
+ url: "www.foo.bar",
+ options: { title: "New title" },
+ },
+ };
+ const noIdState = Sections(oldState, noIdAction);
+ const noUrlAction = {
+ type: at.SECTION_UPDATE_CARD,
+ data: {
+ id: "foo_bar_2",
+ url: "www.non-existent.url",
+ options: { title: "New title" },
+ },
+ };
+ const noUrlState = Sections(oldState, noUrlAction);
+ assert.deepEqual(noIdState, oldState);
+ assert.deepEqual(noUrlState, oldState);
+ });
+ it("should update the card with the correct data on SECTION_UPDATE_CARD", () => {
+ const action = {
+ type: at.SECTION_UPDATE_CARD,
+ data: {
+ id: "foo_bar_2",
+ url: "www.other.url",
+ options: { title: "Fake new title" },
+ },
+ };
+ const newState = Sections(oldState, action);
+ const updatedSection = newState.find(
+ section => section.id === "foo_bar_2"
+ );
+ const updatedCard = updatedSection.rows.find(
+ card => card.url === "www.other.url"
+ );
+ assert.propertyVal(updatedCard, "title", "Fake new title");
+ });
+ it("should only update the cards belonging to the right section on SECTION_UPDATE_CARD", () => {
+ const action = {
+ type: at.SECTION_UPDATE_CARD,
+ data: {
+ id: "foo_bar_2",
+ url: "www.other.url",
+ options: { title: "Fake new title" },
+ },
+ };
+ const newState = Sections(oldState, action);
+ newState.forEach((section, i) => {
+ if (section.id !== "foo_bar_2") {
+ assert.deepEqual(section, oldState[i]);
+ }
+ });
+ });
+ it("should allow action.data to set .initialized", () => {
+ const data = { rows: [], initialized: false, id: "foo_bar_2" };
+ const action = { type: at.SECTION_UPDATE, data };
+ const newState = Sections(oldState, action);
+ const updatedSection = newState.find(
+ section => section.id === "foo_bar_2"
+ );
+ assert.propertyVal(updatedSection, "initialized", false);
+ });
+ it("should dedupe based on dedupeConfigurations", () => {
+ const site = { url: "foo.com" };
+ const highlights = { rows: [site], id: "highlights" };
+ const topstories = { rows: [site], id: "topstories" };
+ const dedupeConfigurations = [
+ { id: "topstories", dedupeFrom: ["highlights"] },
+ ];
+ const action = { data: { dedupeConfigurations }, type: "SECTION_UPDATE" };
+ const state = [highlights, topstories];
+
+ const nextState = Sections(state, action);
+
+ assert.equal(nextState.find(s => s.id === "highlights").rows.length, 1);
+ assert.equal(nextState.find(s => s.id === "topstories").rows.length, 0);
+ });
+ it("should remove blocked and deleted urls from all rows in all sections", () => {
+ const blockAction = {
+ type: at.PLACES_LINK_BLOCKED,
+ data: { url: "www.foo.bar" },
+ };
+ const deleteAction = {
+ type: at.PLACES_LINKS_DELETED,
+ data: { urls: ["www.foo.bar"] },
+ };
+ const newBlockState = Sections(oldState, blockAction);
+ const newDeleteState = Sections(oldState, deleteAction);
+ newBlockState.concat(newDeleteState).forEach(section => {
+ assert.deepEqual(section.rows, [{ url: "www.other.url" }]);
+ });
+ });
+ it("should not update state for empty action.data on PLACES_LINK_BLOCKED", () => {
+ const nextState = Sections(undefined, { type: at.PLACES_LINK_BLOCKED });
+ assert.equal(nextState, INITIAL_STATE.Sections);
+ });
+ it("should not update state for empty action.data on PLACES_LINKS_DELETED", () => {
+ const nextState = Sections(undefined, { type: at.PLACES_LINKS_DELETED });
+ assert.equal(nextState, INITIAL_STATE.Sections);
+ });
+ 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);
+ });
+ it("should bookmark an item when PLACES_BOOKMARK_ADDED is received", () => {
+ const action = {
+ type: at.PLACES_BOOKMARK_ADDED,
+ data: {
+ url: "www.foo.bar",
+ bookmarkGuid: "bookmark123",
+ bookmarkTitle: "Title for bar.com",
+ dateAdded: 1234567,
+ },
+ };
+ const nextState = Sections(oldState, action);
+ // check a section to ensure the correct url was bookmarked
+ const [newRow, oldRow] = nextState[0].rows;
+
+ // new row has bookmark data
+ assert.equal(newRow.url, action.data.url);
+ assert.equal(newRow.type, "bookmark");
+ assert.equal(newRow.bookmarkGuid, action.data.bookmarkGuid);
+ assert.equal(newRow.bookmarkTitle, action.data.bookmarkTitle);
+ assert.equal(newRow.bookmarkDateCreated, action.data.dateAdded);
+
+ // old row is unchanged
+ assert.equal(oldRow, oldState[0].rows[1]);
+ });
+ it("should not update state for empty action.data on PLACES_BOOKMARKS_REMOVED", () => {
+ const nextState = Sections(undefined, {
+ type: at.PLACES_BOOKMARKS_REMOVED,
+ });
+ assert.equal(nextState, INITIAL_STATE.Sections);
+ });
+ it("should remove the bookmark when PLACES_BOOKMARKS_REMOVED is received", () => {
+ const action = {
+ type: at.PLACES_BOOKMARKS_REMOVED,
+ data: {
+ urls: ["www.foo.bar"],
+ bookmarkGuid: "bookmark123",
+ },
+ };
+ // add some bookmark data for the first url in rows
+ oldState.forEach(item => {
+ item.rows[0].bookmarkGuid = "bookmark123";
+ item.rows[0].bookmarkTitle = "Title for bar.com";
+ item.rows[0].bookmarkDateCreated = 1234567;
+ item.rows[0].type = "bookmark";
+ });
+ const nextState = Sections(oldState, action);
+ // check a section to ensure the correct bookmark was removed
+ const [newRow, oldRow] = nextState[0].rows;
+
+ // new row isn't a bookmark
+ assert.equal(newRow.url, action.data.urls[0]);
+ assert.equal(newRow.type, "history");
+ assert.isUndefined(newRow.bookmarkGuid);
+ 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]);
+ });
+ it("should remove all content on SNIPPETS_PREVIEW_MODE", () => {
+ const previewMode = { type: at.SNIPPETS_PREVIEW_MODE };
+ const newState = Sections(oldState, previewMode);
+ newState.forEach(section => {
+ assert.lengthOf(section.rows, 0);
+ });
+ });
+ });
+ describe("#insertPinned", () => {
+ let links;
+
+ beforeEach(() => {
+ links = new Array(12).fill(null).map((v, i) => ({ url: `site${i}.com` }));
+ });
+
+ it("should place pinned links where they belong", () => {
+ const pinned = [
+ { url: "http://github.com/mozilla/activity-stream", title: "moz/a-s" },
+ { url: "http://example.com", title: "example" },
+ ];
+ const result = insertPinned(links, pinned);
+ for (let index of [0, 1]) {
+ assert.equal(result[index].url, pinned[index].url);
+ assert.ok(result[index].isPinned);
+ assert.equal(result[index].pinIndex, index);
+ }
+ assert.deepEqual(result.slice(2), links);
+ });
+ it("should handle empty slots in the pinned list", () => {
+ const pinned = [
+ null,
+ { url: "http://github.com/mozilla/activity-stream", title: "moz/a-s" },
+ null,
+ null,
+ { url: "http://example.com", title: "example" },
+ ];
+ const result = insertPinned(links, pinned);
+ for (let index of [1, 4]) {
+ assert.equal(result[index].url, pinned[index].url);
+ assert.ok(result[index].isPinned);
+ assert.equal(result[index].pinIndex, index);
+ }
+ result.splice(4, 1);
+ result.splice(1, 1);
+ assert.deepEqual(result, links);
+ });
+ it("should handle a pinned site past the end of the list of links", () => {
+ const pinned = [];
+ pinned[11] = {
+ url: "http://github.com/mozilla/activity-stream",
+ title: "moz/a-s",
+ };
+ const result = insertPinned([], pinned);
+ assert.equal(result[11].url, pinned[11].url);
+ assert.isTrue(result[11].isPinned);
+ assert.equal(result[11].pinIndex, 11);
+ });
+ it("should unpin previously pinned links no longer in the pinned list", () => {
+ const pinned = [];
+ links[2].isPinned = true;
+ links[2].pinIndex = 2;
+ const result = insertPinned(links, pinned);
+ assert.notProperty(result[2], "isPinned");
+ assert.notProperty(result[2], "pinIndex");
+ });
+ it("should handle a link present in both the links and pinned list", () => {
+ const pinned = [links[7]];
+ const result = insertPinned(links, pinned);
+ assert.equal(links.length, result.length);
+ });
+ it("should not modify the original data", () => {
+ const pinned = [{ url: "http://example.com" }];
+
+ insertPinned(links, pinned);
+
+ assert.equal(typeof pinned[0].isPinned, "undefined");
+ });
+ });
+ describe("Snippets", () => {
+ it("should return INITIAL_STATE by default", () => {
+ assert.equal(
+ Snippets(undefined, { type: "some_action" }),
+ INITIAL_STATE.Snippets
+ );
+ });
+ it("should set initialized to true on a SNIPPETS_DATA action", () => {
+ const state = Snippets(undefined, { type: at.SNIPPETS_DATA, data: {} });
+ assert.isTrue(state.initialized);
+ });
+ it("should set the snippet data on a SNIPPETS_DATA action", () => {
+ const data = { snippetsURL: "foo.com", version: 4 };
+ const state = Snippets(undefined, { type: at.SNIPPETS_DATA, data });
+ assert.propertyVal(state, "snippetsURL", data.snippetsURL);
+ assert.propertyVal(state, "version", data.version);
+ });
+ it("should reset to the initial state on a SNIPPETS_RESET action", () => {
+ const state = Snippets(
+ { initialized: true, foo: "bar" },
+ { type: at.SNIPPETS_RESET }
+ );
+ assert.equal(state, INITIAL_STATE.Snippets);
+ });
+ it("should set the new blocklist on SNIPPET_BLOCKED", () => {
+ const state = Snippets(
+ { blockList: [] },
+ { type: at.SNIPPET_BLOCKED, data: 1 }
+ );
+ assert.deepEqual(state.blockList, [1]);
+ });
+ it("should clear the blocklist on SNIPPETS_BLOCKLIST_CLEARED", () => {
+ const state = Snippets(
+ { blockList: [1, 2] },
+ { type: at.SNIPPETS_BLOCKLIST_CLEARED }
+ );
+ assert.deepEqual(state.blockList, []);
+ });
+ });
+ describe("Pocket", () => {
+ it("should return INITIAL_STATE by default", () => {
+ assert.equal(
+ Pocket(undefined, { type: "some_action" }),
+ INITIAL_STATE.Pocket
+ );
+ });
+ it("should set waitingForSpoc on a POCKET_WAITING_FOR_SPOC action", () => {
+ const state = Pocket(undefined, {
+ type: at.POCKET_WAITING_FOR_SPOC,
+ data: false,
+ });
+ assert.isFalse(state.waitingForSpoc);
+ });
+ it("should have undefined for initial isUserLoggedIn state", () => {
+ assert.isNull(Pocket(undefined, { type: "some_action" }).isUserLoggedIn);
+ });
+ it("should set isUserLoggedIn to false on a POCKET_LOGGED_IN with null", () => {
+ const state = Pocket(undefined, {
+ type: at.POCKET_LOGGED_IN,
+ data: null,
+ });
+ assert.isFalse(state.isUserLoggedIn);
+ });
+ it("should set isUserLoggedIn to false on a POCKET_LOGGED_IN with false", () => {
+ const state = Pocket(undefined, {
+ type: at.POCKET_LOGGED_IN,
+ data: false,
+ });
+ assert.isFalse(state.isUserLoggedIn);
+ });
+ it("should set isUserLoggedIn to true on a POCKET_LOGGED_IN with true", () => {
+ const state = Pocket(undefined, {
+ type: at.POCKET_LOGGED_IN,
+ data: true,
+ });
+ assert.isTrue(state.isUserLoggedIn);
+ });
+ it("should set pocketCta with correct object on a POCKET_CTA", () => {
+ const data = {
+ cta_button: "cta button",
+ cta_text: "cta text",
+ cta_url: "https://cta-url.com",
+ use_cta: true,
+ };
+ const state = Pocket(undefined, { type: at.POCKET_CTA, data });
+ assert.equal(state.pocketCta.ctaButton, data.cta_button);
+ assert.equal(state.pocketCta.ctaText, data.cta_text);
+ assert.equal(state.pocketCta.ctaUrl, data.cta_url);
+ assert.equal(state.pocketCta.useCta, data.use_cta);
+ });
+ });
+ describe("Personalization", () => {
+ it("should return INITIAL_STATE by default", () => {
+ assert.equal(
+ Personalization(undefined, { type: "some_action" }),
+ INITIAL_STATE.Personalization
+ );
+ });
+ it("should set lastUpdated with DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED", () => {
+ const state = Personalization(undefined, {
+ type: at.DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED,
+ data: {
+ lastUpdated: 123,
+ },
+ });
+ assert.equal(state.lastUpdated, 123);
+ });
+ it("should set initialized to true with DISCOVERY_STREAM_PERSONALIZATION_INIT", () => {
+ const state = Personalization(undefined, {
+ type: at.DISCOVERY_STREAM_PERSONALIZATION_INIT,
+ });
+ assert.equal(state.initialized, true);
+ });
+ });
+ describe("DiscoveryStream", () => {
+ it("should return INITIAL_STATE by default", () => {
+ assert.equal(
+ DiscoveryStream(undefined, { type: "some_action" }),
+ INITIAL_STATE.DiscoveryStream
+ );
+ });
+ it("should set isPrivacyInfoModalVisible to true with SHOW_PRIVACY_INFO", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.SHOW_PRIVACY_INFO,
+ });
+ assert.equal(state.isPrivacyInfoModalVisible, true);
+ });
+ it("should set isPrivacyInfoModalVisible to false with HIDE_PRIVACY_INFO", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.HIDE_PRIVACY_INFO,
+ });
+ assert.equal(state.isPrivacyInfoModalVisible, false);
+ });
+ it("should set layout data with DISCOVERY_STREAM_LAYOUT_UPDATE", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
+ data: { layout: ["test"], lastUpdated: 123 },
+ });
+ assert.equal(state.layout[0], "test");
+ assert.equal(state.lastUpdated, 123);
+ });
+ it("should reset layout data with DISCOVERY_STREAM_LAYOUT_RESET", () => {
+ const layoutData = { layout: ["test"], lastUpdated: 123 };
+ const feedsData = {
+ "https://foo.com/feed1": { lastUpdated: 123, data: [1, 2, 3] },
+ };
+ const spocsData = {
+ lastUpdated: 123,
+ spocs: [1, 2, 3],
+ };
+ let state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
+ data: layoutData,
+ });
+ state = DiscoveryStream(state, {
+ type: at.DISCOVERY_STREAM_FEEDS_UPDATE,
+ data: feedsData,
+ });
+ state = DiscoveryStream(state, {
+ type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
+ data: spocsData,
+ });
+ state = DiscoveryStream(state, {
+ type: at.DISCOVERY_STREAM_LAYOUT_RESET,
+ });
+
+ assert.deepEqual(state, INITIAL_STATE.DiscoveryStream);
+ });
+ it("should set config data with DISCOVERY_STREAM_CONFIG_CHANGE", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_CONFIG_CHANGE,
+ data: { enabled: true },
+ });
+ assert.deepEqual(state.config, { enabled: true });
+ });
+ it("should set recentSavesEnabled with DISCOVERY_STREAM_PREFS_SETUP", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_PREFS_SETUP,
+ data: { recentSavesEnabled: true },
+ });
+ assert.isTrue(state.recentSavesEnabled);
+ });
+ it("should set recentSavesData with DISCOVERY_STREAM_RECENT_SAVES", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_RECENT_SAVES,
+ data: { recentSaves: [1, 2, 3] },
+ });
+ assert.deepEqual(state.recentSavesData, [1, 2, 3]);
+ });
+ it("should set isUserLoggedIn with DISCOVERY_STREAM_POCKET_STATE_SET", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_POCKET_STATE_SET,
+ data: { isUserLoggedIn: true },
+ });
+ assert.isTrue(state.isUserLoggedIn);
+ });
+ it("should set feeds as loaded with DISCOVERY_STREAM_FEEDS_UPDATE", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_FEEDS_UPDATE,
+ });
+ assert.isTrue(state.feeds.loaded);
+ });
+ it("should set spoc_endpoint with DISCOVERY_STREAM_SPOCS_ENDPOINT", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_SPOCS_ENDPOINT,
+ data: { url: "foo.com" },
+ });
+ assert.equal(state.spocs.spocs_endpoint, "foo.com");
+ });
+ it("should use initial state with DISCOVERY_STREAM_SPOCS_PLACEMENTS", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_SPOCS_PLACEMENTS,
+ data: {},
+ });
+ assert.deepEqual(state.spocs.placements, []);
+ });
+ it("should set placements with DISCOVERY_STREAM_SPOCS_PLACEMENTS", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_SPOCS_PLACEMENTS,
+ data: {
+ placements: [1, 2, 3],
+ },
+ });
+ assert.deepEqual(state.spocs.placements, [1, 2, 3]);
+ });
+ it("should set spocs with DISCOVERY_STREAM_SPOCS_UPDATE", () => {
+ const data = {
+ lastUpdated: 123,
+ spocs: [1, 2, 3],
+ };
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
+ data,
+ });
+ assert.deepEqual(state.spocs, {
+ spocs_endpoint: "",
+ data: [1, 2, 3],
+ lastUpdated: 123,
+ loaded: true,
+ frequency_caps: [],
+ blocked: [],
+ placements: [],
+ });
+ });
+ it("should default to a single spoc placement", () => {
+ const deleteAction = {
+ type: at.DISCOVERY_STREAM_LINK_BLOCKED,
+ data: { url: "https://foo.com" },
+ };
+ const oldState = {
+ spocs: {
+ data: {
+ spocs: {
+ items: [
+ {
+ url: "test-spoc.com",
+ },
+ ],
+ },
+ },
+ loaded: true,
+ },
+ feeds: {
+ data: {},
+ loaded: true,
+ },
+ };
+
+ const newState = DiscoveryStream(oldState, deleteAction);
+
+ assert.equal(newState.spocs.data.spocs.items.length, 1);
+ });
+ it("should handle no data from DISCOVERY_STREAM_SPOCS_UPDATE", () => {
+ const data = null;
+ const state = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_SPOCS_UPDATE,
+ data,
+ });
+ assert.deepEqual(state.spocs, INITIAL_STATE.DiscoveryStream.spocs);
+ });
+ it("should add blocked spocs to blocked array with DISCOVERY_STREAM_SPOC_BLOCKED", () => {
+ const firstState = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_SPOC_BLOCKED,
+ data: { url: "https://foo.com" },
+ });
+ const secondState = DiscoveryStream(firstState, {
+ type: at.DISCOVERY_STREAM_SPOC_BLOCKED,
+ data: { url: "https://bar.com" },
+ });
+ assert.deepEqual(firstState.spocs.blocked, ["https://foo.com"]);
+ assert.deepEqual(secondState.spocs.blocked, [
+ "https://foo.com",
+ "https://bar.com",
+ ]);
+ });
+ it("should not update state for empty action.data on DISCOVERY_STREAM_LINK_BLOCKED", () => {
+ const newState = DiscoveryStream(undefined, {
+ type: at.DISCOVERY_STREAM_LINK_BLOCKED,
+ });
+ assert.equal(newState, INITIAL_STATE.DiscoveryStream);
+ });
+ it("should not update state if feeds are not loaded", () => {
+ const deleteAction = {
+ type: at.DISCOVERY_STREAM_LINK_BLOCKED,
+ data: { url: "foo.com" },
+ };
+ const newState = DiscoveryStream(undefined, deleteAction);
+ assert.equal(newState, INITIAL_STATE.DiscoveryStream);
+ });
+ it("should not update state if spocs and feeds data is undefined", () => {
+ const deleteAction = {
+ type: at.DISCOVERY_STREAM_LINK_BLOCKED,
+ data: { url: "foo.com" },
+ };
+ const oldState = {
+ spocs: {
+ data: {},
+ loaded: true,
+ placements: [{ name: "spocs" }],
+ },
+ feeds: {
+ data: {},
+ loaded: true,
+ },
+ };
+ const newState = DiscoveryStream(oldState, deleteAction);
+ assert.deepEqual(newState, oldState);
+ });
+ it("should remove the site on DISCOVERY_STREAM_LINK_BLOCKED from spocs if feeds data is empty", () => {
+ const deleteAction = {
+ type: at.DISCOVERY_STREAM_LINK_BLOCKED,
+ data: { url: "https://foo.com" },
+ };
+ const oldState = {
+ spocs: {
+ data: {
+ spocs: {
+ items: [{ url: "https://foo.com" }, { url: "test-spoc.com" }],
+ },
+ },
+ loaded: true,
+ placements: [{ name: "spocs" }],
+ },
+ feeds: {
+ data: {},
+ loaded: true,
+ },
+ };
+ const newState = DiscoveryStream(oldState, deleteAction);
+ assert.deepEqual(newState.spocs.data.spocs.items, [
+ { url: "test-spoc.com" },
+ ]);
+ });
+ it("should remove the site on DISCOVERY_STREAM_LINK_BLOCKED from feeds if spocs data is empty", () => {
+ const deleteAction = {
+ type: at.DISCOVERY_STREAM_LINK_BLOCKED,
+ data: { url: "https://foo.com" },
+ };
+ const oldState = {
+ spocs: {
+ data: {},
+ loaded: true,
+ placements: [{ name: "spocs" }],
+ },
+ feeds: {
+ data: {
+ "https://foo.com/feed1": {
+ data: {
+ recommendations: [
+ { url: "https://foo.com" },
+ { url: "test.com" },
+ ],
+ },
+ },
+ },
+ loaded: true,
+ },
+ };
+ const newState = DiscoveryStream(oldState, deleteAction);
+ assert.deepEqual(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations,
+ [{ url: "test.com" }]
+ );
+ });
+ it("should remove the site on DISCOVERY_STREAM_LINK_BLOCKED from both feeds and spocs", () => {
+ const oldState = {
+ feeds: {
+ data: {
+ "https://foo.com/feed1": {
+ data: {
+ recommendations: [
+ { url: "https://foo.com" },
+ { url: "test.com" },
+ ],
+ },
+ },
+ },
+ loaded: true,
+ },
+ spocs: {
+ data: {
+ spocs: {
+ items: [{ url: "https://foo.com" }, { url: "test-spoc.com" }],
+ },
+ },
+ loaded: true,
+ placements: [{ name: "spocs" }],
+ },
+ };
+ const deleteAction = {
+ type: at.DISCOVERY_STREAM_LINK_BLOCKED,
+ data: { url: "https://foo.com" },
+ };
+ const newState = DiscoveryStream(oldState, deleteAction);
+ assert.deepEqual(newState.spocs.data.spocs.items, [
+ { url: "test-spoc.com" },
+ ]);
+ assert.deepEqual(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations,
+ [{ url: "test.com" }]
+ );
+ });
+ it("should not update state for empty action.data on PLACES_SAVED_TO_POCKET", () => {
+ const newState = DiscoveryStream(undefined, {
+ type: at.PLACES_SAVED_TO_POCKET,
+ });
+ assert.equal(newState, INITIAL_STATE.DiscoveryStream);
+ });
+ it("should add pocket_id on PLACES_SAVED_TO_POCKET in both feeds and spocs", () => {
+ const oldState = {
+ feeds: {
+ data: {
+ "https://foo.com/feed1": {
+ data: {
+ recommendations: [
+ { url: "https://foo.com" },
+ { url: "test.com" },
+ ],
+ },
+ },
+ },
+ loaded: true,
+ },
+ spocs: {
+ data: {
+ spocs: {
+ items: [{ url: "https://foo.com" }, { url: "test-spoc.com" }],
+ },
+ },
+ placements: [{ name: "spocs" }],
+ loaded: true,
+ },
+ };
+ const action = {
+ type: at.PLACES_SAVED_TO_POCKET,
+ data: {
+ url: "https://foo.com",
+ pocket_id: 1234,
+ open_url: "https://foo-1234",
+ },
+ };
+
+ const newState = DiscoveryStream(oldState, action);
+
+ assert.lengthOf(newState.spocs.data.spocs.items, 2);
+ assert.equal(
+ newState.spocs.data.spocs.items[0].pocket_id,
+ action.data.pocket_id
+ );
+ assert.equal(
+ newState.spocs.data.spocs.items[0].open_url,
+ action.data.open_url
+ );
+ assert.isUndefined(newState.spocs.data.spocs.items[1].pocket_id);
+
+ assert.lengthOf(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations,
+ 2
+ );
+ assert.equal(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations[0]
+ .pocket_id,
+ action.data.pocket_id
+ );
+ assert.equal(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations[0]
+ .open_url,
+ action.data.open_url
+ );
+ assert.isUndefined(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations[1]
+ .pocket_id
+ );
+ });
+ it("should not update state for empty action.data on DELETE_FROM_POCKET", () => {
+ const newState = DiscoveryStream(undefined, {
+ type: at.DELETE_FROM_POCKET,
+ });
+ assert.equal(newState, INITIAL_STATE.DiscoveryStream);
+ });
+ it("should remove site on DELETE_FROM_POCKET in both feeds and spocs", () => {
+ const oldState = {
+ feeds: {
+ data: {
+ "https://foo.com/feed1": {
+ data: {
+ recommendations: [
+ { url: "https://foo.com", pocket_id: 1234 },
+ { url: "test.com" },
+ ],
+ },
+ },
+ },
+ loaded: true,
+ },
+ spocs: {
+ data: {
+ spocs: {
+ items: [
+ { url: "https://foo.com", pocket_id: 1234 },
+ { url: "test-spoc.com" },
+ ],
+ },
+ },
+ loaded: true,
+ placements: [{ name: "spocs" }],
+ },
+ };
+ const deleteAction = {
+ type: at.DELETE_FROM_POCKET,
+ data: {
+ pocket_id: 1234,
+ },
+ };
+
+ const newState = DiscoveryStream(oldState, deleteAction);
+ assert.deepEqual(newState.spocs.data.spocs.items, [
+ { url: "test-spoc.com" },
+ ]);
+ assert.deepEqual(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations,
+ [{ url: "test.com" }]
+ );
+ });
+ it("should remove site on ARCHIVE_FROM_POCKET in both feeds and spocs", () => {
+ const oldState = {
+ feeds: {
+ data: {
+ "https://foo.com/feed1": {
+ data: {
+ recommendations: [
+ { url: "https://foo.com", pocket_id: 1234 },
+ { url: "test.com" },
+ ],
+ },
+ },
+ },
+ loaded: true,
+ },
+ spocs: {
+ data: {
+ spocs: {
+ items: [
+ { url: "https://foo.com", pocket_id: 1234 },
+ { url: "test-spoc.com" },
+ ],
+ },
+ },
+ loaded: true,
+ placements: [{ name: "spocs" }],
+ },
+ };
+ const deleteAction = {
+ type: at.ARCHIVE_FROM_POCKET,
+ data: {
+ pocket_id: 1234,
+ },
+ };
+
+ const newState = DiscoveryStream(oldState, deleteAction);
+ assert.deepEqual(newState.spocs.data.spocs.items, [
+ { url: "test-spoc.com" },
+ ]);
+ assert.deepEqual(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations,
+ [{ url: "test.com" }]
+ );
+ });
+ it("should add boookmark details on PLACES_BOOKMARK_ADDED in both feeds and spocs", () => {
+ const oldState = {
+ feeds: {
+ data: {
+ "https://foo.com/feed1": {
+ data: {
+ recommendations: [
+ { url: "https://foo.com" },
+ { url: "test.com" },
+ ],
+ },
+ },
+ },
+ loaded: true,
+ },
+ spocs: {
+ data: {
+ spocs: {
+ items: [{ url: "https://foo.com" }, { url: "test-spoc.com" }],
+ },
+ },
+ loaded: true,
+ placements: [{ name: "spocs" }],
+ },
+ };
+ const bookmarkAction = {
+ type: at.PLACES_BOOKMARK_ADDED,
+ data: {
+ url: "https://foo.com",
+ bookmarkGuid: "bookmark123",
+ bookmarkTitle: "Title for bar.com",
+ dateAdded: 1234567,
+ },
+ };
+
+ const newState = DiscoveryStream(oldState, bookmarkAction);
+
+ assert.lengthOf(newState.spocs.data.spocs.items, 2);
+ assert.equal(
+ newState.spocs.data.spocs.items[0].bookmarkGuid,
+ bookmarkAction.data.bookmarkGuid
+ );
+ assert.equal(
+ newState.spocs.data.spocs.items[0].bookmarkTitle,
+ bookmarkAction.data.bookmarkTitle
+ );
+ assert.isUndefined(newState.spocs.data.spocs.items[1].bookmarkGuid);
+
+ assert.lengthOf(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations,
+ 2
+ );
+ assert.equal(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations[0]
+ .bookmarkGuid,
+ bookmarkAction.data.bookmarkGuid
+ );
+ assert.equal(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations[0]
+ .bookmarkTitle,
+ bookmarkAction.data.bookmarkTitle
+ );
+ assert.isUndefined(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations[1]
+ .bookmarkGuid
+ );
+ });
+
+ it("should remove boookmark details on PLACES_BOOKMARKS_REMOVED in both feeds and spocs", () => {
+ const oldState = {
+ feeds: {
+ data: {
+ "https://foo.com/feed1": {
+ data: {
+ recommendations: [
+ {
+ url: "https://foo.com",
+ bookmarkGuid: "bookmark123",
+ bookmarkTitle: "Title for bar.com",
+ },
+ { url: "test.com" },
+ ],
+ },
+ },
+ },
+ loaded: true,
+ },
+ spocs: {
+ data: {
+ spocs: {
+ items: [
+ {
+ url: "https://foo.com",
+ bookmarkGuid: "bookmark123",
+ bookmarkTitle: "Title for bar.com",
+ },
+ { url: "test-spoc.com" },
+ ],
+ },
+ },
+ loaded: true,
+ placements: [{ name: "spocs" }],
+ },
+ };
+ const action = {
+ type: at.PLACES_BOOKMARKS_REMOVED,
+ data: {
+ urls: ["https://foo.com"],
+ },
+ };
+
+ const newState = DiscoveryStream(oldState, action);
+
+ assert.lengthOf(newState.spocs.data.spocs.items, 2);
+ assert.isUndefined(newState.spocs.data.spocs.items[0].bookmarkGuid);
+ assert.isUndefined(newState.spocs.data.spocs.items[0].bookmarkTitle);
+
+ assert.lengthOf(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations,
+ 2
+ );
+ assert.isUndefined(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations[0]
+ .bookmarkGuid
+ );
+ assert.isUndefined(
+ newState.feeds.data["https://foo.com/feed1"].data.recommendations[0]
+ .bookmarkTitle
+ );
+ });
+ describe("PREF_CHANGED", () => {
+ it("should set isCollectionDismissible", () => {
+ const state = DiscoveryStream(undefined, {
+ type: at.PREF_CHANGED,
+ data: {
+ name: "discoverystream.isCollectionDismissible",
+ value: true,
+ },
+ });
+ assert.equal(state.isCollectionDismissible, true);
+ });
+ });
+ });
+ describe("Search", () => {
+ it("should return INITIAL_STATE by default", () => {
+ assert.equal(
+ Search(undefined, { type: "some_action" }),
+ INITIAL_STATE.Search
+ );
+ });
+ it("should set disable to true on DISABLE_SEARCH", () => {
+ const nextState = Search(undefined, { type: "DISABLE_SEARCH" });
+ assert.propertyVal(nextState, "disable", true);
+ });
+ it("should set focus to true on FAKE_FOCUS_SEARCH", () => {
+ const nextState = Search(undefined, { type: "FAKE_FOCUS_SEARCH" });
+ assert.propertyVal(nextState, "fakeFocus", true);
+ });
+ it("should set focus and disable to false on SHOW_SEARCH", () => {
+ const nextState = Search(undefined, { type: "SHOW_SEARCH" });
+ assert.propertyVal(nextState, "fakeFocus", false);
+ assert.propertyVal(nextState, "disable", false);
+ });
+ });
+ it("should set initialized to true on AS_ROUTER_INITIALIZED", () => {
+ const nextState = ASRouter(undefined, { type: "AS_ROUTER_INITIALIZED" });
+ assert.propertyVal(nextState, "initialized", true);
+ });
+});