Bug 1462390 - Extract history from JSTerm component; r=nchevobbe

MozReview-Commit-ID: DTlW1h2ACoI

--HG--
extra : rebase_source : 90ecbeee7d198c779c8f19986722dcfd8970fad9
This commit is contained in:
Jan Odvarko 2018-05-31 12:41:29 +02:00
parent bce82ccf82
commit 0f7d120ed3
19 changed files with 452 additions and 130 deletions

View File

@ -45,6 +45,6 @@ add_task(async function() {
isnot(result.textContent.indexOf('<p id="console-var-multi">'), -1,
"variable temp1 references correct node");
jsterm.clearHistory();
hud.ui.consoleOutput.dispatchClearHistory();
}
});

View File

@ -3,6 +3,10 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const {
getHistoryEntries,
} = require("devtools/client/webconsole/selectors/history");
// Tests for menuitem functionality that doesn't fit into any specific category
const TEST_URL = URL_ROOT + "doc_inspector_menu.html";
add_task(async function() {
@ -30,7 +34,9 @@ add_task(async function() {
let messagesAdded = webconsoleUI.once("new-messages");
await messagesAdded;
info("Checking if 'inspect($0)' was evaluated");
ok(webconsoleUI.jsterm.history[0] === "inspect($0)");
let state = webconsoleUI.consoleOutput.getStore().getState();
ok(getHistoryEntries(state)[0] === "inspect($0)");
await toolbox.toggleSplitConsole();
}
async function testDuplicateNode() {

View File

@ -0,0 +1,65 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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/. */
"use strict";
const {
APPEND_TO_HISTORY,
CLEAR_HISTORY,
HISTORY_LOADED,
UPDATE_HISTORY_PLACEHOLDER,
} = require("devtools/client/webconsole/constants");
/**
* Append a new value in the history of executed expressions,
* or overwrite the most recent entry. The most recent entry may
* contain the last edited input value that was not evaluated yet.
*/
function appendToHistory(expression) {
return {
type: APPEND_TO_HISTORY,
expression: expression,
};
}
/**
* Clear the console history altogether. Note that this will not affect
* other consoles that are already opened (since they have their own copy),
* but it will reset the array for all newly-opened consoles.
*/
function clearHistory() {
return {
type: CLEAR_HISTORY,
};
}
/**
* Fired when the console history from previous Firefox sessions is loaded.
*/
function historyLoaded(entries) {
return {
type: HISTORY_LOADED,
entries,
};
}
/**
* Update place-holder position in the history list.
*/
function updatePlaceHolder(direction, expression) {
return {
type: UPDATE_HISTORY_PLACEHOLDER,
direction,
expression,
};
}
module.exports = {
appendToHistory,
clearHistory,
historyLoaded,
updatePlaceHolder,
};

View File

@ -11,6 +11,7 @@ const actionModules = [
require("./messages"),
require("./ui"),
require("./notifications"),
require("./history"),
];
const actions = Object.assign({}, ...actionModules);

View File

@ -5,6 +5,7 @@
DevToolsModules(
'filters.js',
'history.js',
'index.js',
'messages.js',
'notifications.js',

View File

@ -14,7 +14,6 @@ loader.lazyRequireGetter(this, "defer", "devtools/shared/defer");
loader.lazyRequireGetter(this, "Debugger", "Debugger");
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
@ -22,13 +21,7 @@ loader.lazyRequireGetter(this, "Editor", "devtools/client/sourceeditor/editor");
const l10n = require("devtools/client/webconsole/webconsole-l10n");
// Constants used for defining the direction of JSTerm input history navigation.
const HISTORY_BACK = -1;
const HISTORY_FORWARD = 1;
const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
const PREF_INPUT_HISTORY_COUNT = "devtools.webconsole.inputHistoryCount";
const PREF_AUTO_MULTILINE = "devtools.webconsole.autoMultiline";
function gSequenceId() {
@ -36,8 +29,23 @@ function gSequenceId() {
}
gSequenceId.n = 0;
// React & Redux
const { Component } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { connect } = require("devtools/client/shared/vendor/react-redux");
// History Modules
const {
getHistory,
getHistoryValue
} = require("devtools/client/webconsole/selectors/history");
const historyActions = require("devtools/client/webconsole/actions/history");
// Constants used for defining the direction of JSTerm input history navigation.
const {
HISTORY_BACK,
HISTORY_FORWARD
} = require("devtools/client/webconsole/constants");
/**
* Create a JSTerminal (a JavaScript command line). This is attached to an
@ -51,10 +59,22 @@ const dom = require("devtools/client/shared/vendor/react-dom-factories");
class JSTerm extends Component {
static get propTypes() {
return {
// Append new executed expression into history list (action).
appendToHistory: PropTypes.func.isRequired,
// Remove all entries from the history list (action).
clearHistory: PropTypes.func.isRequired,
// Returns previous or next value from the history
// (depending on direction argument).
getValueFromHistory: PropTypes.func.isRequired,
// History of executed expression (state).
history: PropTypes.object.isRequired,
// Console object.
hud: PropTypes.object.isRequired,
// Handler for clipboard 'paste' event (also used for 'drop' event).
// Handler for clipboard 'paste' event (also used for 'drop' event, callback).
onPaste: PropTypes.func,
codeMirrorEnabled: PropTypes.bool,
// Update position in the history after executing an expression (action).
updatePlaceHolder: PropTypes.func.isRequired,
};
}
@ -67,8 +87,6 @@ class JSTerm extends Component {
this.hud = hud;
this.hudId = this.hud.hudId;
this.inputHistoryCount = Services.prefs.getIntPref(PREF_INPUT_HISTORY_COUNT);
this._loadHistory();
/**
* Stores the data for the last completion.
@ -128,11 +146,6 @@ class JSTerm extends Component {
*/
this._autocompletePopupNavigated = false;
/**
* History of code that was executed.
* @type array
*/
this.history = [];
this.autocompletePopup = null;
this.inputNode = null;
this.completeNode = null;
@ -214,62 +227,13 @@ class JSTerm extends Component {
this.lastInputValue && this.setInputValue(this.lastInputValue);
}
shouldComponentUpdate() {
// XXX: For now, everything is handled in an imperative way and we only want React
// to do the initial rendering of the component.
shouldComponentUpdate(nextProps, nextState) {
// XXX: For now, everything is handled in an imperative way and we
// only want React to do the initial rendering of the component.
// This should be modified when the actual refactoring will take place.
return false;
}
/**
* Load the console history from previous sessions.
* @private
*/
_loadHistory() {
this.history = [];
this.historyIndex = this.historyPlaceHolder = 0;
this.historyLoaded = asyncStorage.getItem("webConsoleHistory")
.then(value => {
if (Array.isArray(value)) {
// Since it was gotten asynchronously, there could be items already in
// the history. It's not likely but stick them onto the end anyway.
this.history = value.concat(this.history);
// Holds the number of entries in history. This value is incremented
// in this.execute().
this.historyIndex = this.history.length;
// Holds the index of the history entry that the user is currently
// viewing. This is reset to this.history.length when this.execute()
// is invoked.
this.historyPlaceHolder = this.history.length;
}
}, console.error);
}
/**
* Clear the console history altogether. Note that this will not affect
* other consoles that are already opened (since they have their own copy),
* but it will reset the array for all newly-opened consoles.
* @returns Promise
* Resolves once the changes have been persisted.
*/
clearHistory() {
this.history = [];
this.historyIndex = this.historyPlaceHolder = 0;
return this.storeHistory();
}
/**
* Stores the console history for future console instances.
* @returns Promise
* Resolves once the changes have been persisted.
*/
storeHistory() {
return asyncStorage.setItem("webConsoleHistory", this.history);
}
/**
* Getter for the element that holds the messages we display.
* @type Element
@ -328,7 +292,7 @@ class JSTerm extends Component {
this.clearOutput();
break;
case "clearHistory":
this.clearHistory();
this.props.clearHistory();
break;
case "inspectObject":
this.inspectObjectActor(helperResult.object);
@ -394,17 +358,9 @@ class JSTerm extends Component {
return null;
}
// Append a new value in the history of executed code, or overwrite the most
// recent entry. The most recent entry may contain the last edited input
// value that was not evaluated yet.
this.history[this.historyIndex++] = executeString;
this.historyPlaceHolder = this.history.length;
// Append executed expression into the history list.
this.props.appendToHistory(executeString);
if (this.history.length > this.inputHistoryCount) {
this.history.splice(0, this.history.length - this.inputHistoryCount);
this.historyIndex = this.historyPlaceHolder = this.history.length;
}
this.storeHistory();
WebConsoleUtils.usageCount++;
this.setInputValue("");
this.clearCompletion();
@ -892,39 +848,26 @@ class JSTerm extends Component {
* True if the input value changed, false otherwise.
*/
historyPeruse(direction) {
if (!this.history.length) {
let {
history,
updatePlaceHolder,
getValueFromHistory,
} = this.props;
if (!history.entries.length) {
return false;
}
// Up Arrow key
if (direction == HISTORY_BACK) {
if (this.historyPlaceHolder <= 0) {
return false;
}
let inputVal = this.history[--this.historyPlaceHolder];
let newInputValue = getValueFromHistory(direction);
let expression = this.getInputValue();
updatePlaceHolder(direction, expression);
// Save the current input value as the latest entry in history, only if
// the user is already at the last entry.
// Note: this code does not store changes to items that are already in
// history.
if (this.historyPlaceHolder + 1 == this.historyIndex) {
this.history[this.historyIndex] = this.getInputValue() || "";
}
this.setInputValue(inputVal);
} else if (direction == HISTORY_FORWARD) {
// Down Arrow key
if (this.historyPlaceHolder >= (this.history.length - 1)) {
return false;
}
let inputVal = this.history[++this.historyPlaceHolder];
this.setInputValue(inputVal);
} else {
throw new Error("Invalid argument 0");
if (newInputValue != null) {
this.setInputValue(newInputValue);
return true;
}
return true;
return false;
}
/**
@ -1404,4 +1347,22 @@ class JSTerm extends Component {
}
}
module.exports = JSTerm;
// Redux connect
function mapStateToProps(state) {
return {
history: getHistory(state),
getValueFromHistory: (direction) => getHistoryValue(state, direction),
};
}
function mapDispatchToProps(dispatch) {
return {
appendToHistory: (expr) => dispatch(historyActions.appendToHistory(expr)),
clearHistory: () => dispatch(historyActions.clearHistory()),
updatePlaceHolder: (direction, expression) =>
dispatch(historyActions.updatePlaceHolder(direction, expression)),
};
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(JSTerm);

View File

@ -30,6 +30,10 @@ const actionTypes = {
APPEND_NOTIFICATION: "APPEND_NOTIFICATION",
REMOVE_NOTIFICATION: "REMOVE_NOTIFICATION",
SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE: "SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE",
APPEND_TO_HISTORY: "APPEND_TO_HISTORY",
CLEAR_HISTORY: "CLEAR_HISTORY",
HISTORY_LOADED: "HISTORY_LOADED",
UPDATE_HISTORY_PLACEHOLDER: "UPDATE_HISTORY_PLACEHOLDER",
};
const prefs = {
@ -52,6 +56,8 @@ const prefs = {
FILTER_BAR: "ui.filterbar",
// Persist is only used by the webconsole.
PERSIST: "devtools.webconsole.persistlog",
// Max number of entries in history list.
INPUT_HISTORY_COUNT: "devtools.webconsole.inputHistoryCount",
},
FEATURES: {
// We use the same pref to enable the sidebar on webconsole and browser console.
@ -138,6 +144,12 @@ const jstermCommands = {
}
};
// Constants used for defining the direction of JSTerm input history navigation.
const historyCommands = {
HISTORY_BACK: -1,
HISTORY_FORWARD: 1,
};
// Combine into a single constants object
module.exports = Object.assign({
FILTERS,
@ -148,4 +160,5 @@ module.exports = Object.assign({
chromeRDPEnums,
jstermCommands,
prefs,
historyCommands,
);

View File

@ -0,0 +1,126 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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/. */
"use strict";
const {
APPEND_TO_HISTORY,
CLEAR_HISTORY,
HISTORY_LOADED,
UPDATE_HISTORY_PLACEHOLDER,
HISTORY_BACK,
HISTORY_FORWARD,
} = require("devtools/client/webconsole/constants");
/**
* Create default initial state for this reducer.
*/
function getInitialState() {
return {
// Array with history entries
entries: [],
// Holds the index of the history entry that the user is currently
// viewing. This is reset to this.history.length when APPEND_TO_HISTORY
// action is fired.
placeHolder: undefined,
// Holds the number of entries in history. This value is incremented
// when APPEND_TO_HISTORY action is fired and used to get previous
// value from the command line when the user goes backward.
index: 0,
};
}
function history(state = getInitialState(), action, prefsState) {
switch (action.type) {
case APPEND_TO_HISTORY:
return appendToHistory(state, prefsState, action.expression);
case CLEAR_HISTORY:
return clearHistory(state);
case HISTORY_LOADED:
return historyLoaded(state, action.entries);
case UPDATE_HISTORY_PLACEHOLDER:
return updatePlaceHolder(state, action.direction, action.expression);
}
return state;
}
function appendToHistory(state, prefsState, expression) {
// Clone state
state = {...state};
state.entries = [...state.entries];
// Append new expression
state.entries[state.index++] = expression;
state.placeHolder = state.entries.length;
// Remove entries if the limit is reached
if (state.entries.length > prefsState.historyCount) {
state.entries.splice(0, state.entries.length - prefsState.historyCount);
state.index = state.placeHolder = state.entries.length;
}
return state;
}
function clearHistory(state) {
return getInitialState();
}
/**
* Handling HISTORY_LOADED action that is fired when history
* entries created in previous Firefox session are loaded
* from async-storage.
*
* Loaded entries are appended before the ones that were
* added to the state in this session.
*/
function historyLoaded(state, entries) {
let newEntries = [...entries, ...state.entries];
return {
...state,
entries: newEntries,
placeHolder: newEntries.length,
index: newEntries.length,
};
}
function updatePlaceHolder(state, direction, expression) {
// Handle UP arrow key => HISTORY_BACK
// Handle DOWN arrow key => HISTORY_FORWARD
if (direction == HISTORY_BACK) {
if (state.placeHolder <= 0) {
return state;
}
// Clone state
state = {...state};
// Save the current input value as the latest entry in history, only if
// the user is already at the last entry.
// Note: this code does not store changes to items that are already in
// history.
if (state.placeHolder == state.index) {
state.entries = [...state.entries];
state.entries[state.index] = expression || "";
}
state.placeHolder--;
} else if (direction == HISTORY_FORWARD) {
if (state.placeHolder >= (state.entries.length - 1)) {
return state;
}
state = {
...state,
placeHolder: state.placeHolder + 1,
};
}
return state;
}
exports.history = history;

View File

@ -10,6 +10,7 @@ const { messages } = require("./messages");
const { prefs } = require("./prefs");
const { ui } = require("./ui");
const { notifications } = require("./notifications");
const { history } = require("./history");
exports.reducers = {
filters,
@ -17,4 +18,5 @@ exports.reducers = {
prefs,
ui,
notifications,
history,
};

View File

@ -5,6 +5,7 @@
DevToolsModules(
'filters.js',
'history.js',
'index.js',
'messages.js',
'notifications.js',

View File

@ -9,6 +9,7 @@ const PrefState = (overrides) => Object.freeze(Object.assign({
logLimit: 1000,
sidebarToggle: false,
jstermCodeMirror: false,
historyCount: 50,
}, overrides));
function prefs(state = PrefState(), action) {

View File

@ -0,0 +1,50 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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/. */
"use strict";
const {
HISTORY_BACK,
HISTORY_FORWARD,
} = require("devtools/client/webconsole/constants");
function getHistory(state) {
return state.history;
}
function getHistoryEntries(state) {
return state.history.entries;
}
function getHistoryValue(state, direction) {
if (direction == HISTORY_BACK) {
return getPreviousHistoryValue(state);
}
if (direction == HISTORY_FORWARD) {
return getNextHistoryValue(state);
}
return null;
}
function getNextHistoryValue(state) {
if (state.history.placeHolder < (state.history.entries.length - 1)) {
return state.history.entries[state.history.placeHolder + 1];
}
return null;
}
function getPreviousHistoryValue(state) {
if (state.history.placeHolder > 0) {
return state.history.entries[state.history.placeHolder - 1];
}
return null;
}
module.exports = {
getHistory,
getHistoryEntries,
getHistoryValue,
};

View File

@ -5,6 +5,7 @@
DevToolsModules(
'filters.js',
'history.js',
'messages.js',
'notifications.js',
'prefs.js',

View File

@ -24,6 +24,8 @@ const {
PREFS,
INITIALIZE,
FILTER_TOGGLE,
APPEND_TO_HISTORY,
CLEAR_HISTORY,
} = require("devtools/client/webconsole/constants");
const { reducers } = require("./reducers/index");
const {
@ -35,6 +37,9 @@ const {
getAllNetworkMessagesUpdateById,
} = require("devtools/client/webconsole/selectors/messages");
const {getPrefsService} = require("devtools/client/webconsole/utils/prefs");
const historyActions = require("devtools/client/webconsole/actions/history");
loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
/**
* Create and configure store for the Console panel. This is the place
@ -51,12 +56,14 @@ function configureStore(hud, options = {}) {
|| Math.max(getIntPref("devtools.hud.loglimit"), 1);
const sidebarToggle = getBoolPref(PREFS.FEATURES.SIDEBAR_TOGGLE);
const jstermCodeMirror = getBoolPref(PREFS.FEATURES.JSTERM_CODE_MIRROR);
const historyCount = getIntPref(PREFS.UI.INPUT_HISTORY_COUNT);
const initialState = {
prefs: PrefState({
logLimit,
sidebarToggle,
jstermCodeMirror,
historyCount,
}),
filters: FilterState({
error: getBoolPref(PREFS.FILTER.ERROR),
@ -75,11 +82,17 @@ function configureStore(hud, options = {}) {
})
};
// Prepare middleware.
let middleware = applyMiddleware(
thunk.bind(null, {prefsService}),
historyPersistenceMiddleware,
);
return createStore(
createRootReducer(),
initialState,
compose(
applyMiddleware(thunk.bind(null, {prefsService})),
middleware,
enableActorReleaser(hud),
enableBatching(),
enableNetProvider(hud),
@ -99,14 +112,19 @@ function thunk(options = {}, { dispatch, getState }) {
function createRootReducer() {
return function rootReducer(state, action) {
// We want to compute the new state for all properties except "messages".
// We want to compute the new state for all properties except
// "messages" and "history". These two reducers are handled
// separately since they are receiving additional arguments.
const newState = [...Object.entries(reducers)].reduce((res, [key, reducer]) => {
if (key !== "messages") {
if (key !== "messages" && key !== "history") {
res[key] = reducer(state[key], action);
}
return res;
}, {});
// Pass prefs state as additional argument to the history reducer.
newState.history = reducers.history(state.history, action, newState.prefs);
return Object.assign(newState, {
// specifically pass the updated filters and prefs state as additional arguments.
messages: reducers.messages(
@ -227,7 +245,7 @@ function enableNetProvider(hud) {
// Data provider implements async logic for fetching
// data from the backend. It's created the first
// time it's needed.
if (!dataProvider) {
if (!dataProvider && proxy.webConsoleClient) {
dataProvider = new DataProvider({
actions,
webConsoleClient: proxy.webConsoleClient
@ -323,6 +341,41 @@ function releaseActors(removedActors, proxy) {
removedActors.forEach(actor => proxy.releaseActor(actor));
}
/**
* History persistence middleware is responsible for loading
* and maintaining history of executed expressions in JSTerm.
*/
function historyPersistenceMiddleware(store) {
let historyLoaded = false;
asyncStorage.getItem("webConsoleHistory").then(value => {
if (Array.isArray(value)) {
store.dispatch(historyActions.historyLoaded(value));
}
historyLoaded = true;
}, err => {
historyLoaded = true;
console.error(err);
});
return next => action => {
const res = next(action);
let triggerStoreActions = [
APPEND_TO_HISTORY,
CLEAR_HISTORY,
];
// Save the current history entries when modified, but wait till
// entries from the previous session are loaded.
if (historyLoaded && triggerStoreActions.includes(action.type)) {
const state = store.getState();
asyncStorage.setItem("webConsoleHistory", state.history.entries);
}
return res;
};
}
// Provide the store factory for test code so that each test is working with
// its own instance.
module.exports.configureStore = configureStore;

View File

@ -16,8 +16,16 @@ const TEST_URI = `data:text/html;charset=utf-8,
</head>
<body>bug 873250 - test pressing return with open popup, but no selection</body>`;
const {
getHistoryEntries,
} = require("devtools/client/webconsole/selectors/history");
add_task(async function() {
let { jsterm } = await openNewTabAndConsole(TEST_URI);
const {
jsterm,
ui,
} = await openNewTabAndConsole(TEST_URI);
const {
autocompletePopup: popup,
completeNode,
@ -43,6 +51,9 @@ add_task(async function() {
ok(!popup.isOpen, "popup is not open after KEY_Enter");
is(jsterm.getInputValue(), "", "inputNode is empty after KEY_Enter");
is(completeNode.value, "", "completeNode is empty");
is(jsterm.history[jsterm.history.length - 1], "window.testBug",
const state = ui.consoleOutput.getStore().getState();
const entries = getHistoryEntries(state);
is(entries[entries.length - 1], "window.testBug",
"jsterm history is correct");
});

View File

@ -8,10 +8,13 @@
"use strict";
const TEST_URI = "data:text/html;charset=UTF-8,test";
const HISTORY_BACK = -1;
const HISTORY_FORWARD = 1;
const COMMANDS = ["document", "window", "window.location"];
const {
HISTORY_BACK,
HISTORY_FORWARD,
} = require("devtools/client/webconsole/constants");
add_task(async function() {
const { jsterm } = await openNewTabAndConsole(TEST_URI);
const { inputNode } = jsterm;

View File

@ -14,6 +14,10 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for " +
"persisting history - bug 943306";
const INPUT_HISTORY_COUNT = 10;
const {
getHistoryEntries,
} = require("devtools/client/webconsole/selectors/history");
add_task(async function() {
info("Setting custom input history pref to " + INPUT_HISTORY_COUNT);
Services.prefs.setIntPref("devtools.webconsole.inputHistoryCount", INPUT_HISTORY_COUNT);
@ -21,28 +25,37 @@ add_task(async function() {
// First tab: run a bunch of commands and then make sure that you can
// navigate through their history.
let hud1 = await openNewTabAndConsole(TEST_URI);
is(JSON.stringify(hud1.jsterm.history), "[]", "No history on first tab initially");
let state1 = hud1.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state1)),
"[]",
"No history on first tab initially");
await populateInputHistory(hud1);
is(JSON.stringify(hud1.jsterm.history),
state1 = hud1.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state1)),
'["0","1","2","3","4","5","6","7","8","9"]',
"First tab has populated history");
// Second tab: Just make sure that you can navigate through the history
// generated by the first tab.
let hud2 = await openNewTabAndConsole(TEST_URI, false);
is(JSON.stringify(hud2.jsterm.history),
let state2 = hud2.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state2)),
'["0","1","2","3","4","5","6","7","8","9"]',
"Second tab has populated history");
await testNavigatingHistoryInUI(hud2);
is(JSON.stringify(hud2.jsterm.history),
state2 = hud2.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state2)),
'["0","1","2","3","4","5","6","7","8","9",""]',
"An empty entry has been added in the second tab due to history perusal");
// Third tab: Should have the same history as first tab, but if we run a
// command, then the history of the first and second shouldn't be affected
let hud3 = await openNewTabAndConsole(TEST_URI, false);
is(JSON.stringify(hud3.jsterm.history),
let state3 = hud3.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state3)),
'["0","1","2","3","4","5","6","7","8","9"]',
"Third tab has populated history");
@ -51,13 +64,18 @@ add_task(async function() {
hud3.jsterm.setInputValue('"hello from third tab"');
await hud3.jsterm.execute();
is(JSON.stringify(hud1.jsterm.history),
state1 = hud1.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state1)),
'["0","1","2","3","4","5","6","7","8","9"]',
"First tab history hasn't changed due to command in third tab");
is(JSON.stringify(hud2.jsterm.history),
state2 = hud2.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state2)),
'["0","1","2","3","4","5","6","7","8","9",""]',
"Second tab history hasn't changed due to command in third tab");
is(JSON.stringify(hud3.jsterm.history),
state3 = hud3.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state3)),
'["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
"Third tab has updated history (and purged the first result) after " +
"running a command");
@ -65,15 +83,20 @@ add_task(async function() {
// Fourth tab: Should have the latest command from the third tab, followed
// by the rest of the history from the first tab.
let hud4 = await openNewTabAndConsole(TEST_URI, false);
is(JSON.stringify(hud4.jsterm.history),
let state4 = hud4.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state4)),
'["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
"Fourth tab has most recent history");
await hud4.jsterm.clearHistory();
is(JSON.stringify(hud4.jsterm.history), "[]", "Clearing history for a tab works");
await hud4.jsterm.props.clearHistory();
state4 = hud4.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state4)),
"[]",
"Clearing history for a tab works");
let hud5 = await openNewTabAndConsole(TEST_URI, false);
is(JSON.stringify(hud5.jsterm.history), "[]",
let state5 = hud5.ui.consoleOutput.getStore().getState();
is(JSON.stringify(getHistoryEntries(state5)), "[]",
"Clearing history carries over to a new tab");
info("Clearing custom input history pref");

View File

@ -70,7 +70,7 @@ async function openNewTabAndConsole(url, clearJstermHistory = true) {
if (clearJstermHistory) {
// Clearing history that might have been set in previous tests.
await hud.jsterm.clearHistory();
await hud.ui.consoleOutput.dispatchClearHistory();
}
return hud;

View File

@ -367,6 +367,10 @@ WebConsoleOutputWrapper.prototype = {
this.setTimeoutIfNeeded();
},
dispatchClearHistory: function() {
store.dispatch(actions.clearHistory());
},
/**
* Returns a Promise that resolves once any async dispatch is finally dispatched.
*/