Fix Bug 1514337 - Activity Stream layout pref and layout data for rapid experimentation (#4593)

* Fix Bug 1514337 - Activity Stream layout pref and layout data for rapid experimentation

* lint

* Adding tests a a few tweaks.

* bug fix

* removing use_layout pref

* Updates to tests

* test updates

* deepEqual usage
This commit is contained in:
ScottDowne 2018-12-18 16:05:24 -05:00 коммит произвёл GitHub
Родитель d46b46f488
Коммит 325b45ae96
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 69 добавлений и 3 удалений

Просмотреть файл

@ -33,6 +33,7 @@ for (const type of [
"AS_ROUTER_TELEMETRY_USER_EVENT",
"BLOCK_URL",
"BOOKMARK_URL",
"CONTENT_LAYOUT",
"COPY_DOWNLOAD_LINK",
"DELETE_BOOKMARK_BY_ID",
"DELETE_FROM_POCKET",

Просмотреть файл

@ -47,6 +47,7 @@ const INITIAL_STATE = {
pocketCta: {},
waitingForSpoc: true,
},
Layout: [],
};
function App(prevState = INITIAL_STATE.App, action) {
@ -429,10 +430,19 @@ function Pocket(prevState = INITIAL_STATE.Pocket, action) {
}
}
function Layout(prevState = INITIAL_STATE.Layout, action) {
switch (action.type) {
case at.CONTENT_LAYOUT:
return action.data;
default:
return prevState;
}
}
this.INITIAL_STATE = INITIAL_STATE;
this.TOP_SITES_DEFAULT_ROWS = TOP_SITES_DEFAULT_ROWS;
this.TOP_SITES_MAX_SITES_PER_ROW = TOP_SITES_MAX_SITES_PER_ROW;
this.reducers = {TopSites, App, ASRouter, Snippets, Prefs, Dialog, Sections, Pocket};
this.reducers = {TopSites, App, ASRouter, Snippets, Prefs, Dialog, Sections, Pocket, Layout};
const EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE", "insertPinned", "TOP_SITES_DEFAULT_ROWS", "TOP_SITES_MAX_SITES_PER_ROW"];

Просмотреть файл

@ -315,4 +315,4 @@ export class _Sections extends React.PureComponent {
}
}
export const Sections = connect(state => ({Sections: state.Sections, Prefs: state.Prefs}))(_Sections);
export const Sections = connect(state => ({Sections: state.Sections, Prefs: state.Prefs, Layout: state.Layout}))(_Sections);

Просмотреть файл

@ -112,6 +112,13 @@ this.TopStoriesFeed = class TopStoriesFeed {
this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : ac.AlsoToPreloaded(action));
}
maybeDispatchLayoutUpdate(data, shouldBroadcast) {
if (data && data.length) {
const action = {type: at.CONTENT_LAYOUT, data};
this.store.dispatch(shouldBroadcast ? ac.BroadcastToContent(action) : ac.AlsoToPreloaded(action));
}
}
doContentUpdate(shouldBroadcast) {
let updateProps = {};
if (this.stories) {
@ -174,6 +181,7 @@ this.TopStoriesFeed = class TopStoriesFeed {
}
const body = await response.json();
this.maybeDispatchLayoutUpdate(body.layout);
this.updateSettings(body.settings);
this.stories = this.rotate(this.transform(body.recommendations));
this.cleanUpTopRecImpressionPref();
@ -194,7 +202,9 @@ this.TopStoriesFeed = class TopStoriesFeed {
async loadCachedData() {
const data = await this.cache.get();
let stories = data.stories && data.stories.recommendations;
let layout = data.stories && data.stories.layout;
let topics = data.topics && data.topics.topics;
this.maybeDispatchLayoutUpdate(layout);
let affinities = data.domainAffinities;
if (this.personalized && affinities && affinities.scores) {

Просмотреть файл

@ -1,5 +1,5 @@
import {INITIAL_STATE, insertPinned, reducers} from "common/Reducers.jsm";
const {TopSites, App, Snippets, Prefs, Dialog, Sections, Pocket} = reducers;
const {TopSites, App, Snippets, Prefs, Dialog, Sections, Pocket, Layout} = reducers;
import {actionTypes as at} from "common/Actions.jsm";
describe("Reducers", () => {
@ -655,4 +655,13 @@ describe("Reducers", () => {
assert.equal(state.pocketCta.useCta, data.use_cta);
});
});
describe("Layout", () => {
it("should return INITIAL_STATE by default", () => {
assert.equal(Layout(undefined, {type: "some_action"}), INITIAL_STATE.Layout);
});
it("should set layout data with layout.type CONTENT_LAYOUT", () => {
const state = Layout(undefined, {type: at.CONTENT_LAYOUT, data: ["test"]});
assert.equal(state[0], "test");
});
});
});

Просмотреть файл

@ -1460,4 +1460,40 @@ describe("Top Stories Feed", () => {
assert.calledOnce(instance.uninit);
assert.calledOnce(instance.init);
});
describe("#layout", () => {
it("should call maybeDispatchLayoutUpdate from fetchStories", async () => {
instance.stories_endpoint = "stories-endpoint";
let fetchStub = globals.sandbox.stub();
const response = {
"layout": [1, 2],
};
globals.set("fetch", fetchStub);
fetchStub.resolves({ok: true, status: 200, json: () => Promise.resolve(response)});
sinon.spy(instance, "maybeDispatchLayoutUpdate");
await instance.fetchStories();
assert.calledOnce(instance.maybeDispatchLayoutUpdate);
assert.calledWith(instance.maybeDispatchLayoutUpdate, [1, 2]);
});
it("should call maybeDispatchLayoutUpdate from loadCachedData", async () => {
sinon.spy(instance, "maybeDispatchLayoutUpdate");
instance.cache.get = () => ({stories: {layout: [2, 3]}});
await instance.loadCachedData();
assert.calledOnce(instance.maybeDispatchLayoutUpdate);
assert.calledWith(instance.maybeDispatchLayoutUpdate, [2, 3]);
});
it("should call dispatch from maybeDispatchLayoutUpdate with available data", () => {
instance.maybeDispatchLayoutUpdate([1, 2]);
assert.calledOnce(instance.store.dispatch);
const [action] = instance.store.dispatch.firstCall.args;
assert.equal(action.type, "CONTENT_LAYOUT");
assert.deepEqual(action.data, [1, 2]);
});
it("should not call dispatch from maybeDispatchLayoutUpdate with no available data", () => {
instance.maybeDispatchLayoutUpdate([]);
assert.notCalled(instance.store.dispatch);
});
});
});