зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1500540 - Add end-of-year snippet, contextual-feature-recommender preference and bug fixes to Activity Stream r=k88hudson
Differential Revision: https://phabricator.services.mozilla.com/D9297 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
9f4a9ec916
Коммит
310cf60a08
|
@ -6,17 +6,7 @@ import {LocalizationProvider} from "fluent-react";
|
|||
import {OnboardingMessage} from "./templates/OnboardingMessage/OnboardingMessage";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import {SendToDeviceSnippet} from "./templates/SendToDeviceSnippet/SendToDeviceSnippet";
|
||||
import {SimpleSnippet} from "./templates/SimpleSnippet/SimpleSnippet";
|
||||
import {SubmitFormSnippet} from "./templates/SubmitFormSnippet/SubmitFormSnippet";
|
||||
|
||||
// Key names matching schema name of templates
|
||||
const SnippetComponents = {
|
||||
simple_snippet: SimpleSnippet,
|
||||
newsletter_snippet: props => <SubmitFormSnippet {...props} form_method="POST" />,
|
||||
fxa_signup_snippet: props => <SubmitFormSnippet {...props} form_method="GET" />,
|
||||
send_to_device_snippet: SendToDeviceSnippet,
|
||||
};
|
||||
import {SnippetsTemplates} from "./templates/template-manifest";
|
||||
|
||||
const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
|
||||
const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
|
||||
|
@ -196,7 +186,7 @@ export class ASRouterUISurface extends React.PureComponent {
|
|||
}
|
||||
|
||||
renderSnippets() {
|
||||
const SnippetComponent = SnippetComponents[this.state.message.template];
|
||||
const SnippetComponent = SnippetsTemplates[this.state.message.template];
|
||||
const {content} = this.state.message;
|
||||
|
||||
return (
|
||||
|
|
|
@ -17,7 +17,7 @@ const ALLOWED_TAGS = {
|
|||
* Transform an object (tag name: {url}) into (tag name: anchor) where the url
|
||||
* is used as href, in order to render links inside a Fluent.Localized component.
|
||||
*/
|
||||
export function convertLinks(links, sendClick, autoBlock) {
|
||||
export function convertLinks(links, sendClick, doNotAutoBlock) {
|
||||
if (links) {
|
||||
return Object.keys(links).reduce((acc, linkTag) => {
|
||||
const {action} = links[linkTag];
|
||||
|
@ -25,11 +25,11 @@ export function convertLinks(links, sendClick, autoBlock) {
|
|||
const url = action ? false : safeURI(links[linkTag].url);
|
||||
|
||||
acc[linkTag] = (<a href={url}
|
||||
target={autoBlock === false ? "_blank" : ""}
|
||||
target={doNotAutoBlock ? "_blank" : ""}
|
||||
data-metric={links[linkTag].metric}
|
||||
data-action={action}
|
||||
data-args={links[linkTag].args}
|
||||
data-do_not_autoblock={autoBlock === false}
|
||||
data-do_not_autoblock={doNotAutoBlock}
|
||||
onClick={sendClick} />);
|
||||
return acc;
|
||||
}, {});
|
||||
|
@ -46,7 +46,7 @@ export function RichText(props) {
|
|||
throw new Error(`ASRouter: ${props.localization_id} is not a valid rich text property. If you want it to be processed, you need to add it to asrouter/rich-text-strings.js`);
|
||||
}
|
||||
return (
|
||||
<Localized id={props.localization_id} {...ALLOWED_TAGS} {...convertLinks(props.links, props.sendClick, props.autoBlock)}>
|
||||
<Localized id={props.localization_id} {...ALLOWED_TAGS} {...props.customElements} {...convertLinks(props.links, props.sendClick, props.doNotAutoBlock)}>
|
||||
<span>{props.text}</span>
|
||||
</Localized>
|
||||
);
|
||||
|
|
|
@ -30,7 +30,7 @@ export class SnippetBase extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<button className="blockButton" title={this.props.content.block_button_text} onClick={this.onBlockClicked} />
|
||||
<button className="blockButton" title={this.props.content.block_button_text || "Remove this"} onClick={this.onBlockClicked} />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ export class SnippetBase extends React.PureComponent {
|
|||
|
||||
const containerClassName = `SnippetBaseContainer${props.className ? ` ${props.className}` : ""}`;
|
||||
|
||||
return (<div className={containerClassName}>
|
||||
return (<div className={containerClassName} style={this.props.textStyle}>
|
||||
<div className="innerWrapper">
|
||||
{props.children}
|
||||
</div>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
a {
|
||||
cursor: pointer;
|
||||
color: var(--newtab-link-primary-color);
|
||||
text-decoration: underline;
|
||||
|
||||
[lwt-newtab-brighttext] & {
|
||||
font-weight: bold;
|
||||
|
|
|
@ -16,7 +16,8 @@ Please note that some targeting attributes require stricter controls on the tele
|
|||
* [firefoxVersion](#firefoxversion)
|
||||
* [locale](#locale)
|
||||
* [localeLanguageCode](#localelanguagecode)
|
||||
* [usesFirefoxSync](#usesfirefoxsync)
|
||||
* [needsUpdate](#needsupdate)
|
||||
* [pinnedSites](#pinnedsites)
|
||||
* [previousSessionEnd](#previoussessionend)
|
||||
* [profileAgeCreated](#profileagecreated)
|
||||
* [profileAgeReset](#profileagereset)
|
||||
|
@ -26,8 +27,8 @@ Please note that some targeting attributes require stricter controls on the tele
|
|||
* [sync](#sync)
|
||||
* [topFrecentSites](#topfrecentsites)
|
||||
* [totalBookmarksCount](#totalbookmarkscount)
|
||||
* [usesFirefoxSync](#usesfirefoxsync)
|
||||
* [xpinstallEnabled](#xpinstallEnabled)
|
||||
* [needsUpdate](#needsupdate)
|
||||
|
||||
## Detailed usage
|
||||
|
||||
|
@ -210,14 +211,39 @@ localeLanguageCode == "en"
|
|||
declare const localeLanguageCode: string;
|
||||
```
|
||||
|
||||
### `usesFirefoxSync`
|
||||
### `needsUpdate`
|
||||
|
||||
Does the user use Firefox sync?
|
||||
|
||||
#### Definition
|
||||
Does the client have the latest available version installed
|
||||
|
||||
```ts
|
||||
declare const usesFirefoxSync: boolean;
|
||||
declare const needsUpdate: boolean;
|
||||
```
|
||||
|
||||
### `pinnedSites`
|
||||
The sites (including search shortcuts) that are pinned on a user's new tab page.
|
||||
|
||||
#### Examples
|
||||
* Has the user pinned any site on `foo.com`?
|
||||
```java
|
||||
"foo.com" in pinnedSites|mapToProperty("host")
|
||||
```
|
||||
|
||||
* Does the user have a pinned `duckduckgo.com` search shortcut?
|
||||
```java
|
||||
"duckduckgo.com" in pinnedSites[.searchTopSite == true]|mapToProperty("host")
|
||||
```
|
||||
|
||||
#### Definition
|
||||
```ts
|
||||
interface PinnedSite {
|
||||
// e.g. https://foo.mozilla.com/foo/bar
|
||||
url: string;
|
||||
// e.g. foo.mozilla.com
|
||||
host: string;
|
||||
// is the pin a search shortcut?
|
||||
searchTopSite: boolean;
|
||||
}
|
||||
declare const pinnedSites: Array<PinnedSite>
|
||||
```
|
||||
|
||||
### `previousSessionEnd`
|
||||
|
@ -376,6 +402,16 @@ Total number of bookmarks.
|
|||
declare const totalBookmarksCount: number;
|
||||
```
|
||||
|
||||
### `usesFirefoxSync`
|
||||
|
||||
Does the user use Firefox sync?
|
||||
|
||||
#### Definition
|
||||
|
||||
```ts
|
||||
declare const usesFirefoxSync: boolean;
|
||||
```
|
||||
|
||||
### `xpinstallEnabled`
|
||||
|
||||
Pref used by system administrators to disallow add-ons from installed altogether.
|
||||
|
@ -385,11 +421,3 @@ Pref used by system administrators to disallow add-ons from installed altogether
|
|||
```ts
|
||||
declare const xpinstallEnabled: boolean;
|
||||
```
|
||||
|
||||
### `needsUpdate`
|
||||
|
||||
Does the client have the latest available version installed
|
||||
|
||||
```ts
|
||||
declare const needsUpdate: boolean;
|
||||
```
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import React from "react";
|
||||
import {SimpleSnippet} from "../SimpleSnippet/SimpleSnippet";
|
||||
|
||||
class EOYSnippetBase extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* setFrequencyValue - `frequency` form parameter value should be `monthly`
|
||||
* if `monthly-checkbox` is selected or `single` otherwise
|
||||
*/
|
||||
setFrequencyValue() {
|
||||
const frequencyCheckbox = this.refs.form.querySelector("#monthly-checkbox");
|
||||
if (frequencyCheckbox.checked) {
|
||||
this.refs.form.querySelector("[name='frequency']").value = "monthly";
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
this.setFrequencyValue();
|
||||
this.refs.form.submit();
|
||||
if (!this.props.content.do_not_autoblock) {
|
||||
this.props.onBlock();
|
||||
}
|
||||
}
|
||||
|
||||
renderDonations() {
|
||||
const fieldNames = ["first", "second", "third", "fourth"];
|
||||
const numberFormat = new Intl.NumberFormat(this.props.content.locale || navigator.language, {
|
||||
style: "currency",
|
||||
currency: this.props.content.currency_code,
|
||||
minimumFractionDigits: 0,
|
||||
});
|
||||
// Default to `second` button
|
||||
const {selected_button} = this.props.content;
|
||||
const btnStyle = {
|
||||
color: this.props.content.button_color,
|
||||
backgroundColor: this.props.content.button_background_color,
|
||||
};
|
||||
|
||||
return (<form className="EOYSnippetForm" action={this.props.content.donation_form_url} method={this.props.form_method} onSubmit={this.handleSubmit} ref="form">
|
||||
{fieldNames.map((field, idx) => {
|
||||
const button_name = `donation_amount_${field}`;
|
||||
const amount = this.props.content[button_name];
|
||||
return (<React.Fragment key={idx}>
|
||||
<input type="radio" name="amount" value={amount} id={field} defaultChecked={button_name === selected_button} />
|
||||
<label htmlFor={field} className="donation-amount">
|
||||
{numberFormat.format(amount)}
|
||||
</label>
|
||||
</React.Fragment>);
|
||||
})}
|
||||
|
||||
<div className="monthly-checkbox-container">
|
||||
<input id="monthly-checkbox" type="checkbox" />
|
||||
<label htmlFor="monthly-checkbox">
|
||||
{this.props.content.monthly_checkbox_label_text}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="frequency" value="single" />
|
||||
<input type="hidden" name="currency" value={this.props.content.currency_code} />
|
||||
<input type="hidden" name="presets" value={fieldNames.map(field => this.props.content[`donation_amount_${field}`])} />
|
||||
<button style={btnStyle} type="submit" className="ASRouterButton donation-form-url">{this.props.content.button_label}</button>
|
||||
</form>);
|
||||
}
|
||||
|
||||
render() {
|
||||
const textStyle = {
|
||||
color: this.props.content.text_color,
|
||||
backgroundColor: this.props.content.background_color,
|
||||
};
|
||||
const customElement = <em style={{backgroundColor: this.props.content.highlight_color}} />;
|
||||
return (<SimpleSnippet {...this.props}
|
||||
className={this.props.content.test}
|
||||
customElements={{em: customElement}}
|
||||
textStyle={textStyle}
|
||||
extraContent={this.renderDonations()} />);
|
||||
}
|
||||
}
|
||||
|
||||
export const EOYSnippet = props => {
|
||||
const extendedContent = {
|
||||
monthly_checkbox_label_text: "Make my donation monthly",
|
||||
locale: "en-US",
|
||||
currency_code: "usd",
|
||||
selected_button: "donation_amount_second",
|
||||
...props.content,
|
||||
};
|
||||
|
||||
return (<EOYSnippetBase
|
||||
{...props}
|
||||
content={extendedContent}
|
||||
form_method="GET" />);
|
||||
};
|
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
"title": "EOYSnippet",
|
||||
"description": "Fundraising Snippet",
|
||||
"version": "1.0.0",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"plainText": {
|
||||
"description": "Plain text (no HTML allowed)",
|
||||
"type": "string"
|
||||
},
|
||||
"richText": {
|
||||
"description": "Text with HTML subset allowed: i, b, u, strong, em, br",
|
||||
"type": "string"
|
||||
},
|
||||
"link_url": {
|
||||
"description": "Target for links or buttons",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"donation_form_url": {
|
||||
"type": "string",
|
||||
"description": "Url to the donation form."
|
||||
},
|
||||
"currency_code": {
|
||||
"type": "string",
|
||||
"description": "The code for the currency. Examle gbp, cad, usd."
|
||||
},
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"description": "String for the locale code."
|
||||
},
|
||||
"text": {
|
||||
"allOf": [
|
||||
{"$ref": "#/definitions/richText"},
|
||||
{"description": "Main body text of snippet. HTML subset allowed: i, b, u, strong, em, br"}
|
||||
]
|
||||
},
|
||||
"text_color": {
|
||||
"type": "string",
|
||||
"description": "Modify the text message color"
|
||||
},
|
||||
"background_color": {
|
||||
"type": "string",
|
||||
"description": "Snippet background color."
|
||||
},
|
||||
"highlight_color": {
|
||||
"type": "string",
|
||||
"description": "Paragraph em highlight color."
|
||||
},
|
||||
"donation_amount_first": {
|
||||
"type": "number",
|
||||
"description": "First button amount."
|
||||
},
|
||||
"donation_amount_second": {
|
||||
"type": "number",
|
||||
"description": "Second button amount."
|
||||
},
|
||||
"donation_amount_third": {
|
||||
"type": "number",
|
||||
"description": "Third button amount."
|
||||
},
|
||||
"donation_amount_fourth": {
|
||||
"type": "number",
|
||||
"description": "Fourth button amount."
|
||||
},
|
||||
"selected_button": {
|
||||
"type": "string",
|
||||
"description": "Default donation_amount_second. Donation amount button that's selected by default."
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "Snippet icon. 64x64px. SVG or PNG preferred."
|
||||
},
|
||||
"title_icon": {
|
||||
"type": "string",
|
||||
"description": "Small icon that shows up before the title / text. 16x16px. SVG or PNG preferred. Grayscale."
|
||||
},
|
||||
"button_label": {
|
||||
"allOf": [
|
||||
{"$ref": "#/definitions/plainText"},
|
||||
{"description": "Text for a button next to main snippet text that links to button_url. Requires button_url."}
|
||||
]
|
||||
},
|
||||
"button_color": {
|
||||
"type": "string",
|
||||
"description": "The text color of the button. Valid CSS color."
|
||||
},
|
||||
"button_background_color": {
|
||||
"type": "string",
|
||||
"description": "The background color of the button. Valid CSS color."
|
||||
},
|
||||
"block_button_text": {
|
||||
"type": "string",
|
||||
"description": "Tooltip text used for dismiss button."
|
||||
},
|
||||
"monthly_checkbox_label_text": {
|
||||
"type": "string",
|
||||
"description": "Label text for monthly checkbox."
|
||||
},
|
||||
"test": {
|
||||
"type": "string",
|
||||
"description": "Different styles for the snippet. Options are bold and takeover."
|
||||
},
|
||||
"do_not_autoblock": {
|
||||
"type": "boolean",
|
||||
"description": "Used to prevent blocking the snippet after the CTA (link or button) has been clicked"
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
"allOf": [
|
||||
{"$ref": "#/definitions/link_url"},
|
||||
{"description": "The url where the link points to."}
|
||||
]
|
||||
},
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"description": "Custom event name sent with telemetry event."
|
||||
},
|
||||
"args": {
|
||||
"type": "string",
|
||||
"description": "Additional parameters for link action, example which specific menu the button should open"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["text", "donation_form_url", "donation_amount_first", "donation_amount_second", "donation_amount_third", "donation_amount_fourth", "button_label", "currency_code"],
|
||||
"dependencies": {
|
||||
"button_color": ["button_label"],
|
||||
"button_background_color": ["button_label"]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
.EOYSnippetForm {
|
||||
margin-top: 12px;
|
||||
align-self: start;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.donation-amount,
|
||||
.donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 5px 14px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.donation-amount {
|
||||
color: $grey-90;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid $grey-40;
|
||||
background: $grey-10;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
&[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px;
|
||||
|
||||
&:checked+.donation-amount {
|
||||
background: $grey-50;
|
||||
color: $white;
|
||||
border: 1px solid $grey-60;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.monthly-checkbox-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
background-color: $snippets-donation-button-bg;
|
||||
border: 0;
|
||||
color: $white;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import React from "react";
|
||||
import {SubmitFormSnippet} from "../SubmitFormSnippet/SubmitFormSnippet.jsx";
|
||||
|
||||
export const FXASignupSnippet = props => {
|
||||
const userAgent = window.navigator.userAgent.match(/Firefox\/([0-9]+)\./);
|
||||
const firefox_version = userAgent ? parseInt(userAgent[1], 10) : 0;
|
||||
const extendedContent = {
|
||||
form_action: "https://accounts.firefox.com/",
|
||||
...props.content,
|
||||
hidden_inputs: {
|
||||
action: "email",
|
||||
context: "fx_desktop_v3",
|
||||
entrypoint: "snippets",
|
||||
service: "sync",
|
||||
utm_source: "snippet",
|
||||
utm_content: firefox_version,
|
||||
utm_campaign: props.content.utm_campaign,
|
||||
utm_term: props.content.utm_term,
|
||||
...props.content.hidden_inputs,
|
||||
},
|
||||
};
|
||||
|
||||
return (<SubmitFormSnippet
|
||||
{...props}
|
||||
content={extendedContent}
|
||||
form_method="GET" />);
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
import React from "react";
|
||||
import {SubmitFormSnippet} from "../SubmitFormSnippet/SubmitFormSnippet.jsx";
|
||||
|
||||
export const NewsletterSnippet = props => {
|
||||
const extendedContent = {
|
||||
form_action: "https://basket.mozilla.org/subscribe.json",
|
||||
...props.content,
|
||||
hidden_inputs: {
|
||||
newsletters: props.content.scene2_newsletter || "mozilla-foundation",
|
||||
fmt: "H",
|
||||
lang: "en-US",
|
||||
source_url: `https://snippets.mozilla.com/show/${props.id}`,
|
||||
...props.content.hidden_inputs,
|
||||
},
|
||||
};
|
||||
|
||||
return (<SubmitFormSnippet
|
||||
{...props}
|
||||
content={extendedContent}
|
||||
form_method="POST" />);
|
||||
};
|
|
@ -55,6 +55,7 @@ export class SimpleSnippet extends React.PureComponent {
|
|||
renderText() {
|
||||
const {props} = this;
|
||||
return (<RichText text={props.content.text}
|
||||
customElements={this.props.customElements}
|
||||
localization_id="text"
|
||||
links={props.content.links}
|
||||
sendClick={props.sendClick} />);
|
||||
|
@ -62,11 +63,18 @@ export class SimpleSnippet extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const {props} = this;
|
||||
const className = `SimpleSnippet${props.content.tall ? " tall" : ""}`;
|
||||
return (<SnippetBase {...props} className={className}>
|
||||
let className = "SimpleSnippet";
|
||||
if (props.className) {
|
||||
className += ` ${props.className}`;
|
||||
}
|
||||
if (props.content.tall) {
|
||||
className += " tall";
|
||||
}
|
||||
return (<SnippetBase {...props} className={className} textStyle={this.props.textStyle}>
|
||||
<img src={safeURI(props.content.icon) || DEFAULT_ICON_PATH} className="icon" />
|
||||
<div>
|
||||
{this.renderTitleIcon()} {this.renderTitle()} <p className="body">{this.renderText()}</p>
|
||||
{this.props.extraContent}
|
||||
</div>
|
||||
{<div>{this.renderButton()}</div>}
|
||||
</SnippetBase>);
|
||||
|
|
|
@ -3,6 +3,51 @@
|
|||
padding: 27px 0;
|
||||
}
|
||||
|
||||
p em {
|
||||
color: $grey-90;
|
||||
font-style: normal;
|
||||
background: $yellow-50;
|
||||
}
|
||||
|
||||
&.bold,
|
||||
&.takeover {
|
||||
.donation-form-url,
|
||||
.donation-amount {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&.bold {
|
||||
height: 176px;
|
||||
|
||||
.body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 71px;
|
||||
height: 71px;
|
||||
}
|
||||
}
|
||||
|
||||
&.takeover {
|
||||
height: 344px;
|
||||
|
||||
.body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 79px;
|
||||
height: 79px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline;
|
||||
font-size: inherit;
|
||||
|
@ -29,6 +74,17 @@
|
|||
margin-inline-end: 20px;
|
||||
}
|
||||
|
||||
&.takeover,
|
||||
&.bold {
|
||||
.icon {
|
||||
margin-inline-end: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.ASRouterButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,9 @@ export class SubmitFormSnippet extends React.PureComponent {
|
|||
|
||||
if (json && json.status === "ok") {
|
||||
this.setState({signupSuccess: true, signupSubmitted: true});
|
||||
this.props.onBlock({preventDismiss: true});
|
||||
if (!this.props.content.do_not_autoblock) {
|
||||
this.props.onBlock({preventDismiss: true});
|
||||
}
|
||||
this.props.sendUserActionTelemetry({event: "CLICK_BUTTON", value: "subscribe-success", id: "NEWTAB_FOOTER_BAR_CONTENT"});
|
||||
} else {
|
||||
console.error("There was a problem submitting the form", json || "[No JSON response]"); // eslint-disable-line no-console
|
||||
|
@ -86,7 +88,7 @@ export class SubmitFormSnippet extends React.PureComponent {
|
|||
<RichText text={content.scene2_disclaimer_html}
|
||||
localization_id="disclaimer_html"
|
||||
links={content.links}
|
||||
autoBlock={false}
|
||||
doNotAutoBlock={true}
|
||||
sendClick={this.props.sendClick} />
|
||||
</p>);
|
||||
}
|
||||
|
@ -102,7 +104,7 @@ export class SubmitFormSnippet extends React.PureComponent {
|
|||
<span><RichText text={content.scene2_privacy_html}
|
||||
localization_id="privacy_html"
|
||||
links={content.links}
|
||||
autoBlock={false}
|
||||
doNotAutoBlock={true}
|
||||
sendClick={this.props.sendClick} />
|
||||
</span>
|
||||
</p>
|
||||
|
|
|
@ -103,6 +103,10 @@
|
|||
"type": "string",
|
||||
"description": "(send to device) Image to display above the form. 98x98px. SVG or PNG preferred."
|
||||
},
|
||||
"scene2_newsletter": {
|
||||
"type": "string",
|
||||
"description": "Newsletter/basket id user is subscribing to. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/. Default 'mozilla-foundation'."
|
||||
},
|
||||
"hidden_inputs": {
|
||||
"type": "object",
|
||||
"description": "Each entry represents a hidden input, key is used as value for the name property."
|
||||
|
@ -137,6 +141,14 @@
|
|||
"type": "string",
|
||||
"description": "(send to device) Newsletter/basket id representing the email message to be sent. Must be a value from the 'Slug' column here: https://basket.mozilla.org/news/."
|
||||
},
|
||||
"utm_campaign": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_campaign."
|
||||
},
|
||||
"utm_term": {
|
||||
"type": "string",
|
||||
"description": "(fxa) Value to pass through to GA as utm_term."
|
||||
},
|
||||
"links": {
|
||||
"additionalProperties": {
|
||||
"url": {
|
||||
|
@ -153,7 +165,7 @@
|
|||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["scene1_text", "form_action", "scene2_text", "hidden_inputs", "error_text", "success_text", "scene1_button_label"],
|
||||
"required": ["scene1_text", "scene2_text", "scene1_button_label"],
|
||||
"dependencies": {
|
||||
"scene1_button_color": ["scene1_button_label"],
|
||||
"scene1_button_background_color": ["scene1_button_label"]
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import {EOYSnippet} from "./EOYSnippet/EOYSnippet";
|
||||
import {FXASignupSnippet} from "./FXASignupSnippet/FXASignupSnippet";
|
||||
import {NewsletterSnippet} from "./NewsletterSnippet/NewsletterSnippet";
|
||||
import {SendToDeviceSnippet} from "./SendToDeviceSnippet/SendToDeviceSnippet";
|
||||
import {SimpleSnippet} from "./SimpleSnippet/SimpleSnippet";
|
||||
|
||||
// Key names matching schema name of templates
|
||||
export const SnippetsTemplates = {
|
||||
simple_snippet: SimpleSnippet,
|
||||
newsletter_snippet: NewsletterSnippet,
|
||||
fxa_signup_snippet: FXASignupSnippet,
|
||||
send_to_device_snippet: SendToDeviceSnippet,
|
||||
eoy_snippet: EOYSnippet,
|
||||
};
|
|
@ -5,8 +5,10 @@ export class ASRouterAdmin extends React.PureComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
this.handleEnabledToggle = this.handleEnabledToggle.bind(this);
|
||||
this.onChangeMessageFilter = this.onChangeMessageFilter.bind(this);
|
||||
this.findOtherBundledMessagesOfSameTemplate = this.findOtherBundledMessagesOfSameTemplate.bind(this);
|
||||
this.state = {};
|
||||
this.state = {messageFilter: "all"};
|
||||
}
|
||||
|
||||
onMessage({data: action}) {
|
||||
|
@ -55,6 +57,10 @@ export class ASRouterAdmin extends React.PureComponent {
|
|||
ASRouterUtils.sendMessage({type: "EXPIRE_QUERY_CACHE"});
|
||||
}
|
||||
|
||||
resetPref() {
|
||||
ASRouterUtils.sendMessage({type: "RESET_PROVIDER_PREF"});
|
||||
}
|
||||
|
||||
renderMessageItem(msg) {
|
||||
const isCurrent = msg.id === this.state.lastMessageId;
|
||||
const isBlocked = this.state.messageBlockList.includes(msg.id);
|
||||
|
@ -81,34 +87,61 @@ export class ASRouterAdmin extends React.PureComponent {
|
|||
if (!this.state.messages) {
|
||||
return null;
|
||||
}
|
||||
const messagesToShow = this.state.messageFilter === "all" ? this.state.messages : this.state.messages.filter(message => message.provider === this.state.messageFilter);
|
||||
return (<table><tbody>
|
||||
{this.state.messages.map(msg => this.renderMessageItem(msg))}
|
||||
{messagesToShow.map(msg => this.renderMessageItem(msg))}
|
||||
</tbody></table>);
|
||||
}
|
||||
|
||||
onChangeMessageFilter(event) {
|
||||
this.setState({messageFilter: event.target.value});
|
||||
}
|
||||
|
||||
renderMessageFilter() {
|
||||
if (!this.state.providers) {
|
||||
return null;
|
||||
}
|
||||
return (<p>Show messages from <select value={this.state.messageFilter} onChange={this.onChangeMessageFilter}>
|
||||
<option value="all">all providers</option>
|
||||
{this.state.providers.map(provider => (<option key={provider.id} value={provider.id}>{provider.id}</option>))}
|
||||
</select></p>);
|
||||
}
|
||||
|
||||
renderTableHead() {
|
||||
return (<thead>
|
||||
<tr className="message-item">
|
||||
<td>id</td>
|
||||
<td>enabled</td>
|
||||
<td>source</td>
|
||||
<td>last updated</td>
|
||||
</tr>
|
||||
</thead>);
|
||||
}
|
||||
|
||||
handleEnabledToggle(event) {
|
||||
const action = {type: event.target.checked ? "ENABLE_PROVIDER" : "DISABLE_PROVIDER", data: event.target.name};
|
||||
ASRouterUtils.sendMessage(action);
|
||||
this.setState({messageFilter: "all"});
|
||||
}
|
||||
|
||||
renderProviders() {
|
||||
const providersConfig = this.state.providerPrefs;
|
||||
const providerInfo = this.state.providers;
|
||||
return (<table>{this.renderTableHead()}<tbody>
|
||||
{this.state.providers.map((provider, i) => {
|
||||
{providersConfig.map((provider, i) => {
|
||||
const isTestProvider = provider.id === "snippets_local_testing";
|
||||
const info = providerInfo.find(p => p.id === provider.id) || {};
|
||||
let label = "(local)";
|
||||
if (provider.type === "remote") {
|
||||
label = <a target="_blank" href={provider.url}>{provider.url}</a>;
|
||||
label = <a target="_blank" href={info.url}>{info.url}</a>;
|
||||
} else if (provider.type === "remote-settings") {
|
||||
label = `${provider.bucket} (Remote Settings)`;
|
||||
}
|
||||
return (<tr className="message-item" key={i}>
|
||||
<td>{provider.id}</td>
|
||||
<td>{isTestProvider ? null : <input type="checkbox" name={provider.id} checked={provider.enabled} onChange={this.handleEnabledToggle} />}</td>
|
||||
<td>{label}</td>
|
||||
<td>{provider.lastUpdated ? new Date(provider.lastUpdated).toString() : ""}</td>
|
||||
<td style={{whiteSpace: "nowrap"}}>{info.lastUpdated ? new Date(info.lastUpdated).toLocaleString() : ""}</td>
|
||||
</tr>);
|
||||
})}
|
||||
</tbody></table>);
|
||||
|
@ -120,8 +153,10 @@ export class ASRouterAdmin extends React.PureComponent {
|
|||
<h2>Targeting Utilities</h2>
|
||||
<button className="button" onClick={this.expireCache}>Expire Cache</button> (This expires the cache in ASR Targeting for bookmarks and top sites)
|
||||
<h2>Message Providers</h2>
|
||||
<button className="button" onClick={this.resetPref}>Restore defaults</button>
|
||||
{this.state.providers ? this.renderProviders() : null}
|
||||
<h2>Messages</h2>
|
||||
{this.renderMessageFilter()}
|
||||
{this.renderMessages()}
|
||||
</div>);
|
||||
}
|
||||
|
|
|
@ -82,8 +82,11 @@ export class _Base extends React.PureComponent {
|
|||
const {initialized} = App;
|
||||
|
||||
const prefs = props.Prefs.values;
|
||||
if (prefs["asrouter.devtoolsEnabled"] && window.location.hash === "#asrouter") {
|
||||
return (<ASRouterAdmin />);
|
||||
if (prefs["asrouter.devtoolsEnabled"]) {
|
||||
if (window.location.hash === "#asrouter") {
|
||||
return (<ASRouterAdmin />);
|
||||
}
|
||||
console.log("ASRouter devtools enabled. To access visit %cabout:newtab#asrouter", "font-weight: bold"); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
if (!props.isPrerendered && !initialized) {
|
||||
|
|
|
@ -152,3 +152,4 @@ input {
|
|||
@import '../asrouter/templates/SimpleSnippet/SimpleSnippet';
|
||||
@import '../asrouter/templates/SubmitFormSnippet/SubmitFormSnippet';
|
||||
@import '../asrouter/templates/OnboardingMessage/OnboardingMessage';
|
||||
@import '../asrouter/templates/EOYSnippet/EOYSnippet';
|
||||
|
|
|
@ -131,6 +131,7 @@ $error-fallback-line-height: 1.5;
|
|||
$image-path: '../data/content/assets/';
|
||||
|
||||
$snippets-container-height: 120px;
|
||||
$snippets-donation-button-bg: #0C99D5;
|
||||
|
||||
$textbox-shadow-size: 4px;
|
||||
|
||||
|
|
|
@ -1931,7 +1931,8 @@ a.firstrun-link {
|
|||
align-items: center; }
|
||||
.SnippetBaseContainer a {
|
||||
cursor: pointer;
|
||||
color: var(--newtab-link-primary-color); }
|
||||
color: var(--newtab-link-primary-color);
|
||||
text-decoration: underline; }
|
||||
[lwt-newtab-brighttext] .SnippetBaseContainer a {
|
||||
font-weight: bold; }
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
|
@ -2069,6 +2070,37 @@ a.firstrun-link {
|
|||
.SimpleSnippet.tall {
|
||||
padding: 27px 0; }
|
||||
|
||||
.SimpleSnippet p em {
|
||||
color: #0C0C0D;
|
||||
font-style: normal;
|
||||
background: #FFE900; }
|
||||
|
||||
.SimpleSnippet.bold .donation-form-url,
|
||||
.SimpleSnippet.bold .donation-amount, .SimpleSnippet.takeover .donation-form-url,
|
||||
.SimpleSnippet.takeover .donation-amount {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px; }
|
||||
|
||||
.SimpleSnippet.bold {
|
||||
height: 176px; }
|
||||
.SimpleSnippet.bold .body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 20px; }
|
||||
.SimpleSnippet.bold .icon {
|
||||
width: 71px;
|
||||
height: 71px; }
|
||||
|
||||
.SimpleSnippet.takeover {
|
||||
height: 344px; }
|
||||
.SimpleSnippet.takeover .body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 35px; }
|
||||
.SimpleSnippet.takeover .icon {
|
||||
width: 79px;
|
||||
height: 79px; }
|
||||
|
||||
.SimpleSnippet .title {
|
||||
display: inline;
|
||||
font-size: inherit;
|
||||
|
@ -2091,6 +2123,12 @@ a.firstrun-link {
|
|||
.SimpleSnippet.tall .icon {
|
||||
margin-inline-end: 20px; }
|
||||
|
||||
.SimpleSnippet.takeover .icon, .SimpleSnippet.bold .icon {
|
||||
margin-inline-end: 20px; }
|
||||
|
||||
.SimpleSnippet .icon {
|
||||
align-self: flex-start; }
|
||||
|
||||
.SimpleSnippet .ASRouterButton {
|
||||
cursor: pointer; }
|
||||
|
||||
|
@ -2273,4 +2311,40 @@ a.firstrun-link {
|
|||
.onboardingMessage:last-child::before {
|
||||
content: none; }
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin-top: 12px;
|
||||
align-self: start;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 5px 14px;
|
||||
border-radius: 2px; }
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: #0C0C0D;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid #B1B1B3;
|
||||
background: #F9F9FA;
|
||||
cursor: pointer; }
|
||||
.EOYSnippetForm input[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px; }
|
||||
.EOYSnippetForm input[type='radio']:checked + .donation-amount {
|
||||
background: #737373;
|
||||
color: #FFF;
|
||||
border: 1px solid #4A4A4F; }
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
width: 100%; }
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
background-color: #0C99D5;
|
||||
border: 0;
|
||||
color: #FFF;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
cursor: pointer; }
|
||||
|
||||
/*# sourceMappingURL=activity-stream-linux.css.map */
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1934,7 +1934,8 @@ a.firstrun-link {
|
|||
align-items: center; }
|
||||
.SnippetBaseContainer a {
|
||||
cursor: pointer;
|
||||
color: var(--newtab-link-primary-color); }
|
||||
color: var(--newtab-link-primary-color);
|
||||
text-decoration: underline; }
|
||||
[lwt-newtab-brighttext] .SnippetBaseContainer a {
|
||||
font-weight: bold; }
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
|
@ -2072,6 +2073,37 @@ a.firstrun-link {
|
|||
.SimpleSnippet.tall {
|
||||
padding: 27px 0; }
|
||||
|
||||
.SimpleSnippet p em {
|
||||
color: #0C0C0D;
|
||||
font-style: normal;
|
||||
background: #FFE900; }
|
||||
|
||||
.SimpleSnippet.bold .donation-form-url,
|
||||
.SimpleSnippet.bold .donation-amount, .SimpleSnippet.takeover .donation-form-url,
|
||||
.SimpleSnippet.takeover .donation-amount {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px; }
|
||||
|
||||
.SimpleSnippet.bold {
|
||||
height: 176px; }
|
||||
.SimpleSnippet.bold .body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 20px; }
|
||||
.SimpleSnippet.bold .icon {
|
||||
width: 71px;
|
||||
height: 71px; }
|
||||
|
||||
.SimpleSnippet.takeover {
|
||||
height: 344px; }
|
||||
.SimpleSnippet.takeover .body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 35px; }
|
||||
.SimpleSnippet.takeover .icon {
|
||||
width: 79px;
|
||||
height: 79px; }
|
||||
|
||||
.SimpleSnippet .title {
|
||||
display: inline;
|
||||
font-size: inherit;
|
||||
|
@ -2094,6 +2126,12 @@ a.firstrun-link {
|
|||
.SimpleSnippet.tall .icon {
|
||||
margin-inline-end: 20px; }
|
||||
|
||||
.SimpleSnippet.takeover .icon, .SimpleSnippet.bold .icon {
|
||||
margin-inline-end: 20px; }
|
||||
|
||||
.SimpleSnippet .icon {
|
||||
align-self: flex-start; }
|
||||
|
||||
.SimpleSnippet .ASRouterButton {
|
||||
cursor: pointer; }
|
||||
|
||||
|
@ -2276,4 +2314,40 @@ a.firstrun-link {
|
|||
.onboardingMessage:last-child::before {
|
||||
content: none; }
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin-top: 12px;
|
||||
align-self: start;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 5px 14px;
|
||||
border-radius: 2px; }
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: #0C0C0D;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid #B1B1B3;
|
||||
background: #F9F9FA;
|
||||
cursor: pointer; }
|
||||
.EOYSnippetForm input[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px; }
|
||||
.EOYSnippetForm input[type='radio']:checked + .donation-amount {
|
||||
background: #737373;
|
||||
color: #FFF;
|
||||
border: 1px solid #4A4A4F; }
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
width: 100%; }
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
background-color: #0C99D5;
|
||||
border: 0;
|
||||
color: #FFF;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
cursor: pointer; }
|
||||
|
||||
/*# sourceMappingURL=activity-stream-mac.css.map */
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1931,7 +1931,8 @@ a.firstrun-link {
|
|||
align-items: center; }
|
||||
.SnippetBaseContainer a {
|
||||
cursor: pointer;
|
||||
color: var(--newtab-link-primary-color); }
|
||||
color: var(--newtab-link-primary-color);
|
||||
text-decoration: underline; }
|
||||
[lwt-newtab-brighttext] .SnippetBaseContainer a {
|
||||
font-weight: bold; }
|
||||
.SnippetBaseContainer .innerWrapper {
|
||||
|
@ -2069,6 +2070,37 @@ a.firstrun-link {
|
|||
.SimpleSnippet.tall {
|
||||
padding: 27px 0; }
|
||||
|
||||
.SimpleSnippet p em {
|
||||
color: #0C0C0D;
|
||||
font-style: normal;
|
||||
background: #FFE900; }
|
||||
|
||||
.SimpleSnippet.bold .donation-form-url,
|
||||
.SimpleSnippet.bold .donation-amount, .SimpleSnippet.takeover .donation-form-url,
|
||||
.SimpleSnippet.takeover .donation-amount {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px; }
|
||||
|
||||
.SimpleSnippet.bold {
|
||||
height: 176px; }
|
||||
.SimpleSnippet.bold .body {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 20px; }
|
||||
.SimpleSnippet.bold .icon {
|
||||
width: 71px;
|
||||
height: 71px; }
|
||||
|
||||
.SimpleSnippet.takeover {
|
||||
height: 344px; }
|
||||
.SimpleSnippet.takeover .body {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 35px; }
|
||||
.SimpleSnippet.takeover .icon {
|
||||
width: 79px;
|
||||
height: 79px; }
|
||||
|
||||
.SimpleSnippet .title {
|
||||
display: inline;
|
||||
font-size: inherit;
|
||||
|
@ -2091,6 +2123,12 @@ a.firstrun-link {
|
|||
.SimpleSnippet.tall .icon {
|
||||
margin-inline-end: 20px; }
|
||||
|
||||
.SimpleSnippet.takeover .icon, .SimpleSnippet.bold .icon {
|
||||
margin-inline-end: 20px; }
|
||||
|
||||
.SimpleSnippet .icon {
|
||||
align-self: flex-start; }
|
||||
|
||||
.SimpleSnippet .ASRouterButton {
|
||||
cursor: pointer; }
|
||||
|
||||
|
@ -2273,4 +2311,40 @@ a.firstrun-link {
|
|||
.onboardingMessage:last-child::before {
|
||||
content: none; }
|
||||
|
||||
.EOYSnippetForm {
|
||||
margin-top: 12px;
|
||||
align-self: start;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
.EOYSnippetForm .donation-amount,
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
padding: 5px 14px;
|
||||
border-radius: 2px; }
|
||||
.EOYSnippetForm .donation-amount {
|
||||
color: #0C0C0D;
|
||||
margin-inline-end: 18px;
|
||||
border: 1px solid #B1B1B3;
|
||||
background: #F9F9FA;
|
||||
cursor: pointer; }
|
||||
.EOYSnippetForm input[type='radio'] {
|
||||
opacity: 0;
|
||||
margin-inline-end: -18px; }
|
||||
.EOYSnippetForm input[type='radio']:checked + .donation-amount {
|
||||
background: #737373;
|
||||
color: #FFF;
|
||||
border: 1px solid #4A4A4F; }
|
||||
.EOYSnippetForm .monthly-checkbox-container {
|
||||
width: 100%; }
|
||||
.EOYSnippetForm .donation-form-url {
|
||||
margin-inline-start: 18px;
|
||||
background-color: #0C99D5;
|
||||
border: 0;
|
||||
color: #FFF;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
cursor: pointer; }
|
||||
|
||||
/*# sourceMappingURL=activity-stream-windows.css.map */
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -497,7 +497,13 @@ class _ASRouter {
|
|||
|
||||
_updateAdminState(target) {
|
||||
const channel = target || this.messageChannel;
|
||||
channel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {type: "ADMIN_SET_STATE", data: this.state});
|
||||
channel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
|
||||
type: "ADMIN_SET_STATE",
|
||||
data: {
|
||||
...this.state,
|
||||
providerPrefs: ASRouterPreferences.providers,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_handleTargetingError(type, error, message) {
|
||||
|
@ -996,6 +1002,15 @@ class _ASRouter {
|
|||
case "EXPIRE_QUERY_CACHE":
|
||||
QueryCache.expireAll();
|
||||
break;
|
||||
case "ENABLE_PROVIDER":
|
||||
ASRouterPreferences.enableOrDisableProvider(action.data, true);
|
||||
break;
|
||||
case "DISABLE_PROVIDER":
|
||||
ASRouterPreferences.enableOrDisableProvider(action.data, false);
|
||||
break;
|
||||
case "RESET_PROVIDER_PREF":
|
||||
ASRouterPreferences.resetProviderPref();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ const DEFAULT_STATE = {
|
|||
|
||||
const USER_PREFERENCES = {
|
||||
snippets: "browser.newtabpage.activity-stream.feeds.snippets",
|
||||
cfr: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr",
|
||||
};
|
||||
|
||||
const TEST_PROVIDER = {
|
||||
|
@ -33,16 +34,19 @@ class _ASRouterPreferences {
|
|||
this._callbacks = new Set();
|
||||
}
|
||||
|
||||
_getProviderConfig() {
|
||||
try {
|
||||
return JSON.parse(Services.prefs.getStringPref(this._providerPref, ""));
|
||||
} catch (e) {
|
||||
Cu.reportError(`Could not parse ASRouter preference. Try resetting ${this._providerPref} in about:config.`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get providers() {
|
||||
if (!this._initialized || this._providers === null) {
|
||||
let providers;
|
||||
try {
|
||||
const parsed = JSON.parse(Services.prefs.getStringPref(this._providerPref, ""));
|
||||
providers = parsed.map(provider => Object.freeze(provider));
|
||||
} catch (e) {
|
||||
Cu.reportError("Problem parsing JSON message provider pref for ASRouter");
|
||||
providers = [];
|
||||
}
|
||||
const config = this._getProviderConfig() || [];
|
||||
const providers = config.map(provider => Object.freeze(provider));
|
||||
if (this.devtoolsEnabled) {
|
||||
providers.unshift(TEST_PROVIDER);
|
||||
}
|
||||
|
@ -52,6 +56,33 @@ class _ASRouterPreferences {
|
|||
return this._providers;
|
||||
}
|
||||
|
||||
enableOrDisableProvider(id, value) {
|
||||
const providers = this._getProviderConfig();
|
||||
if (!providers) {
|
||||
Cu.reportError(`Cannot enable/disable providers if ${this._providerPref} is unparseable.`);
|
||||
return;
|
||||
}
|
||||
if (!providers.find(p => p.id === id)) {
|
||||
Cu.reportError(`Cannot set enabled state for '${id}' because it does not exist in ${this._providerPref}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const newConfig = providers.map(provider => {
|
||||
if (provider.id === id) {
|
||||
return {...provider, enabled: value};
|
||||
}
|
||||
return provider;
|
||||
});
|
||||
Services.prefs.setStringPref(this._providerPref, JSON.stringify(newConfig));
|
||||
}
|
||||
|
||||
resetProviderPref() {
|
||||
Services.prefs.clearUserPref(this._providerPref);
|
||||
for (const id of Object.keys(USER_PREFERENCES)) {
|
||||
Services.prefs.clearUserPref(USER_PREFERENCES[id]);
|
||||
}
|
||||
}
|
||||
|
||||
get devtoolsEnabled() {
|
||||
if (!this._initialized || this._devtoolsEnabled === null) {
|
||||
this._devtoolsEnabled = Services.prefs.getBoolPref(this._devtoolsPref, false);
|
||||
|
|
|
@ -15,6 +15,8 @@ ChromeUtils.defineModuleGetter(this, "TelemetryEnvironment",
|
|||
"resource://gre/modules/TelemetryEnvironment.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "NewTabUtils",
|
||||
"resource://gre/modules/NewTabUtils.jsm");
|
||||
|
||||
const FXA_USERNAME_PREF = "services.sync.username";
|
||||
const SEARCH_REGION_PREF = "browser.search.region";
|
||||
|
@ -246,6 +248,13 @@ const TargetingGetters = {
|
|||
}
|
||||
)));
|
||||
},
|
||||
get pinnedSites() {
|
||||
return NewTabUtils.pinnedLinks.links.map(site => ({
|
||||
url: site.url,
|
||||
host: (new URL(site.url)).hostname,
|
||||
searchTopSite: site.searchTopSite,
|
||||
}));
|
||||
},
|
||||
get providerCohorts() {
|
||||
return ASRouterPreferences.providers.reduce((prev, current) => {
|
||||
prev[current.id] = current.cohort || "";
|
||||
|
|
|
@ -198,6 +198,10 @@ const PREFS_CONFIG = new Map([
|
|||
title: "Are the asrouter devtools enabled?",
|
||||
value: false,
|
||||
}],
|
||||
["asrouter.userprefs.cfr", {
|
||||
title: "Does the user allow CFR recommendations?",
|
||||
value: true,
|
||||
}],
|
||||
["asrouter.messageProviders", {
|
||||
title: "Configuration for ASRouter message providers",
|
||||
|
||||
|
|
|
@ -16,6 +16,19 @@ const MESSAGES = () => ([
|
|||
"block_button_text": "Block",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "SIMPLE_TEST_TALL",
|
||||
"template": "simple_snippet",
|
||||
"content": {
|
||||
"icon": TEST_ICON,
|
||||
"text": "<syncLink>Sync it, link it, take it with you</syncLink>. All this and more with a Firefox Account.",
|
||||
"links": {"syncLink": {"url": "https://www.mozilla.org/en-US/firefox/accounts"}},
|
||||
"button_label": "Get one now!",
|
||||
"button_url": "https://www.mozilla.org/en-US/firefox/accounts",
|
||||
"block_button_text": "Block",
|
||||
"tall": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "SIMPLE_TEST_BUTTON_URL_1",
|
||||
"template": "simple_snippet",
|
||||
|
@ -84,11 +97,6 @@ const MESSAGES = () => ([
|
|||
"scene2_button_label": "Continue",
|
||||
"scene2_dismiss_button_text": "Dismiss",
|
||||
"form_action": "https://basket.mozilla.org/subscribe.json",
|
||||
|
||||
// TODO: This should not be required
|
||||
"success_text": "Check your inbox for the confirmation!",
|
||||
"error_text": "Error!",
|
||||
"hidden_inputs": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -121,10 +129,59 @@ const MESSAGES = () => ([
|
|||
success_title: "Your download link was sent.",
|
||||
success_text: "Check your device for the email message!",
|
||||
links: {"privacyLink": {"url": "https://www.mozilla.org/privacy/websites/?sample_rate=0.001&snippet_name=7894"}},
|
||||
|
||||
// TODO: Not actually defined in the send to device schema
|
||||
form_action: "https://basket.mozilla.org/subscribe.json",
|
||||
hidden_inputs: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "EOY_TEST_1",
|
||||
"template": "eoy_snippet",
|
||||
"content": {
|
||||
"highlight_color": "#f05",
|
||||
"selected_button": "donation_amount_first",
|
||||
"icon": TEST_ICON,
|
||||
"button_label": "Donate",
|
||||
"monthly_checkbox_label_text": "Make my donation monthly",
|
||||
"currency_code": "usd",
|
||||
"donation_amount_first": 50,
|
||||
"donation_amount_second": 25,
|
||||
"donation_amount_third": 10,
|
||||
"donation_amount_fourth": 5,
|
||||
"donation_form_url": "https://donate.mozilla.org",
|
||||
"text": "Big corporations want to restrict how we access the web. Fake news is making it harder for us to find the truth. Online bullies are silencing inspired voices. The <em>not-for-profit Mozilla Foundation</em> fights for a healthy internet with programs like our Tech Policy Fellowships and Internet Health Report; <b>will you donate today</b>?",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "EOY_BOLD_TEST_1",
|
||||
"template": "eoy_snippet",
|
||||
"content": {
|
||||
"icon": TEST_ICON,
|
||||
"selected_button": "donation_amount_second",
|
||||
"button_label": "Donate",
|
||||
"monthly_checkbox_label_text": "Make my donation monthly",
|
||||
"currency_code": "usd",
|
||||
"donation_amount_first": 50,
|
||||
"donation_amount_second": 25,
|
||||
"donation_amount_third": 10,
|
||||
"donation_amount_fourth": 5,
|
||||
"donation_form_url": "https://donate.mozilla.org",
|
||||
"text": "Big corporations want to restrict how we access the web. Fake news is making it harder for us to find the truth. Online bullies are silencing inspired voices. The <em>not-for-profit Mozilla Foundation</em> fights for a healthy internet with programs like our Tech Policy Fellowships and Internet Health Report; <b>will you donate today</b>?",
|
||||
"test": "bold",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "EOY_TAKEOVER_TEST_1",
|
||||
"template": "eoy_snippet",
|
||||
"content": {
|
||||
"icon": TEST_ICON,
|
||||
"button_label": "Donate",
|
||||
"monthly_checkbox_label_text": "Make my donation monthly",
|
||||
"currency_code": "usd",
|
||||
"donation_amount_first": 50,
|
||||
"donation_amount_second": 25,
|
||||
"donation_amount_third": 10,
|
||||
"donation_amount_fourth": 5,
|
||||
"donation_form_url": "https://donate.mozilla.org",
|
||||
"text": "Big corporations want to restrict how we access the web. Fake news is making it harder for us to find the truth. Online bullies are silencing inspired voices. The <em>not-for-profit Mozilla Foundation</em> fights for a healthy internet with programs like our Tech Policy Fellowships and Internet Health Report; <b>will you donate today</b>?",
|
||||
"test": "takeover",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -492,6 +492,7 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
async sendPageTakeoverData() {
|
||||
if (this.telemetryEnabled) {
|
||||
const value = {};
|
||||
let page;
|
||||
|
||||
// Check whether or not about:home and about:newtab are set to a custom URL.
|
||||
// If so, classify them.
|
||||
|
@ -499,21 +500,25 @@ this.TelemetryFeed = class TelemetryFeed {
|
|||
aboutNewTabService.overridden &&
|
||||
!aboutNewTabService.newTabURL.startsWith("moz-extension://")) {
|
||||
value.newtab_url_category = await this._classifySite(aboutNewTabService.newTabURL);
|
||||
page = "about:newtab";
|
||||
}
|
||||
|
||||
const homePageURL = HomePage.get();
|
||||
if (!["about:home", "about:blank"].includes(homePageURL) &&
|
||||
!homePageURL.startsWith("moz-extension://")) {
|
||||
value.home_url_category = await this._classifySite(homePageURL);
|
||||
page = page ? "both" : "about:home";
|
||||
}
|
||||
|
||||
if (value.newtab_url_category || value.home_url_category) {
|
||||
if (page) {
|
||||
const event = Object.assign(
|
||||
this.createPing(),
|
||||
{
|
||||
action: "activity_stream_user_event",
|
||||
event: "PAGE_TAKEOVER_DATA",
|
||||
value,
|
||||
page,
|
||||
session_id: "n/a",
|
||||
},
|
||||
);
|
||||
this.sendEvent(event);
|
||||
|
|
|
@ -143,9 +143,9 @@ pocket_read_more=Популярни теми:
|
|||
# LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
|
||||
# end of the list of popular topic links.
|
||||
pocket_read_even_more=Повече публикации
|
||||
|
||||
pocket_more_reccommendations=Повече препоръчани
|
||||
pocket_learn_more=Научете повече
|
||||
pocket_how_it_works=Как работи
|
||||
pocket_cta_button=Вземете Pocket
|
||||
pocket_cta_text=Запазете статиите, които харесвате в Pocket и заредете ума си с увлекателни четива.
|
||||
|
||||
|
@ -196,7 +196,6 @@ firstrun_form_header=Въведете своята ел. поща,
|
|||
firstrun_form_sub_header=за да продължите към Firefox Sync
|
||||
|
||||
firstrun_email_input_placeholder=адрес на електронна поща
|
||||
|
||||
firstrun_invalid_input=Необходим е валиден адрес на ел. поща
|
||||
|
||||
# LOCALIZATION NOTE (firstrun_extra_legal_links): {terms} is equal to firstrun_terms_of_service, and
|
||||
|
|
|
@ -1,33 +1,18 @@
|
|||
newtab_page_title=Nuova scheda
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
newtab_page_title=Nuova scheda
|
||||
header_top_sites=Siti principali
|
||||
header_highlights=In evidenza
|
||||
# LOCALIZATION NOTE(header_recommended_by): This is followed by the name
|
||||
# of the corresponding content provider.
|
||||
header_recommended_by=Consigliati da {provider}
|
||||
|
||||
# LOCALIZATION NOTE(context_menu_button_sr): This is for screen readers when
|
||||
# the context menu button is focused/active. Title is the label or hostname of
|
||||
# the site.
|
||||
context_menu_button_sr=Apri menu contestuale per {title}
|
||||
|
||||
# LOCALIZATION NOTE(section_context_menu_button_sr): This is for screen readers when
|
||||
# the section edit context menu button is focused/active.
|
||||
section_context_menu_button_sr=Apri il menu contestuale per la sezione
|
||||
|
||||
# LOCALIZATION NOTE (type_label_*): These labels are associated to pages to give
|
||||
# context on how the element is related to the user, e.g. type indicates that
|
||||
# the page is bookmarked, or is currently open on another device
|
||||
type_label_visited=Visitato
|
||||
type_label_bookmarked=Nei segnalibri
|
||||
type_label_recommended=Di tendenza
|
||||
type_label_pocket=Salvato in Pocket
|
||||
type_label_downloaded=Scaricata
|
||||
|
||||
# LOCALIZATION NOTE (menu_action_*): These strings are displayed in a context
|
||||
# menu and are meant as a call to action for a given page.
|
||||
# LOCALIZATION NOTE (menu_action_bookmark): Bookmark is a verb, as in "Add to
|
||||
# bookmarks"
|
||||
menu_action_bookmark=Aggiungi ai segnalibri
|
||||
menu_action_remove_bookmark=Elimina segnalibro
|
||||
menu_action_open_new_window=Apri in una nuova finestra
|
||||
|
@ -37,63 +22,26 @@ menu_action_delete=Elimina dalla cronologia
|
|||
menu_action_pin=Appunta
|
||||
menu_action_unpin=Rilascia
|
||||
confirm_history_delete_p1=Eliminare tutte le occorrenze di questa pagina dalla cronologia?
|
||||
# LOCALIZATION NOTE (confirm_history_delete_notice_p2): this string is displayed in
|
||||
# the same dialog as confirm_history_delete_p1. "This action" refers to deleting a
|
||||
# page from history.
|
||||
confirm_history_delete_notice_p2=Questa operazione non può essere annullata.
|
||||
menu_action_save_to_pocket=Salva in Pocket
|
||||
menu_action_delete_pocket=Elimina da Pocket
|
||||
menu_action_archive_pocket=Archivia in Pocket
|
||||
|
||||
# LOCALIZATION NOTE (menu_action_show_file_*): These are platform specific strings
|
||||
# found in the context menu of an item that has been downloaded. The intention behind
|
||||
# "this action" is that it will show where the downloaded file exists on the file system
|
||||
# for each operating system.
|
||||
menu_action_show_file_mac_os=Mostra nel Finder
|
||||
menu_action_show_file_windows=Apri cartella di destinazione
|
||||
menu_action_show_file_linux=Apri cartella di destinazione
|
||||
menu_action_show_file_default=Mostra file
|
||||
menu_action_open_file=Apri file
|
||||
|
||||
# LOCALIZATION NOTE (menu_action_copy_download_link, menu_action_go_to_download_page):
|
||||
# "Download" here, in both cases, is not a verb, it is a noun. As in, "Copy the
|
||||
# link that belongs to this downloaded item"
|
||||
menu_action_copy_download_link=Copia indirizzo di origine
|
||||
menu_action_go_to_download_page=Vai alla pagina di download
|
||||
menu_action_remove_download=Elimina dalla cronologia
|
||||
|
||||
# LOCALIZATION NOTE (search_button): This is screenreader only text for the
|
||||
# search button.
|
||||
search_button=Cerca
|
||||
|
||||
# LOCALIZATION NOTE (search_header): Displayed at the top of the panel
|
||||
# showing search suggestions. {search_engine_name} is replaced with the name of
|
||||
# the current default search engine. e.g. 'Google Search'
|
||||
search_header=Ricerca {search_engine_name}
|
||||
|
||||
# LOCALIZATION NOTE (search_web_placeholder): This is shown in the searchbox when
|
||||
# the user hasn't typed anything yet.
|
||||
search_web_placeholder=Cerca sul Web
|
||||
|
||||
# LOCALIZATION NOTE (section_disclaimer_topstories): This is shown below
|
||||
# the topstories section title to provide additional information about
|
||||
# how the stories are selected.
|
||||
section_disclaimer_topstories=Le storie più interessanti del Web, selezionate in base alle tue letture. Direttamente da Pocket, ora parte del gruppo Mozilla.
|
||||
section_disclaimer_topstories_linktext=Scopri come funziona.
|
||||
# LOCALIZATION NOTE (section_disclaimer_topstories_buttontext): The text of
|
||||
# the button used to acknowledge, and hide this disclaimer in the future.
|
||||
section_disclaimer_topstories_buttontext=Ho capito.
|
||||
|
||||
# LOCALIZATION NOTE (prefs_*, settings_*): These are shown in about:preferences
|
||||
# for a "Firefox Home" section. "Firefox" should be treated as a brand and kept
|
||||
# in English, while "Home" should be localized matching the about:preferences
|
||||
# sidebar mozilla-central string for the panel that has preferences related to
|
||||
# what is shown for the homepage, new windows, and new tabs.
|
||||
prefs_home_header=Pagina iniziale di Firefox
|
||||
prefs_home_description=Scegli i contenuti da visualizzare nella pagina iniziale di Firefox.
|
||||
# LOCALIZATION NOTE (prefs_section_rows_option): This is a semi-colon list of
|
||||
# plural forms used in a drop down of multiple row options (1 row, 2 rows).
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
prefs_section_rows_option={num} riga;{num} righe
|
||||
prefs_search_header=Ricerca sul Web
|
||||
prefs_topsites_description=I siti più visitati
|
||||
|
@ -109,18 +57,9 @@ settings_pane_button_label=Personalizza la pagina Nuova scheda
|
|||
settings_pane_topsites_header=Siti principali
|
||||
settings_pane_highlights_header=In evidenza
|
||||
settings_pane_highlights_options_bookmarks=Segnalibri
|
||||
# LOCALIZATION NOTE(settings_pane_snippets_header): For the "Snippets" feature
|
||||
# traditionally on about:home. Alternative translation options: "Small Note" or
|
||||
# something that expresses the idea of "a small message, shortened from
|
||||
# something else, and non-essential but also not entirely trivial and useless."
|
||||
settings_pane_snippets_header=Snippet
|
||||
|
||||
# LOCALIZATION NOTE (edit_topsites_*): This is shown in the Edit Top Sites modal
|
||||
# dialog.
|
||||
edit_topsites_button_text=Modifica
|
||||
edit_topsites_edit_button=Modifica questo sito
|
||||
|
||||
# LOCALIZATION NOTE (topsites_form_*): This is shown in the New/Edit Topsite modal.
|
||||
topsites_form_add_header=Nuovi sito principale
|
||||
topsites_form_edit_header=Modifica sito principale
|
||||
topsites_form_title_label=Titolo
|
||||
|
@ -129,49 +68,26 @@ topsites_form_url_label=URL
|
|||
topsites_form_image_url_label=Indirizzo immagine personalizzata
|
||||
topsites_form_url_placeholder=Digitare o incollare un URL
|
||||
topsites_form_use_image_link=Utilizza un’immagine personalizzata…
|
||||
# LOCALIZATION NOTE (topsites_form_*_button): These are verbs/actions.
|
||||
topsites_form_preview_button=Anteprima
|
||||
topsites_form_add_button=Aggiungi
|
||||
topsites_form_save_button=Salva
|
||||
topsites_form_cancel_button=Annulla
|
||||
topsites_form_url_validation=È necessario fornire un URL valido
|
||||
topsites_form_image_validation=Errore durante il caricamento dell’immagine. Prova con un altro indirizzo.
|
||||
|
||||
# LOCALIZATION NOTE (pocket_read_more): This is shown at the bottom of the
|
||||
# trending stories section and precedes a list of links to popular topics.
|
||||
pocket_read_more=Argomenti popolari:
|
||||
# LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
|
||||
# end of the list of popular topic links.
|
||||
pocket_read_even_more=Visualizza altre storie
|
||||
pocket_more_reccommendations = Altri suggerimenti
|
||||
pocket_learn_more = Ulteriori informazioni
|
||||
pocket_how_it_works = Come funziona
|
||||
pocket_cta_button = Ottieni Pocket
|
||||
pocket_cta_text = Salva le storie che ami in Pocket e nutri la tua mente con letture appassionanti.
|
||||
|
||||
highlights_empty_state=Inizia a navigare e, in questa sezione, verranno visualizzati articoli, video e altre pagine visitate di recente o aggiunte ai segnalibri.
|
||||
# LOCALIZATION NOTE (topstories_empty_state): When there are no recommendations,
|
||||
# in the space that would have shown a few stories, this is shown instead.
|
||||
# {provider} is replaced by the name of the content provider for this section.
|
||||
topstories_empty_state=Non c’è altro. Controlla più tardi per altre storie da {provider}. Non vuoi aspettare? Seleziona un argomento tra quelli più popolari per scoprire altre notizie interessanti dal Web.
|
||||
|
||||
# LOCALIZATION NOTE (manual_migration_explanation2): This message is shown to encourage users to
|
||||
# import their browser profile from another browser they might be using.
|
||||
manual_migration_explanation2=Prova Firefox con i segnalibri, la cronologia e le password di un altro browser.
|
||||
# LOCALIZATION NOTE (manual_migration_cancel_button): This message is shown on a button that cancels the
|
||||
# process of importing another browser’s profile into Firefox.
|
||||
manual_migration_cancel_button=No grazie
|
||||
# LOCALIZATION NOTE (manual_migration_import_button): This message is shown on a button that starts the process
|
||||
# of importing another browser’s profile profile into Firefox.
|
||||
manual_migration_import_button=Importa adesso
|
||||
|
||||
# LOCALIZATION NOTE (error_fallback_default_*): This message and suggested
|
||||
# action link are shown in each section of UI that fails to render
|
||||
error_fallback_default_info=Oops, qualcosa è andato storto durante il tentativo di caricare questo contenuto.
|
||||
error_fallback_default_refresh_suggestion=Aggiornare la pagina per riprovare.
|
||||
|
||||
# LOCALIZATION NOTE (section_menu_action_*). These strings are displayed in the section
|
||||
# context menu and are meant as a call to action for the given section.
|
||||
section_menu_action_remove_section=Rimuovi sezione
|
||||
section_menu_action_collapse_section=Comprimi sezione
|
||||
section_menu_action_expand_section=Espandi sezione
|
||||
|
@ -182,27 +98,16 @@ section_menu_action_add_search_engine=Aggiungi motore di ricerca
|
|||
section_menu_action_move_up=Sposta su
|
||||
section_menu_action_move_down=Sposta giù
|
||||
section_menu_action_privacy_notice=Informativa sulla privacy
|
||||
|
||||
# LOCALIZATION NOTE (firstrun_*). These strings are displayed only once, on the
|
||||
# firstrun of the browser, they give an introduction to Firefox and Sync.
|
||||
firstrun_title=Porta Firefox con te
|
||||
firstrun_content=Ritrova segnalibri, cronologia, password e altre impostazioni su tutti i tuoi dispositivi.
|
||||
firstrun_learn_more_link=Scopri di più sull’account Firefox
|
||||
|
||||
# LOCALIZATION NOTE (firstrun_form_header and firstrun_form_sub_header):
|
||||
# firstrun_form_sub_header is a continuation of firstrun_form_header, they are one sentence.
|
||||
# firstrun_form_header is displayed more boldly as the call to action.
|
||||
firstrun_form_header=Inserisci email
|
||||
firstrun_form_sub_header=per utilizzare Firefox Sync.
|
||||
|
||||
firstrun_email_input_placeholder=Email
|
||||
firstrun_invalid_input=Inserire un indirizzo email valido
|
||||
|
||||
# LOCALIZATION NOTE (firstrun_extra_legal_links): {terms} is equal to firstrun_terms_of_service, and
|
||||
# {privacy} is equal to firstrun_privacy_notice. {terms} and {privacy} are clickable links.
|
||||
firstrun_extra_legal_links=Proseguendo, accetto le {terms} e l’{privacy}.
|
||||
firstrun_terms_of_service=condizioni di utilizzo del servizio
|
||||
firstrun_privacy_notice=informativa sulla privacy
|
||||
|
||||
firstrun_continue_to_login=Continua
|
||||
firstrun_skip_login=Ignora questo passaggio
|
||||
context_menu_title=Apri menu
|
||||
|
|
|
@ -143,9 +143,9 @@ pocket_read_more=Populārās tēmas:
|
|||
# LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
|
||||
# end of the list of popular topic links.
|
||||
pocket_read_even_more=Parādīt vairāk lapas
|
||||
|
||||
pocket_more_reccommendations=Vairāk ieteikumu
|
||||
pocket_learn_more=Uzzināt vairāk
|
||||
pocket_how_it_works=Kā tas strādā
|
||||
pocket_cta_button=Izmēģiniet Pocket
|
||||
pocket_cta_text=Saglabājiet interesantus stāstus Pocket un barojiet savu prātu ar interesantu lasāmvielu.
|
||||
|
||||
|
@ -196,7 +196,6 @@ firstrun_form_header=Ievadiet savu epastu
|
|||
firstrun_form_sub_header=lai turpinātu Firefox Sync.
|
||||
|
||||
firstrun_email_input_placeholder=Epasts
|
||||
|
||||
firstrun_invalid_input=Nepieciešams derīgs epasts
|
||||
|
||||
# LOCALIZATION NOTE (firstrun_extra_legal_links): {terms} is equal to firstrun_terms_of_service, and
|
||||
|
|
|
@ -206,3 +206,6 @@ firstrun_privacy_notice=Privacyverklaring
|
|||
|
||||
firstrun_continue_to_login=Doorgaan
|
||||
firstrun_skip_login=Deze stap overslaan
|
||||
|
||||
# LOCALIZATION NOTE (context_menu_title): Action tooltip to open a context menu
|
||||
context_menu_title=Menu openen
|
||||
|
|
|
@ -143,9 +143,9 @@ pocket_read_more=Temas populars:
|
|||
# LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
|
||||
# end of the list of popular topic links.
|
||||
pocket_read_even_more=Mussar dapli artitgels
|
||||
|
||||
pocket_more_reccommendations=Dapli propostas
|
||||
pocket_learn_more=Ulteriuras infurmaziuns
|
||||
pocket_how_it_works=Co ch'i funcziuna
|
||||
pocket_cta_button=Obtegnair Pocket
|
||||
pocket_cta_text=Memorisescha ils artitgels che ta plaschan en Pocket e procura per inspiraziun cuntinuanta cun lectura fascinanta.
|
||||
|
||||
|
@ -196,7 +196,6 @@ firstrun_form_header=Endatescha tia adressa dad e-mail
|
|||
firstrun_form_sub_header=per cuntinuar cun Firefox Sync.
|
||||
|
||||
firstrun_email_input_placeholder=E-mail
|
||||
|
||||
firstrun_invalid_input=Adressa dad e-mail valida è obligatorica
|
||||
|
||||
# LOCALIZATION NOTE (firstrun_extra_legal_links): {terms} is equal to firstrun_terms_of_service, and
|
||||
|
|
|
@ -26,7 +26,7 @@ type_label_downloaded=ڈاؤن لوڈ شدہ
|
|||
# menu and are meant as a call to action for a given page.
|
||||
# LOCALIZATION NOTE (menu_action_bookmark): Bookmark is a verb, as in "Add to
|
||||
# bookmarks"
|
||||
menu_action_bookmark=نشانی
|
||||
menu_action_bookmark=بک مارک
|
||||
menu_action_remove_bookmark=نشانى ہٹائيں
|
||||
menu_action_open_new_window=نئے دریچے میں کھولیں
|
||||
menu_action_open_private_window=نئی نجی دریچے میں کھولیں
|
||||
|
@ -100,7 +100,7 @@ prefs_snippets_description=Mozilla اورFirefox کی جانب سے تازہ ک
|
|||
settings_pane_button_label=اپنے نئے ٹیب کہ صفحہ کی تخصیص کریں
|
||||
settings_pane_topsites_header=بہترین سائٹیں
|
||||
settings_pane_highlights_header=شہ سرخياں
|
||||
settings_pane_highlights_options_bookmarks=نشانیاں
|
||||
settings_pane_highlights_options_bookmarks=بک مارک
|
||||
# LOCALIZATION NOTE(settings_pane_snippets_header): For the "Snippets" feature
|
||||
# traditionally on about:home. Alternative translation options: "Small Note" or
|
||||
# something that expresses the idea of "a small message, shortened from
|
||||
|
@ -133,7 +133,6 @@ pocket_read_more=مشہور مضامین:
|
|||
# LOCALIZATION NOTE (pocket_read_even_more): This is shown as a link at the
|
||||
# end of the list of popular topic links.
|
||||
pocket_read_even_more=مزید کہانیاں دیکھیں
|
||||
|
||||
pocket_more_reccommendations=اور زیادہ سفارشات
|
||||
pocket_learn_more=مزید سیکھیں
|
||||
pocket_how_it_works=یہ کس طرح کام کرتا ہے
|
||||
|
@ -158,6 +157,9 @@ manual_migration_import_button=ابھی درآمد کری
|
|||
# LOCALIZATION NOTE (section_menu_action_*). These strings are displayed in the section
|
||||
# context menu and are meant as a call to action for the given section.
|
||||
section_menu_action_remove_section=صیغہ ہٹائیں
|
||||
section_menu_action_collapse_section=صیغہ تفصیل سے دیکھیں
|
||||
section_menu_action_expand_section=صیغہ کو توسیع کریں
|
||||
section_menu_action_manage_section=صیغہ کابندرست کریں
|
||||
section_menu_action_manage_webext=توسیع کابندرست کریں
|
||||
section_menu_action_add_topsite=بہترین سائٹ شامل کریں
|
||||
section_menu_action_add_search_engine=تلاش انجن کا اضافہ کریں
|
||||
|
@ -174,7 +176,6 @@ section_menu_action_privacy_notice=رازداری کا نوٹس
|
|||
firstrun_form_header=اپنی ای میل داخل کریں
|
||||
|
||||
firstrun_email_input_placeholder=ای میل
|
||||
|
||||
firstrun_invalid_input=جائز ای میل کی ظرورت ہے
|
||||
|
||||
# LOCALIZATION NOTE (firstrun_extra_legal_links): {terms} is equal to firstrun_terms_of_service, and
|
||||
|
|
|
@ -14,9 +14,10 @@ cd /activity-stream && npm install . && npm run buildmc
|
|||
# Build latest m-c with Activity Stream changes
|
||||
cd /mozilla-central && ./mach build \
|
||||
&& ./mach lint -l codespell browser/components/newtab \
|
||||
&& ./mach test browser/components/newtab/test/browser --headless \
|
||||
&& ./mach test browser/components/newtab/test/xpcshell \
|
||||
&& ./mach test --log-tbpl test_run_log \
|
||||
browser_parsable_css \
|
||||
browser/components/newtab \
|
||||
browser/components/preferences/in-content/tests/browser_hometab_restore_defaults.js \
|
||||
browser/components/preferences/in-content/tests/browser_newtab_menu.js \
|
||||
browser/components/enterprisepolicies/tests/browser/browser_policy_set_homepage.js \
|
||||
|
|
|
@ -75,7 +75,7 @@ window.gActivityStreamStrings = {
|
|||
"pocket_read_more": "Популярни теми:",
|
||||
"pocket_read_even_more": "Повече публикации",
|
||||
"pocket_more_reccommendations": "Повече препоръчани",
|
||||
"pocket_how_it_works": "How it works",
|
||||
"pocket_how_it_works": "Как работи",
|
||||
"pocket_cta_button": "Вземете Pocket",
|
||||
"pocket_cta_text": "Запазете статиите, които харесвате в Pocket и заредете ума си с увлекателни четива.",
|
||||
"highlights_empty_state": "Разглеждайте и тук ще ви покажем някои от най-добрите статии, видео и други страници, които сте посетили или отметнали наскоро.",
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -107,6 +107,6 @@ window.gActivityStreamStrings = {
|
|||
"firstrun_privacy_notice": "informativa sulla privacy",
|
||||
"firstrun_continue_to_login": "Continua",
|
||||
"firstrun_skip_login": "Ignora questo passaggio",
|
||||
"context_menu_title": "Open menu",
|
||||
"context_menu_title": "Apri menu",
|
||||
"pocket_learn_more": "Ulteriori informazioni"
|
||||
};
|
||||
|
|
|
@ -75,7 +75,7 @@ window.gActivityStreamStrings = {
|
|||
"pocket_read_more": "Populārās tēmas:",
|
||||
"pocket_read_even_more": "Parādīt vairāk lapas",
|
||||
"pocket_more_reccommendations": "Vairāk ieteikumu",
|
||||
"pocket_how_it_works": "How it works",
|
||||
"pocket_how_it_works": "Kā tas strādā",
|
||||
"pocket_cta_button": "Izmēģiniet Pocket",
|
||||
"pocket_cta_text": "Saglabājiet interesantus stāstus Pocket un barojiet savu prātu ar interesantu lasāmvielu.",
|
||||
"highlights_empty_state": "Sāciet pārlūkošanu un mēs šeit parādīsim lieliskus rakstus, video un citas apmeklētās lapas.",
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -107,6 +107,6 @@ window.gActivityStreamStrings = {
|
|||
"firstrun_privacy_notice": "Privacyverklaring",
|
||||
"firstrun_continue_to_login": "Doorgaan",
|
||||
"firstrun_skip_login": "Deze stap overslaan",
|
||||
"context_menu_title": "Open menu",
|
||||
"context_menu_title": "Menu openen",
|
||||
"pocket_learn_more": "Meer info"
|
||||
};
|
||||
|
|
|
@ -75,7 +75,7 @@ window.gActivityStreamStrings = {
|
|||
"pocket_read_more": "Temas populars:",
|
||||
"pocket_read_even_more": "Mussar dapli artitgels",
|
||||
"pocket_more_reccommendations": "Dapli propostas",
|
||||
"pocket_how_it_works": "How it works",
|
||||
"pocket_how_it_works": "Co ch'i funcziuna",
|
||||
"pocket_cta_button": "Obtegnair Pocket",
|
||||
"pocket_cta_text": "Memorisescha ils artitgels che ta plaschan en Pocket e procura per inspiraziun cuntinuanta cun lectura fascinanta.",
|
||||
"highlights_empty_state": "Cumenza a navigar e nus ta mussain qua artitgels, videos ed autras paginas che ti has visità dacurt u che ti has agiuntà dacurt sco segnapagina.",
|
||||
|
|
|
@ -11,7 +11,7 @@ window.gActivityStreamStrings = {
|
|||
"type_label_recommended": "رجحان سازی",
|
||||
"type_label_pocket": "Pocket میں محفوظ شدہ",
|
||||
"type_label_downloaded": "ڈاؤن لوڈ شدہ",
|
||||
"menu_action_bookmark": "نشانی",
|
||||
"menu_action_bookmark": "بک مارک",
|
||||
"menu_action_remove_bookmark": "نشانى ہٹائيں",
|
||||
"menu_action_open_new_window": "نئے دریچے میں کھولیں",
|
||||
"menu_action_open_private_window": "نئی نجی دریچے میں کھولیں",
|
||||
|
@ -54,7 +54,7 @@ window.gActivityStreamStrings = {
|
|||
"settings_pane_button_label": "اپنے نئے ٹیب کہ صفحہ کی تخصیص کریں",
|
||||
"settings_pane_topsites_header": "بہترین سائٹیں",
|
||||
"settings_pane_highlights_header": "شہ سرخياں",
|
||||
"settings_pane_highlights_options_bookmarks": "نشانیاں",
|
||||
"settings_pane_highlights_options_bookmarks": "بک مارک",
|
||||
"settings_pane_snippets_header": "سنپیٹ",
|
||||
"edit_topsites_button_text": "تدوین",
|
||||
"edit_topsites_edit_button": "اس سائٹ کی تدوین کریں",
|
||||
|
@ -86,9 +86,9 @@ window.gActivityStreamStrings = {
|
|||
"error_fallback_default_info": "Oops, something went wrong loading this content.",
|
||||
"error_fallback_default_refresh_suggestion": "Refresh page to try again.",
|
||||
"section_menu_action_remove_section": "صیغہ ہٹائیں",
|
||||
"section_menu_action_collapse_section": "Collapse Section",
|
||||
"section_menu_action_expand_section": "Expand Section",
|
||||
"section_menu_action_manage_section": "Manage Section",
|
||||
"section_menu_action_collapse_section": "صیغہ تفصیل سے دیکھیں",
|
||||
"section_menu_action_expand_section": "صیغہ کو توسیع کریں",
|
||||
"section_menu_action_manage_section": "صیغہ کابندرست کریں",
|
||||
"section_menu_action_manage_webext": "توسیع کابندرست کریں",
|
||||
"section_menu_action_add_topsite": "بہترین سائٹ شامل کریں",
|
||||
"section_menu_action_add_search_engine": "تلاش انجن کا اضافہ کریں",
|
||||
|
|
|
@ -340,6 +340,36 @@ add_task(async function checkFrecentSites() {
|
|||
await clearHistoryAndBookmarks();
|
||||
});
|
||||
|
||||
add_task(async function check_pinned_sites() {
|
||||
const originalPin = JSON.stringify(NewTabUtils.pinnedLinks.links);
|
||||
const sitesToPin = [
|
||||
{url: "https://foo.com"},
|
||||
{url: "https://floogle.com", searchTopSite: true},
|
||||
];
|
||||
sitesToPin.forEach((site => NewTabUtils.pinnedLinks.pin(site, NewTabUtils.pinnedLinks.links.length)));
|
||||
|
||||
let message;
|
||||
|
||||
message = {id: "foo", targeting: "'https://foo.com' in pinnedSites|mapToProperty('url')"};
|
||||
is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
|
||||
"should select correct item by url in pinnedSites");
|
||||
|
||||
message = {id: "foo", targeting: "'foo.com' in pinnedSites|mapToProperty('host')"};
|
||||
is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
|
||||
"should select correct item by host in pinnedSites");
|
||||
|
||||
message = {id: "foo", targeting: "'floogle.com' in pinnedSites[.searchTopSite == true]|mapToProperty('host')"};
|
||||
is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
|
||||
"should select correct item by host and searchTopSite in pinnedSites");
|
||||
|
||||
// Cleanup
|
||||
sitesToPin.forEach(site => NewTabUtils.pinnedLinks.unpin(site));
|
||||
|
||||
await clearHistoryAndBookmarks();
|
||||
is(JSON.stringify(NewTabUtils.pinnedLinks.links), originalPin,
|
||||
"should restore pinned sites to its original state");
|
||||
});
|
||||
|
||||
add_task(async function check_firefox_version() {
|
||||
const message = {id: "foo", targeting: "firefoxVersion > 0"};
|
||||
is(await ASRouterTargeting.findMatchingMessage({messages: [message]}), message,
|
||||
|
|
|
@ -7,7 +7,7 @@ export const baseKeys = {
|
|||
addon_version: Joi.string().required(),
|
||||
locale: Joi.string().required(),
|
||||
session_id: Joi.string(),
|
||||
page: Joi.valid(["about:home", "about:newtab", "about:welcome", "unknown"]),
|
||||
page: Joi.valid(["about:home", "about:newtab", "about:welcome", "both", "unknown"]),
|
||||
user_prefs: Joi.number().integer().required(),
|
||||
};
|
||||
|
||||
|
@ -24,12 +24,16 @@ export const eventsTelemetryExtraKeys = Joi.object().keys({
|
|||
export const UserEventPing = Joi.object().keys(Object.assign({}, baseKeys, {
|
||||
session_id: baseKeys.session_id.required(),
|
||||
page: baseKeys.page.required(),
|
||||
source: Joi.string().required(),
|
||||
source: Joi.string(),
|
||||
event: Joi.string().required(),
|
||||
action: Joi.valid("activity_stream_user_event").required(),
|
||||
metadata_source: Joi.string(),
|
||||
highlight_type: Joi.valid(["bookmarks", "recommendation", "history"]),
|
||||
recommender_type: Joi.string(),
|
||||
value: Joi.object().keys({
|
||||
newtab_url_category: Joi.string(),
|
||||
home_url_category: Joi.string(),
|
||||
}),
|
||||
}));
|
||||
|
||||
export const UTUserEventPing = Joi.array().items(
|
||||
|
|
|
@ -208,7 +208,11 @@ describe("ASRouter", () => {
|
|||
sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => true);
|
||||
await Router.setState({foo: 123});
|
||||
|
||||
assert.calledWith(channel.sendAsyncMessage, "ASRouter:parent-to-child", {type: "ADMIN_SET_STATE", data: Router.state});
|
||||
assert.calledOnce(channel.sendAsyncMessage);
|
||||
assert.deepEqual(channel.sendAsyncMessage.firstCall.args[1], {
|
||||
type: "ADMIN_SET_STATE",
|
||||
data: Object.assign({}, Router.state, {providerPrefs: ASRouterPreferences.providers}),
|
||||
});
|
||||
});
|
||||
it("should not send a message on a state change asrouter.devtoolsEnabled pref is on", async () => {
|
||||
sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => false);
|
||||
|
@ -393,452 +397,491 @@ describe("ASRouter", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: SNIPPETS_REQUEST", () => {
|
||||
it("should set state.lastMessageId to a message id", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "SNIPPETS_REQUEST"}));
|
||||
describe("onMessage", () => {
|
||||
describe("#onMessage: SNIPPETS_REQUEST", () => {
|
||||
it("should set state.lastMessageId to a message id", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "SNIPPETS_REQUEST"}));
|
||||
|
||||
assert.include(ALL_MESSAGE_IDS, Router.state.lastMessageId);
|
||||
});
|
||||
it("should send a message back to the to the target", async () => {
|
||||
// force the only message to be a regular message so getRandomItemFromArray picks it
|
||||
await Router.setState({messages: [{id: "foo", template: "simple_template", content: {title: "Foo", body: "Foo123"}}]});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
const [currentMessage] = Router.state.messages.filter(message => message.id === Router.state.lastMessageId);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_MESSAGE", data: currentMessage});
|
||||
});
|
||||
it("should send a message back to the to the target if there is a bundle, too", async () => {
|
||||
// force the only message to be a bundled message so getRandomItemFromArray picks it
|
||||
sandbox.stub(Router, "_findProvider").returns(null);
|
||||
await Router.setState({messages: [{id: "foo1", template: "simple_template", bundled: 1, content: {title: "Foo1", body: "Foo123-1"}}]});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
const [currentMessage] = Router.state.messages.filter(message => message.id === Router.state.lastMessageId);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME);
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].type, "SET_BUNDLED_MESSAGES");
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].data.bundle[0].content, currentMessage.content);
|
||||
});
|
||||
it("should properly order the message's bundle if specified", async () => {
|
||||
// force the only messages to be a bundled messages so getRandomItemFromArray picks one of them
|
||||
sandbox.stub(Router, "_findProvider").returns(null);
|
||||
const firstMessage = {id: "foo2", template: "simple_template", bundled: 2, order: 1, content: {title: "Foo2", body: "Foo123-2"}};
|
||||
const secondMessage = {id: "foo1", template: "simple_template", bundled: 2, order: 2, content: {title: "Foo1", body: "Foo123-1"}};
|
||||
await Router.setState({messages: [secondMessage, firstMessage]});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME);
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].type, "SET_BUNDLED_MESSAGES");
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].data.bundle[0].content, firstMessage.content);
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].data.bundle[1].content, secondMessage.content);
|
||||
});
|
||||
it("should return a null bundle if we do not have enough messages to fill the bundle", async () => {
|
||||
// force the only message to be a bundled message that needs 2 messages in the bundle
|
||||
await Router.setState({messages: [{id: "foo1", template: "simple_template", bundled: 2, content: {title: "Foo1", body: "Foo123-1"}}]});
|
||||
const bundle = await Router._getBundledMessages(Router.state.messages[0]);
|
||||
assert.equal(bundle, null);
|
||||
});
|
||||
it("should send down extra attributes in the bundle if they exist", async () => {
|
||||
sandbox.stub(Router, "_findProvider").returns({getExtraAttributes() { return Promise.resolve({header: "header"}); }});
|
||||
await Router.setState({messages: [{id: "foo1", template: "simple_template", bundled: 1, content: {title: "Foo1", body: "Foo123-1"}}]});
|
||||
const result = await Router._getBundledMessages(Router.state.messages[0]);
|
||||
assert.equal(result.extraTemplateStrings.header, "header");
|
||||
});
|
||||
it("should send a CLEAR_ALL message if no bundle available", async () => {
|
||||
// force the only message to be a bundled message that needs 2 messages in the bundle
|
||||
await Router.setState({messages: [{id: "foo1", template: "simple_template", bundled: 2, content: {title: "Foo1", body: "Foo123-1"}}]});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_ALL"});
|
||||
});
|
||||
it("should send a CLEAR_ALL message if no messages are available", async () => {
|
||||
await Router.setState({messages: []});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
assert.include(ALL_MESSAGE_IDS, Router.state.lastMessageId);
|
||||
});
|
||||
it("should send a message back to the to the target", async () => {
|
||||
// force the only message to be a regular message so getRandomItemFromArray picks it
|
||||
await Router.setState({messages: [{id: "foo", template: "simple_template", content: {title: "Foo", body: "Foo123"}}]});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
const [currentMessage] = Router.state.messages.filter(message => message.id === Router.state.lastMessageId);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_MESSAGE", data: currentMessage});
|
||||
});
|
||||
it("should send a message back to the to the target if there is a bundle, too", async () => {
|
||||
// force the only message to be a bundled message so getRandomItemFromArray picks it
|
||||
sandbox.stub(Router, "_findProvider").returns(null);
|
||||
await Router.setState({messages: [{id: "foo1", template: "simple_template", bundled: 1, content: {title: "Foo1", body: "Foo123-1"}}]});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
const [currentMessage] = Router.state.messages.filter(message => message.id === Router.state.lastMessageId);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME);
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].type, "SET_BUNDLED_MESSAGES");
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].data.bundle[0].content, currentMessage.content);
|
||||
});
|
||||
it("should properly order the message's bundle if specified", async () => {
|
||||
// force the only messages to be a bundled messages so getRandomItemFromArray picks one of them
|
||||
sandbox.stub(Router, "_findProvider").returns(null);
|
||||
const firstMessage = {id: "foo2", template: "simple_template", bundled: 2, order: 1, content: {title: "Foo2", body: "Foo123-2"}};
|
||||
const secondMessage = {id: "foo1", template: "simple_template", bundled: 2, order: 2, content: {title: "Foo1", body: "Foo123-1"}};
|
||||
await Router.setState({messages: [secondMessage, firstMessage]});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME);
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].type, "SET_BUNDLED_MESSAGES");
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].data.bundle[0].content, firstMessage.content);
|
||||
assert.equal(msg.target.sendAsyncMessage.firstCall.args[1].data.bundle[1].content, secondMessage.content);
|
||||
});
|
||||
it("should return a null bundle if we do not have enough messages to fill the bundle", async () => {
|
||||
// force the only message to be a bundled message that needs 2 messages in the bundle
|
||||
await Router.setState({messages: [{id: "foo1", template: "simple_template", bundled: 2, content: {title: "Foo1", body: "Foo123-1"}}]});
|
||||
const bundle = await Router._getBundledMessages(Router.state.messages[0]);
|
||||
assert.equal(bundle, null);
|
||||
});
|
||||
it("should send down extra attributes in the bundle if they exist", async () => {
|
||||
sandbox.stub(Router, "_findProvider").returns({getExtraAttributes() { return Promise.resolve({header: "header"}); }});
|
||||
await Router.setState({messages: [{id: "foo1", template: "simple_template", bundled: 1, content: {title: "Foo1", body: "Foo123-1"}}]});
|
||||
const result = await Router._getBundledMessages(Router.state.messages[0]);
|
||||
assert.equal(result.extraTemplateStrings.header, "header");
|
||||
});
|
||||
it("should send a CLEAR_ALL message if no bundle available", async () => {
|
||||
// force the only message to be a bundled message that needs 2 messages in the bundle
|
||||
await Router.setState({messages: [{id: "foo1", template: "simple_template", bundled: 2, content: {title: "Foo1", body: "Foo123-1"}}]});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_ALL"});
|
||||
});
|
||||
it("should send a CLEAR_ALL message if no messages are available", async () => {
|
||||
await Router.setState({messages: []});
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_ALL"});
|
||||
});
|
||||
it("should make a request to the provided endpoint on SNIPPETS_REQUEST", async () => {
|
||||
const url = "https://snippets-admin.mozilla.org/foo";
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_ALL"});
|
||||
});
|
||||
it("should make a request to the provided endpoint on SNIPPETS_REQUEST", async () => {
|
||||
const url = "https://snippets-admin.mozilla.org/foo";
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(global.fetch, url);
|
||||
assert.lengthOf(Router.state.providers.filter(p => p.url === url), 0);
|
||||
});
|
||||
it("should make a request to the provided endpoint on ADMIN_CONNECT_STATE and remove the endpoint", async () => {
|
||||
const url = "https://snippets-admin.mozilla.org/foo";
|
||||
const msg = fakeAsyncMessage({type: "ADMIN_CONNECT_STATE", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(global.fetch, url);
|
||||
assert.lengthOf(Router.state.providers.filter(p => p.url === url), 0);
|
||||
});
|
||||
it("should make a request to the provided endpoint on ADMIN_CONNECT_STATE and remove the endpoint", async () => {
|
||||
const url = "https://snippets-admin.mozilla.org/foo";
|
||||
const msg = fakeAsyncMessage({type: "ADMIN_CONNECT_STATE", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(global.fetch, url);
|
||||
assert.lengthOf(Router.state.providers.filter(p => p.url === url), 0);
|
||||
});
|
||||
it("should dispatch SNIPPETS_PREVIEW_MODE when adding a preview endpoint", async () => {
|
||||
const url = "https://snippets-admin.mozilla.org/foo";
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(global.fetch, url);
|
||||
assert.lengthOf(Router.state.providers.filter(p => p.url === url), 0);
|
||||
});
|
||||
it("should dispatch SNIPPETS_PREVIEW_MODE when adding a preview endpoint", async () => {
|
||||
const url = "https://snippets-admin.mozilla.org/foo";
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWithExactly(Router.dispatchToAS, ac.OnlyToOneContent({type: "SNIPPETS_PREVIEW_MODE"}, msg.target.portID));
|
||||
});
|
||||
it("should not add a url that is not from a whitelisted host", async () => {
|
||||
const url = "https://mozilla.org";
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWithExactly(Router.dispatchToAS, ac.OnlyToOneContent({type: "SNIPPETS_PREVIEW_MODE"}, msg.target.portID));
|
||||
});
|
||||
it("should not add a url that is not from a whitelisted host", async () => {
|
||||
const url = "https://mozilla.org";
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.lengthOf(Router.state.providers.filter(p => p.url === url), 0);
|
||||
});
|
||||
it("should reject bad urls", async () => {
|
||||
const url = "foo";
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
assert.lengthOf(Router.state.providers.filter(p => p.url === url), 0);
|
||||
});
|
||||
it("should reject bad urls", async () => {
|
||||
const url = "foo";
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST", data: {endpoint: {url}}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.lengthOf(Router.state.providers.filter(p => p.url === url), 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: BLOCK_MESSAGE_BY_ID", () => {
|
||||
it("should add the id to the messageBlockList and broadcast a CLEAR_MESSAGE message with the id", async () => {
|
||||
await Router.setState({lastMessageId: "foo"});
|
||||
const msg = fakeAsyncMessage({type: "BLOCK_MESSAGE_BY_ID", data: {id: "foo"}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.isTrue(Router.state.messageBlockList.includes("foo"));
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_MESSAGE", data: {id: "foo"}});
|
||||
});
|
||||
it("should not broadcast CLEAR_MESSAGE if preventDismiss is true", async () => {
|
||||
const msg = fakeAsyncMessage({type: "BLOCK_MESSAGE_BY_ID", data: {id: "foo", preventDismiss: true}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.notCalled(channel.sendAsyncMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: DISMISS_MESSAGE_BY_ID", () => {
|
||||
it("should reply with CLEAR_MESSAGE with the correct id", async () => {
|
||||
const msg = fakeAsyncMessage({type: "DISMISS_MESSAGE_BY_ID", data: {id: "foo"}});
|
||||
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_MESSAGE", data: {id: "foo"}});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: BLOCK_PROVIDER_BY_ID", () => {
|
||||
it("should add the provider id to the providerBlockList and broadcast a CLEAR_PROVIDER with the provider id", async () => {
|
||||
const msg = fakeAsyncMessage({type: "BLOCK_PROVIDER_BY_ID", data: {id: "bar"}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.isTrue(Router.state.providerBlockList.includes("bar"));
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_PROVIDER", data: {id: "bar"}});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: BLOCK_BUNDLE", () => {
|
||||
it("should add all the ids in the bundle to the messageBlockList and send a CLEAR_BUNDLE message", async () => {
|
||||
const bundleIds = [FAKE_BUNDLE[0].id, FAKE_BUNDLE[1].id];
|
||||
await Router.setState({lastMessageId: "foo"});
|
||||
const msg = fakeAsyncMessage({type: "BLOCK_BUNDLE", data: {bundle: FAKE_BUNDLE}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[0].id));
|
||||
assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[1].id));
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_BUNDLE"});
|
||||
assert.calledWithExactly(Router._storage.set, "messageBlockList", bundleIds);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: UNBLOCK_MESSAGE_BY_ID", () => {
|
||||
it("should remove the id from the messageBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "BLOCK_MESSAGE_BY_ID", data: {id: "foo"}}));
|
||||
assert.isTrue(Router.state.messageBlockList.includes("foo"));
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_MESSAGE_BY_ID", data: {id: "foo"}}));
|
||||
|
||||
assert.isFalse(Router.state.messageBlockList.includes("foo"));
|
||||
});
|
||||
it("should save the messageBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_MESSAGE_BY_ID", data: {id: "foo"}}));
|
||||
|
||||
assert.calledWithExactly(Router._storage.set, "messageBlockList", []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: UNBLOCK_PROVIDER_BY_ID", () => {
|
||||
it("should remove the id from the providerBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "BLOCK_PROVIDER_BY_ID", data: {id: "foo"}}));
|
||||
assert.isTrue(Router.state.providerBlockList.includes("foo"));
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_PROVIDER_BY_ID", data: {id: "foo"}}));
|
||||
|
||||
assert.isFalse(Router.state.providerBlockList.includes("foo"));
|
||||
});
|
||||
it("should save the providerBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_PROVIDER_BY_ID", data: {id: "foo"}}));
|
||||
|
||||
assert.calledWithExactly(Router._storage.set, "providerBlockList", []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: UNBLOCK_BUNDLE", () => {
|
||||
it("should remove all the ids in the bundle from the messageBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "BLOCK_BUNDLE", data: {bundle: FAKE_BUNDLE}}));
|
||||
assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[0].id));
|
||||
assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[1].id));
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_BUNDLE", data: {bundle: FAKE_BUNDLE}}));
|
||||
|
||||
assert.isFalse(Router.state.messageBlockList.includes(FAKE_BUNDLE[0].id));
|
||||
assert.isFalse(Router.state.messageBlockList.includes(FAKE_BUNDLE[1].id));
|
||||
});
|
||||
it("should save the messageBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_BUNDLE", data: {bundle: FAKE_BUNDLE}}));
|
||||
|
||||
assert.calledWithExactly(Router._storage.set, "messageBlockList", []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: ADMIN_CONNECT_STATE", () => {
|
||||
it("should send a message containing the whole state", async () => {
|
||||
const msg = fakeAsyncMessage({type: "ADMIN_CONNECT_STATE"});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "ADMIN_SET_STATE", data: Router.state});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: SNIPPETS_REQUEST", () => {
|
||||
it("should call sendNextMessage on SNIPPETS_REQUEST", async () => {
|
||||
sandbox.stub(Router, "sendNextMessage").resolves();
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(Router.sendNextMessage);
|
||||
assert.calledWithExactly(Router.sendNextMessage, sinon.match.instanceOf(FakeRemotePageManager), {});
|
||||
});
|
||||
it("should return the preview message if that's available and remove it from Router.state", async () => {
|
||||
const expectedObj = {provider: "preview"};
|
||||
Router.setState({messages: [expectedObj]});
|
||||
|
||||
await Router.sendNextMessage(channel);
|
||||
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_MESSAGE", data: expectedObj});
|
||||
assert.isUndefined(Router.state.messages.find(m => m.provider === "preview"));
|
||||
});
|
||||
it("should call _getBundledMessages if we request a message that needs to be bundled", async () => {
|
||||
sandbox.stub(Router, "_getBundledMessages").resolves();
|
||||
// forcefully pick a message which needs to be bundled (the second message in FAKE_LOCAL_MESSAGES)
|
||||
const [, testMessage] = Router.state.messages;
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage.id}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(Router._getBundledMessages);
|
||||
});
|
||||
it("should properly pick another message of the same template if it is bundled; force = true", async () => {
|
||||
// forcefully pick a message which needs to be bundled (the second message in FAKE_LOCAL_MESSAGES)
|
||||
const [, testMessage1, testMessage2] = Router.state.messages;
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage1.id}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
// Expected object should have some properties of the original message it picked (testMessage1)
|
||||
// plus the bundled content of the others that it picked of the same template (testMessage2)
|
||||
const expectedObj = {
|
||||
template: testMessage1.template,
|
||||
provider: testMessage1.provider,
|
||||
bundle: [{content: testMessage1.content, id: testMessage1.id, order: 1}, {content: testMessage2.content, id: testMessage2.id}],
|
||||
};
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_BUNDLED_MESSAGES", data: expectedObj});
|
||||
});
|
||||
it("should properly pick another message of the same template if it is bundled; force = false", async () => {
|
||||
// forcefully pick a message which needs to be bundled (the second message in FAKE_LOCAL_MESSAGES)
|
||||
const [, testMessage1, testMessage2] = Router.state.messages;
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage1.id}});
|
||||
await Router.setMessageById(testMessage1.id, msg.target, false);
|
||||
|
||||
// Expected object should have some properties of the original message it picked (testMessage1)
|
||||
// plus the bundled content of the others that it picked of the same template (testMessage2)
|
||||
const expectedObj = {
|
||||
template: testMessage1.template,
|
||||
provider: testMessage1.provider,
|
||||
bundle: [{content: testMessage1.content, id: testMessage1.id, order: 1}, {content: testMessage2.content, id: testMessage2.id, order: 2}],
|
||||
};
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_BUNDLED_MESSAGES", data: expectedObj});
|
||||
});
|
||||
it("should get the bundle and send the message if the message has a bundle", async () => {
|
||||
sandbox.stub(Router, "sendNextMessage").resolves();
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
msg.bundled = 2; // force this message to want to be bundled
|
||||
await Router.onMessage(msg);
|
||||
assert.calledOnce(Router.sendNextMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: TRIGGER", () => {
|
||||
it("should pass the trigger to ASRouterTargeting on TRIGGER message", async () => {
|
||||
sandbox.stub(Router, "_findMessage").resolves();
|
||||
const msg = fakeAsyncMessage({type: "TRIGGER", data: {trigger: {id: "firstRun"}}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(Router._findMessage);
|
||||
assert.deepEqual(Router._findMessage.firstCall.args[1], {id: "firstRun"});
|
||||
});
|
||||
it("consider the trigger when picking a message", async () => {
|
||||
const messages = [
|
||||
{id: "foo1", template: "simple_template", bundled: 1, trigger: {id: "foo"}, content: {title: "Foo1", body: "Foo123-1"}},
|
||||
];
|
||||
|
||||
const {data} = fakeAsyncMessage({type: "TRIGGER", data: {trigger: {id: "foo"}}});
|
||||
const message = await Router._findMessage(messages, data.data.trigger);
|
||||
assert.equal(message, messages[0]);
|
||||
});
|
||||
it("should pick a message with the right targeting and trigger", async () => {
|
||||
let messages = [
|
||||
{id: "foo1", template: "simple_template", bundled: 2, trigger: {id: "foo"}, content: {title: "Foo1", body: "Foo123-1"}},
|
||||
{id: "foo2", template: "simple_template", bundled: 2, trigger: {id: "bar"}, content: {title: "Foo2", body: "Foo123-2"}},
|
||||
{id: "foo3", template: "simple_template", bundled: 2, trigger: {id: "foo"}, content: {title: "Foo3", body: "Foo123-3"}},
|
||||
];
|
||||
sandbox.stub(Router, "_findProvider").returns(null);
|
||||
await Router.setState({messages});
|
||||
const {target} = fakeAsyncMessage({type: "TRIGGER", data: {trigger: {id: "foo"}}});
|
||||
let {bundle} = await Router._getBundledMessages(messages[0], target, {id: "foo"});
|
||||
assert.equal(bundle.length, 2);
|
||||
// it should have picked foo1 and foo3 only
|
||||
assert.isTrue(bundle.every(elem => elem.id === "foo1" || elem.id === "foo3"));
|
||||
});
|
||||
it("should have previousSessionEnd in the message context", () => {
|
||||
assert.propertyVal(Router._getMessagesContext(), "previousSessionEnd", 100);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: OVERRIDE_MESSAGE", () => {
|
||||
it("should broadcast a SET_MESSAGE message to all clients with a particular id", async () => {
|
||||
const [testMessage] = Router.state.messages;
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage.id}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_MESSAGE", data: testMessage});
|
||||
assert.lengthOf(Router.state.providers.filter(p => p.url === url), 0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should call CFRPageActions.forceRecommendation if the template is cfr_action and force is true", async () => {
|
||||
sandbox.stub(CFRPageActions, "forceRecommendation");
|
||||
const testMessage = {id: "foo", template: "cfr_doorhanger"};
|
||||
await Router.setState({messages: [testMessage]});
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage.id}});
|
||||
await Router.onMessage(msg);
|
||||
describe("#onMessage: BLOCK_MESSAGE_BY_ID", () => {
|
||||
it("should add the id to the messageBlockList and broadcast a CLEAR_MESSAGE message with the id", async () => {
|
||||
await Router.setState({lastMessageId: "foo"});
|
||||
const msg = fakeAsyncMessage({type: "BLOCK_MESSAGE_BY_ID", data: {id: "foo"}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.notCalled(msg.target.sendAsyncMessage);
|
||||
assert.calledOnce(CFRPageActions.forceRecommendation);
|
||||
assert.isTrue(Router.state.messageBlockList.includes("foo"));
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_MESSAGE", data: {id: "foo"}});
|
||||
});
|
||||
it("should not broadcast CLEAR_MESSAGE if preventDismiss is true", async () => {
|
||||
const msg = fakeAsyncMessage({type: "BLOCK_MESSAGE_BY_ID", data: {id: "foo", preventDismiss: true}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.notCalled(channel.sendAsyncMessage);
|
||||
});
|
||||
});
|
||||
|
||||
it("should call CFRPageActions.addRecommendation if the template is cfr_action and force is false", async () => {
|
||||
sandbox.stub(CFRPageActions, "addRecommendation");
|
||||
const testMessage = {id: "foo", template: "cfr_doorhanger"};
|
||||
await Router.setState({messages: [testMessage]});
|
||||
await Router._sendMessageToTarget(testMessage, {}, {}, false);
|
||||
describe("#onMessage: DISMISS_MESSAGE_BY_ID", () => {
|
||||
it("should reply with CLEAR_MESSAGE with the correct id", async () => {
|
||||
const msg = fakeAsyncMessage({type: "DISMISS_MESSAGE_BY_ID", data: {id: "foo"}});
|
||||
|
||||
assert.calledOnce(CFRPageActions.addRecommendation);
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_MESSAGE", data: {id: "foo"}});
|
||||
});
|
||||
});
|
||||
|
||||
it("should broadcast CLEAR_ALL if provided id did not resolve to a message", async () => {
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: -1}});
|
||||
await Router.onMessage(msg);
|
||||
describe("#onMessage: BLOCK_PROVIDER_BY_ID", () => {
|
||||
it("should add the provider id to the providerBlockList and broadcast a CLEAR_PROVIDER with the provider id", async () => {
|
||||
const msg = fakeAsyncMessage({type: "BLOCK_PROVIDER_BY_ID", data: {id: "bar"}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_ALL"});
|
||||
assert.isTrue(Router.state.providerBlockList.includes("bar"));
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_PROVIDER", data: {id: "bar"}});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: Onboarding actions", () => {
|
||||
it("should call OpenBrowserWindow with a private window on OPEN_PRIVATE_BROWSER_WINDOW", async () => {
|
||||
let [testMessage] = Router.state.messages;
|
||||
const msg = fakeExecuteUserAction({type: "OPEN_PRIVATE_BROWSER_WINDOW", data: testMessage});
|
||||
await Router.onMessage(msg);
|
||||
describe("#onMessage: BLOCK_BUNDLE", () => {
|
||||
it("should add all the ids in the bundle to the messageBlockList and send a CLEAR_BUNDLE message", async () => {
|
||||
const bundleIds = [FAKE_BUNDLE[0].id, FAKE_BUNDLE[1].id];
|
||||
await Router.setState({lastMessageId: "foo"});
|
||||
const msg = fakeAsyncMessage({type: "BLOCK_BUNDLE", data: {bundle: FAKE_BUNDLE}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(msg.target.browser.ownerGlobal.OpenBrowserWindow, {private: true});
|
||||
assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[0].id));
|
||||
assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[1].id));
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_BUNDLE"});
|
||||
assert.calledWithExactly(Router._storage.set, "messageBlockList", bundleIds);
|
||||
});
|
||||
});
|
||||
it("should call openLinkIn with the correct params on OPEN_URL", async () => {
|
||||
let [testMessage] = Router.state.messages;
|
||||
testMessage.button_action = {type: "OPEN_URL", data: {args: "some/url.com"}};
|
||||
const msg = fakeExecuteUserAction(testMessage.button_action);
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(msg.target.browser.ownerGlobal.openLinkIn);
|
||||
assert.calledWith(msg.target.browser.ownerGlobal.openLinkIn,
|
||||
"some/url.com", "tabshifted", {"private": false, "triggeringPrincipal": undefined});
|
||||
describe("#onMessage: UNBLOCK_MESSAGE_BY_ID", () => {
|
||||
it("should remove the id from the messageBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "BLOCK_MESSAGE_BY_ID", data: {id: "foo"}}));
|
||||
assert.isTrue(Router.state.messageBlockList.includes("foo"));
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_MESSAGE_BY_ID", data: {id: "foo"}}));
|
||||
|
||||
assert.isFalse(Router.state.messageBlockList.includes("foo"));
|
||||
});
|
||||
it("should save the messageBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_MESSAGE_BY_ID", data: {id: "foo"}}));
|
||||
|
||||
assert.calledWithExactly(Router._storage.set, "messageBlockList", []);
|
||||
});
|
||||
});
|
||||
it("should call openLinkIn with the correct params on OPEN_ABOUT_PAGE", async () => {
|
||||
let [testMessage] = Router.state.messages;
|
||||
testMessage.button_action = {type: "OPEN_ABOUT_PAGE", data: {args: "something"}};
|
||||
const msg = fakeExecuteUserAction(testMessage.button_action);
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(msg.target.browser.ownerGlobal.openTrustedLinkIn);
|
||||
assert.calledWith(msg.target.browser.ownerGlobal.openTrustedLinkIn, "about:something", "tab");
|
||||
describe("#onMessage: UNBLOCK_PROVIDER_BY_ID", () => {
|
||||
it("should remove the id from the providerBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "BLOCK_PROVIDER_BY_ID", data: {id: "foo"}}));
|
||||
assert.isTrue(Router.state.providerBlockList.includes("foo"));
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_PROVIDER_BY_ID", data: {id: "foo"}}));
|
||||
|
||||
assert.isFalse(Router.state.providerBlockList.includes("foo"));
|
||||
});
|
||||
it("should save the providerBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_PROVIDER_BY_ID", data: {id: "foo"}}));
|
||||
|
||||
assert.calledWithExactly(Router._storage.set, "providerBlockList", []);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: SHOW_FIREFOX_ACCOUNTS", () => {
|
||||
let globals;
|
||||
beforeEach(() => {
|
||||
globals = new GlobalOverrider();
|
||||
globals.set("FxAccounts", {config: {promiseSignUpURI: sandbox.stub().resolves("some/url")}});
|
||||
describe("#onMessage: UNBLOCK_BUNDLE", () => {
|
||||
it("should remove all the ids in the bundle from the messageBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "BLOCK_BUNDLE", data: {bundle: FAKE_BUNDLE}}));
|
||||
assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[0].id));
|
||||
assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[1].id));
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_BUNDLE", data: {bundle: FAKE_BUNDLE}}));
|
||||
|
||||
assert.isFalse(Router.state.messageBlockList.includes(FAKE_BUNDLE[0].id));
|
||||
assert.isFalse(Router.state.messageBlockList.includes(FAKE_BUNDLE[1].id));
|
||||
});
|
||||
it("should save the messageBlockList", async () => {
|
||||
await Router.onMessage(fakeAsyncMessage({type: "UNBLOCK_BUNDLE", data: {bundle: FAKE_BUNDLE}}));
|
||||
|
||||
assert.calledWithExactly(Router._storage.set, "messageBlockList", []);
|
||||
});
|
||||
});
|
||||
it("should call openLinkIn with the correct params on OPEN_URL", async () => {
|
||||
let [testMessage] = Router.state.messages;
|
||||
testMessage.button_action = {type: "SHOW_FIREFOX_ACCOUNTS"};
|
||||
const msg = fakeExecuteUserAction(testMessage.button_action);
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(msg.target.browser.ownerGlobal.openLinkIn);
|
||||
assert.calledWith(msg.target.browser.ownerGlobal.openLinkIn,
|
||||
"some/url", "tabshifted", {"private": false, "triggeringPrincipal": undefined});
|
||||
describe("#onMessage: ADMIN_CONNECT_STATE", () => {
|
||||
it("should send a message containing the whole state", async () => {
|
||||
const msg = fakeAsyncMessage({type: "ADMIN_CONNECT_STATE"});
|
||||
await Router.onMessage(msg);
|
||||
assert.calledOnce(msg.target.sendAsyncMessage);
|
||||
assert.deepEqual(msg.target.sendAsyncMessage.firstCall.args[1], {
|
||||
type: "ADMIN_SET_STATE",
|
||||
data: Object.assign({}, Router.state, {providerPrefs: ASRouterPreferences.providers}),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: INSTALL_ADDON_FROM_URL", () => {
|
||||
it("should call installAddonFromURL with correct arguments", async () => {
|
||||
sandbox.stub(MessageLoaderUtils, "installAddonFromURL").resolves(null);
|
||||
const msg = fakeExecuteUserAction({type: "INSTALL_ADDON_FROM_URL", data: {url: "foo.com"}});
|
||||
describe("#onMessage: SNIPPETS_REQUEST", () => {
|
||||
it("should call sendNextMessage on SNIPPETS_REQUEST", async () => {
|
||||
sandbox.stub(Router, "sendNextMessage").resolves();
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
|
||||
await Router.onMessage(msg);
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(MessageLoaderUtils.installAddonFromURL);
|
||||
assert.calledWithExactly(MessageLoaderUtils.installAddonFromURL, msg.target.browser, "foo.com");
|
||||
assert.calledOnce(Router.sendNextMessage);
|
||||
assert.calledWithExactly(Router.sendNextMessage, sinon.match.instanceOf(FakeRemotePageManager), {});
|
||||
});
|
||||
it("should return the preview message if that's available and remove it from Router.state", async () => {
|
||||
const expectedObj = {provider: "preview"};
|
||||
Router.setState({messages: [expectedObj]});
|
||||
|
||||
await Router.sendNextMessage(channel);
|
||||
|
||||
assert.calledWith(channel.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_MESSAGE", data: expectedObj});
|
||||
assert.isUndefined(Router.state.messages.find(m => m.provider === "preview"));
|
||||
});
|
||||
it("should call _getBundledMessages if we request a message that needs to be bundled", async () => {
|
||||
sandbox.stub(Router, "_getBundledMessages").resolves();
|
||||
// forcefully pick a message which needs to be bundled (the second message in FAKE_LOCAL_MESSAGES)
|
||||
const [, testMessage] = Router.state.messages;
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage.id}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(Router._getBundledMessages);
|
||||
});
|
||||
it("should properly pick another message of the same template if it is bundled; force = true", async () => {
|
||||
// forcefully pick a message which needs to be bundled (the second message in FAKE_LOCAL_MESSAGES)
|
||||
const [, testMessage1, testMessage2] = Router.state.messages;
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage1.id}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
// Expected object should have some properties of the original message it picked (testMessage1)
|
||||
// plus the bundled content of the others that it picked of the same template (testMessage2)
|
||||
const expectedObj = {
|
||||
template: testMessage1.template,
|
||||
provider: testMessage1.provider,
|
||||
bundle: [{content: testMessage1.content, id: testMessage1.id, order: 1}, {content: testMessage2.content, id: testMessage2.id}],
|
||||
};
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_BUNDLED_MESSAGES", data: expectedObj});
|
||||
});
|
||||
it("should properly pick another message of the same template if it is bundled; force = false", async () => {
|
||||
// forcefully pick a message which needs to be bundled (the second message in FAKE_LOCAL_MESSAGES)
|
||||
const [, testMessage1, testMessage2] = Router.state.messages;
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage1.id}});
|
||||
await Router.setMessageById(testMessage1.id, msg.target, false);
|
||||
|
||||
// Expected object should have some properties of the original message it picked (testMessage1)
|
||||
// plus the bundled content of the others that it picked of the same template (testMessage2)
|
||||
const expectedObj = {
|
||||
template: testMessage1.template,
|
||||
provider: testMessage1.provider,
|
||||
bundle: [{content: testMessage1.content, id: testMessage1.id, order: 1}, {content: testMessage2.content, id: testMessage2.id, order: 2}],
|
||||
};
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_BUNDLED_MESSAGES", data: expectedObj});
|
||||
});
|
||||
it("should get the bundle and send the message if the message has a bundle", async () => {
|
||||
sandbox.stub(Router, "sendNextMessage").resolves();
|
||||
const msg = fakeAsyncMessage({type: "SNIPPETS_REQUEST"});
|
||||
msg.bundled = 2; // force this message to want to be bundled
|
||||
await Router.onMessage(msg);
|
||||
assert.calledOnce(Router.sendNextMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#dispatch(action, target)", () => {
|
||||
it("should an action and target to onMessage", async () => {
|
||||
// use the IMPRESSION action to make sure actions are actually getting processed
|
||||
sandbox.stub(Router, "addImpression");
|
||||
sandbox.spy(Router, "onMessage");
|
||||
const target = {};
|
||||
const action = {type: "IMPRESSION"};
|
||||
describe("#onMessage: TRIGGER", () => {
|
||||
it("should pass the trigger to ASRouterTargeting on TRIGGER message", async () => {
|
||||
sandbox.stub(Router, "_findMessage").resolves();
|
||||
const msg = fakeAsyncMessage({type: "TRIGGER", data: {trigger: {id: "firstRun"}}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
Router.dispatch(action, target);
|
||||
assert.calledOnce(Router._findMessage);
|
||||
assert.deepEqual(Router._findMessage.firstCall.args[1], {id: "firstRun"});
|
||||
});
|
||||
it("consider the trigger when picking a message", async () => {
|
||||
const messages = [
|
||||
{id: "foo1", template: "simple_template", bundled: 1, trigger: {id: "foo"}, content: {title: "Foo1", body: "Foo123-1"}},
|
||||
];
|
||||
|
||||
assert.calledWith(Router.onMessage, {data: action, target});
|
||||
assert.calledOnce(Router.addImpression);
|
||||
const {data} = fakeAsyncMessage({type: "TRIGGER", data: {trigger: {id: "foo"}}});
|
||||
const message = await Router._findMessage(messages, data.data.trigger);
|
||||
assert.equal(message, messages[0]);
|
||||
});
|
||||
it("should pick a message with the right targeting and trigger", async () => {
|
||||
let messages = [
|
||||
{id: "foo1", template: "simple_template", bundled: 2, trigger: {id: "foo"}, content: {title: "Foo1", body: "Foo123-1"}},
|
||||
{id: "foo2", template: "simple_template", bundled: 2, trigger: {id: "bar"}, content: {title: "Foo2", body: "Foo123-2"}},
|
||||
{id: "foo3", template: "simple_template", bundled: 2, trigger: {id: "foo"}, content: {title: "Foo3", body: "Foo123-3"}},
|
||||
];
|
||||
sandbox.stub(Router, "_findProvider").returns(null);
|
||||
await Router.setState({messages});
|
||||
const {target} = fakeAsyncMessage({type: "TRIGGER", data: {trigger: {id: "foo"}}});
|
||||
let {bundle} = await Router._getBundledMessages(messages[0], target, {id: "foo"});
|
||||
assert.equal(bundle.length, 2);
|
||||
// it should have picked foo1 and foo3 only
|
||||
assert.isTrue(bundle.every(elem => elem.id === "foo1" || elem.id === "foo3"));
|
||||
});
|
||||
it("should have previousSessionEnd in the message context", () => {
|
||||
assert.propertyVal(Router._getMessagesContext(), "previousSessionEnd", 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: DOORHANGER_TELEMETRY", () => {
|
||||
it("should dispatch an AS_ROUTER_TELEMETRY_USER_EVENT on DOORHANGER_TELEMETRY message", async () => {
|
||||
const msg = fakeAsyncMessage({type: "DOORHANGER_TELEMETRY", data: {message_id: "foo"}});
|
||||
dispatchStub.reset();
|
||||
describe("#onMessage: OVERRIDE_MESSAGE", () => {
|
||||
it("should broadcast a SET_MESSAGE message to all clients with a particular id", async () => {
|
||||
const [testMessage] = Router.state.messages;
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage.id}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "SET_MESSAGE", data: testMessage});
|
||||
});
|
||||
|
||||
assert.calledOnce(dispatchStub);
|
||||
const [action] = dispatchStub.firstCall.args;
|
||||
assert.equal(action.type, "AS_ROUTER_TELEMETRY_USER_EVENT");
|
||||
assert.equal(action.data.message_id, "foo");
|
||||
it("should call CFRPageActions.forceRecommendation if the template is cfr_action and force is true", async () => {
|
||||
sandbox.stub(CFRPageActions, "forceRecommendation");
|
||||
const testMessage = {id: "foo", template: "cfr_doorhanger"};
|
||||
await Router.setState({messages: [testMessage]});
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: testMessage.id}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.notCalled(msg.target.sendAsyncMessage);
|
||||
assert.calledOnce(CFRPageActions.forceRecommendation);
|
||||
});
|
||||
|
||||
it("should call CFRPageActions.addRecommendation if the template is cfr_action and force is false", async () => {
|
||||
sandbox.stub(CFRPageActions, "addRecommendation");
|
||||
const testMessage = {id: "foo", template: "cfr_doorhanger"};
|
||||
await Router.setState({messages: [testMessage]});
|
||||
await Router._sendMessageToTarget(testMessage, {}, {}, false);
|
||||
|
||||
assert.calledOnce(CFRPageActions.addRecommendation);
|
||||
});
|
||||
|
||||
it("should broadcast CLEAR_ALL if provided id did not resolve to a message", async () => {
|
||||
const msg = fakeAsyncMessage({type: "OVERRIDE_MESSAGE", data: {id: -1}});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(msg.target.sendAsyncMessage, PARENT_TO_CHILD_MESSAGE_NAME, {type: "CLEAR_ALL"});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: EXPIRE_QUERY_CACHE", () => {
|
||||
it("should clear all QueryCache getters", async () => {
|
||||
const msg = fakeAsyncMessage({type: "EXPIRE_QUERY_CACHE"});
|
||||
sandbox.stub(QueryCache, "expireAll");
|
||||
describe("#onMessage: Onboarding actions", () => {
|
||||
it("should call OpenBrowserWindow with a private window on OPEN_PRIVATE_BROWSER_WINDOW", async () => {
|
||||
let [testMessage] = Router.state.messages;
|
||||
const msg = fakeExecuteUserAction({type: "OPEN_PRIVATE_BROWSER_WINDOW", data: testMessage});
|
||||
await Router.onMessage(msg);
|
||||
|
||||
await Router.onMessage(msg);
|
||||
assert.calledWith(msg.target.browser.ownerGlobal.OpenBrowserWindow, {private: true});
|
||||
});
|
||||
it("should call openLinkIn with the correct params on OPEN_URL", async () => {
|
||||
let [testMessage] = Router.state.messages;
|
||||
testMessage.button_action = {type: "OPEN_URL", data: {args: "some/url.com"}};
|
||||
const msg = fakeExecuteUserAction(testMessage.button_action);
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(QueryCache.expireAll);
|
||||
assert.calledOnce(msg.target.browser.ownerGlobal.openLinkIn);
|
||||
assert.calledWith(msg.target.browser.ownerGlobal.openLinkIn,
|
||||
"some/url.com", "tabshifted", {"private": false, "triggeringPrincipal": undefined});
|
||||
});
|
||||
it("should call openLinkIn with the correct params on OPEN_ABOUT_PAGE", async () => {
|
||||
let [testMessage] = Router.state.messages;
|
||||
testMessage.button_action = {type: "OPEN_ABOUT_PAGE", data: {args: "something"}};
|
||||
const msg = fakeExecuteUserAction(testMessage.button_action);
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(msg.target.browser.ownerGlobal.openTrustedLinkIn);
|
||||
assert.calledWith(msg.target.browser.ownerGlobal.openTrustedLinkIn, "about:something", "tab");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: SHOW_FIREFOX_ACCOUNTS", () => {
|
||||
let globals;
|
||||
beforeEach(() => {
|
||||
globals = new GlobalOverrider();
|
||||
globals.set("FxAccounts", {config: {promiseSignUpURI: sandbox.stub().resolves("some/url")}});
|
||||
});
|
||||
it("should call openLinkIn with the correct params on OPEN_URL", async () => {
|
||||
let [testMessage] = Router.state.messages;
|
||||
testMessage.button_action = {type: "SHOW_FIREFOX_ACCOUNTS"};
|
||||
const msg = fakeExecuteUserAction(testMessage.button_action);
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(msg.target.browser.ownerGlobal.openLinkIn);
|
||||
assert.calledWith(msg.target.browser.ownerGlobal.openLinkIn,
|
||||
"some/url", "tabshifted", {"private": false, "triggeringPrincipal": undefined});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: INSTALL_ADDON_FROM_URL", () => {
|
||||
it("should call installAddonFromURL with correct arguments", async () => {
|
||||
sandbox.stub(MessageLoaderUtils, "installAddonFromURL").resolves(null);
|
||||
const msg = fakeExecuteUserAction({type: "INSTALL_ADDON_FROM_URL", data: {url: "foo.com"}});
|
||||
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(MessageLoaderUtils.installAddonFromURL);
|
||||
assert.calledWithExactly(MessageLoaderUtils.installAddonFromURL, msg.target.browser, "foo.com");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#dispatch(action, target)", () => {
|
||||
it("should an action and target to onMessage", async () => {
|
||||
// use the IMPRESSION action to make sure actions are actually getting processed
|
||||
sandbox.stub(Router, "addImpression");
|
||||
sandbox.spy(Router, "onMessage");
|
||||
const target = {};
|
||||
const action = {type: "IMPRESSION"};
|
||||
|
||||
Router.dispatch(action, target);
|
||||
|
||||
assert.calledWith(Router.onMessage, {data: action, target});
|
||||
assert.calledOnce(Router.addImpression);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: DOORHANGER_TELEMETRY", () => {
|
||||
it("should dispatch an AS_ROUTER_TELEMETRY_USER_EVENT on DOORHANGER_TELEMETRY message", async () => {
|
||||
const msg = fakeAsyncMessage({type: "DOORHANGER_TELEMETRY", data: {message_id: "foo"}});
|
||||
dispatchStub.reset();
|
||||
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(dispatchStub);
|
||||
const [action] = dispatchStub.firstCall.args;
|
||||
assert.equal(action.type, "AS_ROUTER_TELEMETRY_USER_EVENT");
|
||||
assert.equal(action.data.message_id, "foo");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: EXPIRE_QUERY_CACHE", () => {
|
||||
it("should clear all QueryCache getters", async () => {
|
||||
const msg = fakeAsyncMessage({type: "EXPIRE_QUERY_CACHE"});
|
||||
sandbox.stub(QueryCache, "expireAll");
|
||||
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(QueryCache.expireAll);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: ENABLE_PROVIDER", () => {
|
||||
it("should enable the provider via ASRouterPreferences", async () => {
|
||||
const msg = fakeAsyncMessage({type: "ENABLE_PROVIDER", data: "foo"});
|
||||
sandbox.stub(ASRouterPreferences, "enableOrDisableProvider");
|
||||
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(ASRouterPreferences.enableOrDisableProvider, "foo", true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: DISABLE_PROVIDER", () => {
|
||||
it("should disable the provider via ASRouterPreferences", async () => {
|
||||
const msg = fakeAsyncMessage({type: "DISABLE_PROVIDER", data: "foo"});
|
||||
sandbox.stub(ASRouterPreferences, "enableOrDisableProvider");
|
||||
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledWith(ASRouterPreferences.enableOrDisableProvider, "foo", false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#onMessage: RESET_PROVIDER_PREF", () => {
|
||||
it("should reset provider pref via ASRouterPreferences", async () => {
|
||||
const msg = fakeAsyncMessage({type: "RESET_PROVIDER_PREF", data: "foo"});
|
||||
sandbox.stub(ASRouterPreferences, "resetProviderPref");
|
||||
|
||||
await Router.onMessage(msg);
|
||||
|
||||
assert.calledOnce(ASRouterPreferences.resetProviderPref);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -9,8 +9,9 @@ const SNIPPETS_USER_PREF = "browser.newtabpage.activity-stream.feeds.snippets";
|
|||
* 1. asrouter.messageProvider
|
||||
* 2. asrouter.devtoolsEnabled
|
||||
* 3. browser.newtabpage.activity-stream.feeds.snippets (user preference - snippets)
|
||||
* 4. browser.newtabpage.activity-stream.asrouter.userprefs.cfr (user preference - cfr)
|
||||
*/
|
||||
const NUMBER_OF_PREFS_TO_OBSERVE = 3;
|
||||
const NUMBER_OF_PREFS_TO_OBSERVE = 4;
|
||||
|
||||
describe("ASRouterPreferences", () => {
|
||||
let ASRouterPreferences;
|
||||
|
@ -163,6 +164,45 @@ describe("ASRouterPreferences", () => {
|
|||
assert.isTrue(ASRouterPreferences.getUserPreference("snippets"));
|
||||
});
|
||||
});
|
||||
describe("#enableOrDisableProvider", () => {
|
||||
it("should enable an existing provider if second param is true", () => {
|
||||
const setStub = sandbox.stub(global.Services.prefs, "setStringPref");
|
||||
stringPrefStub.withArgs(PROVIDER_PREF).returns(JSON.stringify([{id: "foo", enabled: false}, {id: "bar", enabled: false}]));
|
||||
assert.isFalse(ASRouterPreferences.providers[0].enabled);
|
||||
|
||||
ASRouterPreferences.enableOrDisableProvider("foo", true);
|
||||
|
||||
assert.calledWith(setStub, PROVIDER_PREF, JSON.stringify([{id: "foo", enabled: true}, {id: "bar", enabled: false}]));
|
||||
});
|
||||
it("should disable an existing provider if second param is false", () => {
|
||||
const setStub = sandbox.stub(global.Services.prefs, "setStringPref");
|
||||
stringPrefStub.withArgs(PROVIDER_PREF).returns(JSON.stringify([{id: "foo", enabled: true}, {id: "bar", enabled: true}]));
|
||||
assert.isTrue(ASRouterPreferences.providers[0].enabled);
|
||||
|
||||
ASRouterPreferences.enableOrDisableProvider("foo", false);
|
||||
|
||||
assert.calledWith(setStub, PROVIDER_PREF, JSON.stringify([{id: "foo", enabled: false}, {id: "bar", enabled: true}]));
|
||||
});
|
||||
it("should not throw if the id does not exist", () => {
|
||||
assert.doesNotThrow(() => {
|
||||
ASRouterPreferences.enableOrDisableProvider("does_not_exist", true);
|
||||
});
|
||||
});
|
||||
it("should not throw if pref is not parseable", () => {
|
||||
stringPrefStub.withArgs(PROVIDER_PREF).returns("not valid");
|
||||
assert.doesNotThrow(() => {
|
||||
ASRouterPreferences.enableOrDisableProvider("foo", true);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("#resetProviderPref", () => {
|
||||
it("should reset the pref and user prefs", () => {
|
||||
const resetStub = sandbox.stub(global.Services.prefs, "clearUserPref");
|
||||
ASRouterPreferences.resetProviderPref();
|
||||
assert.calledWith(resetStub, PROVIDER_PREF);
|
||||
assert.calledWith(resetStub, SNIPPETS_USER_PREF);
|
||||
});
|
||||
});
|
||||
describe("observer, listeners", () => {
|
||||
it("should invalidate .providers when the pref is changed", () => {
|
||||
const testProviders = [{id: "newstuff"}];
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import {convertLinks} from "content-src/asrouter/components/RichText/RichText";
|
||||
import {convertLinks, RichText} from "content-src/asrouter/components/RichText/RichText";
|
||||
import {Localized} from "fluent-react";
|
||||
import {mount} from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
describe("convertLinks", () => {
|
||||
let sandbox;
|
||||
|
@ -40,4 +43,10 @@ describe("convertLinks", () => {
|
|||
assert.propertyVal(result.cta.props, "data-args", cta.args);
|
||||
assert.propertyVal(result.cta.props, "onClick", stub);
|
||||
});
|
||||
it("should allow for custom elements & styles", () => {
|
||||
const wrapper = mount(<RichText customElements={{em: <em style={{color: "#f05"}} />}} text="<em>foo</em>" localization_id="text" />);
|
||||
|
||||
const localized = wrapper.find(Localized);
|
||||
assert.propertyVal(localized.props().em.props.style, "color", "#f05");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import EOYSnippetSchema from "../../../content-src/asrouter/templates/EOYSnippet/EOYSnippet.schema.json";
|
||||
import SimpleSnippetSchema from "../../../content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.schema.json";
|
||||
import {SnippetsTestMessageProvider} from "../../../lib/SnippetsTestMessageProvider.jsm";
|
||||
import SubmitFormSnippetSchema from "../../../content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.schema.json";
|
||||
|
@ -7,6 +8,7 @@ const schemas = {
|
|||
"newsletter_snippet": SubmitFormSnippetSchema,
|
||||
"fxa_signup_snippet": SubmitFormSnippetSchema,
|
||||
"send_to_device_snippet": SubmitFormSnippetSchema,
|
||||
"eoy_snippet": EOYSnippetSchema,
|
||||
};
|
||||
|
||||
describe("SnippetsTestMessageProvider", () => {
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import EOYSnippetSchema from "content-src/asrouter/templates/EOYSnippet/EOYSnippet.schema.json";
|
||||
import {expectedValues} from "./snippets-fx57";
|
||||
import SimpleSnippetSchema from "content-src/asrouter/templates/SimpleSnippet/SimpleSnippet.schema.json";
|
||||
import SubmitFormSchema from "content-src/asrouter/templates/SubmitFormSnippet/SubmitFormSnippet.schema.json";
|
||||
|
||||
export const SnippetsSchemas = {
|
||||
eoy_snippet: EOYSnippetSchema,
|
||||
simple_snippet: SimpleSnippetSchema,
|
||||
newsletter_snippet: SubmitFormSchema,
|
||||
fxa_signup_snippet: SubmitFormSchema,
|
||||
send_to_device_snippet: SubmitFormSchema,
|
||||
};
|
||||
|
||||
describe("Firefox 57 compatibility test", () => {
|
||||
Object.keys(expectedValues).forEach(template => {
|
||||
describe(template, () => {
|
||||
const schema = SnippetsSchemas[template];
|
||||
it(`should have a schema for ${template}`, () => {
|
||||
assert.ok(schema);
|
||||
});
|
||||
it(`should validate with the schema for ${template}`, () => {
|
||||
assert.jsonSchema(expectedValues[template], schema);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* IMPORTANT NOTE!!!
|
||||
*
|
||||
* Please DO NOT introduce breaking changes file without contacting snippets endpoint engineers
|
||||
* and changing the schema version to reflect a breaking change.
|
||||
*
|
||||
*/
|
||||
|
||||
const DATA_URI_IMAGE = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
||||
|
||||
export const expectedValues = {
|
||||
|
||||
// Simple Snippet (https://github.com/mozmeao/snippets/blob/master/activity-stream/simple-snippet.html)
|
||||
simple_snippet: {
|
||||
icon: DATA_URI_IMAGE,
|
||||
button_label: "Click me",
|
||||
button_url: "https://mozilla.org",
|
||||
button_background_color: "#FF0000",
|
||||
button_color: "#FFFFFF",
|
||||
text: "Hello world",
|
||||
title: "Hi!",
|
||||
title_icon: DATA_URI_IMAGE,
|
||||
tall: true,
|
||||
},
|
||||
|
||||
// FXA Snippet (https://github.com/mozmeao/snippets/blob/master/activity-stream/fxa.html)
|
||||
fxa_signup_snippet: {
|
||||
scene1_icon: DATA_URI_IMAGE,
|
||||
scene1_button_label: "Click me",
|
||||
scene1_button_background_color: "#FF0000",
|
||||
scene1_button_color: "#FFFFFF",
|
||||
scene1_text: "Hello <em>world</em>",
|
||||
scene1_title: "Hi!",
|
||||
scene1_title_icon: DATA_URI_IMAGE,
|
||||
|
||||
scene2_text: "Second scene",
|
||||
scene2_title: "Second scene title",
|
||||
scene2_email_placeholder_text: "Email here",
|
||||
scene2_button_label: "Sign Me Up",
|
||||
scene2_dismiss_button_text: "Dismiss",
|
||||
|
||||
utm_campaign: "snippets123",
|
||||
utm_term: "123term",
|
||||
},
|
||||
|
||||
// Send To Device Snippet (https://github.com/mozmeao/snippets/blob/master/activity-stream/send-to-device.html)
|
||||
send_to_device_snippet: {
|
||||
include_sms: true,
|
||||
locale: "de",
|
||||
country: "DE",
|
||||
message_id_sms: "foo",
|
||||
message_id_email: "foo",
|
||||
scene1_button_background_color: "#FF0000",
|
||||
scene1_button_color: "#FFFFFF",
|
||||
scene1_button_label: "Click me",
|
||||
scene1_icon: DATA_URI_IMAGE,
|
||||
scene1_text: "Hello world",
|
||||
scene1_title: "Hi!",
|
||||
scene1_title_icon: DATA_URI_IMAGE,
|
||||
|
||||
scene2_button_label: "Sign Me Up",
|
||||
scene2_disclaimer_html: "Hello <em>world</em>",
|
||||
scene2_dismiss_button_text: "Dismiss",
|
||||
scene2_icon: DATA_URI_IMAGE,
|
||||
scene2_input_placeholder: "Email here",
|
||||
|
||||
scene2_text: "Second scene",
|
||||
scene2_title: "Second scene title",
|
||||
|
||||
error_text: "error",
|
||||
success_text: "all good",
|
||||
success_title: "Ok!",
|
||||
},
|
||||
|
||||
// Newsletter Snippet (https://github.com/mozmeao/snippets/blob/master/activity-stream/newsletter-subscribe.html)
|
||||
newsletter_snippet: {
|
||||
scene1_icon: DATA_URI_IMAGE,
|
||||
scene1_button_label: "Click me",
|
||||
scene1_button_background_color: "#FF0000",
|
||||
scene1_button_color: "#FFFFFF",
|
||||
scene1_text: "Hello world",
|
||||
scene1_title: "Hi!",
|
||||
scene1_title_icon: DATA_URI_IMAGE,
|
||||
|
||||
scene2_text: "Second scene",
|
||||
scene2_title: "Second scene title",
|
||||
scene2_newsletter: "foo",
|
||||
scene2_email_placeholder_text: "Email here",
|
||||
scene2_button_label: "Sign Me Up",
|
||||
scene2_privacy_html: "Hello <em>world</em>",
|
||||
scene2_dismiss_button_text: "Dismiss",
|
||||
|
||||
locale: "de",
|
||||
|
||||
error_text: "error",
|
||||
success_text: "all good",
|
||||
},
|
||||
|
||||
// EOY Snippet (https://github.com/mozmeao/snippets/blob/master/activity-stream/mofo-eoy-2017.html)
|
||||
eoy_snippet: {
|
||||
block_button_text: "Block",
|
||||
|
||||
donation_form_url: "https://donate.mozilla.org/",
|
||||
text: "Big corporations want to restrict how we access the web. Fake news is making it harder for us to find the truth. Online bullies are silencing inspired voices. The not-for-profit Mozilla Foundation fights for a healthy internet with programs like our Tech Policy Fellowships and Internet Health Report; will you donate today?",
|
||||
icon: DATA_URI_IMAGE,
|
||||
button_label: "Donate",
|
||||
monthly_checkbox_label_text: "Make my donation monthly",
|
||||
button_background_color: "#0060DF",
|
||||
button_color: "#FFFFFF",
|
||||
background_color: "#FFFFFF",
|
||||
text_color: "#000000",
|
||||
highlight_color: "#FFE900",
|
||||
|
||||
locale: "en-US",
|
||||
currency_code: "usd",
|
||||
|
||||
donation_amount_first: 50,
|
||||
donation_amount_second: 25,
|
||||
donation_amount_third: 10,
|
||||
donation_amount_fourth: 3,
|
||||
selected_button: "donation_amount_second",
|
||||
|
||||
test: "bold",
|
||||
},
|
||||
};
|
|
@ -0,0 +1,117 @@
|
|||
import {EOYSnippet} from "content-src/asrouter/templates/EOYSnippet/EOYSnippet";
|
||||
import {GlobalOverrider} from "test/unit/utils";
|
||||
import {mount} from "enzyme";
|
||||
import React from "react";
|
||||
import schema from "content-src/asrouter/templates/EOYSnippet/EOYSnippet.schema.json";
|
||||
|
||||
const DEFAULT_CONTENT = {
|
||||
text: "foo",
|
||||
donation_amount_first: 50,
|
||||
donation_amount_second: 25,
|
||||
donation_amount_third: 10,
|
||||
donation_amount_fourth: 5,
|
||||
donation_form_url: "https://submit.form",
|
||||
button_label: "Donate",
|
||||
currency_code: "usd",
|
||||
};
|
||||
|
||||
describe("EOYSnippet", () => {
|
||||
let sandbox;
|
||||
let wrapper;
|
||||
|
||||
/**
|
||||
* mountAndCheckProps - Mounts a EOYSnippet with DEFAULT_CONTENT extended with any props
|
||||
* passed in the content param and validates props against the schema.
|
||||
* @param {obj} content Object containing custom message content (e.g. {text, icon, title})
|
||||
* @returns enzyme wrapper for EOYSnippet
|
||||
*/
|
||||
function mountAndCheckProps(content = {}, provider = "test-provider") {
|
||||
const props = {
|
||||
content: Object.assign({}, DEFAULT_CONTENT, content),
|
||||
provider,
|
||||
onAction: sandbox.stub(),
|
||||
onBlock: sandbox.stub(),
|
||||
};
|
||||
assert.jsonSchema(props.content, schema);
|
||||
return mount(<EOYSnippet {...props} />);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
wrapper = mountAndCheckProps();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it("should render 4 donation options", () => {
|
||||
assert.lengthOf(wrapper.find("input[type='radio']"), 4);
|
||||
});
|
||||
|
||||
it("should select the second donation option", () => {
|
||||
wrapper = mountAndCheckProps({selected_button: "donation_amount_second"});
|
||||
|
||||
assert.propertyVal(wrapper.find("input[type='radio']").get(1).props, "defaultChecked", true);
|
||||
});
|
||||
|
||||
it("should set frequency value to monthly", () => {
|
||||
const form = wrapper.find("form").instance();
|
||||
assert.equal(form.querySelector("[name='frequency']").value, "single");
|
||||
|
||||
form.querySelector("#monthly-checkbox").checked = true;
|
||||
wrapper.find("form").simulate("submit");
|
||||
|
||||
assert.equal(form.querySelector("[name='frequency']").value, "monthly");
|
||||
});
|
||||
|
||||
it("should block after submitting the form", () => {
|
||||
const onBlockStub = sandbox.stub();
|
||||
wrapper.setProps({onBlock: onBlockStub});
|
||||
|
||||
wrapper.find("form").simulate("submit");
|
||||
|
||||
assert.calledOnce(onBlockStub);
|
||||
});
|
||||
|
||||
it("should not block if do_not_autoblock is true", () => {
|
||||
const onBlockStub = sandbox.stub();
|
||||
wrapper = mountAndCheckProps({do_not_autoblock: true});
|
||||
wrapper.setProps({onBlock: onBlockStub});
|
||||
|
||||
wrapper.find("form").simulate("submit");
|
||||
|
||||
assert.notCalled(onBlockStub);
|
||||
});
|
||||
|
||||
describe("locale", () => {
|
||||
let stub;
|
||||
let globals;
|
||||
beforeEach(() => {
|
||||
globals = new GlobalOverrider();
|
||||
stub = sandbox.stub().returns({format: () => {}});
|
||||
|
||||
globals = new GlobalOverrider();
|
||||
globals.set({"Intl": {NumberFormat: stub}});
|
||||
});
|
||||
afterEach(() => {
|
||||
globals.restore();
|
||||
});
|
||||
|
||||
it("should use content.locale for Intl", () => {
|
||||
// triggers component rendering and calls the function we're testing
|
||||
wrapper.setProps({content: {locale: "locale-foo"}});
|
||||
|
||||
assert.calledOnce(stub);
|
||||
assert.calledWithExactly(stub, "locale-foo", sinon.match.object);
|
||||
});
|
||||
|
||||
it("should use navigator.language as locale fallback", () => {
|
||||
// triggers component rendering and calls the function we're testing
|
||||
wrapper.setProps({content: {locale: null}});
|
||||
|
||||
assert.calledOnce(stub);
|
||||
assert.calledWithExactly(stub, navigator.language, sinon.match.object);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -173,6 +173,21 @@ describe("SubmitFormSnippet", () => {
|
|||
assert.equal(wrapper.state().signupSubmitted, true);
|
||||
assert.notCalled(onBlockStub);
|
||||
});
|
||||
it("should not block if do_not_autoblock is true", async () => {
|
||||
sandbox.stub(window, "fetch").resolves(fetchOk);
|
||||
wrapper = mountAndCheckProps({
|
||||
scene1_text: "bar",
|
||||
scene2_email_placeholder_text: "Email",
|
||||
scene2_text: "signup",
|
||||
do_not_autoblock: true,
|
||||
});
|
||||
wrapper.setState({expanded: true});
|
||||
await wrapper.instance().handleSubmit({preventDefault: sandbox.stub()});
|
||||
|
||||
assert.equal(wrapper.state().signupSuccess, true);
|
||||
assert.equal(wrapper.state().signupSubmitted, true);
|
||||
assert.notCalled(onBlockStub);
|
||||
});
|
||||
it("should send user telemetry if submission failed", async () => {
|
||||
sandbox.stub(window, "fetch").resolves(fetchFail);
|
||||
wrapper.setState({expanded: true});
|
||||
|
|
|
@ -992,6 +992,7 @@ describe("TelemetryFeed", () => {
|
|||
home_url_category: "other",
|
||||
newtab_url_category: "other",
|
||||
});
|
||||
assert.validate(sendEvent.firstCall.args[0], UserEventPing);
|
||||
});
|
||||
it("should not send an event if neither about:{home,newtab} are set to custom URL", async () => {
|
||||
instance._prefs.set(TELEMETRY_PREF, true);
|
||||
|
|
|
@ -140,6 +140,7 @@ const TEST_GLOBAL = {
|
|||
getPrefType() {},
|
||||
clearUserPref() {},
|
||||
getStringPref() {},
|
||||
setStringPref() {},
|
||||
getIntPref() {},
|
||||
getBoolPref() {},
|
||||
setBoolPref() {},
|
||||
|
|
Загрузка…
Ссылка в новой задаче