1
0
Fork 0
firefox/devtools/client/webconsole/test/node/store/messages.test.js
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

1315 lines
45 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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.timeStamp = 1;
packet2.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.columnNumber = packet.columnNumber + 1;
dispatch(actions.messagesAdd([packet]));
// Dispatch same packet with modified line number.
packet.lineNumber = packet.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("does not increment repeat after adding different Symbols", () => {
const { getState } = setupStore([
"console.log(Symbol.for('foo'))",
"console.log(Symbol.for('bar'))",
]);
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.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.arguments = [`group-1`];
dispatch(actions.messagesAdd([packetGroup]));
packetGroup.arguments = [`group-1-1`];
dispatch(actions.messagesAdd([packetGroup]));
packetGroup.arguments = [`group-1-1-1`];
dispatch(actions.messagesAdd([packetGroup]));
packet.arguments = [`message-in-group-1`];
dispatch(actions.messagesAdd([packet]));
packet.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.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.arguments = [`message-${i}-a`];
dispatch(actions.messagesAdd([packet]));
packet.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.arguments = [`group-${i}`];
dispatch(actions.messagesAdd([packetGroupCollapsed]));
packet.arguments = [`message-${i}-a`];
dispatch(actions.messagesAdd([packet]));
packet.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.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.timeStamp = naNpacket.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.timeStamp = naNpacket.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.timeStamp = packet1.timeStamp + 1;
packet2.timeStamp = packet2.timeStamp + 2;
packet3.timeStamp = packet3.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]);
});
});
});