summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/test/node/store
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/webconsole/test/node/store
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/webconsole/test/node/store')
-rw-r--r--devtools/client/webconsole/test/node/store/filters.test.js345
-rw-r--r--devtools/client/webconsole/test/node/store/hidden-messages.test.js199
-rw-r--r--devtools/client/webconsole/test/node/store/messages.test.js1305
-rw-r--r--devtools/client/webconsole/test/node/store/network-messages.test.js133
-rw-r--r--devtools/client/webconsole/test/node/store/private-messages.test.js234
-rw-r--r--devtools/client/webconsole/test/node/store/release-actors.test.js172
-rw-r--r--devtools/client/webconsole/test/node/store/search.test.js115
-rw-r--r--devtools/client/webconsole/test/node/store/ui.test.js119
8 files changed, 2622 insertions, 0 deletions
diff --git a/devtools/client/webconsole/test/node/store/filters.test.js b/devtools/client/webconsole/test/node/store/filters.test.js
new file mode 100644
index 0000000000..eddf59e537
--- /dev/null
+++ b/devtools/client/webconsole/test/node/store/filters.test.js
@@ -0,0 +1,345 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const expect = require("expect");
+
+const actions = require("resource://devtools/client/webconsole/actions/index.js");
+const {
+ messagesAdd,
+} = require("resource://devtools/client/webconsole/actions/index.js");
+const {
+ ConsoleCommand,
+} = require("resource://devtools/client/webconsole/types.js");
+const {
+ getVisibleMessages,
+} = require("resource://devtools/client/webconsole/selectors/messages.js");
+const {
+ getAllFilters,
+} = require("resource://devtools/client/webconsole/selectors/filters.js");
+const {
+ setupStore,
+ getFiltersPrefs,
+} = require("resource://devtools/client/webconsole/test/node/helpers.js");
+const {
+ FILTERS,
+ PREFS,
+} = require("resource://devtools/client/webconsole/constants.js");
+const {
+ stubPackets,
+} = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
+const {
+ stubPreparedMessages,
+} = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
+
+describe("Filtering", () => {
+ let store;
+ let numMessages;
+ // Number of messages in prepareBaseStore which are not filtered out, i.e. Evaluation
+ // Results and console commands.
+ const numUnfilterableMessages = 2;
+
+ beforeEach(() => {
+ store = prepareBaseStore();
+ store.dispatch(actions.filtersClear());
+ numMessages = getVisibleMessages(store.getState()).length;
+ });
+
+ /**
+ * Tests for filter buttons in Console toolbar. The test switches off
+ * all filters and consequently tests one by one on the list of messages
+ * created in `prepareBaseStore` method.
+ */
+ describe("Level filter", () => {
+ beforeEach(() => {
+ // Switch off all filters (include those which are on by default).
+ store.dispatch(actions.filtersClear());
+ store.dispatch(actions.filterToggle(FILTERS.DEBUG));
+ store.dispatch(actions.filterToggle(FILTERS.ERROR));
+ store.dispatch(actions.filterToggle(FILTERS.INFO));
+ store.dispatch(actions.filterToggle(FILTERS.LOG));
+ store.dispatch(actions.filterToggle(FILTERS.WARN));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages);
+ });
+
+ it("filters log messages", () => {
+ store.dispatch(actions.filterToggle(FILTERS.LOG));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages + 6);
+ });
+
+ it("filters debug messages", () => {
+ store.dispatch(actions.filterToggle(FILTERS.DEBUG));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages + 1);
+ });
+
+ it("filters info messages", () => {
+ store.dispatch(actions.filterToggle(FILTERS.INFO));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages + 1);
+ });
+
+ it("filters warning messages", () => {
+ store.dispatch(actions.filterToggle(FILTERS.WARN));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages + 1);
+ });
+
+ it("filters error messages", () => {
+ store.dispatch(actions.filterToggle(FILTERS.ERROR));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages + 5);
+ });
+
+ it("filters css messages", () => {
+ const message = stubPreparedMessages.get(
+ "Unknown property ‘such-unknown-property’. Declaration dropped."
+ );
+ store.dispatch(messagesAdd([message]));
+
+ let messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages);
+
+ store.dispatch(actions.filterToggle("css"));
+ messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages + 1);
+ });
+
+ it("filters xhr messages", () => {
+ const message = stubPreparedMessages.get("XHR GET request");
+ store.dispatch(messagesAdd([message]));
+
+ let messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages);
+
+ store.dispatch(actions.filterToggle("netxhr"));
+ messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages + 1);
+ });
+
+ it("filters network messages", () => {
+ const message = stubPreparedMessages.get("GET request update");
+ store.dispatch(messagesAdd([message]));
+
+ let messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages);
+
+ store.dispatch(actions.filterToggle("net"));
+ messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numUnfilterableMessages + 1);
+ });
+ });
+
+ describe("Text filter", () => {
+ it("set the expected property on the store", () => {
+ store.dispatch(actions.filterTextSet("danger"));
+ expect(getAllFilters(store.getState()).text).toEqual("danger");
+ });
+
+ it("matches on value grips", () => {
+ store.dispatch(actions.filterTextSet("danger"));
+ let messages = getVisibleMessages(store.getState());
+ expect(messages.length - numUnfilterableMessages).toEqual(1);
+
+ // Checks that trimming works.
+ store.dispatch(actions.filterTextSet(" danger "));
+ messages = getVisibleMessages(store.getState());
+ expect(messages.length - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("matches unicode values", () => {
+ store.dispatch(actions.filterTextSet("鼬"));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("matches locations", () => {
+ // Add a message with a different filename.
+ const locationMsg = Object.assign(
+ {},
+ stubPackets.get("console.log('foobar', 'test')")
+ );
+ locationMsg.message = Object.assign({}, locationMsg.message, {
+ filename: "search-location-test.js",
+ });
+ store.dispatch(messagesAdd([locationMsg]));
+
+ store.dispatch(actions.filterTextSet("search-location-test.js"));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("matches stacktrace functionName", () => {
+ const traceMessage = stubPackets.get("console.trace()");
+ store.dispatch(messagesAdd([traceMessage]));
+
+ store.dispatch(actions.filterTextSet("testStacktraceFiltering"));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("matches stacktrace location", () => {
+ const traceMessage = stubPackets.get("console.trace()");
+ traceMessage.message = Object.assign({}, traceMessage.message, {
+ filename: "search-location-test.js",
+ lineNumber: 85,
+ columnNumber: 13,
+ });
+
+ store.dispatch(messagesAdd([traceMessage]));
+
+ store.dispatch(actions.filterTextSet("search-location-test.js:85:13"));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("matches prefixed log message", () => {
+ const stub = {
+ level: "debug",
+ filename: "resource:///modules/CustomizableUI.sys.mjs",
+ lineNumber: 181,
+ functionName: "initialize",
+ timeStamp: 1519311532912,
+ arguments: ["Initializing"],
+ prefix: "MyNicePrefix",
+ workerType: "none",
+ styles: [],
+ category: "webdev",
+ _type: "ConsoleAPI",
+ };
+ store.dispatch(messagesAdd([stub]));
+
+ store.dispatch(actions.filterTextSet("MyNice"));
+ let messages = getVisibleMessages(store.getState());
+ expect(messages.length - numUnfilterableMessages).toEqual(1);
+
+ store.dispatch(actions.filterTextSet("MyNicePrefix"));
+ messages = getVisibleMessages(store.getState());
+ expect(messages.length - numUnfilterableMessages).toEqual(1);
+
+ store.dispatch(actions.filterTextSet("MyNicePrefix:"));
+ messages = getVisibleMessages(store.getState());
+ expect(messages.length - numUnfilterableMessages).toEqual(1);
+ });
+
+ it("restores all messages once text is cleared", () => {
+ store.dispatch(actions.filterTextSet("danger"));
+ store.dispatch(actions.filterTextSet(""));
+
+ const messages = getVisibleMessages(store.getState());
+ expect(messages.length).toEqual(numMessages);
+ });
+ });
+
+ describe("Combined filters", () => {
+ // @TODO add test
+ it.todo("filters");
+ });
+});
+
+describe("Clear filters", () => {
+ it("clears all filters", () => {
+ const store = setupStore();
+
+ // Setup test case
+ store.dispatch(actions.filterToggle(FILTERS.ERROR));
+ store.dispatch(actions.filterToggle(FILTERS.CSS));
+ store.dispatch(actions.filterToggle(FILTERS.NET));
+ store.dispatch(actions.filterToggle(FILTERS.NETXHR));
+ store.dispatch(actions.filterTextSet("foobar"));
+
+ expect(getAllFilters(store.getState())).toEqual({
+ // default
+ [FILTERS.WARN]: true,
+ [FILTERS.LOG]: true,
+ [FILTERS.INFO]: true,
+ [FILTERS.DEBUG]: true,
+ // changed
+ [FILTERS.ERROR]: false,
+ [FILTERS.CSS]: true,
+ [FILTERS.NET]: true,
+ [FILTERS.NETXHR]: true,
+ [FILTERS.TEXT]: "foobar",
+ });
+ expect(getFiltersPrefs()).toEqual({
+ [PREFS.FILTER.WARN]: true,
+ [PREFS.FILTER.LOG]: true,
+ [PREFS.FILTER.INFO]: true,
+ [PREFS.FILTER.DEBUG]: true,
+ [PREFS.FILTER.ERROR]: false,
+ [PREFS.FILTER.CSS]: true,
+ [PREFS.FILTER.NET]: true,
+ [PREFS.FILTER.NETXHR]: true,
+ });
+
+ store.dispatch(actions.filtersClear());
+
+ expect(getAllFilters(store.getState())).toEqual({
+ [FILTERS.CSS]: false,
+ [FILTERS.DEBUG]: true,
+ [FILTERS.ERROR]: true,
+ [FILTERS.INFO]: true,
+ [FILTERS.LOG]: true,
+ [FILTERS.NET]: false,
+ [FILTERS.NETXHR]: false,
+ [FILTERS.WARN]: true,
+ [FILTERS.TEXT]: "",
+ });
+
+ expect(getFiltersPrefs()).toEqual({
+ [PREFS.FILTER.CSS]: false,
+ [PREFS.FILTER.DEBUG]: true,
+ [PREFS.FILTER.ERROR]: true,
+ [PREFS.FILTER.INFO]: true,
+ [PREFS.FILTER.LOG]: true,
+ [PREFS.FILTER.NET]: false,
+ [PREFS.FILTER.NETXHR]: false,
+ [PREFS.FILTER.WARN]: true,
+ });
+ });
+});
+
+function prepareBaseStore() {
+ const store = setupStore([
+ // Console API
+ "console.log('foobar', 'test')",
+ "console.warn('danger, will robinson!')",
+ "console.log(undefined)",
+ "console.count('bar')",
+ "console.log('鼬')",
+ // Evaluation Result - never filtered
+ "new Date(0)",
+ // PageError
+ "ReferenceError: asdf is not defined",
+ "TypeError longString message",
+ "console.debug('debug message');",
+ "console.info('info message');",
+ "console.error('error message');",
+ "console.table(['red', 'green', 'blue']);",
+ "console.assert(false, {message: 'foobar'})",
+ // This is a 404 request, it's displayed as an error
+ "GET request update",
+ "console.group('bar')",
+ "console.groupEnd()",
+ ]);
+
+ // Console Command - never filtered
+ store.dispatch(
+ messagesAdd([new ConsoleCommand({ messageText: `console.warn("x")` })])
+ );
+
+ return store;
+}
diff --git a/devtools/client/webconsole/test/node/store/hidden-messages.test.js b/devtools/client/webconsole/test/node/store/hidden-messages.test.js
new file mode 100644
index 0000000000..c135c9b304
--- /dev/null
+++ b/devtools/client/webconsole/test/node/store/hidden-messages.test.js
@@ -0,0 +1,199 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const expect = require("expect");
+
+const actions = require("resource://devtools/client/webconsole/actions/index.js");
+const {
+ getFilteredMessagesCount,
+} = require("resource://devtools/client/webconsole/selectors/messages.js");
+const {
+ setupStore,
+} = require("resource://devtools/client/webconsole/test/node/helpers.js");
+const {
+ FILTERS,
+} = require("resource://devtools/client/webconsole/constants.js");
+const {
+ stubPackets,
+} = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
+
+describe("Filtering - Hidden messages", () => {
+ let store;
+
+ beforeEach(() => {
+ store = prepareBaseStore();
+ // Switch off all filters (include those which are on by default).
+ store.dispatch(actions.filtersClear());
+ store.dispatch(actions.filterToggle(FILTERS.DEBUG));
+ store.dispatch(actions.filterToggle(FILTERS.ERROR));
+ store.dispatch(actions.filterToggle(FILTERS.INFO));
+ store.dispatch(actions.filterToggle(FILTERS.LOG));
+ store.dispatch(actions.filterToggle(FILTERS.WARN));
+ });
+
+ it("has the expected numbers", () => {
+ const counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual(BASIC_TEST_CASE_FILTERED_MESSAGE_COUNT);
+ });
+
+ it("has the expected numbers when there is a text search", () => {
+ // "info" is disabled and the filter input only matches a warning message.
+ store.dispatch(actions.filtersClear());
+ store.dispatch(actions.filterToggle(FILTERS.INFO));
+ store.dispatch(actions.filterTextSet("danger, will robinson!"));
+
+ let counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual({
+ [FILTERS.ERROR]: 0,
+ [FILTERS.WARN]: 0,
+ [FILTERS.LOG]: 0,
+ [FILTERS.INFO]: 1,
+ [FILTERS.DEBUG]: 0,
+ [FILTERS.TEXT]: 9,
+ global: 10,
+ });
+
+ // Numbers update if the text search is cleared.
+ store.dispatch(actions.filterTextSet(""));
+ counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual({
+ [FILTERS.ERROR]: 0,
+ [FILTERS.WARN]: 0,
+ [FILTERS.LOG]: 0,
+ [FILTERS.INFO]: 1,
+ [FILTERS.DEBUG]: 0,
+ [FILTERS.TEXT]: 0,
+ global: 1,
+ });
+ });
+
+ it("has the expected numbers when there's a text search on disabled categories", () => {
+ store.dispatch(actions.filterTextSet("danger, will robinson!"));
+ let counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual({
+ [FILTERS.ERROR]: 3,
+ [FILTERS.WARN]: 1,
+ [FILTERS.LOG]: 5,
+ [FILTERS.INFO]: 1,
+ [FILTERS.DEBUG]: 1,
+ [FILTERS.TEXT]: 0,
+ global: 11,
+ });
+
+ // Numbers update if the text search is cleared.
+ store.dispatch(actions.filterTextSet(""));
+ counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual(BASIC_TEST_CASE_FILTERED_MESSAGE_COUNT);
+ });
+
+ it("updates when messages are added", () => {
+ const packets = MESSAGES.map(key => stubPackets.get(key));
+ store.dispatch(actions.messagesAdd(packets));
+
+ const counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual({
+ [FILTERS.ERROR]: 6,
+ [FILTERS.WARN]: 2,
+ [FILTERS.LOG]: 10,
+ [FILTERS.INFO]: 2,
+ [FILTERS.DEBUG]: 2,
+ [FILTERS.TEXT]: 0,
+ global: 22,
+ });
+ });
+
+ it("updates when filters are toggled", () => {
+ store.dispatch(actions.filterToggle(FILTERS.LOG));
+
+ let counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual(
+ Object.assign({}, BASIC_TEST_CASE_FILTERED_MESSAGE_COUNT, {
+ [FILTERS.LOG]: 0,
+ global: 6,
+ })
+ );
+
+ store.dispatch(actions.filterToggle(FILTERS.ERROR));
+
+ counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual(
+ Object.assign({}, BASIC_TEST_CASE_FILTERED_MESSAGE_COUNT, {
+ [FILTERS.ERROR]: 0,
+ [FILTERS.LOG]: 0,
+ global: 3,
+ })
+ );
+
+ store.dispatch(actions.filterToggle(FILTERS.LOG));
+ store.dispatch(actions.filterToggle(FILTERS.ERROR));
+ counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual(BASIC_TEST_CASE_FILTERED_MESSAGE_COUNT);
+ });
+
+ it("has the expected numbers after message clear", () => {
+ // Add a text search to make sure it is handled as well.
+ store.dispatch(actions.filterTextSet("danger, will robinson!"));
+ store.dispatch(actions.messagesClear());
+ const counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual({
+ [FILTERS.ERROR]: 0,
+ [FILTERS.WARN]: 0,
+ [FILTERS.LOG]: 0,
+ [FILTERS.INFO]: 0,
+ [FILTERS.DEBUG]: 0,
+ [FILTERS.TEXT]: 0,
+ global: 0,
+ });
+ });
+
+ it("has the expected numbers after filter clear", () => {
+ // Add a text search to make sure it is handled as well.
+ store.dispatch(actions.filterTextSet("danger, will robinson!"));
+ store.dispatch(actions.filtersClear());
+ const counter = getFilteredMessagesCount(store.getState());
+ expect(counter).toEqual({
+ [FILTERS.ERROR]: 0,
+ [FILTERS.WARN]: 0,
+ [FILTERS.LOG]: 0,
+ [FILTERS.INFO]: 0,
+ [FILTERS.DEBUG]: 0,
+ [FILTERS.TEXT]: 0,
+ global: 0,
+ });
+ });
+});
+
+const MESSAGES = [
+ // Error
+ "ReferenceError: asdf is not defined",
+ "console.error('error message');",
+ "console.assert(false, {message: 'foobar'})",
+ // Warning
+ "console.warn('danger, will robinson!')",
+ // Log
+ "console.log('foobar', 'test')",
+ "console.log(undefined)",
+ "console.count('bar')",
+ "console.log('鼬')",
+ "console.table(['red', 'green', 'blue']);",
+ // Info
+ "console.info('info message');",
+ // Debug
+ "console.debug('debug message');",
+];
+
+const BASIC_TEST_CASE_FILTERED_MESSAGE_COUNT = {
+ [FILTERS.ERROR]: 3,
+ [FILTERS.WARN]: 1,
+ [FILTERS.LOG]: 5,
+ [FILTERS.INFO]: 1,
+ [FILTERS.DEBUG]: 1,
+ [FILTERS.TEXT]: 0,
+ global: 11,
+};
+
+function prepareBaseStore() {
+ return setupStore(MESSAGES);
+}
diff --git a/devtools/client/webconsole/test/node/store/messages.test.js b/devtools/client/webconsole/test/node/store/messages.test.js
new file mode 100644
index 0000000000..d91325bcfa
--- /dev/null
+++ b/devtools/client/webconsole/test/node/store/messages.test.js
@@ -0,0 +1,1305 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {
+ getAllMessagesUiById,
+ getAllCssMessagesMatchingElements,
+ getAllNetworkMessagesUpdateById,
+ getAllRepeatById,
+ getAllDisabledMessagesById,
+ getCurrentGroup,
+ getGroupsById,
+ getMutableMessagesById,
+ getVisibleMessages,
+} = require("resource://devtools/client/webconsole/selectors/messages.js");
+
+const {
+ clonePacket,
+ getFirstMessage,
+ getLastMessage,
+ getMessageAt,
+ setupActions,
+ setupStore,
+} = require("resource://devtools/client/webconsole/test/node/helpers.js");
+const {
+ stubPackets,
+ stubPreparedMessages,
+} = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
+const {
+ MESSAGE_TYPE,
+ CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
+} = require("resource://devtools/client/webconsole/constants.js");
+const {
+ createWarningGroupMessage,
+} = require("resource://devtools/client/webconsole/utils/messages.js");
+
+const expect = require("expect");
+
+describe("Message reducer:", () => {
+ let actions;
+
+ beforeAll(() => {
+ actions = setupActions();
+ });
+
+ describe("mutableMessagesById", () => {
+ it("adds a message to an empty store", () => {
+ const { dispatch, getState } = setupStore();
+
+ const packet = stubPackets.get("console.log('foobar', 'test')");
+ dispatch(actions.messagesAdd([packet]));
+
+ const message = stubPreparedMessages.get("console.log('foobar', 'test')");
+
+ expect(getFirstMessage(getState())).toEqual(message);
+ });
+
+ it("increments repeat on a repeating log message", () => {
+ const key1 = "console.log('foobar', 'test')";
+ const { dispatch, getState } = setupStore([key1, key1], { actions });
+
+ const packet = clonePacket(stubPackets.get(key1));
+ const packet2 = clonePacket(packet);
+
+ // Repeat ID must be the same even if the timestamp is different.
+ packet.message.timeStamp = 1;
+ packet2.message.timeStamp = 2;
+ dispatch(actions.messagesAdd([packet, packet2]));
+
+ const messages = getMutableMessagesById(getState());
+
+ expect(messages.size).toBe(1);
+ const repeat = getAllRepeatById(getState());
+ expect(repeat[getFirstMessage(getState()).id]).toBe(4);
+ });
+
+ it("doesn't increment repeat on same log message with different locations", () => {
+ const key1 = "console.log('foobar', 'test')";
+ const { dispatch, getState } = setupStore();
+
+ const packet = clonePacket(stubPackets.get(key1));
+
+ // Dispatch original packet.
+ dispatch(actions.messagesAdd([packet]));
+
+ // Dispatch same packet with modified column number.
+ packet.message.columnNumber = packet.message.columnNumber + 1;
+ dispatch(actions.messagesAdd([packet]));
+
+ // Dispatch same packet with modified line number.
+ packet.message.lineNumber = packet.message.lineNumber + 1;
+ dispatch(actions.messagesAdd([packet]));
+
+ const messages = getMutableMessagesById(getState());
+
+ expect(messages.size).toBe(3);
+
+ const repeat = getAllRepeatById(getState());
+ expect(Object.keys(repeat).length).toBe(0);
+ });
+
+ it("increments repeat on a repeating css message", () => {
+ const key1 =
+ "Unknown property ‘such-unknown-property’. Declaration dropped.";
+ const { dispatch, getState } = setupStore([key1, key1]);
+
+ const packet = clonePacket(stubPackets.get(key1));
+
+ // Repeat ID must be the same even if the timestamp is different.
+ packet.pageError.timeStamp = 1;
+ dispatch(actions.messagesAdd([packet]));
+ packet.pageError.timeStamp = 2;
+ dispatch(actions.messagesAdd([packet]));
+
+ const messages = getMutableMessagesById(getState());
+
+ expect(messages.size).toBe(1);
+
+ const repeat = getAllRepeatById(getState());
+ expect(repeat[getFirstMessage(getState()).id]).toBe(4);
+ });
+
+ it("doesn't increment repeat on same css message with different locations", () => {
+ const key1 =
+ "Unknown property ‘such-unknown-property’. Declaration dropped.";
+ const { dispatch, getState } = setupStore();
+
+ const packet = clonePacket(stubPackets.get(key1));
+
+ // Dispatch original packet.
+ dispatch(actions.messagesAdd([packet]));
+
+ // Dispatch same packet with modified column number.
+ packet.pageError.columnNumber = packet.pageError.columnNumber + 1;
+ dispatch(actions.messagesAdd([packet]));
+
+ // Dispatch same packet with modified line number.
+ packet.pageError.lineNumber = packet.pageError.lineNumber + 1;
+ dispatch(actions.messagesAdd([packet]));
+
+ const messages = getMutableMessagesById(getState());
+
+ expect(messages.size).toBe(3);
+
+ const repeat = getAllRepeatById(getState());
+ expect(Object.keys(repeat).length).toBe(0);
+ });
+
+ it("increments repeat on a repeating error message", () => {
+ const key1 = "ReferenceError: asdf is not defined";
+ const { dispatch, getState } = setupStore([key1, key1]);
+
+ const packet = clonePacket(stubPackets.get(key1));
+
+ // Repeat ID must be the same even if the timestamp is different.
+ packet.pageError.timeStamp = 1;
+ dispatch(actions.messagesAdd([packet]));
+ packet.pageError.timeStamp = 2;
+ dispatch(actions.messagesAdd([packet]));
+
+ const messages = getMutableMessagesById(getState());
+
+ expect(messages.size).toBe(1);
+
+ const repeat = getAllRepeatById(getState());
+ expect(repeat[getFirstMessage(getState()).id]).toBe(4);
+ });
+
+ it("does not increment repeat after closing a group", () => {
+ const logKey = "console.log('foobar', 'test')";
+ const { getState } = setupStore([
+ logKey,
+ logKey,
+ "console.group('bar')",
+ logKey,
+ logKey,
+ logKey,
+ "console.groupEnd()",
+ logKey,
+ ]);
+
+ const messages = getMutableMessagesById(getState());
+
+ expect(messages.size).toBe(4);
+ const repeat = getAllRepeatById(getState());
+ expect(repeat[getFirstMessage(getState()).id]).toBe(2);
+ expect(repeat[getMessageAt(getState(), 2).id]).toBe(3);
+ expect(repeat[getLastMessage(getState()).id]).toBe(undefined);
+ });
+
+ it("doesn't increment undefined messages coming from different places", () => {
+ const { getState } = setupStore(["console.log(undefined)", "undefined"]);
+
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(2);
+
+ const repeat = getAllRepeatById(getState());
+ expect(Object.keys(repeat).length).toBe(0);
+ });
+
+ it("doesn't increment successive falsy but different messages", () => {
+ const { getState } = setupStore(
+ ["console.log(NaN)", "console.log(undefined)", "console.log(null)"],
+ { actions }
+ );
+
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(3);
+ const repeat = getAllRepeatById(getState());
+ expect(Object.keys(repeat).length).toBe(0);
+ });
+
+ it("increment falsy messages when expected", () => {
+ const { dispatch, getState } = setupStore();
+
+ const nanPacket = stubPackets.get("console.log(NaN)");
+ dispatch(actions.messagesAdd([nanPacket, nanPacket]));
+ let messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(1);
+ let repeat = getAllRepeatById(getState());
+ expect(repeat[getLastMessage(getState()).id]).toBe(2);
+
+ const undefinedPacket = stubPackets.get("console.log(undefined)");
+ dispatch(actions.messagesAdd([undefinedPacket, undefinedPacket]));
+ messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(2);
+ repeat = getAllRepeatById(getState());
+ expect(repeat[getLastMessage(getState()).id]).toBe(2);
+
+ const nullPacket = stubPackets.get("console.log(null)");
+ dispatch(actions.messagesAdd([nullPacket, nullPacket]));
+ messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(3);
+ repeat = getAllRepeatById(getState());
+ expect(repeat[getLastMessage(getState()).id]).toBe(2);
+ });
+
+ it("does not clobber a unique message", () => {
+ const key1 = "console.log('foobar', 'test')";
+ const { dispatch, getState } = setupStore([key1, key1]);
+
+ const packet = stubPackets.get(key1);
+ dispatch(actions.messagesAdd([packet]));
+
+ const packet2 = stubPackets.get("console.log(undefined)");
+ dispatch(actions.messagesAdd([packet2]));
+
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(2);
+
+ const repeat = getAllRepeatById(getState());
+ expect(repeat[getFirstMessage(getState()).id]).toBe(3);
+ expect(repeat[getLastMessage(getState()).id]).toBe(undefined);
+ });
+
+ it("does not increment repeat after adding similar warning group", () => {
+ const { dispatch, getState } = setupStore();
+
+ // Mocking a warning message that would create a warning group
+ const warningMessage = stubPreparedMessages.get(
+ "ReferenceError: asdf is not defined"
+ );
+ warningMessage.messageText =
+ "The resource at “https://evil.com” was blocked.";
+ warningMessage.category = "cookieBlockedPermission";
+
+ const type = MESSAGE_TYPE.CONTENT_BLOCKING_GROUP;
+ const firstMessageId = `${warningMessage.type}-${warningMessage.innerWindowID}`;
+ const message1 = createWarningGroupMessage(
+ firstMessageId,
+ type,
+ warningMessage
+ );
+ const secondMessageId = `${warningMessage.type}-${
+ warningMessage.innerWindowID + 10
+ }`;
+ const message2 = createWarningGroupMessage(
+ secondMessageId,
+ type,
+ warningMessage
+ );
+
+ dispatch(actions.messagesAdd([message1, message2]));
+
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(2);
+
+ const repeat = getAllRepeatById(getState());
+ expect(Object.keys(repeat).length).toBe(0);
+ });
+
+ it("adds a message in response to console.clear()", () => {
+ const { dispatch, getState } = setupStore([]);
+
+ dispatch(actions.messagesAdd([stubPackets.get("console.clear()")]));
+
+ const messages = getMutableMessagesById(getState());
+
+ expect(messages.size).toBe(1);
+ expect(getFirstMessage(getState()).parameters[0]).toBe(
+ "Console was cleared."
+ );
+ });
+
+ it("clears the messages list in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore([
+ "console.log('foobar', 'test')",
+ "console.log('foobar', 'test')",
+ "console.log(undefined)",
+ "console.table(['red', 'green', 'blue']);",
+ "console.group('bar')",
+ ]);
+
+ dispatch(actions.messagesClear());
+
+ const state = getState();
+ expect(getMutableMessagesById(state).size).toBe(0);
+ expect(getVisibleMessages(state).length).toBe(0);
+ expect(getAllMessagesUiById(state).length).toBe(0);
+ expect(getGroupsById(state).size).toBe(0);
+ expect(getAllCssMessagesMatchingElements(state).size).toBe(0);
+ expect(getCurrentGroup(state)).toBe(null);
+ expect(getAllRepeatById(state)).toEqual({});
+ expect(state.messages.mutableMessagesOrder).toEqual([]);
+ });
+
+ it("cleans the repeatsById object when messages are pruned", () => {
+ const { dispatch, getState } = setupStore(
+ [
+ "console.log('foobar', 'test')",
+ "console.log('foobar', 'test')",
+ "console.log(undefined)",
+ "console.log(undefined)",
+ ],
+ {
+ actions,
+ storeOptions: {
+ logLimit: 2,
+ },
+ }
+ );
+
+ // Check that we have the expected data.
+ let repeats = getAllRepeatById(getState());
+ expect(Object.keys(repeats).length).toBe(2);
+ const lastMessageId = getLastMessage(getState()).id;
+
+ // This addition will prune the first message out of the store.
+ let packet = stubPackets.get("console.log('foobar', 'test')");
+ dispatch(actions.messagesAdd([packet]));
+
+ repeats = getAllRepeatById(getState());
+
+ // There should be only the data for the "undefined" message.
+ expect(Object.keys(repeats)).toEqual([lastMessageId]);
+ expect(Object.keys(repeats).length).toBe(1);
+ expect(repeats[lastMessageId]).toBe(2);
+
+ // This addition will prune the first message out of the store.
+ packet = stubPackets.get("console.log(undefined)");
+ dispatch(actions.messagesAdd([packet]));
+
+ // repeatById should now be empty.
+ expect(getAllRepeatById(getState())).toEqual({});
+ });
+
+ it("properly limits number of messages", () => {
+ const logLimit = 1000;
+ const { dispatch, getState } = setupStore([], {
+ storeOptions: {
+ logLimit,
+ },
+ });
+
+ const packet = clonePacket(stubPackets.get("console.log(undefined)"));
+
+ for (let i = 1; i <= logLimit + 2; i++) {
+ packet.message.arguments = [`message num ${i}`];
+ dispatch(actions.messagesAdd([packet]));
+ }
+
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(logLimit);
+ expect(getFirstMessage(getState()).parameters[0]).toBe(`message num 3`);
+ expect(getLastMessage(getState()).parameters[0]).toBe(
+ `message num ${logLimit + 2}`
+ );
+
+ const { mutableMessagesOrder } = getState().messages;
+ expect(mutableMessagesOrder.length).toBe(logLimit);
+ });
+
+ it("properly limits number of messages when there are nested groups", () => {
+ const logLimit = 1000;
+ const { dispatch, getState } = setupStore([], {
+ storeOptions: {
+ logLimit,
+ },
+ });
+
+ const packet = clonePacket(stubPackets.get("console.log(undefined)"));
+ const packetGroup = clonePacket(stubPackets.get("console.group('bar')"));
+ const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
+
+ packetGroup.message.arguments = [`group-1`];
+ dispatch(actions.messagesAdd([packetGroup]));
+ packetGroup.message.arguments = [`group-1-1`];
+ dispatch(actions.messagesAdd([packetGroup]));
+ packetGroup.message.arguments = [`group-1-1-1`];
+ dispatch(actions.messagesAdd([packetGroup]));
+ packet.message.arguments = [`message-in-group-1`];
+ dispatch(actions.messagesAdd([packet]));
+ packet.message.arguments = [`message-in-group-2`];
+ dispatch(actions.messagesAdd([packet]));
+ // Closing group-1-1-1
+ dispatch(actions.messagesAdd([packetGroupEnd]));
+ // Closing group-1-1
+ dispatch(actions.messagesAdd([packetGroupEnd]));
+ // Closing group-1
+ dispatch(actions.messagesAdd([packetGroupEnd]));
+
+ for (let i = 0; i < logLimit; i++) {
+ packet.message.arguments = [`message-${i}`];
+ dispatch(actions.messagesAdd([packet]));
+ }
+
+ const visibleMessages = getVisibleMessages(getState());
+ const messages = getMutableMessagesById(getState());
+ const { mutableMessagesOrder } = getState().messages;
+
+ expect(messages.size).toBe(logLimit);
+ expect(visibleMessages.length).toBe(logLimit);
+ expect(mutableMessagesOrder.length).toBe(logLimit);
+
+ expect(messages.get(visibleMessages[0]).parameters[0]).toBe(`message-0`);
+ expect(messages.get(visibleMessages[logLimit - 1]).parameters[0]).toBe(
+ `message-${logLimit - 1}`
+ );
+
+ // The groups were cleaned up.
+ const groups = getGroupsById(getState());
+ expect(groups.size).toBe(0);
+ });
+
+ it("properly limits number of groups", () => {
+ const logLimit = 100;
+ const { dispatch, getState } = setupStore([], {
+ storeOptions: { logLimit },
+ });
+
+ const packet = clonePacket(stubPackets.get("console.log(undefined)"));
+ const packetGroup = clonePacket(stubPackets.get("console.group('bar')"));
+ const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
+
+ for (let i = 0; i < logLimit + 2; i++) {
+ dispatch(actions.messagesAdd([packetGroup]));
+ packet.message.arguments = [`message-${i}-a`];
+ dispatch(actions.messagesAdd([packet]));
+ packet.message.arguments = [`message-${i}-b`];
+ dispatch(actions.messagesAdd([packet]));
+ dispatch(actions.messagesAdd([packetGroupEnd]));
+ }
+
+ const visibleMessages = getVisibleMessages(getState());
+ const messages = getMutableMessagesById(getState());
+ // We should have three times the logLimit since each group has one message inside.
+ expect(messages.size).toBe(logLimit * 3);
+
+ // We should have logLimit number of groups
+ const groups = getGroupsById(getState());
+ expect(groups.size).toBe(logLimit);
+
+ expect(messages.get(visibleMessages[1]).parameters[0]).toBe(
+ `message-2-a`
+ );
+ expect(getLastMessage(getState()).parameters[0]).toBe(
+ `message-${logLimit + 1}-b`
+ );
+ });
+
+ it("properly limits number of collapsed groups", () => {
+ const logLimit = 100;
+ const { dispatch, getState } = setupStore([], {
+ storeOptions: { logLimit },
+ });
+
+ const packet = clonePacket(stubPackets.get("console.log(undefined)"));
+ const packetGroupCollapsed = clonePacket(
+ stubPackets.get("console.groupCollapsed('foo')")
+ );
+ const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
+
+ for (let i = 0; i < logLimit + 2; i++) {
+ packetGroupCollapsed.message.arguments = [`group-${i}`];
+ dispatch(actions.messagesAdd([packetGroupCollapsed]));
+ packet.message.arguments = [`message-${i}-a`];
+ dispatch(actions.messagesAdd([packet]));
+ packet.message.arguments = [`message-${i}-b`];
+ dispatch(actions.messagesAdd([packet]));
+ dispatch(actions.messagesAdd([packetGroupEnd]));
+ }
+
+ const messages = getMutableMessagesById(getState());
+ // We should have three times the logLimit since each group has two message inside.
+ expect(messages.size).toBe(logLimit * 3);
+
+ // We should have logLimit number of groups
+ const groups = getGroupsById(getState());
+ expect(groups.size).toBe(logLimit);
+
+ expect(getFirstMessage(getState()).parameters[0]).toBe(`group-2`);
+ expect(getLastMessage(getState()).parameters[0]).toBe(
+ `message-${logLimit + 1}-b`
+ );
+
+ const visibleMessages = getVisibleMessages(getState());
+ expect(visibleMessages.length).toBe(logLimit);
+ const lastVisibleMessageId = visibleMessages[visibleMessages.length - 1];
+ expect(messages.get(lastVisibleMessageId).parameters[0]).toBe(
+ `group-${logLimit + 1}`
+ );
+ });
+
+ it("does not add null messages to the store", () => {
+ const { dispatch, getState } = setupStore();
+
+ const message = stubPackets.get("console.time('bar')");
+ dispatch(actions.messagesAdd([message]));
+
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(0);
+ });
+
+ it("adds console.table call with unsupported type as console.log", () => {
+ const { dispatch, getState } = setupStore();
+
+ const packet = stubPackets.get("console.table('bar')");
+ dispatch(actions.messagesAdd([packet]));
+
+ const tableMessage = getLastMessage(getState());
+ expect(tableMessage.level).toEqual(MESSAGE_TYPE.LOG);
+ });
+
+ it("adds console.group messages to the store", () => {
+ const { dispatch, getState } = setupStore();
+
+ const message = stubPackets.get("console.group('bar')");
+ dispatch(actions.messagesAdd([message]));
+
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(1);
+ });
+
+ it("adds messages in console.group to the store", () => {
+ const { dispatch, getState } = setupStore();
+
+ const groupPacket = stubPackets.get("console.group('bar')");
+ const groupEndPacket = stubPackets.get("console.groupEnd('bar')");
+ const logPacket = stubPackets.get("console.log('foobar', 'test')");
+
+ const packets = [
+ groupPacket,
+ logPacket,
+ groupPacket,
+ groupPacket,
+ logPacket,
+ groupEndPacket,
+ logPacket,
+ groupEndPacket,
+ logPacket,
+ groupEndPacket,
+ logPacket,
+ ];
+ dispatch(actions.messagesAdd(packets));
+
+ // Here is what we should have (8 messages)
+ // ▼ bar
+ // | foobar test
+ // | ▼ bar
+ // | | ▼ bar
+ // | | | foobar test
+ // | | foobar test
+ // | foobar test
+ // foobar test
+
+ const isNotGroupEnd = p => p !== groupEndPacket;
+ const messageCount = packets.filter(isNotGroupEnd).length;
+
+ const messages = getMutableMessagesById(getState());
+ const visibleMessages = getVisibleMessages(getState());
+ expect(messages.size).toBe(messageCount);
+ expect(visibleMessages.length).toBe(messageCount);
+ });
+
+ it("sets groupId property as expected", () => {
+ const { dispatch, getState } = setupStore();
+
+ dispatch(
+ actions.messagesAdd([
+ stubPackets.get("console.group('bar')"),
+ stubPackets.get("console.log('foobar', 'test')"),
+ ])
+ );
+
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(2);
+ expect(getLastMessage(getState()).groupId).toBe(
+ getFirstMessage(getState()).id
+ );
+ });
+
+ it("does not display console.groupEnd messages to the store", () => {
+ const { dispatch, getState } = setupStore();
+
+ const message = stubPackets.get("console.groupEnd('bar')");
+ dispatch(actions.messagesAdd([message]));
+
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(0);
+ });
+
+ it("filters out message added after a console.groupCollapsed message", () => {
+ const { dispatch, getState } = setupStore();
+
+ dispatch(
+ actions.messagesAdd([
+ stubPackets.get("console.groupCollapsed('foo')"),
+ stubPackets.get("console.log('foobar', 'test')"),
+ ])
+ );
+
+ const messages = getVisibleMessages(getState());
+ expect(messages.length).toBe(1);
+ });
+
+ it("adds console.dirxml call as console.log", () => {
+ const { dispatch, getState } = setupStore();
+
+ const packet = stubPackets.get("console.dirxml(window)");
+ dispatch(actions.messagesAdd([packet]));
+
+ const dirxmlMessage = getLastMessage(getState());
+ expect(dirxmlMessage.level).toEqual(MESSAGE_TYPE.LOG);
+ });
+
+ it("does not throw when adding incomplete console.count packet", () => {
+ const { dispatch, getState } = setupStore();
+ const packet = clonePacket(stubPackets.get(`console.count('bar')`));
+
+ // Remove counter information to mimick packet we receive in the browser console.
+ delete packet.message.counter;
+
+ dispatch(actions.messagesAdd([packet]));
+ // The message should not be added to the state.
+ expect(getMutableMessagesById(getState()).size).toBe(0);
+ });
+ });
+
+ describe("mutableMessagesOrder", () => {
+ it("adds a message to an empty store", () => {
+ const { dispatch, getState } = setupStore();
+
+ const packet = stubPackets.get("console.log('foobar', 'test')");
+ dispatch(actions.messagesAdd([packet]));
+
+ const { mutableMessagesOrder } = getState().messages;
+ expect(mutableMessagesOrder.length).toBe(1);
+ expect(mutableMessagesOrder[0]).toBe(
+ // Don't get getMessageIndexAt/getFirstMessage since it relies on mutableMessagesOrder
+ [...getMutableMessagesById(getState()).keys()][0]
+ );
+ });
+
+ it("reorder messages", () => {
+ const { dispatch, getState } = setupStore();
+
+ const naNpacket = stubPackets.get("console.log(NaN)");
+ dispatch(actions.messagesAdd([naNpacket]));
+
+ // Add a message that has a shorter timestamp than the previous one, and thus, should
+ // be displayed before
+ const nullPacket = clonePacket(stubPackets.get("console.log(null)"));
+ nullPacket.message.timeStamp = naNpacket.message.timeStamp - 10;
+ dispatch(actions.messagesAdd([nullPacket]));
+
+ // Add a message that should be display between the 2 previous messages
+ const undefinedPacket = clonePacket(
+ stubPackets.get("console.log(undefined)")
+ );
+ undefinedPacket.message.timeStamp = naNpacket.message.timeStamp - 5;
+ dispatch(actions.messagesAdd([undefinedPacket]));
+
+ const { mutableMessagesOrder } = getState().messages;
+ const [nanMessage, nullMessage, undefinedMessage] = [
+ ...getMutableMessagesById(getState()).values(),
+ ];
+ const visibleMessages = getVisibleMessages(getState());
+
+ // Checking that messages in the Map are the expected ones
+ expect(nanMessage.parameters[0].type).toBe("NaN");
+ expect(nullMessage.parameters[0].type).toBe("null");
+ expect(undefinedMessage.parameters[0].type).toBe("undefined");
+
+ // Check that mutableMessagesOrder has the message ids in the chronological order
+ expect(mutableMessagesOrder).toEqual([
+ nullMessage.id,
+ undefinedMessage.id,
+ nanMessage.id,
+ ]);
+
+ // Since we didn't filtered anything, visibleMessages should be similar to mutableMessagesOrder
+ expect(mutableMessagesOrder).toEqual(visibleMessages);
+
+ // Check that visibleMessages is computed from mutableMessagesOrder when filtering
+ dispatch(actions.filterToggle("log"));
+ expect(getVisibleMessages(getState())).toEqual([]);
+ dispatch(actions.filterToggle("log"));
+ expect(getVisibleMessages(getState())).toEqual([
+ nullMessage.id,
+ undefinedMessage.id,
+ nanMessage.id,
+ ]);
+ });
+ });
+
+ describe("expandedMessageIds", () => {
+ it("opens console.trace messages when they are added", () => {
+ const { dispatch, getState } = setupStore();
+
+ const message = stubPackets.get("console.trace()");
+ dispatch(actions.messagesAdd([message]));
+
+ const expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(1);
+ expect(expanded[0]).toBe(getFirstMessage(getState()).id);
+ });
+
+ it("clears the messages UI list in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore([
+ "console.log('foobar', 'test')",
+ "console.log(undefined)",
+ ]);
+
+ const traceMessage = stubPackets.get("console.trace()");
+ dispatch(actions.messagesAdd([traceMessage]));
+
+ dispatch(actions.messagesClear());
+
+ const expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(0);
+ });
+
+ it("cleans the messages UI list when messages are pruned", () => {
+ const { dispatch, getState } = setupStore(
+ ["console.trace()", "console.log(undefined)", "console.trace()"],
+ {
+ storeOptions: {
+ logLimit: 3,
+ },
+ }
+ );
+
+ // Check that we have the expected data.
+ let expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(2);
+ expect(expanded[0]).toBe(getFirstMessage(getState()).id);
+ const lastMessageId = getLastMessage(getState()).id;
+ expect(expanded[expanded.length - 1]).toBe(lastMessageId);
+
+ // This addition will prune the first message out of the store.
+ let packet = stubPackets.get("console.log(undefined)");
+ dispatch(actions.messagesAdd([packet]));
+
+ expanded = getAllMessagesUiById(getState());
+
+ // There should be only the id of the last console.trace message.
+ expect(expanded.length).toBe(1);
+ expect(expanded[0]).toBe(lastMessageId);
+
+ // These additions will prune the last console.trace message out of the store.
+ packet = stubPackets.get("console.log('foobar', 'test')");
+ dispatch(actions.messagesAdd([packet]));
+ packet = stubPackets.get("console.log(undefined)");
+ dispatch(actions.messagesAdd([packet]));
+
+ // expandedMessageIds should now be empty.
+ expect(getAllMessagesUiById(getState()).length).toBe(0);
+ });
+
+ it("opens console.group messages when they are added", () => {
+ const { dispatch, getState } = setupStore();
+
+ const message = stubPackets.get("console.group('bar')");
+ dispatch(actions.messagesAdd([message]));
+
+ const expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(1);
+ expect(expanded[0]).toBe(getFirstMessage(getState()).id);
+ });
+
+ it("does not open console.groupCollapsed messages when they are added", () => {
+ const { dispatch, getState } = setupStore();
+
+ const message = stubPackets.get("console.groupCollapsed('foo')");
+ dispatch(actions.messagesAdd([message]));
+
+ const expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(0);
+ });
+
+ it("reacts to messageClose/messageOpen actions on console.group", () => {
+ const { dispatch, getState } = setupStore(["console.group('bar')"]);
+ const firstMessageId = getFirstMessage(getState()).id;
+
+ let expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(1);
+ expect(expanded[0]).toBe(firstMessageId);
+
+ dispatch(actions.messageClose(firstMessageId));
+
+ expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(0);
+
+ dispatch(actions.messageOpen(firstMessageId));
+
+ expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(1);
+ expect(expanded[0]).toBe(firstMessageId);
+ });
+
+ it("reacts to messageClose/messageOpen actions on exception", () => {
+ const { dispatch, getState } = setupStore([
+ "ReferenceError: asdf is not defined",
+ ]);
+ const firstMessageId = getFirstMessage(getState()).id;
+
+ let expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(0);
+
+ dispatch(actions.messageOpen(firstMessageId));
+
+ expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(1);
+ expect(expanded[0]).toBe(firstMessageId);
+
+ dispatch(actions.messageClose(firstMessageId));
+
+ expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(0);
+ });
+ });
+
+ describe("currentGroup", () => {
+ it("sets the currentGroup when console.group message is added", () => {
+ const { dispatch, getState } = setupStore();
+
+ const packet = stubPackets.get("console.group('bar')");
+ dispatch(actions.messagesAdd([packet]));
+
+ const currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(getFirstMessage(getState()).id);
+ });
+
+ it("sets currentGroup to expected value when console.groupEnd is added", () => {
+ const { dispatch, getState } = setupStore([
+ "console.group('bar')",
+ "console.groupCollapsed('foo')",
+ "console.group('bar')",
+ "console.groupEnd('bar')",
+ ]);
+
+ let currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(getMessageAt(getState(), 1).id);
+
+ const endFooPacket = stubPackets.get("console.groupEnd('foo')");
+ dispatch(actions.messagesAdd([endFooPacket]));
+ currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(getFirstMessage(getState()).id);
+
+ const endBarPacket = stubPackets.get("console.groupEnd('bar')");
+ dispatch(actions.messagesAdd([endBarPacket]));
+ currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(null);
+ });
+
+ it("resets the currentGroup to null in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore(["console.group('bar')"]);
+
+ dispatch(actions.messagesClear());
+
+ const currentGroup = getCurrentGroup(getState());
+ expect(currentGroup).toBe(null);
+ });
+ });
+
+ describe("groupsById", () => {
+ it("adds the group with expected array when console.group message is added", () => {
+ const { dispatch, getState } = setupStore();
+
+ const barPacket = stubPackets.get("console.group('bar')");
+ dispatch(actions.messagesAdd([barPacket]));
+
+ let groupsById = getGroupsById(getState());
+ expect(groupsById.size).toBe(1);
+ expect(groupsById.has(getFirstMessage(getState()).id)).toBe(true);
+ expect(groupsById.get(getFirstMessage(getState()).id)).toEqual([]);
+
+ const fooPacket = stubPackets.get("console.groupCollapsed('foo')");
+ dispatch(actions.messagesAdd([fooPacket]));
+
+ groupsById = getGroupsById(getState());
+ expect(groupsById.size).toBe(2);
+ expect(groupsById.has(getLastMessage(getState()).id)).toBe(true);
+ expect(groupsById.get(getLastMessage(getState()).id)).toEqual([
+ getFirstMessage(getState()).id,
+ ]);
+ });
+
+ it("resets groupsById in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore([
+ "console.group('bar')",
+ "console.groupCollapsed('foo')",
+ ]);
+
+ let groupsById = getGroupsById(getState());
+ expect(groupsById.size).toBe(2);
+
+ dispatch(actions.messagesClear());
+
+ groupsById = getGroupsById(getState());
+ expect(groupsById.size).toBe(0);
+ });
+
+ it("cleans the groupsById property when messages are pruned", () => {
+ const { dispatch, getState } = setupStore(
+ [
+ "console.group('bar')",
+ "console.group()",
+ "console.groupEnd()",
+ "console.groupEnd('bar')",
+ "console.group('bar')",
+ "console.groupEnd('bar')",
+ "console.log('foobar', 'test')",
+ ],
+ {
+ actions,
+ storeOptions: {
+ logLimit: 3,
+ },
+ }
+ );
+
+ /*
+ * Here is the initial state of the console:
+ * ▶︎ bar
+ * ▶︎ noLabel
+ * ▶︎ bar
+ * foobar test
+ */
+
+ // Check that we have the expected data.
+ let groupsById = getGroupsById(getState());
+ expect(groupsById.size).toBe(3);
+
+ // This addition will prune the first group (and its child group) out of the store.
+ /*
+ * ▶︎ bar
+ * foobar test
+ * undefined
+ */
+ let packet = stubPackets.get("console.log(undefined)");
+ dispatch(actions.messagesAdd([packet]));
+
+ groupsById = getGroupsById(getState());
+
+ // There should be only the id of the last console.group message.
+ expect(groupsById.size).toBe(1);
+
+ // This additions will prune the last group message out of the store.
+ /*
+ * foobar test
+ * undefined
+ * foobar test
+ */
+ packet = stubPackets.get("console.log('foobar', 'test')");
+ dispatch(actions.messagesAdd([packet]));
+
+ // groupsById should now be empty.
+ expect(getGroupsById(getState()).size).toBe(0);
+ });
+ });
+
+ describe("networkMessagesUpdateById", () => {
+ it("adds the network update message when network update action is called", () => {
+ const { dispatch, getState } = setupStore();
+
+ let packet = clonePacket(stubPackets.get("GET request"));
+ let updatePacket = clonePacket(stubPackets.get("GET request update"));
+
+ packet.actor = "message1";
+ updatePacket.actor = "message1";
+ dispatch(actions.messagesAdd([packet]));
+ dispatch(actions.networkMessageUpdates([updatePacket], null));
+
+ let networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(Object.keys(networkUpdates)).toEqual(["message1"]);
+
+ packet = clonePacket(stubPackets.get("GET request"));
+ updatePacket = stubPackets.get("XHR GET request update");
+ packet.actor = "message2";
+ updatePacket.actor = "message2";
+ dispatch(actions.messagesAdd([packet]));
+ dispatch(actions.networkMessageUpdates([updatePacket], null));
+
+ networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(Object.keys(networkUpdates)).toEqual(["message1", "message2"]);
+ });
+
+ it("resets networkMessagesUpdateById in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore(["XHR GET request"]);
+
+ const updatePacket = stubPackets.get("XHR GET request update");
+ dispatch(actions.networkMessageUpdates([updatePacket], null));
+
+ let networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(!!Object.keys(networkUpdates).length).toBe(true);
+
+ dispatch(actions.messagesClear());
+
+ networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(Object.keys(networkUpdates).length).toBe(0);
+ });
+
+ it("cleans the networkMessagesUpdateById property when messages are pruned", () => {
+ const { dispatch, getState } = setupStore([], {
+ storeOptions: {
+ logLimit: 3,
+ },
+ });
+
+ // Add 3 network messages and their updates
+ let packet = clonePacket(stubPackets.get("XHR GET request"));
+ const updatePacket = clonePacket(
+ stubPackets.get("XHR GET request update")
+ );
+ packet.actor = "message1";
+ updatePacket.actor = "message1";
+ dispatch(actions.messagesAdd([packet]));
+ dispatch(actions.networkMessageUpdates([updatePacket], null));
+
+ packet.actor = "message2";
+ updatePacket.actor = "message2";
+ dispatch(actions.messagesAdd([packet]));
+ dispatch(actions.networkMessageUpdates([updatePacket], null));
+
+ packet.actor = "message3";
+ updatePacket.actor = "message3";
+ dispatch(actions.messagesAdd([packet]));
+ dispatch(actions.networkMessageUpdates([updatePacket], null));
+
+ // Check that we have the expected data.
+ const messages = getMutableMessagesById(getState());
+ const [
+ firstNetworkMessageId,
+ secondNetworkMessageId,
+ thirdNetworkMessageId,
+ ] = [...messages.keys()];
+
+ let networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(Object.keys(networkUpdates)).toEqual([
+ firstNetworkMessageId,
+ secondNetworkMessageId,
+ thirdNetworkMessageId,
+ ]);
+
+ // This addition will remove the first network message.
+ packet = stubPackets.get("console.log(undefined)");
+ dispatch(actions.messagesAdd([packet]));
+
+ networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(Object.keys(networkUpdates)).toEqual([
+ secondNetworkMessageId,
+ thirdNetworkMessageId,
+ ]);
+
+ // This addition will remove the second network message.
+ packet = stubPackets.get("console.log('foobar', 'test')");
+ dispatch(actions.messagesAdd([packet]));
+
+ networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(Object.keys(networkUpdates)).toEqual([thirdNetworkMessageId]);
+
+ // This addition will remove the last network message.
+ packet = stubPackets.get("console.log(undefined)");
+ dispatch(actions.messagesAdd([packet]));
+
+ // networkMessageUpdateById should now be empty.
+ networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(Object.keys(networkUpdates)).toEqual([]);
+ });
+ });
+
+ describe("cssMessagesMatchingElements", () => {
+ it("resets cssMessagesMatchingElements in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore([
+ `Unknown property ‘such-unknown-property’. Declaration dropped.`,
+ ]);
+
+ const data = Symbol();
+ dispatch({
+ type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
+ id: getFirstMessage(getState()).id,
+ elements: data,
+ });
+
+ const matchingElements = getAllCssMessagesMatchingElements(getState());
+ expect(matchingElements.size).toBe(1);
+ expect(matchingElements.get(getFirstMessage(getState()).id)).toBe(data);
+
+ dispatch(actions.messagesClear());
+
+ expect(getAllCssMessagesMatchingElements(getState()).size).toBe(0);
+ });
+
+ it("cleans the cssMessagesMatchingElements property when messages are pruned", () => {
+ const { dispatch, getState } = setupStore([], {
+ storeOptions: {
+ logLimit: 2,
+ },
+ });
+
+ // Add 2 css warnings message and their associated data.
+ dispatch(
+ actions.messagesAdd([
+ stubPackets.get(
+ `Unknown property ‘such-unknown-property’. Declaration dropped.`
+ ),
+ ])
+ );
+ dispatch(
+ actions.messagesAdd([
+ stubPackets.get(
+ `Error in parsing value for ‘padding-top’. Declaration dropped.`
+ ),
+ ])
+ );
+
+ const messages = getMutableMessagesById(getState());
+
+ const data1 = Symbol();
+ const data2 = Symbol();
+ const [id1, id2] = [...messages.keys()];
+
+ dispatch({
+ type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
+ id: id1,
+ elements: data1,
+ });
+ dispatch({
+ type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
+ id: id2,
+ elements: data2,
+ });
+
+ let matchingElements = getAllCssMessagesMatchingElements(getState());
+ expect(matchingElements.size).toBe(2);
+
+ // This addition will remove the first css warning.
+ dispatch(
+ actions.messagesAdd([stubPackets.get("console.log(undefined)")])
+ );
+
+ matchingElements = getAllCssMessagesMatchingElements(getState());
+ expect(matchingElements.size).toBe(1);
+ expect(matchingElements.get(id2)).toBe(data2);
+
+ // This addition will remove the second css warning.
+ dispatch(
+ actions.messagesAdd([stubPackets.get("console.log('foobar', 'test')")])
+ );
+
+ expect(getAllCssMessagesMatchingElements(getState()).size).toBe(0);
+ });
+ });
+
+ describe("messagesAdd", () => {
+ it("still log repeated message over logLimit, but only repeated ones", () => {
+ // Log two distinct messages
+ const key1 = "console.log('foobar', 'test')";
+ const key2 = "console.log(undefined)";
+ const { dispatch, getState } = setupStore([key1, key2], {
+ storeOptions: {
+ logLimit: 2,
+ },
+ });
+
+ // Then repeat the last one two times and log the first one again
+ const packet1 = clonePacket(stubPackets.get(key2));
+ const packet2 = clonePacket(stubPackets.get(key2));
+ const packet3 = clonePacket(stubPackets.get(key1));
+
+ // Repeat ID must be the same even if the timestamp is different.
+ packet1.message.timeStamp = packet1.message.timeStamp + 1;
+ packet2.message.timeStamp = packet2.message.timeStamp + 2;
+ packet3.message.timeStamp = packet3.message.timeStamp + 3;
+ dispatch(actions.messagesAdd([packet1, packet2, packet3]));
+
+ // There is still only two messages being logged,
+ const messages = getMutableMessagesById(getState());
+ expect(messages.size).toBe(2);
+
+ // the second one being repeated 3 times
+ const repeat = getAllRepeatById(getState());
+ expect(repeat[getFirstMessage(getState()).id]).toBe(3);
+ expect(repeat[getLastMessage(getState()).id]).toBe(undefined);
+ });
+ });
+
+ describe("messageRemove", () => {
+ it("removes the message from the store", () => {
+ const { dispatch, getState } = setupStore([
+ "console.trace()",
+ "console.log(undefined)",
+ "console.trace()",
+ "console.log(undefined)",
+ ]);
+
+ let expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(2);
+
+ const secondTraceMessage = getMessageAt(getState(), 2);
+ dispatch(actions.messageRemove(secondTraceMessage.id));
+
+ const messages = getMutableMessagesById(getState());
+ const { mutableMessagesOrder } = getState().messages;
+ // The messages was removed
+ expect(messages.size).toBe(3);
+ expect(mutableMessagesOrder.length).toBe(3);
+
+ // Its id was removed from the messagesUI property as well
+ expanded = getAllMessagesUiById(getState());
+ expect(expanded.length).toBe(1);
+ expect(expanded.includes(secondTraceMessage.id)).toBeFalsy();
+ });
+ });
+
+ describe("disabledMessagesById", () => {
+ it("adds messages ids to disabledMessagesById when message disable action is called", () => {
+ const { dispatch, getState } = setupStore();
+
+ dispatch(actions.messagesDisable(["message1", "message2"]));
+
+ const disabledMessages = getAllDisabledMessagesById(getState());
+ expect(disabledMessages).toEqual(["message1", "message2"]);
+ });
+
+ it("clears disabledMessagesById in response to MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore();
+
+ dispatch(actions.messagesDisable(["message1", "message2"]));
+
+ let disabledMessages = getAllDisabledMessagesById(getState());
+ expect(disabledMessages).toEqual(["message1", "message2"]);
+
+ dispatch(actions.messagesClear());
+
+ disabledMessages = getAllDisabledMessagesById(getState());
+ expect(disabledMessages).toEqual([]);
+ });
+
+ it("remove message id from disabledMessagesById when the message is removed", () => {
+ const { dispatch, getState } = setupStore(
+ [
+ "console.log('foobar', 'test')",
+ "XHR GET request update",
+ "console.log(undefined)",
+ ],
+ {
+ actions,
+ storeOptions: {
+ logLimit: 3,
+ },
+ }
+ );
+
+ // This is `console.log('foobar', 'test'`
+ const firstMessageId = getMessageAt(getState(), 0).id;
+ // This is for `XHR GET request update`
+ const secondMessageId = getMessageAt(getState(), 1).id;
+
+ dispatch(actions.messagesDisable([firstMessageId, secondMessageId]));
+
+ let disabledMessages = getAllDisabledMessagesById(getState());
+ expect(disabledMessages).toEqual([firstMessageId, secondMessageId]);
+
+ // Adding a new message should prune the first(oldest) message and should
+ // remove its id from the disabled messages list.
+ const packet = stubPackets.get("GET request");
+ dispatch(actions.messagesAdd([packet]));
+
+ disabledMessages = getAllDisabledMessagesById(getState());
+ expect(disabledMessages).toEqual([secondMessageId]);
+ });
+ });
+});
diff --git a/devtools/client/webconsole/test/node/store/network-messages.test.js b/devtools/client/webconsole/test/node/store/network-messages.test.js
new file mode 100644
index 0000000000..1daba02f2d
--- /dev/null
+++ b/devtools/client/webconsole/test/node/store/network-messages.test.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {
+ getAllNetworkMessagesUpdateById,
+} = require("resource://devtools/client/webconsole/selectors/messages.js");
+const {
+ setupActions,
+ setupStore,
+ clonePacket,
+} = require("resource://devtools/client/webconsole/test/node/helpers.js");
+const {
+ stubPackets,
+} = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
+
+const expect = require("expect");
+
+describe("Network message reducer:", () => {
+ let actions;
+ let getState;
+ let dispatch;
+
+ beforeAll(() => {
+ actions = setupActions();
+ });
+
+ beforeEach(() => {
+ const store = setupStore();
+
+ getState = store.getState;
+ dispatch = store.dispatch;
+
+ const packet = clonePacket(stubPackets.get("GET request"));
+ const updatePacket = clonePacket(stubPackets.get("GET request update"));
+
+ packet.actor = "message1";
+ updatePacket.actor = "message1";
+ dispatch(actions.messagesAdd([packet]));
+ dispatch(actions.networkMessageUpdates([updatePacket], null));
+ });
+
+ describe("networkMessagesUpdateById", () => {
+ it("adds fetched HTTP request headers", () => {
+ const headers = {
+ headers: [],
+ };
+
+ dispatch(
+ actions.networkUpdateRequests([
+ {
+ id: "message1",
+ data: {
+ requestHeaders: headers,
+ },
+ },
+ ])
+ );
+
+ const networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(networkUpdates.message1.requestHeaders).toBe(headers);
+ });
+
+ it("makes sure multiple HTTP updates of same request does not override", () => {
+ dispatch(
+ actions.networkUpdateRequests([
+ {
+ id: "message1",
+ data: {
+ stacktrace: [{}],
+ },
+ },
+ {
+ id: "message1",
+ data: {
+ requestHeaders: { headers: [] },
+ },
+ },
+ ])
+ );
+
+ const networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(networkUpdates.message1.requestHeaders).toNotBe(undefined);
+ expect(networkUpdates.message1.stacktrace).toNotBe(undefined);
+ });
+
+ it("adds fetched HTTP security info", () => {
+ const securityInfo = {
+ state: "insecure",
+ };
+
+ dispatch(
+ actions.networkUpdateRequests([
+ {
+ id: "message1",
+ data: {
+ securityInfo,
+ },
+ },
+ ])
+ );
+
+ const networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(networkUpdates.message1.securityInfo).toBe(securityInfo);
+ expect(networkUpdates.message1.securityState).toBe("insecure");
+ });
+
+ it("adds fetched HTTP post data", () => {
+ const uploadHeaders = Symbol();
+ const requestPostData = {
+ postData: {
+ text: "",
+ },
+ uploadHeaders,
+ };
+
+ dispatch(
+ actions.networkUpdateRequests([
+ {
+ id: "message1",
+ data: {
+ requestPostData,
+ },
+ },
+ ])
+ );
+
+ const { message1 } = getAllNetworkMessagesUpdateById(getState());
+ expect(message1.requestPostData).toBe(requestPostData);
+ expect(message1.requestHeadersFromUploadStream).toBe(uploadHeaders);
+ });
+ });
+});
diff --git a/devtools/client/webconsole/test/node/store/private-messages.test.js b/devtools/client/webconsole/test/node/store/private-messages.test.js
new file mode 100644
index 0000000000..762f2ff3df
--- /dev/null
+++ b/devtools/client/webconsole/test/node/store/private-messages.test.js
@@ -0,0 +1,234 @@
+/* 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 {
+ getAllMessagesUiById,
+ getAllCssMessagesMatchingElements,
+ getAllNetworkMessagesUpdateById,
+ getAllRepeatById,
+ getCurrentGroup,
+ getGroupsById,
+ getMutableMessagesById,
+ getVisibleMessages,
+} = require("resource://devtools/client/webconsole/selectors/messages.js");
+const {
+ getFirstMessage,
+ getLastMessage,
+ getPrivatePacket,
+ setupActions,
+ setupStore,
+} = require("resource://devtools/client/webconsole/test/node/helpers.js");
+const {
+ stubPackets,
+} = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
+const {
+ CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
+} = require("resource://devtools/client/webconsole/constants.js");
+
+const expect = require("expect");
+
+describe("private messages", () => {
+ let actions;
+ beforeAll(() => {
+ actions = setupActions();
+ });
+
+ it("removes private messages on PRIVATE_MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore();
+
+ dispatch(
+ actions.messagesAdd([
+ getPrivatePacket("console.trace()"),
+ stubPackets.get("console.log('mymap')"),
+ getPrivatePacket("console.log(undefined)"),
+ getPrivatePacket("GET request"),
+ ])
+ );
+
+ let state = getState();
+ const messages = getMutableMessagesById(state);
+ expect(messages.size).toBe(4);
+
+ dispatch(actions.privateMessagesClear());
+
+ state = getState();
+ expect(getMutableMessagesById(state).size).toBe(1);
+ expect(getVisibleMessages(state).length).toBe(1);
+ });
+
+ it("cleans messagesUiById on PRIVATE_MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore();
+
+ dispatch(
+ actions.messagesAdd([
+ getPrivatePacket("console.trace()"),
+ stubPackets.get("console.trace()"),
+ ])
+ );
+
+ let state = getState();
+ expect(getAllMessagesUiById(state).length).toBe(2);
+
+ dispatch(actions.privateMessagesClear());
+
+ state = getState();
+ expect(getAllMessagesUiById(state).length).toBe(1);
+ });
+
+ it("cleans repeatsById on PRIVATE_MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore();
+
+ dispatch(
+ actions.messagesAdd([
+ getPrivatePacket("console.log(undefined)"),
+ getPrivatePacket("console.log(undefined)"),
+ stubPackets.get("console.log(undefined)"),
+ stubPackets.get("console.log(undefined)"),
+ ])
+ );
+
+ let state = getState();
+ expect(getAllRepeatById(state)).toEqual({
+ [getFirstMessage(state).id]: 2,
+ [getLastMessage(state).id]: 2,
+ });
+
+ dispatch(actions.privateMessagesClear());
+
+ state = getState();
+ expect(Object.keys(getAllRepeatById(state)).length).toBe(1);
+ expect(getAllRepeatById(state)).toEqual({
+ [getFirstMessage(state).id]: 2,
+ });
+ });
+
+ it("cleans cssMessagesMatchingElements on PRIVATE_MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore();
+
+ dispatch(
+ actions.messagesAdd([
+ getPrivatePacket(
+ `Unknown property ‘such-unknown-property’. Declaration dropped.`
+ ),
+ stubPackets.get(
+ `Error in parsing value for ‘padding-top’. Declaration dropped.`
+ ),
+ ])
+ );
+
+ const privateData = Symbol("privateData");
+ const publicData = Symbol("publicData");
+
+ dispatch({
+ type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
+ id: getFirstMessage(getState()).id,
+ elements: privateData,
+ });
+
+ dispatch({
+ type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
+ id: getLastMessage(getState()).id,
+ elements: publicData,
+ });
+
+ let state = getState();
+ expect(getAllCssMessagesMatchingElements(state).size).toBe(2);
+
+ dispatch(actions.privateMessagesClear());
+
+ state = getState();
+ expect(getAllCssMessagesMatchingElements(state).size).toBe(1);
+ expect(
+ getAllCssMessagesMatchingElements(state).get(
+ getFirstMessage(getState()).id
+ )
+ ).toBe(publicData);
+ });
+
+ it("cleans group properties on PRIVATE_MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore();
+ dispatch(
+ actions.messagesAdd([
+ stubPackets.get("console.group()"),
+ getPrivatePacket("console.group()"),
+ ])
+ );
+
+ let state = getState();
+ const publicMessageId = getFirstMessage(state).id;
+ const privateMessageId = getLastMessage(state).id;
+ expect(getCurrentGroup(state)).toBe(privateMessageId);
+ expect(getGroupsById(state).size).toBe(2);
+
+ dispatch(actions.privateMessagesClear());
+
+ state = getState();
+ expect(getGroupsById(state).size).toBe(1);
+ expect(getGroupsById(state).has(publicMessageId)).toBe(true);
+ expect(getCurrentGroup(state)).toBe(publicMessageId);
+ });
+
+ it("cleans networkMessagesUpdateById on PRIVATE_MESSAGES_CLEAR action", () => {
+ const { dispatch, getState } = setupStore();
+
+ const publicActor = "network/public";
+ const privateActor = "network/private";
+ const publicPacket = {
+ ...stubPackets.get("GET request"),
+ actor: publicActor,
+ };
+ const privatePacket = {
+ ...getPrivatePacket("XHR GET request"),
+ actor: privateActor,
+ };
+
+ // We need to reassign the timeStamp of the packet to guarantee the order.
+ publicPacket.timeStamp = publicPacket.timeStamp + 1;
+ privatePacket.timeStamp = privatePacket.timeStamp + 2;
+
+ dispatch(actions.messagesAdd([publicPacket, privatePacket]));
+
+ let networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(Object.keys(networkUpdates)).toEqual([publicActor, privateActor]);
+
+ dispatch(actions.privateMessagesClear());
+
+ networkUpdates = getAllNetworkMessagesUpdateById(getState());
+ expect(Object.keys(networkUpdates)).toEqual([publicActor]);
+ });
+
+ it("releases private backend actors on PRIVATE_MESSAGES_CLEAR action", () => {
+ const releasedActors = [];
+ const { dispatch, getState } = setupStore([]);
+ const mockFrontRelease = function () {
+ releasedActors.push(this.actorID);
+ };
+
+ const publicPacket = stubPackets.get(
+ "console.log('myarray', ['red', 'green', 'blue'])"
+ );
+ const privatePacket = getPrivatePacket("console.log('mymap')");
+
+ publicPacket.message.arguments[1].release = mockFrontRelease;
+ privatePacket.message.arguments[1].release = mockFrontRelease;
+
+ // Add a log message.
+ dispatch(actions.messagesAdd([publicPacket, privatePacket]));
+
+ const firstMessage = getFirstMessage(getState());
+ const firstMessageActor = firstMessage.parameters[1].actorID;
+
+ const lastMessage = getLastMessage(getState());
+ const lastMessageActor = lastMessage.parameters[1].actorID;
+
+ // Kick-off the actor release.
+ dispatch(actions.privateMessagesClear());
+
+ expect(releasedActors.length).toBe(1);
+ expect(releasedActors).toInclude(lastMessageActor);
+ expect(releasedActors).toNotInclude(firstMessageActor);
+ });
+});
diff --git a/devtools/client/webconsole/test/node/store/release-actors.test.js b/devtools/client/webconsole/test/node/store/release-actors.test.js
new file mode 100644
index 0000000000..d2f6246cab
--- /dev/null
+++ b/devtools/client/webconsole/test/node/store/release-actors.test.js
@@ -0,0 +1,172 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const {
+ getFirstMessage,
+ setupActions,
+ setupStore,
+} = require("resource://devtools/client/webconsole/test/node/helpers.js");
+
+const {
+ stubPackets,
+} = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
+const expect = require("expect");
+
+describe("Release actor enhancer:", () => {
+ let actions;
+
+ beforeAll(() => {
+ actions = setupActions();
+ });
+
+ describe("release", () => {
+ it("releases backend actors when limit reached adding a single message", () => {
+ const logLimit = 100;
+ const releasedActors = [];
+ const mockFrontRelease = function () {
+ releasedActors.push(this.actorID);
+ };
+
+ const { dispatch, getState } = setupStore([], {
+ storeOptions: { logLimit },
+ });
+
+ // Add a log message.
+ const packet = stubPackets.get(
+ "console.log('myarray', ['red', 'green', 'blue'])"
+ );
+ packet.message.arguments[1].release = mockFrontRelease;
+ dispatch(actions.messagesAdd([packet]));
+
+ const firstMessage = getFirstMessage(getState());
+ const firstMessageActor = firstMessage.parameters[1].actorID;
+
+ // Add an evaluation result message (see Bug 1408321).
+ const evaluationResultPacket = stubPackets.get("new Date(0)");
+ evaluationResultPacket.result.release = mockFrontRelease;
+ dispatch(actions.messagesAdd([evaluationResultPacket]));
+ const secondMessageActor = evaluationResultPacket.result.actorID;
+
+ const logCount = logLimit + 1;
+ const assertPacket = stubPackets.get(
+ "console.assert(false, {message: 'foobar'})"
+ );
+ assertPacket.message.arguments[0].release = mockFrontRelease;
+ const thirdMessageActor = assertPacket.message.arguments[0].actorID;
+
+ for (let i = 1; i <= logCount; i++) {
+ assertPacket.message.arguments.push(`message num ${i}`);
+ dispatch(actions.messagesAdd([assertPacket]));
+ }
+
+ expect(releasedActors.length).toBe(3);
+ expect(releasedActors).toInclude(firstMessageActor);
+ expect(releasedActors).toInclude(secondMessageActor);
+ expect(releasedActors).toInclude(thirdMessageActor);
+ });
+
+ it("releases backend actors when limit reached adding multiple messages", () => {
+ const logLimit = 100;
+ const releasedActors = [];
+ const { dispatch, getState } = setupStore([], {
+ storeOptions: { logLimit },
+ });
+
+ const mockFrontRelease = function () {
+ releasedActors.push(this.actorID);
+ };
+
+ // Add a log message.
+ const logPacket = stubPackets.get(
+ "console.log('myarray', ['red', 'green', 'blue'])"
+ );
+ logPacket.message.arguments[1].release = mockFrontRelease;
+ dispatch(actions.messagesAdd([logPacket]));
+
+ const firstMessage = getFirstMessage(getState());
+ const firstMessageActor = firstMessage.parameters[1].actorID;
+
+ // Add an evaluation result message (see Bug 1408321).
+ const evaluationResultPacket = stubPackets.get("new Date(0)");
+ evaluationResultPacket.result.release = mockFrontRelease;
+ dispatch(actions.messagesAdd([evaluationResultPacket]));
+ const secondMessageActor = evaluationResultPacket.result.actorID;
+
+ // Add an assertion message.
+ const assertPacket = stubPackets.get(
+ "console.assert(false, {message: 'foobar'})"
+ );
+ assertPacket.message.arguments[0].release = mockFrontRelease;
+ dispatch(actions.messagesAdd([assertPacket]));
+ const thirdMessageActor = assertPacket.message.arguments[0].actorID;
+
+ // Add ${logLimit} messages so we prune the ones we added before.
+ const packets = [];
+ // Alternate between 2 packets so we don't trigger the repeat message mechanism.
+ const oddPacket = stubPackets.get("console.log(undefined)");
+ const evenPacket = stubPackets.get("console.log('foobar', 'test')");
+ for (let i = 0; i < logLimit; i++) {
+ const packet = i % 2 === 0 ? evenPacket : oddPacket;
+ packets.push(packet);
+ }
+
+ // Add all the packets at once. This will prune the first 3 messages.
+ dispatch(actions.messagesAdd(packets));
+
+ expect(releasedActors.length).toBe(3);
+ expect(releasedActors).toInclude(firstMessageActor);
+ expect(releasedActors).toInclude(secondMessageActor);
+ expect(releasedActors).toInclude(thirdMessageActor);
+ });
+
+ it("properly releases backend actors after clear", () => {
+ const releasedActors = [];
+ const { dispatch, getState } = setupStore([]);
+
+ const mockFrontRelease = function () {
+ releasedActors.push(this.actorID);
+ };
+
+ // Add a log message.
+ const logPacket = stubPackets.get(
+ "console.log('myarray', ['red', 'green', 'blue'])"
+ );
+ logPacket.message.arguments[1].release = mockFrontRelease;
+ dispatch(actions.messagesAdd([logPacket]));
+
+ const firstMessage = getFirstMessage(getState());
+ const firstMessageActor = firstMessage.parameters[1].actorID;
+
+ // Add an assertion message.
+ const assertPacket = stubPackets.get(
+ "console.assert(false, {message: 'foobar'})"
+ );
+ assertPacket.message.arguments[0].release = mockFrontRelease;
+ dispatch(actions.messagesAdd([assertPacket]));
+ const secondMessageActor = assertPacket.message.arguments[0].actorID;
+
+ // Add an evaluation result message (see Bug 1408321).
+ const evaluationResultPacket = stubPackets.get("new Date(0)");
+ evaluationResultPacket.result.release = mockFrontRelease;
+ dispatch(actions.messagesAdd([evaluationResultPacket]));
+ const thirdMessageActor = evaluationResultPacket.result.actorID;
+
+ // Add a message with a long string messageText property.
+ const longStringPacket = stubPackets.get("TypeError longString message");
+ longStringPacket.pageError.errorMessage.release = mockFrontRelease;
+ dispatch(actions.messagesAdd([longStringPacket]));
+ const fourthMessageActor =
+ longStringPacket.pageError.errorMessage.actorID;
+
+ // Kick-off the actor release.
+ dispatch(actions.messagesClear());
+
+ expect(releasedActors.length).toBe(4);
+ expect(releasedActors).toInclude(firstMessageActor);
+ expect(releasedActors).toInclude(secondMessageActor);
+ expect(releasedActors).toInclude(thirdMessageActor);
+ expect(releasedActors).toInclude(fourthMessageActor);
+ });
+ });
+});
diff --git a/devtools/client/webconsole/test/node/store/search.test.js b/devtools/client/webconsole/test/node/store/search.test.js
new file mode 100644
index 0000000000..7a0f7ebe22
--- /dev/null
+++ b/devtools/client/webconsole/test/node/store/search.test.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const expect = require("expect");
+
+const actions = require("resource://devtools/client/webconsole/actions/index.js");
+const {
+ getVisibleMessages,
+} = require("resource://devtools/client/webconsole/selectors/messages.js");
+const {
+ setupStore,
+} = require("resource://devtools/client/webconsole/test/node/helpers.js");
+
+describe("Searching in grips", () => {
+ let store;
+
+ beforeEach(() => {
+ store = prepareBaseStore();
+ store.dispatch(actions.filtersClear());
+ });
+
+ describe("Search in table & array & object props", () => {
+ it("matches on value grips", () => {
+ store.dispatch(actions.filterTextSet("red"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(3);
+ });
+ });
+
+ describe("Search in object value", () => {
+ it("matches on value grips", () => {
+ store.dispatch(actions.filterTextSet("redValue"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(1);
+ });
+ });
+
+ describe("Search in regex", () => {
+ it("matches on value grips", () => {
+ store.dispatch(actions.filterTextSet("a.b.c"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(1);
+ });
+ });
+
+ describe("Search in map values", () => {
+ it("matches on value grips", () => {
+ store.dispatch(actions.filterTextSet("value1"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(1);
+ });
+ });
+
+ describe("Search in map keys", () => {
+ it("matches on value grips", () => {
+ store.dispatch(actions.filterTextSet("key1"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(1);
+ });
+ });
+
+ describe("Search in text", () => {
+ it("matches on value grips", () => {
+ store.dispatch(actions.filterTextSet("myobj"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(1);
+ });
+ });
+
+ describe("Search in logs with net messages", () => {
+ it("matches on network messages", () => {
+ store.dispatch(actions.filterToggle("net"));
+ store.dispatch(actions.filterTextSet("get"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(1);
+ });
+ });
+
+ describe("Search in frame", () => {
+ it("matches on file name", () => {
+ store.dispatch(actions.filterTextSet("test-console-api.html:1:35"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(7);
+ });
+
+ it("do not match on full url", () => {
+ store.dispatch(
+ actions.filterTextSet("https://example.com/browser/devtools")
+ );
+ expect(getVisibleMessages(store.getState()).length).toEqual(0);
+ });
+ });
+
+ describe("Reverse search", () => {
+ it("reverse matches on value grips", () => {
+ store.dispatch(actions.filterTextSet("-red"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(6);
+ });
+
+ it("reverse matches on file name", () => {
+ store.dispatch(actions.filterTextSet("-test-console-api.html:1:35"));
+ expect(getVisibleMessages(store.getState()).length).toEqual(2);
+ });
+ });
+});
+
+function prepareBaseStore() {
+ const store = setupStore([
+ "console.log('foobar', 'test')",
+ "console.warn('danger, will robinson!')",
+ "console.table(['red', 'green', 'blue']);",
+ "console.count('bar')",
+ "console.log('myarray', ['red', 'green', 'blue'])",
+ "console.log('myregex', /a.b.c/)",
+ "console.log('mymap')",
+ "console.log('myobject', {red: 'redValue', green: 'greenValue', blue: 'blueValue'});",
+ "GET request update",
+ ]);
+
+ return store;
+}
diff --git a/devtools/client/webconsole/test/node/store/ui.test.js b/devtools/client/webconsole/test/node/store/ui.test.js
new file mode 100644
index 0000000000..2786fb9a0f
--- /dev/null
+++ b/devtools/client/webconsole/test/node/store/ui.test.js
@@ -0,0 +1,119 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const expect = require("expect");
+
+const actions = require("resource://devtools/client/webconsole/actions/index.js");
+const {
+ setupStore,
+ getFirstMessage,
+ getLastMessage,
+} = require("resource://devtools/client/webconsole/test/node/helpers.js");
+const {
+ stubPackets,
+ stubPreparedMessages,
+} = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
+
+describe("Testing UI", () => {
+ let store;
+
+ beforeEach(() => {
+ store = setupStore();
+ });
+
+ describe("Toggle sidebar", () => {
+ it("sidebar is toggled on and off", () => {
+ const packet = stubPackets.get("inspect({a: 1})");
+ const message = stubPreparedMessages.get("inspect({a: 1})");
+ store.dispatch(actions.messagesAdd([packet]));
+
+ const { actorID } = message.parameters[0];
+ const messageId = getFirstMessage(store.getState()).id;
+ store.dispatch(actions.showMessageObjectInSidebar(actorID, messageId));
+
+ expect(store.getState().ui.sidebarVisible).toEqual(true);
+ store.dispatch(actions.sidebarClose());
+ expect(store.getState().ui.sidebarVisible).toEqual(false);
+ });
+ });
+
+ describe("Hide sidebar on clear", () => {
+ it("sidebar is hidden on clear", () => {
+ const packet = stubPackets.get("inspect({a: 1})");
+ const message = stubPreparedMessages.get("inspect({a: 1})");
+ store.dispatch(actions.messagesAdd([packet]));
+
+ const { actorID } = message.parameters[0];
+ const messageId = getFirstMessage(store.getState()).id;
+ store.dispatch(actions.showMessageObjectInSidebar(actorID, messageId));
+
+ expect(store.getState().ui.sidebarVisible).toEqual(true);
+ store.dispatch(actions.messagesClear());
+ expect(store.getState().ui.sidebarVisible).toEqual(false);
+ store.dispatch(actions.messagesClear());
+ expect(store.getState().ui.sidebarVisible).toEqual(false);
+ });
+ });
+
+ describe("Show object in sidebar", () => {
+ it("sidebar is shown with correct object", () => {
+ const packet = stubPackets.get("inspect({a: 1})");
+ const message = stubPreparedMessages.get("inspect({a: 1})");
+ store.dispatch(actions.messagesAdd([packet]));
+
+ const { actorID } = message.parameters[0];
+ const messageId = getFirstMessage(store.getState()).id;
+ store.dispatch(actions.showMessageObjectInSidebar(actorID, messageId));
+
+ expect(store.getState().ui.sidebarVisible).toEqual(true);
+ expect(store.getState().ui.frontInSidebar).toEqual(message.parameters[0]);
+ });
+
+ it("sidebar is not updated for the same object", () => {
+ const packet = stubPackets.get("inspect({a: 1})");
+ const message = stubPreparedMessages.get("inspect({a: 1})");
+ store.dispatch(actions.messagesAdd([packet]));
+
+ const { actorID } = message.parameters[0];
+ const messageId = getFirstMessage(store.getState()).id;
+ store.dispatch(actions.showMessageObjectInSidebar(actorID, messageId));
+
+ expect(store.getState().ui.sidebarVisible).toEqual(true);
+ expect(store.getState().ui.frontInSidebar).toEqual(message.parameters[0]);
+ const state = store.getState().ui;
+
+ store.dispatch(actions.showMessageObjectInSidebar(actorID, messageId));
+ expect(store.getState().ui).toEqual(state);
+ });
+
+ it("sidebar shown and updated for new object", () => {
+ const packet = stubPackets.get("inspect({a: 1})");
+ const message = stubPreparedMessages.get("inspect({a: 1})");
+ store.dispatch(actions.messagesAdd([packet]));
+
+ const { actorID } = message.parameters[0];
+ const messageId = getFirstMessage(store.getState()).id;
+ store.dispatch(actions.showMessageObjectInSidebar(actorID, messageId));
+
+ expect(store.getState().ui.sidebarVisible).toEqual(true);
+ expect(store.getState().ui.frontInSidebar).toEqual(message.parameters[0]);
+
+ const newPacket = stubPackets.get("new Date(0)");
+ const newMessage = stubPreparedMessages.get("new Date(0)");
+ store.dispatch(actions.messagesAdd([newPacket]));
+
+ const newActorID = newMessage.parameters[0].actorID;
+ const newMessageId = getLastMessage(store.getState()).id;
+ store.dispatch(
+ actions.showMessageObjectInSidebar(newActorID, newMessageId)
+ );
+
+ expect(store.getState().ui.sidebarVisible).toEqual(true);
+ expect(store.getState().ui.frontInSidebar).toEqual(
+ newMessage.parameters[0]
+ );
+ });
+ });
+});