summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/test/node/store/messages.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webconsole/test/node/store/messages.test.js')
-rw-r--r--devtools/client/webconsole/test/node/store/messages.test.js1305
1 files changed, 1305 insertions, 0 deletions
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]);
+ });
+ });
+});