Fix Bug 1508388 - Search hand-off (#4599)
This commit is contained in:
Родитель
a45a540a14
Коммит
4a5d8c8475
|
@ -44,6 +44,9 @@ for (const type of [
|
|||
"DISCOVERY_STREAM_LAYOUT_UPDATE",
|
||||
"DOWNLOAD_CHANGED",
|
||||
"FILL_SEARCH_TERM",
|
||||
"FOCUS_SEARCH",
|
||||
"HANDOFF_SEARCH_TO_AWESOMEBAR",
|
||||
"HIDE_SEARCH",
|
||||
"INIT",
|
||||
"MIGRATION_CANCEL",
|
||||
"MIGRATION_COMPLETED",
|
||||
|
@ -93,6 +96,7 @@ for (const type of [
|
|||
"SET_PREF",
|
||||
"SHOW_DOWNLOAD_FILE",
|
||||
"SHOW_FIREFOX_ACCOUNTS",
|
||||
"SHOW_SEARCH",
|
||||
"SKIPPED_SIGNIN",
|
||||
"SNIPPETS_BLOCKLIST_CLEARED",
|
||||
"SNIPPETS_BLOCKLIST_UPDATED",
|
||||
|
|
|
@ -53,6 +53,12 @@ const INITIAL_STATE = {
|
|||
config: {enabled: false, layout_endpoint: ""},
|
||||
layout: [],
|
||||
},
|
||||
Search: {
|
||||
// Pretend the search box is focused after handing off to AwesomeBar.
|
||||
focus: false,
|
||||
// Hide the search box after handing off to AwesomeBar and user starts typing.
|
||||
hide: false,
|
||||
},
|
||||
};
|
||||
|
||||
function App(prevState = INITIAL_STATE.App, action) {
|
||||
|
@ -448,10 +454,40 @@ function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
|
|||
}
|
||||
}
|
||||
|
||||
function Search(prevState = INITIAL_STATE.Search, action) {
|
||||
switch (action.type) {
|
||||
case at.HIDE_SEARCH:
|
||||
return Object.assign({...prevState, hide: true});
|
||||
case at.FOCUS_SEARCH:
|
||||
return Object.assign({...prevState, focus: true});
|
||||
case at.SHOW_SEARCH:
|
||||
return Object.assign({...prevState, hide: false, focus: false});
|
||||
default:
|
||||
return prevState;
|
||||
}
|
||||
}
|
||||
|
||||
this.INITIAL_STATE = INITIAL_STATE;
|
||||
this.TOP_SITES_DEFAULT_ROWS = TOP_SITES_DEFAULT_ROWS;
|
||||
this.TOP_SITES_MAX_SITES_PER_ROW = TOP_SITES_MAX_SITES_PER_ROW;
|
||||
|
||||
this.reducers = {TopSites, App, ASRouter, Snippets, Prefs, Dialog, Sections, Pocket, DiscoveryStream};
|
||||
this.reducers = {
|
||||
TopSites,
|
||||
App,
|
||||
ASRouter,
|
||||
Snippets,
|
||||
Prefs,
|
||||
Dialog,
|
||||
Sections,
|
||||
Pocket,
|
||||
DiscoveryStream,
|
||||
Search,
|
||||
};
|
||||
|
||||
const EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE", "insertPinned", "TOP_SITES_DEFAULT_ROWS", "TOP_SITES_MAX_SITES_PER_ROW"];
|
||||
const EXPORTED_SYMBOLS = [
|
||||
"reducers",
|
||||
"INITIAL_STATE",
|
||||
"insertPinned",
|
||||
"TOP_SITES_DEFAULT_ROWS",
|
||||
"TOP_SITES_MAX_SITES_PER_ROW",
|
||||
];
|
||||
|
|
|
@ -141,6 +141,7 @@ export class BaseContent extends React.PureComponent {
|
|||
const shouldBeFixedToTop = PrerenderData.arePrefsValid(name => prefs[name]);
|
||||
const noSectionsEnabled = !prefs["feeds.topsites"] && props.Sections.filter(section => section.enabled).length === 0;
|
||||
const isDiscoveryStream = props.DiscoveryStream.config && props.DiscoveryStream.config.enabled;
|
||||
const searchHandoffEnabled = prefs["improvesearch.handoffToAwesomebar"];
|
||||
|
||||
const outerClassName = [
|
||||
"outer-wrapper",
|
||||
|
@ -156,7 +157,7 @@ export class BaseContent extends React.PureComponent {
|
|||
{prefs.showSearch &&
|
||||
<div className="non-collapsible-section">
|
||||
<ErrorBoundary>
|
||||
<Search showLogo={noSectionsEnabled} />
|
||||
<Search showLogo={noSectionsEnabled} handoffEnabled={searchHandoffEnabled} {...props.Search} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
}
|
||||
|
@ -176,4 +177,10 @@ export class BaseContent extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
export const Base = connect(state => ({App: state.App, Prefs: state.Prefs, Sections: state.Sections, DiscoveryStream: state.DiscoveryStream}))(_Base);
|
||||
export const Base = connect(state => ({
|
||||
App: state.App,
|
||||
Prefs: state.Prefs,
|
||||
Sections: state.Sections,
|
||||
DiscoveryStream: state.DiscoveryStream,
|
||||
Search: state.Search,
|
||||
}))(_Base);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* globals ContentSearchUIController */
|
||||
"use strict";
|
||||
|
||||
import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
|
||||
import {FormattedMessage, injectIntl} from "react-intl";
|
||||
import {actionCreators as ac} from "common/Actions.jsm";
|
||||
import {connect} from "react-redux";
|
||||
import {IS_NEWTAB} from "content-src/lib/constants";
|
||||
import React from "react";
|
||||
|
@ -10,7 +10,8 @@ import React from "react";
|
|||
export class _Search extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onSearchClick = this.onSearchClick.bind(this);
|
||||
this.onSearchHandoffClick = this.onSearchHandoffClick.bind(this);
|
||||
this.onInputMount = this.onInputMount.bind(this);
|
||||
}
|
||||
|
||||
|
@ -21,10 +22,26 @@ export class _Search extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
onSearchClick(event) {
|
||||
window.gContentSearchController.search(event);
|
||||
}
|
||||
|
||||
onSearchHandoffClick(event) {
|
||||
// When search hand-off is enabled, we render a big button that is styled to
|
||||
// look like a search textbox. If the button is clicked with the mouse, we style
|
||||
// the button as if it was a focused search box and show a fake cursor but
|
||||
// really focus the awesomebar without the focus styles.
|
||||
// If the button is clicked from the keyboard, we focus the awesomebar normally.
|
||||
// This is to minimize confusion with users navigating with the keyboard and
|
||||
// users using assistive technologoy.
|
||||
const isKeyboardClick = event.clientX === 0 && event.clientY === 0;
|
||||
const hiddenFocus = !isKeyboardClick;
|
||||
this.props.dispatch(ac.OnlyToMain({type: at.HANDOFF_SEARCH_TO_AWESOMEBAR, data: {hiddenFocus}}));
|
||||
this.props.dispatch({type: at.FOCUS_SEARCH});
|
||||
|
||||
// TODO: Send a telemetry ping. BUG 1514732
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
delete window.gContentSearchController;
|
||||
}
|
||||
|
@ -63,13 +80,20 @@ export class _Search extends React.PureComponent {
|
|||
* in order to execute searches in various tests
|
||||
*/
|
||||
render() {
|
||||
return (<div className="search-wrapper">
|
||||
const wrapperClassName = [
|
||||
"search-wrapper",
|
||||
this.props.hide && "search-hidden",
|
||||
this.props.focus && "search-active",
|
||||
].filter(v => v).join(" ");
|
||||
|
||||
return (<div className={wrapperClassName}>
|
||||
{this.props.showLogo &&
|
||||
<div className="logo-and-wordmark">
|
||||
<div className="logo" />
|
||||
<div className="wordmark" />
|
||||
</div>
|
||||
}
|
||||
{!this.props.handoffEnabled &&
|
||||
<div className="search-inner-wrapper">
|
||||
<label htmlFor="newtab-search-text" className="search-label">
|
||||
<span className="sr-only"><FormattedMessage id="search_web_placeholder" /></span>
|
||||
|
@ -84,11 +108,32 @@ export class _Search extends React.PureComponent {
|
|||
<button
|
||||
id="searchSubmit"
|
||||
className="search-button"
|
||||
onClick={this.onClick}
|
||||
onClick={this.onSearchClick}
|
||||
title={this.props.intl.formatMessage({id: "search_button"})}>
|
||||
<span className="sr-only"><FormattedMessage id="search_button" /></span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
{this.props.handoffEnabled &&
|
||||
<div className="search-inner-wrapper">
|
||||
<button
|
||||
className="search-handoff-button"
|
||||
onClick={this.onSearchHandoffClick}
|
||||
title={this.props.intl.formatMessage({id: "search_web_placeholder"})}>
|
||||
<div className="fake-textbox">{this.props.intl.formatMessage({id: "search_web_placeholder"})}</div>
|
||||
<div className="fake-caret" />
|
||||
<div className="fake-button" />
|
||||
</button>
|
||||
{/*
|
||||
This dummy and hidden input below is so we can load ContentSearchUIController.
|
||||
Why? It sets --newtab-search-icon for us and it isn't trivial to port over.
|
||||
*/}
|
||||
<input
|
||||
type="search"
|
||||
style={{display: "none"}}
|
||||
ref={this.onInputMount} />
|
||||
</div>
|
||||
}
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.search-wrapper {
|
||||
$search-height: 48px;
|
||||
$search-icon-size: 24px;
|
||||
$search-icon-padding: 12px;
|
||||
$search-icon-width: 2 * $search-icon-padding + $search-icon-size -2;
|
||||
$search-button-width: 48px;
|
||||
$glyph-forward: url('chrome://browser/skin/forward.svg');
|
||||
$search-height: 48px;
|
||||
$search-icon-size: 24px;
|
||||
$search-icon-padding: 12px;
|
||||
$search-icon-width: 2 * $search-icon-padding + $search-icon-size -2;
|
||||
$search-button-width: 48px;
|
||||
$glyph-forward: url('chrome://browser/skin/forward.svg');
|
||||
|
||||
.search-wrapper {
|
||||
padding: 34px 0 64px;
|
||||
|
||||
@media (max-height: 700px) {
|
||||
|
@ -137,6 +137,85 @@
|
|||
}
|
||||
}
|
||||
|
||||
.search-handoff-button {
|
||||
background: var(--newtab-textbox-background-color) var(--newtab-search-icon) $search-icon-padding center no-repeat;
|
||||
background-size: $search-icon-size;
|
||||
border: solid 1px var(--newtab-search-border-color);
|
||||
border-radius: 3px;
|
||||
box-shadow: $shadow-secondary, 0 0 0 1px $black-15;
|
||||
cursor: text;
|
||||
font-size: 15px;
|
||||
padding: 0;
|
||||
padding-inline-end: 48px;
|
||||
padding-inline-start: 46px;
|
||||
opacity: 1;
|
||||
transition: opacity 500ms;
|
||||
width: 100%;
|
||||
|
||||
&:dir(rtl) {
|
||||
background-position-x: right $search-icon-padding;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-secondary, 0 0 0 1px $black-25;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
.search-active & {
|
||||
border: $input-border-active;
|
||||
box-shadow: var(--newtab-textbox-focus-boxshadow);
|
||||
}
|
||||
|
||||
.search-hidden & {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.fake-textbox {
|
||||
opacity: 0.54;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.fake-caret {
|
||||
animation: caret-animation 1.3s steps(5, start) infinite;
|
||||
background: var(--newtab-text-primary-color);
|
||||
display: none;
|
||||
inset-inline-start: 47px;
|
||||
height: 17px;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
width: 1px;
|
||||
|
||||
@keyframes caret-animation {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.search-active & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.fake-button {
|
||||
background: $glyph-forward no-repeat center center;
|
||||
background-size: 16px 16px;
|
||||
border: 0;
|
||||
border-radius: 0 $border-radius $border-radius 0;
|
||||
-moz-context-properties: fill;
|
||||
fill: var(--newtab-search-icon-color);
|
||||
height: 100%;
|
||||
inset-inline-end: 0;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
width: $search-button-width;
|
||||
|
||||
&:dir(rtl) {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-height: 701px) {
|
||||
.fixed-search {
|
||||
main {
|
||||
|
@ -172,6 +251,19 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-handoff-button {
|
||||
background-position-x: $search-icon-padding;
|
||||
background-size: $search-icon-size;
|
||||
|
||||
&:dir(rtl) {
|
||||
background-position-x: right $search-icon-padding;
|
||||
}
|
||||
|
||||
.fake-caret {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -193,6 +193,10 @@ const PREFS_CONFIG = new Map([
|
|||
title: "A comma-delimited list of search shortcuts that have previously been pinned",
|
||||
value: "",
|
||||
}],
|
||||
["improvesearch.handoffToAwesomebar", {
|
||||
title: "Should the search box handoff to the Awesomebar?",
|
||||
value: true,
|
||||
}],
|
||||
["asrouter.devtoolsEnabled", {
|
||||
title: "Are the asrouter devtools enabled?",
|
||||
value: false,
|
||||
|
|
|
@ -283,6 +283,38 @@ class PlacesFeed {
|
|||
_target.browser.ownerGlobal.gURLBar.search(`${data.label} `);
|
||||
}
|
||||
|
||||
handoffSearchToAwesomebar({_target, data, meta}) {
|
||||
const urlBar = _target.browser.ownerGlobal.gURLBar;
|
||||
|
||||
if (!data.hiddenFocus) {
|
||||
// Do a normal focus of awesomebar and reset the in content search (remove fake focus styles).
|
||||
urlBar.focus();
|
||||
this.store.dispatch(ac.OnlyToOneContent({type: at.SHOW_SEARCH}, meta.fromTarget));
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus the awesomebar without the style changes.
|
||||
urlBar.hiddenFocus();
|
||||
const onKeydown = () => {
|
||||
// Once the user starts typing, we want to hide the in content search box
|
||||
// and show the focus styles on the awesomebar.
|
||||
this.store.dispatch(ac.OnlyToOneContent({type: at.HIDE_SEARCH}, meta.fromTarget));
|
||||
urlBar.removeHiddenFocus();
|
||||
urlBar.removeEventListener("keydown", onKeydown);
|
||||
};
|
||||
const onDone = () => {
|
||||
// When done, let's cleanup everything.
|
||||
this.store.dispatch(ac.OnlyToOneContent({type: at.SHOW_SEARCH}, meta.fromTarget));
|
||||
urlBar.removeHiddenFocus();
|
||||
urlBar.removeEventListener("keydown", onKeydown);
|
||||
urlBar.removeEventListener("mousedown", onDone);
|
||||
urlBar.removeEventListener("blur", onDone);
|
||||
};
|
||||
urlBar.addEventListener("keydown", onKeydown);
|
||||
urlBar.addEventListener("mousedown", onDone);
|
||||
urlBar.addEventListener("blur", onDone);
|
||||
}
|
||||
|
||||
onAction(action) {
|
||||
switch (action.type) {
|
||||
case at.INIT:
|
||||
|
@ -323,6 +355,9 @@ class PlacesFeed {
|
|||
case at.FILL_SEARCH_TERM:
|
||||
this.fillSearchTopSiteTerm(action);
|
||||
break;
|
||||
case at.HANDOFF_SEARCH_TO_AWESOMEBAR:
|
||||
this.handoffSearchToAwesomebar(action);
|
||||
break;
|
||||
case at.OPEN_LINK: {
|
||||
this.openLink(action);
|
||||
break;
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
"use strict";
|
||||
|
||||
test_newtab(function test_render_search() {
|
||||
let search = content.document.getElementById("newtab-search-text");
|
||||
ok(search, "Got the search box");
|
||||
isnot(search.placeholder, "search_web_placeholder", "Search box is localized");
|
||||
test_newtab({
|
||||
async before({pushPrefs}) {
|
||||
await pushPrefs(["browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", false]);
|
||||
},
|
||||
test: function test_render_search() {
|
||||
let search = content.document.getElementById("newtab-search-text");
|
||||
ok(search, "Got the search box");
|
||||
isnot(search.placeholder, "search_web_placeholder", "Search box is localized");
|
||||
},
|
||||
});
|
||||
|
||||
test_newtab({
|
||||
async before({pushPrefs}) {
|
||||
await pushPrefs(["browser.newtabpage.activity-stream.improvesearch.handoffToAwesomebar", true]);
|
||||
},
|
||||
test: function test_render_search_handoff() {
|
||||
let search = content.document.querySelector(".search-handoff-button");
|
||||
ok(search, "Got the search handoff button");
|
||||
},
|
||||
});
|
||||
|
||||
test_newtab(function test_render_topsites() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {INITIAL_STATE, insertPinned, reducers} from "common/Reducers.jsm";
|
||||
const {TopSites, App, Snippets, Prefs, Dialog, Sections, Pocket, DiscoveryStream} = reducers;
|
||||
const {TopSites, App, Snippets, Prefs, Dialog, Sections, Pocket, DiscoveryStream, Search} = reducers;
|
||||
import {actionTypes as at} from "common/Actions.jsm";
|
||||
|
||||
describe("Reducers", () => {
|
||||
|
@ -668,4 +668,22 @@ describe("Reducers", () => {
|
|||
assert.deepEqual(state.config, {enabled: true});
|
||||
});
|
||||
});
|
||||
describe("Search", () => {
|
||||
it("should return INITIAL_STATE by default", () => {
|
||||
assert.equal(Search(undefined, {type: "some_action"}), INITIAL_STATE.Search);
|
||||
});
|
||||
it("should set hide to true on HIDE_SEARCH", () => {
|
||||
const nextState = Search(undefined, {type: "HIDE_SEARCH"});
|
||||
assert.propertyVal(nextState, "hide", true);
|
||||
});
|
||||
it("should set focus to true on FOCUS_SEARCH", () => {
|
||||
const nextState = Search(undefined, {type: "FOCUS_SEARCH"});
|
||||
assert.propertyVal(nextState, "focus", true);
|
||||
});
|
||||
it("should set focus and hide to false on SHOW_SEARCH", () => {
|
||||
const nextState = Search(undefined, {type: "SHOW_SEARCH"});
|
||||
assert.propertyVal(nextState, "focus", false);
|
||||
assert.propertyVal(nextState, "hide", false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -73,4 +73,34 @@ describe("<Search>", () => {
|
|||
assert.isUserEventAction(action);
|
||||
assert.propertyVal(action.data, "event", "SEARCH");
|
||||
});
|
||||
|
||||
describe("Search Hand-off", () => {
|
||||
it("should render a Search element when hand-off is enabled", () => {
|
||||
const wrapper = shallowWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} />);
|
||||
assert.ok(wrapper.exists());
|
||||
assert.equal(wrapper.find(".search-handoff-button").length, 1);
|
||||
});
|
||||
it("should hand-off search when button is clicked with mouse", () => {
|
||||
const dispatch = sinon.spy();
|
||||
const wrapper = shallowWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
|
||||
wrapper.instance().onSearchHandoffClick({clientX: 101, clientY: 102});
|
||||
assert.calledWith(dispatch, {
|
||||
data: {hiddenFocus: true},
|
||||
meta: {from: "ActivityStream:Content", skipLocal: true, to: "ActivityStream:Main"},
|
||||
type: "HANDOFF_SEARCH_TO_AWESOMEBAR",
|
||||
});
|
||||
assert.calledWith(dispatch, {type: "FOCUS_SEARCH"});
|
||||
});
|
||||
it("should hand-off search when button is clicked with keyboard", () => {
|
||||
const dispatch = sinon.spy();
|
||||
const wrapper = shallowWithIntl(<Search {...DEFAULT_PROPS} handoffEnabled={true} dispatch={dispatch} />);
|
||||
wrapper.instance().onSearchHandoffClick({clientX: 0, clientY: 0});
|
||||
assert.calledWith(dispatch, {
|
||||
data: {hiddenFocus: false},
|
||||
meta: {from: "ActivityStream:Content", skipLocal: true, to: "ActivityStream:Main"},
|
||||
type: "HANDOFF_SEARCH_TO_AWESOMEBAR",
|
||||
});
|
||||
assert.calledWith(dispatch, {type: "FOCUS_SEARCH"});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -306,6 +306,95 @@ describe("PlacesFeed", () => {
|
|||
await feed.saveToPocket(action.data.site, action._target.browser);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
});
|
||||
it("should call handoffSearchToAwesomebar on HANDOFF_SEARCH_TO_AWESOMEBAR", () => {
|
||||
const action = {
|
||||
type: at.HANDOFF_SEARCH_TO_AWESOMEBAR,
|
||||
data: {hiddenFocus: false},
|
||||
meta: {fromTarget: {}},
|
||||
_target: {browser: {ownerGlobal: {gURLBar: {focus: () => {}}}}},
|
||||
};
|
||||
sinon.stub(feed, "handoffSearchToAwesomebar");
|
||||
feed.onAction(action);
|
||||
assert.calledWith(feed.handoffSearchToAwesomebar, action);
|
||||
});
|
||||
});
|
||||
|
||||
describe("handoffSearchToAwesomebar", () => {
|
||||
let fakeUrlBar;
|
||||
let listeners;
|
||||
|
||||
beforeEach(() => {
|
||||
fakeUrlBar = {
|
||||
focus: sinon.spy(),
|
||||
hiddenFocus: sinon.spy(),
|
||||
removeHiddenFocus: sinon.spy(),
|
||||
addEventListener: (ev, cb) => {
|
||||
listeners[ev] = cb;
|
||||
},
|
||||
removeEventListener: sinon.spy(),
|
||||
};
|
||||
listeners = {};
|
||||
});
|
||||
it("should properly handle hiddenFocus=false", () => {
|
||||
feed.handoffSearchToAwesomebar({
|
||||
_target: {browser: {ownerGlobal: {gURLBar: fakeUrlBar}}},
|
||||
data: {hiddenFocus: false},
|
||||
meta: {fromTarget: {}},
|
||||
});
|
||||
assert.calledOnce(fakeUrlBar.focus);
|
||||
assert.notCalled(fakeUrlBar.hiddenFocus);
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
assert.calledWith(feed.store.dispatch, {
|
||||
meta: {
|
||||
from: "ActivityStream:Main",
|
||||
skipMain: true,
|
||||
to: "ActivityStream:Content",
|
||||
toTarget: {},
|
||||
},
|
||||
type: "SHOW_SEARCH",
|
||||
});
|
||||
});
|
||||
it("should properly handle hiddenFocus=true", () => {
|
||||
feed.handoffSearchToAwesomebar({
|
||||
_target: {browser: {ownerGlobal: {gURLBar: fakeUrlBar}}},
|
||||
data: {hiddenFocus: true},
|
||||
meta: {fromTarget: {}},
|
||||
});
|
||||
assert.calledOnce(fakeUrlBar.hiddenFocus);
|
||||
assert.notCalled(fakeUrlBar.focus);
|
||||
assert.notCalled(feed.store.dispatch);
|
||||
|
||||
// Now call keydown listener.
|
||||
feed.store.dispatch.resetHistory();
|
||||
listeners.keydown();
|
||||
assert.calledOnce(fakeUrlBar.removeHiddenFocus);
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
assert.calledWith(feed.store.dispatch, {
|
||||
meta: {
|
||||
from: "ActivityStream:Main",
|
||||
skipMain: true,
|
||||
to: "ActivityStream:Content",
|
||||
toTarget: {},
|
||||
},
|
||||
type: "HIDE_SEARCH",
|
||||
});
|
||||
|
||||
// And then call blur listener.
|
||||
fakeUrlBar.removeHiddenFocus.resetHistory();
|
||||
feed.store.dispatch.resetHistory();
|
||||
listeners.blur();
|
||||
assert.calledOnce(fakeUrlBar.removeHiddenFocus);
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
assert.calledWith(feed.store.dispatch, {
|
||||
meta: {
|
||||
from: "ActivityStream:Main",
|
||||
skipMain: true,
|
||||
to: "ActivityStream:Content",
|
||||
toTarget: {},
|
||||
},
|
||||
type: "SHOW_SEARCH",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#observe", () => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче