summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScottDowne <scott.downe@gmail.com>2019-06-05 12:27:12 -0400
committerGitHub <noreply@github.com>2019-06-05 12:27:12 -0400
commit61746983b59d3b4daad5a18a2c3e5f5184755a1e (patch)
tree8a1205f7769f05472d37e465a40ea806d53a0322
parentb9b953b70e1e222a3bac0b7bbc2747688273ae5d (diff)
Bug 1542863 - Adding feed load fail fallback. (#5085)
-rw-r--r--common/Actions.jsm1
-rw-r--r--content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx1
-rw-r--r--content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx4
-rw-r--r--content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx76
-rw-r--r--content-src/components/DiscoveryStreamComponents/DSEmptyState/_DSEmptyState.scss65
-rw-r--r--content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx5
-rw-r--r--content-src/components/DiscoveryStreamComponents/List/List.jsx10
-rw-r--r--content-src/lib/selectLayoutRender.js6
-rw-r--r--content-src/styles/_theme.scss20
-rw-r--r--content-src/styles/_variables.scss4
-rw-r--r--data/content/assets/spinner.svg1
-rw-r--r--lib/DiscoveryStreamFeed.jsm27
-rw-r--r--test/unit/content-src/components/DiscoveryStreamComponents/DSEmptyState.test.jsx29
-rw-r--r--test/unit/lib/DiscoveryStreamFeed.test.js35
14 files changed, 264 insertions, 20 deletions
diff --git a/common/Actions.jsm b/common/Actions.jsm
index d333ae90..16531feb 100644
--- a/common/Actions.jsm
+++ b/common/Actions.jsm
@@ -50,6 +50,7 @@ for (const type of [
"DISCOVERY_STREAM_LAYOUT_UPDATE",
"DISCOVERY_STREAM_LINK_BLOCKED",
"DISCOVERY_STREAM_LOADED_CONTENT",
+ "DISCOVERY_STREAM_RETRY_FEED",
"DISCOVERY_STREAM_SPOCS_CAPS",
"DISCOVERY_STREAM_SPOCS_ENDPOINT",
"DISCOVERY_STREAM_SPOCS_FILL",
diff --git a/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx b/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
index 7ee73a48..8239813f 100644
--- a/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
+++ b/content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
@@ -141,6 +141,7 @@ export class _DiscoveryStreamBase extends React.PureComponent {
return (
<List
data={component.data}
+ feed={component.feed}
fullWidth={component.properties.full_width}
hasBorders={component.properties.border === "border"}
hasImages={component.properties.has_images}
diff --git a/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx b/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
index ab2ff05c..0e136736 100644
--- a/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
+++ b/content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
@@ -60,7 +60,9 @@ export class CardGrid extends React.PureComponent {
return (<div>
<div className="ds-header">{this.props.title}</div>
{isEmpty ?
- <div className="ds-card-grid empty"><DSEmptyState /></div> :
+ <div className="ds-card-grid empty">
+ <DSEmptyState status={data.status} dispatch={this.props.dispatch} feed={this.props.feed} />
+ </div> :
this.renderCards()
}
</div>);
diff --git a/content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx b/content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx
index bcd995b4..444f9462 100644
--- a/content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx
+++ b/content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx
@@ -1,12 +1,84 @@
+import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
import React from "react";
export class DSEmptyState extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.onReset = this.onReset.bind(this);
+ this.state = {};
+ }
+
+ componentWillUnmount() {
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ }
+ }
+
+ onReset() {
+ if (this.props.dispatch && this.props.feed) {
+ const {feed} = this.props;
+ const {url} = feed;
+ this.props.dispatch({
+ type: at.DISCOVERY_STREAM_FEED_UPDATE,
+ data: {
+ feed: {
+ ...feed,
+ data: {
+ ...feed.data,
+ status: "waiting",
+ },
+ },
+ url,
+ },
+ });
+
+ this.setState({waiting: true});
+ this.timeout = setTimeout(() => {
+ this.timeout = null;
+ this.setState({
+ waiting: false,
+ });
+ }, 300);
+
+ this.props.dispatch(ac.OnlyToMain({type: at.DISCOVERY_STREAM_RETRY_FEED, data: {feed}}));
+ }
+ }
+
+ renderButton() {
+ if (this.props.status === "waiting" || this.state.waiting) {
+ return (
+ <button className="try-again-button waiting">Loading...</button>
+ );
+ }
+
+ return (
+ <button className="try-again-button" onClick={this.onReset}>Try Again</button>
+ );
+ }
+
+ renderState() {
+ if (this.props.status === "waiting" || this.props.status === "failed") {
+ return (
+ <React.Fragment>
+ <h2>Oops! We almost loaded this section, but not quite.</h2>
+ {this.renderButton()}
+ </React.Fragment>
+ );
+ }
+
+ return (
+ <React.Fragment>
+ <h2>You are caught up!</h2>
+ <p>Check back later for more stories.</p>
+ </React.Fragment>
+ );
+ }
+
render() {
return (
<div className="section-empty-state">
<div className="empty-state-message">
- <h2>You are caught up!</h2>
- <p>Check back later for more stories.</p>
+ {this.renderState()}
</div>
</div>
);
diff --git a/content-src/components/DiscoveryStreamComponents/DSEmptyState/_DSEmptyState.scss b/content-src/components/DiscoveryStreamComponents/DSEmptyState/_DSEmptyState.scss
index 23c53088..039369b9 100644
--- a/content-src/components/DiscoveryStreamComponents/DSEmptyState/_DSEmptyState.scss
+++ b/content-src/components/DiscoveryStreamComponents/DSEmptyState/_DSEmptyState.scss
@@ -2,7 +2,7 @@
border: $border-secondary;
border-radius: 4px;
display: flex;
- height: $grid-unit;
+ height: $card-height-compact;
width: 100%;
.empty-state-message {
@@ -14,8 +14,65 @@
max-width: 936px;
}
+ .try-again-button {
+ margin-top: 12px;
+ padding: 6px 32px;
+ border-radius: 2px;
+ border: 0;
+ background: var(--newtab-feed-button-background);
+ color: var(--newtab-feed-button-text);
+ cursor: pointer;
+ position: relative;
+ transition: background 0.2s ease, color 0.2s ease;
+
+ &:not(.waiting) {
+ &:focus {
+ @include ds-fade-in;
+
+ @include dark-theme-only {
+ @include ds-fade-in($blue-40-40);
+ }
+ }
+
+ &:hover {
+ @include ds-fade-in($grey-30);
+
+ @include dark-theme-only {
+ @include ds-fade-in($grey-60);
+ }
+ }
+ }
+
+ &::after {
+ content: '';
+ height: 20px;
+ width: 20px;
+ animation: spinner 1s linear infinite;
+ opacity: 0;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin: -10px 0 0 -10px;
+ mask-image: url('../data/content/assets/spinner.svg');
+ mask-size: 20px;
+ background: var(--newtab-feed-button-spinner);
+ }
+
+ &.waiting {
+ cursor: initial;
+ background: var(--newtab-feed-button-background-faded);
+ color: var(--newtab-feed-button-text-faded);
+ transition: background 0.2s ease;
+
+ &::after {
+ transition: opacity 0.2s ease;
+ opacity: 1;
+ }
+ }
+ }
+
h2 {
- font-size: 14px;
+ font-size: 15px;
font-weight: 600;
margin: 0;
}
@@ -24,3 +81,7 @@
margin: 0;
}
}
+
+@keyframes spinner {
+ to { transform: rotate(360deg); }
+}
diff --git a/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx b/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
index ed404fea..5127d8c9 100644
--- a/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
+++ b/content-src/components/DiscoveryStreamComponents/Hero/Hero.jsx
@@ -111,6 +111,7 @@ export class Hero extends React.PureComponent {
<List
recStartingPoint={1}
data={this.props.data}
+ feed={this.props.feed}
hasImages={true}
hasBorders={this.props.border === `border`}
items={this.props.items - 1}
@@ -144,7 +145,9 @@ export class Hero extends React.PureComponent {
<div>
<div className="ds-header">{this.props.title}</div>
{isEmpty ?
- <div className="ds-hero empty"><DSEmptyState /></div> :
+ <div className="ds-hero empty">
+ <DSEmptyState status={data.status} dispatch={this.props.dispatch} feed={this.props.feed} />
+ </div> :
this.renderHero()
}
</div>
diff --git a/content-src/components/DiscoveryStreamComponents/List/List.jsx b/content-src/components/DiscoveryStreamComponents/List/List.jsx
index 3c31db08..093eae27 100644
--- a/content-src/components/DiscoveryStreamComponents/List/List.jsx
+++ b/content-src/components/DiscoveryStreamComponents/List/List.jsx
@@ -126,19 +126,21 @@ export function _List(props) {
);
};
- const feed = props.data;
- if (!feed || !feed.recommendations) {
+ const {data} = props;
+ if (!data || !data.recommendations) {
return null;
}
// Handle the case where a user has dismissed all recommendations
- const isEmpty = feed.recommendations.length === 0;
+ const isEmpty = data.recommendations.length === 0;
return (
<div>
{props.header && props.header.title ? <div className="ds-header">{props.header.title}</div> : null }
{isEmpty ?
- <div className="ds-list empty"><DSEmptyState /></div> :
+ <div className="ds-list empty">
+ <DSEmptyState status={data.status} dispatch={props.dispatch} feed={props.feed} />
+ </div> :
renderList()
}
</div>
diff --git a/content-src/lib/selectLayoutRender.js b/content-src/lib/selectLayoutRender.js
index 6f46998f..52eee4d8 100644
--- a/content-src/lib/selectLayoutRender.js
+++ b/content-src/lib/selectLayoutRender.js
@@ -79,7 +79,7 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
if (feed && feed.data) {
data = {
...feed.data,
- recommendations: [...feed.data.recommendations],
+ recommendations: [...(feed.data.recommendations || [])],
};
}
@@ -90,8 +90,10 @@ export const selectLayoutRender = (state, prefs, rickRollCache) => {
};
}
+ // Ensure we have recs available for this feed.
+ const hasRecs = data && data.recommendations && data.recommendations.length;
// Do we ever expect to possibly have a spoc.
- if (data && component.spocs && component.spocs.positions && component.spocs.positions.length) {
+ if (hasRecs && component.spocs && component.spocs.positions && component.spocs.positions.length) {
// We expect a spoc, spocs are loaded, and the server returned spocs.
if (spocs.loaded && spocs.data.spocs && spocs.data.spocs.length) {
data = rollForSpocs(data, component.spocs);
diff --git a/content-src/styles/_theme.scss b/content-src/styles/_theme.scss
index 667254ea..5b0691e7 100644
--- a/content-src/styles/_theme.scss
+++ b/content-src/styles/_theme.scss
@@ -24,8 +24,6 @@ body {
--newtab-background-color: #{$grey-10};
--newtab-border-primary-color: #{$grey-40};
--newtab-border-secondary-color: #{$grey-30};
- --newtab-button-primary-color: #{$blue-60};
- --newtab-button-secondary-color: inherit;
--newtab-element-active-color: #{$grey-30-60};
--newtab-element-hover-color: #{$grey-20};
--newtab-icon-primary-color: #{$grey-90-80};
@@ -41,6 +39,17 @@ body {
--newtab-textbox-border: #{$grey-90-20};
@include textbox-focus($blue-60); // sass-lint:disable-line mixins-before-declarations
+ // Buttons
+ --newtab-button-primary-color: #{$blue-60};
+ --newtab-button-secondary-color: inherit;
+ // Feed buttons
+ --newtab-feed-button-background: #{$grey-20};
+ --newtab-feed-button-text: #{$grey-90};
+ --newtab-feed-button-background-faded: #{$grey-20-60};
+ --newtab-feed-button-text-faded: #{$grey-90-00};
+ --newtab-feed-button-spinner: #{$grey-50};
+
+
// Context menu
--newtab-contextmenu-background-color: #{$grey-10};
--newtab-contextmenu-button-color: #{$white};
@@ -107,6 +116,13 @@ body {
--newtab-textbox-border: #{$grey-10-20};
@include textbox-focus($blue-40); // sass-lint:disable-line mixins-before-declarations
+ // Feed buttons
+ --newtab-feed-button-background: #{$grey-70};
+ --newtab-feed-button-text: #{$grey-10};
+ --newtab-feed-button-background-faded: #{$grey-70-60};
+ --newtab-feed-button-text-faded: #{$grey-10-00};
+ --newtab-feed-button-spinner: #{$grey-30};
+
// Context menu
--newtab-contextmenu-background-color: #{$grey-60};
--newtab-contextmenu-button-color: #{$grey-80};
diff --git a/content-src/styles/_variables.scss b/content-src/styles/_variables.scss
index 8e678a5e..3d18caab 100644
--- a/content-src/styles/_variables.scss
+++ b/content-src/styles/_variables.scss
@@ -22,6 +22,7 @@ $yellow-50: #FFE900;
$violet-20: #CB9EFF;
// Photon opacity from http://design.firefox.com/photon/visuals/color.html#opacity
+$grey-10-00: rgba($grey-10, 0);
$grey-10-10: rgba($grey-10, 0.1);
$grey-10-20: rgba($grey-10, 0.2);
$grey-10-30: rgba($grey-10, 0.3);
@@ -35,7 +36,10 @@ $grey-20-80: rgba($grey-20, 0.8);
$grey-30-60: rgba($grey-30, 0.6);
$grey-60-60: rgba($grey-60, 0.6);
$grey-60-70: rgba($grey-60, 0.7);
+$grey-70-40: rgba($grey-70, 0.4);
+$grey-70-60: rgba($grey-70, 0.6);
$grey-80-95: rgba($grey-80, 0.95);
+$grey-90-00: rgba($grey-90, 0);
$grey-90-10: rgba($grey-90, 0.1);
$grey-90-20: rgba($grey-90, 0.2);
$grey-90-30: rgba($grey-90, 0.3);
diff --git a/data/content/assets/spinner.svg b/data/content/assets/spinner.svg
new file mode 100644
index 00000000..8478b886
--- /dev/null
+++ b/data/content/assets/spinner.svg
@@ -0,0 +1 @@
+<svg width="73" height="73" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient x1="93.093%" y1="52.773%" x2="68.513%" y2="119.326%" id="a"><stop stop-color="#FFF" stop-opacity="0" offset="0%"/><stop stop-color="#FFF" offset="69.37%"/><stop stop-color="#FFF" offset="100%"/><stop stop-color="#FFF" stop-opacity=".005" offset="100%"/><stop stop-color="#FFF" stop-opacity="0" offset="100%"/><stop stop-color="#FFF" stop-opacity="0" offset="100%"/></linearGradient><path id="b" d="M0 0h48v60H0z"/></defs><g transform="translate(-5 -1)" fill="none" fill-rule="evenodd"><path d="M41.8 73.8c-19.9 0-36-16.1-36-36 0-19.7 15.8-35.6 35.3-36h.7c2.8.4 5 2.7 5 5.5s-2.2 5.2-5 5.4c-13.8.1-25 11.3-25 25.1s11.2 25 25 25 25-11.2 25-25h11c0 19.9-16.1 36-36 36z" fill="url(#a)"/><mask id="c" fill="#fff"><use xlink:href="#b"/></mask><path d="M41.8 73.8c-19.9 0-36-16.1-36-36 0-19.7 15.8-35.6 35.3-36h.7c2.8.4 5 2.7 5 5.5s-2.2 5.2-5 5.4c-13.8.1-25 11.3-25 25.1s11.2 25 25 25 25-11.2 25-25h11c0 19.9-16.1 36-36 36z" fill="#FFF" mask="url(#c)"/></g></svg> \ No newline at end of file
diff --git a/lib/DiscoveryStreamFeed.jsm b/lib/DiscoveryStreamFeed.jsm
index d957d4f4..a2f34ac4 100644
--- a/lib/DiscoveryStreamFeed.jsm
+++ b/lib/DiscoveryStreamFeed.jsm
@@ -220,6 +220,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
lastUpdated: Date.now(),
spocs: layoutResponse.spocs,
layout: layoutResponse.layout,
+ status: "success",
};
await this.cache.set("layout", layout);
@@ -616,6 +617,19 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
return true;
}
+ async retryFeed(feed) {
+ const {url} = feed;
+ const result = await this.getComponentFeed(url);
+ const newFeed = this.filterRecommendations(result);
+ this.store.dispatch(ac.BroadcastToContent({
+ type: at.DISCOVERY_STREAM_FEED_UPDATE,
+ data: {
+ feed: newFeed,
+ url,
+ },
+ }));
+ }
+
async getComponentFeed(feedUrl, isStartup) {
const cachedData = await this.cache.get() || {};
const {feeds} = cachedData;
@@ -633,6 +647,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
data: {
settings: feedResponse.settings,
recommendations,
+ status: "success",
},
};
} else {
@@ -640,7 +655,12 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
}
}
- return feed;
+ // If we have no feed at this point, both fetch and cache failed for some reason.
+ return feed || {
+ data: {
+ status: "failed",
+ },
+ };
}
/**
@@ -859,7 +879,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
// longer part of the response.
cleanUpTopRecImpressionPref(newFeeds) {
// Need to build a single list of stories.
- const activeStories = Object.keys(newFeeds).reduce((accumulator, currentValue) => {
+ const activeStories = Object.keys(newFeeds).filter(currentValue => newFeeds[currentValue].data).reduce((accumulator, currentValue) => {
const {recommendations} = newFeeds[currentValue].data;
return accumulator.concat(recommendations.map(i => `${i.id}`));
}, []);
@@ -918,6 +938,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
[action.data.name]: action.data.value,
})));
break;
+ case at.DISCOVERY_STREAM_RETRY_FEED:
+ this.retryFeed(action.data.feed);
+ break;
case at.DISCOVERY_STREAM_CONFIG_CHANGE:
// When the config pref changes, load or unload data as needed.
await this.onPrefChange();
diff --git a/test/unit/content-src/components/DiscoveryStreamComponents/DSEmptyState.test.jsx b/test/unit/content-src/components/DiscoveryStreamComponents/DSEmptyState.test.jsx
index e241d994..cd528e50 100644
--- a/test/unit/content-src/components/DiscoveryStreamComponents/DSEmptyState.test.jsx
+++ b/test/unit/content-src/components/DiscoveryStreamComponents/DSEmptyState.test.jsx
@@ -14,9 +14,36 @@ describe("<DSEmptyState>", () => {
assert.ok(wrapper.find(".section-empty-state").exists());
});
- it("should render message", () => {
+ it("should render defaultempty state message", () => {
assert.ok(wrapper.find(".empty-state-message").exists());
assert.ok(wrapper.find("h2").exists());
assert.ok(wrapper.find("p").exists());
});
+
+ it("should render failed state message", () => {
+ wrapper = shallowWithIntl(<DSEmptyState status="failed" />);
+ assert.ok(wrapper.find("button.try-again-button").exists());
+ });
+
+ it("should render waiting state message", () => {
+ wrapper = shallowWithIntl(<DSEmptyState status="waiting" />);
+ assert.ok(wrapper.find("button.try-again-button.waiting").exists());
+ });
+
+ it("should dispatch DISCOVERY_STREAM_RETRY_FEED on failed state button click", () => {
+ const dispatch = sinon.spy();
+
+ wrapper = shallowWithIntl(<DSEmptyState status="failed" dispatch={dispatch} feed={{url: "https://foo.com", data: {}}} />);
+ wrapper.find("button.try-again-button").simulate("click");
+
+ assert.calledTwice(dispatch);
+ let [action] = dispatch.firstCall.args;
+ assert.equal(action.type, "DISCOVERY_STREAM_FEED_UPDATE");
+ assert.deepEqual(action.data.feed, {url: "https://foo.com", data: {status: "waiting"}});
+
+ [action] = dispatch.secondCall.args;
+
+ assert.equal(action.type, "DISCOVERY_STREAM_RETRY_FEED");
+ assert.deepEqual(action.data.feed, {url: "https://foo.com", data: {}});
+ });
});
diff --git a/test/unit/lib/DiscoveryStreamFeed.test.js b/test/unit/lib/DiscoveryStreamFeed.test.js
index 91920401..950aeda4 100644
--- a/test/unit/lib/DiscoveryStreamFeed.test.js
+++ b/test/unit/lib/DiscoveryStreamFeed.test.js
@@ -12,7 +12,7 @@ const REC_IMPRESSION_TRACKING_PREF = "discoverystream.rec.impressions";
const THIRTY_MINUTES = 30 * 60 * 1000;
const ONE_WEEK = 7 * 24 * 60 * 60 * 1000; // 1 week
-describe("DiscoveryStreamFeed", () => {
+describe("DiscoveryStreamFeed", () => { // eslint-disable-line max-statements
let DiscoveryStreamFeed;
let feed;
let sandbox;
@@ -78,6 +78,7 @@ describe("DiscoveryStreamFeed", () => {
sandbox.stub(feed, "_maybeUpdateCachedData").resolves();
globals = new GlobalOverrider();
+ globals.set("setTimeout", callback => { callback(); });
fakeNewTabUtils = {
blockedLinks: {
links: [],
@@ -346,7 +347,7 @@ describe("DiscoveryStreamFeed", () => {
assert.calledWith(feed.store.dispatch.firstCall, {
type: at.DISCOVERY_STREAM_FEED_UPDATE,
- data: {feed: null, url: "foo.com"},
+ data: {feed: {data: {status: "failed"}}, url: "foo.com"},
});
assert.calledWith(feed.store.dispatch.secondCall, {
type: at.DISCOVERY_STREAM_FEEDS_UPDATE,
@@ -440,7 +441,7 @@ describe("DiscoveryStreamFeed", () => {
const feedResp = await feed.getComponentFeed("foo.com");
- assert.isNull(feedResp);
+ assert.deepEqual(feedResp, {data: {status: "failed"}});
});
});
@@ -879,6 +880,25 @@ describe("DiscoveryStreamFeed", () => {
});
});
+ describe("#retryFeed", () => {
+ it("should retry a feed fetch", async () => {
+ sandbox.stub(feed, "getComponentFeed").returns(Promise.resolve({}));
+ sandbox.stub(feed, "filterRecommendations").returns({});
+ sandbox.spy(feed.store, "dispatch");
+
+ await feed.retryFeed({url: "https://feed.com"});
+
+ assert.calledOnce(feed.getComponentFeed);
+ assert.calledOnce(feed.filterRecommendations);
+ assert.calledOnce(feed.store.dispatch);
+ assert.equal(feed.store.dispatch.firstCall.args[0].type, "DISCOVERY_STREAM_FEED_UPDATE");
+ assert.deepEqual(feed.store.dispatch.firstCall.args[0].data, {
+ feed: {},
+ url: "https://feed.com",
+ });
+ });
+ });
+
describe("#recordCampaignImpression", () => {
it("should return false if time based cap is hit", () => {
sandbox.stub(feed, "readImpressionsPref").returns({});
@@ -1207,6 +1227,15 @@ describe("DiscoveryStreamFeed", () => {
});
});
+ describe("#onAction: DISCOVERY_STREAM_RETRY_FEED", async () => {
+ it("should call retryFeed", async () => {
+ sandbox.spy(feed, "retryFeed");
+ feed.onAction({type: at.DISCOVERY_STREAM_RETRY_FEED, data: {feed: {url: "https://feed.com"}}});
+ assert.calledOnce(feed.retryFeed);
+ assert.calledWith(feed.retryFeed, {url: "https://feed.com"});
+ });
+ });
+
describe("#onAction: DISCOVERY_STREAM_CONFIG_CHANGE", () => {
it("should call this.loadLayout if config.enabled changes to true ", async () => {
sandbox.stub(feed.cache, "set").returns(Promise.resolve());