From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../webconsole/test/node/store/filters.test.js | 345 ++++++ .../test/node/store/hidden-messages.test.js | 199 +++ .../webconsole/test/node/store/messages.test.js | 1305 ++++++++++++++++++++ .../test/node/store/network-messages.test.js | 133 ++ .../test/node/store/private-messages.test.js | 234 ++++ .../test/node/store/release-actors.test.js | 172 +++ .../webconsole/test/node/store/search.test.js | 115 ++ .../client/webconsole/test/node/store/ui.test.js | 119 ++ 8 files changed, 2622 insertions(+) create mode 100644 devtools/client/webconsole/test/node/store/filters.test.js create mode 100644 devtools/client/webconsole/test/node/store/hidden-messages.test.js create mode 100644 devtools/client/webconsole/test/node/store/messages.test.js create mode 100644 devtools/client/webconsole/test/node/store/network-messages.test.js create mode 100644 devtools/client/webconsole/test/node/store/private-messages.test.js create mode 100644 devtools/client/webconsole/test/node/store/release-actors.test.js create mode 100644 devtools/client/webconsole/test/node/store/search.test.js create mode 100644 devtools/client/webconsole/test/node/store/ui.test.js (limited to 'devtools/client/webconsole/test/node/store') 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] + ); + }); + }); +}); -- cgit v1.2.3