tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

messages.test.js (47710B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 "use strict";
      4 
      5 const {
      6  getAllMessagesUiById,
      7  getAllCssMessagesMatchingElements,
      8  getAllNetworkMessagesUpdateById,
      9  getAllRepeatById,
     10  getAllDisabledMessagesById,
     11  getCurrentGroup,
     12  getGroupsById,
     13  getMutableMessagesById,
     14  getVisibleMessages,
     15 } = require("resource://devtools/client/webconsole/selectors/messages.js");
     16 
     17 const {
     18  clonePacket,
     19  getFirstMessage,
     20  getLastMessage,
     21  getMessageAt,
     22  setupActions,
     23  setupStore,
     24 } = require("resource://devtools/client/webconsole/test/node/helpers.js");
     25 const {
     26  stubPackets,
     27  stubPreparedMessages,
     28 } = require("resource://devtools/client/webconsole/test/node/fixtures/stubs/index.js");
     29 const {
     30  MESSAGE_TYPE,
     31  CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
     32 } = require("resource://devtools/client/webconsole/constants.js");
     33 const {
     34  createWarningGroupMessage,
     35 } = require("resource://devtools/client/webconsole/utils/messages.js");
     36 
     37 const expect = require("expect");
     38 
     39 describe("Message reducer:", () => {
     40  let actions;
     41 
     42  beforeAll(() => {
     43    actions = setupActions();
     44  });
     45 
     46  describe("mutableMessagesById", () => {
     47    it("adds a message to an empty store", () => {
     48      const { dispatch, getState } = setupStore();
     49 
     50      const packet = stubPackets.get("console.log('foobar', 'test')");
     51      dispatch(actions.messagesAdd([packet]));
     52 
     53      const message = stubPreparedMessages.get("console.log('foobar', 'test')");
     54 
     55      expect(getFirstMessage(getState())).toEqual(message);
     56    });
     57 
     58    it("increments repeat on a repeating log message", () => {
     59      const key1 = "console.log('foobar', 'test')";
     60      const { dispatch, getState } = setupStore([key1, key1], { actions });
     61 
     62      const packet = clonePacket(stubPackets.get(key1));
     63      const packet2 = clonePacket(packet);
     64 
     65      // Repeat ID must be the same even if the timestamp is different.
     66      packet.timeStamp += 1;
     67      packet2.timeStamp += 2;
     68      dispatch(actions.messagesAdd([packet, packet2]));
     69 
     70      let messages = getMutableMessagesById(getState());
     71 
     72      expect(messages.size).toBe(1);
     73      let repeat = getAllRepeatById(getState());
     74      expect(Object.keys(repeat).length).toBe(1);
     75      expect(repeat[getFirstMessage(getState()).id]).toBe(4);
     76 
     77      // Disable repeating messages
     78      dispatch(actions.groupSimilarMessagesToggle());
     79      const packet3 = clonePacket(packet);
     80      const packet4 = clonePacket(packet);
     81      packet3.timeStamp += 3;
     82      packet4.timeStamp += 4;
     83      dispatch(actions.messagesAdd([packet3, packet4]));
     84 
     85      messages = getMutableMessagesById(getState());
     86      // The 2 new messages were properly added
     87      expect(messages.size).toBe(3);
     88      repeat = getAllRepeatById(getState());
     89      // repeat didn't changed
     90      expect(Object.keys(repeat).length).toBe(1);
     91      expect(repeat[getFirstMessage(getState()).id]).toBe(4);
     92 
     93      // Re-enable repeating messages
     94      dispatch(actions.groupSimilarMessagesToggle());
     95      const packet5 = clonePacket(packet);
     96      const packet6 = clonePacket(packet);
     97      packet3.timeStamp += 5;
     98      packet4.timeStamp += 6;
     99      dispatch(actions.messagesAdd([packet5, packet6]));
    100 
    101      messages = getMutableMessagesById(getState());
    102      // The 2 new messages weren't added, we still have 3 messages
    103      expect(messages.size).toBe(3);
    104      repeat = getAllRepeatById(getState());
    105      // we now have 2 items in repeat
    106      expect(Object.keys(repeat).length).toBe(2);
    107      // the first one didn't change
    108      expect(repeat[getFirstMessage(getState()).id]).toBe(4);
    109      // another one was added for packet4, which is repeated 3 times (packet4, packet5 and packet6)
    110      expect(repeat[getLastMessage(getState()).id]).toBe(3);
    111    });
    112 
    113    it("doesn't increment repeat on same log message with different locations", () => {
    114      const key1 = "console.log('foobar', 'test')";
    115      const { dispatch, getState } = setupStore();
    116 
    117      const packet = clonePacket(stubPackets.get(key1));
    118 
    119      // Dispatch original packet.
    120      dispatch(actions.messagesAdd([packet]));
    121 
    122      // Dispatch same packet with modified column number.
    123      packet.columnNumber = packet.columnNumber + 1;
    124      dispatch(actions.messagesAdd([packet]));
    125 
    126      // Dispatch same packet with modified line number.
    127      packet.lineNumber = packet.lineNumber + 1;
    128      dispatch(actions.messagesAdd([packet]));
    129 
    130      const messages = getMutableMessagesById(getState());
    131 
    132      expect(messages.size).toBe(3);
    133 
    134      const repeat = getAllRepeatById(getState());
    135      expect(Object.keys(repeat).length).toBe(0);
    136    });
    137 
    138    it("increments repeat on a repeating css message", () => {
    139      const key1 =
    140        "Unknown property ‘such-unknown-property’.  Declaration dropped.";
    141      const { dispatch, getState } = setupStore([key1, key1]);
    142 
    143      const packet = clonePacket(stubPackets.get(key1));
    144 
    145      // Repeat ID must be the same even if the timestamp is different.
    146      packet.pageError.timeStamp = 1;
    147      dispatch(actions.messagesAdd([packet]));
    148      packet.pageError.timeStamp = 2;
    149      dispatch(actions.messagesAdd([packet]));
    150 
    151      const messages = getMutableMessagesById(getState());
    152 
    153      expect(messages.size).toBe(1);
    154 
    155      const repeat = getAllRepeatById(getState());
    156      expect(repeat[getFirstMessage(getState()).id]).toBe(4);
    157    });
    158 
    159    it("doesn't increment repeat on same css message with different locations", () => {
    160      const key1 =
    161        "Unknown property ‘such-unknown-property’.  Declaration dropped.";
    162      const { dispatch, getState } = setupStore();
    163 
    164      const packet = clonePacket(stubPackets.get(key1));
    165 
    166      // Dispatch original packet.
    167      dispatch(actions.messagesAdd([packet]));
    168 
    169      // Dispatch same packet with modified column number.
    170      packet.pageError.columnNumber = packet.pageError.columnNumber + 1;
    171      dispatch(actions.messagesAdd([packet]));
    172 
    173      // Dispatch same packet with modified line number.
    174      packet.pageError.lineNumber = packet.pageError.lineNumber + 1;
    175      dispatch(actions.messagesAdd([packet]));
    176 
    177      const messages = getMutableMessagesById(getState());
    178 
    179      expect(messages.size).toBe(3);
    180 
    181      const repeat = getAllRepeatById(getState());
    182      expect(Object.keys(repeat).length).toBe(0);
    183    });
    184 
    185    it("increments repeat on a repeating error message", () => {
    186      const key1 = "ReferenceError: asdf is not defined";
    187      const { dispatch, getState } = setupStore([key1, key1]);
    188 
    189      const packet = clonePacket(stubPackets.get(key1));
    190 
    191      // Repeat ID must be the same even if the timestamp is different.
    192      packet.pageError.timeStamp = 1;
    193      dispatch(actions.messagesAdd([packet]));
    194      packet.pageError.timeStamp = 2;
    195      dispatch(actions.messagesAdd([packet]));
    196 
    197      const messages = getMutableMessagesById(getState());
    198 
    199      expect(messages.size).toBe(1);
    200 
    201      const repeat = getAllRepeatById(getState());
    202      expect(repeat[getFirstMessage(getState()).id]).toBe(4);
    203    });
    204 
    205    it("does not increment repeat after closing a group", () => {
    206      const logKey = "console.log('foobar', 'test')";
    207      const { getState } = setupStore([
    208        logKey,
    209        logKey,
    210        "console.group('bar')",
    211        logKey,
    212        logKey,
    213        logKey,
    214        "console.groupEnd()",
    215        logKey,
    216      ]);
    217 
    218      const messages = getMutableMessagesById(getState());
    219 
    220      expect(messages.size).toBe(4);
    221      const repeat = getAllRepeatById(getState());
    222      expect(repeat[getFirstMessage(getState()).id]).toBe(2);
    223      expect(repeat[getMessageAt(getState(), 2).id]).toBe(3);
    224      expect(repeat[getLastMessage(getState()).id]).toBe(undefined);
    225    });
    226 
    227    it("doesn't increment undefined messages coming from different places", () => {
    228      const { getState } = setupStore(["console.log(undefined)", "undefined"]);
    229 
    230      const messages = getMutableMessagesById(getState());
    231      expect(messages.size).toBe(2);
    232 
    233      const repeat = getAllRepeatById(getState());
    234      expect(Object.keys(repeat).length).toBe(0);
    235    });
    236 
    237    it("doesn't increment successive falsy but different messages", () => {
    238      const { getState } = setupStore(
    239        ["console.log(NaN)", "console.log(undefined)", "console.log(null)"],
    240        { actions }
    241      );
    242 
    243      const messages = getMutableMessagesById(getState());
    244      expect(messages.size).toBe(3);
    245      const repeat = getAllRepeatById(getState());
    246      expect(Object.keys(repeat).length).toBe(0);
    247    });
    248 
    249    it("increment falsy messages when expected", () => {
    250      const { dispatch, getState } = setupStore();
    251 
    252      const nanPacket = stubPackets.get("console.log(NaN)");
    253      dispatch(actions.messagesAdd([nanPacket, nanPacket]));
    254      let messages = getMutableMessagesById(getState());
    255      expect(messages.size).toBe(1);
    256      let repeat = getAllRepeatById(getState());
    257      expect(repeat[getLastMessage(getState()).id]).toBe(2);
    258 
    259      const undefinedPacket = stubPackets.get("console.log(undefined)");
    260      dispatch(actions.messagesAdd([undefinedPacket, undefinedPacket]));
    261      messages = getMutableMessagesById(getState());
    262      expect(messages.size).toBe(2);
    263      repeat = getAllRepeatById(getState());
    264      expect(repeat[getLastMessage(getState()).id]).toBe(2);
    265 
    266      const nullPacket = stubPackets.get("console.log(null)");
    267      dispatch(actions.messagesAdd([nullPacket, nullPacket]));
    268      messages = getMutableMessagesById(getState());
    269      expect(messages.size).toBe(3);
    270      repeat = getAllRepeatById(getState());
    271      expect(repeat[getLastMessage(getState()).id]).toBe(2);
    272    });
    273 
    274    it("does not clobber a unique message", () => {
    275      const key1 = "console.log('foobar', 'test')";
    276      const { dispatch, getState } = setupStore([key1, key1]);
    277 
    278      const packet = stubPackets.get(key1);
    279      dispatch(actions.messagesAdd([packet]));
    280 
    281      const packet2 = stubPackets.get("console.log(undefined)");
    282      dispatch(actions.messagesAdd([packet2]));
    283 
    284      const messages = getMutableMessagesById(getState());
    285      expect(messages.size).toBe(2);
    286 
    287      const repeat = getAllRepeatById(getState());
    288      expect(repeat[getFirstMessage(getState()).id]).toBe(3);
    289      expect(repeat[getLastMessage(getState()).id]).toBe(undefined);
    290    });
    291 
    292    it("does not increment repeat after adding similar warning group", () => {
    293      const { dispatch, getState } = setupStore();
    294 
    295      // Mocking a warning message that would create a warning group
    296      const warningMessage = stubPreparedMessages.get(
    297        "ReferenceError: asdf is not defined"
    298      );
    299      warningMessage.messageText =
    300        "The resource at “https://evil.com” was blocked.";
    301      warningMessage.category = "cookieBlockedPermission";
    302 
    303      const type = MESSAGE_TYPE.CONTENT_BLOCKING_GROUP;
    304      const firstMessageId = `${warningMessage.type}-${warningMessage.innerWindowID}`;
    305      const message1 = createWarningGroupMessage(
    306        firstMessageId,
    307        type,
    308        warningMessage
    309      );
    310      const secondMessageId = `${warningMessage.type}-${
    311        warningMessage.innerWindowID + 10
    312      }`;
    313      const message2 = createWarningGroupMessage(
    314        secondMessageId,
    315        type,
    316        warningMessage
    317      );
    318 
    319      dispatch(actions.messagesAdd([message1, message2]));
    320 
    321      const messages = getMutableMessagesById(getState());
    322      expect(messages.size).toBe(2);
    323 
    324      const repeat = getAllRepeatById(getState());
    325      expect(Object.keys(repeat).length).toBe(0);
    326    });
    327 
    328    it("does not increment repeat after adding different Symbols", () => {
    329      const { getState } = setupStore([
    330        "console.log(Symbol.for('foo'))",
    331        "console.log(Symbol.for('bar'))",
    332      ]);
    333 
    334      const repeat = getAllRepeatById(getState());
    335      expect(Object.keys(repeat).length).toBe(0);
    336    });
    337 
    338    it("adds a message in response to console.clear()", () => {
    339      const { dispatch, getState } = setupStore([]);
    340 
    341      dispatch(actions.messagesAdd([stubPackets.get("console.clear()")]));
    342 
    343      const messages = getMutableMessagesById(getState());
    344 
    345      expect(messages.size).toBe(1);
    346      expect(getFirstMessage(getState()).parameters[0]).toBe(
    347        "Console was cleared."
    348      );
    349    });
    350 
    351    it("clears the messages list in response to MESSAGES_CLEAR action", () => {
    352      const { dispatch, getState } = setupStore([
    353        "console.log('foobar', 'test')",
    354        "console.log('foobar', 'test')",
    355        "console.log(undefined)",
    356        "console.table(['red', 'green', 'blue']);",
    357        "console.group('bar')",
    358      ]);
    359 
    360      dispatch(actions.messagesClear());
    361 
    362      const state = getState();
    363      expect(getMutableMessagesById(state).size).toBe(0);
    364      expect(getVisibleMessages(state).length).toBe(0);
    365      expect(getAllMessagesUiById(state).length).toBe(0);
    366      expect(getGroupsById(state).size).toBe(0);
    367      expect(getAllCssMessagesMatchingElements(state).size).toBe(0);
    368      expect(getCurrentGroup(state)).toBe(null);
    369      expect(getAllRepeatById(state)).toEqual({});
    370      expect(state.messages.mutableMessagesOrder).toEqual([]);
    371    });
    372 
    373    it("cleans the repeatsById object when messages are pruned", () => {
    374      const { dispatch, getState } = setupStore(
    375        [
    376          "console.log('foobar', 'test')",
    377          "console.log('foobar', 'test')",
    378          "console.log(undefined)",
    379          "console.log(undefined)",
    380        ],
    381        {
    382          actions,
    383          storeOptions: {
    384            logLimit: 2,
    385          },
    386        }
    387      );
    388 
    389      // Check that we have the expected data.
    390      let repeats = getAllRepeatById(getState());
    391      expect(Object.keys(repeats).length).toBe(2);
    392      const lastMessageId = getLastMessage(getState()).id;
    393 
    394      // This addition will prune the first message out of the store.
    395      let packet = stubPackets.get("console.log('foobar', 'test')");
    396      dispatch(actions.messagesAdd([packet]));
    397 
    398      repeats = getAllRepeatById(getState());
    399 
    400      // There should be only the data for the "undefined" message.
    401      expect(Object.keys(repeats)).toEqual([lastMessageId]);
    402      expect(Object.keys(repeats).length).toBe(1);
    403      expect(repeats[lastMessageId]).toBe(2);
    404 
    405      // This addition will prune the first message out of the store.
    406      packet = stubPackets.get("console.log(undefined)");
    407      dispatch(actions.messagesAdd([packet]));
    408 
    409      // repeatById should now be empty.
    410      expect(getAllRepeatById(getState())).toEqual({});
    411    });
    412 
    413    it("properly limits number of messages", () => {
    414      const logLimit = 1000;
    415      const { dispatch, getState } = setupStore([], {
    416        storeOptions: {
    417          logLimit,
    418        },
    419      });
    420 
    421      const packet = clonePacket(stubPackets.get("console.log(undefined)"));
    422 
    423      for (let i = 1; i <= logLimit + 2; i++) {
    424        packet.arguments = [`message num ${i}`];
    425        dispatch(actions.messagesAdd([packet]));
    426      }
    427 
    428      const messages = getMutableMessagesById(getState());
    429      expect(messages.size).toBe(logLimit);
    430      expect(getFirstMessage(getState()).parameters[0]).toBe(`message num 3`);
    431      expect(getLastMessage(getState()).parameters[0]).toBe(
    432        `message num ${logLimit + 2}`
    433      );
    434 
    435      const { mutableMessagesOrder } = getState().messages;
    436      expect(mutableMessagesOrder.length).toBe(logLimit);
    437    });
    438 
    439    it("properly limits number of messages when there are nested groups", () => {
    440      const logLimit = 1000;
    441      const { dispatch, getState } = setupStore([], {
    442        storeOptions: {
    443          logLimit,
    444        },
    445      });
    446 
    447      const packet = clonePacket(stubPackets.get("console.log(undefined)"));
    448      const packetGroup = clonePacket(stubPackets.get("console.group('bar')"));
    449      const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
    450 
    451      packetGroup.arguments = [`group-1`];
    452      dispatch(actions.messagesAdd([packetGroup]));
    453      packetGroup.arguments = [`group-1-1`];
    454      dispatch(actions.messagesAdd([packetGroup]));
    455      packetGroup.arguments = [`group-1-1-1`];
    456      dispatch(actions.messagesAdd([packetGroup]));
    457      packet.arguments = [`message-in-group-1`];
    458      dispatch(actions.messagesAdd([packet]));
    459      packet.arguments = [`message-in-group-2`];
    460      dispatch(actions.messagesAdd([packet]));
    461      // Closing group-1-1-1
    462      dispatch(actions.messagesAdd([packetGroupEnd]));
    463      // Closing group-1-1
    464      dispatch(actions.messagesAdd([packetGroupEnd]));
    465      // Closing group-1
    466      dispatch(actions.messagesAdd([packetGroupEnd]));
    467 
    468      for (let i = 0; i < logLimit; i++) {
    469        packet.arguments = [`message-${i}`];
    470        dispatch(actions.messagesAdd([packet]));
    471      }
    472 
    473      const visibleMessages = getVisibleMessages(getState());
    474      const messages = getMutableMessagesById(getState());
    475      const { mutableMessagesOrder } = getState().messages;
    476 
    477      expect(messages.size).toBe(logLimit);
    478      expect(visibleMessages.length).toBe(logLimit);
    479      expect(mutableMessagesOrder.length).toBe(logLimit);
    480 
    481      expect(messages.get(visibleMessages[0]).parameters[0]).toBe(`message-0`);
    482      expect(messages.get(visibleMessages[logLimit - 1]).parameters[0]).toBe(
    483        `message-${logLimit - 1}`
    484      );
    485 
    486      // The groups were cleaned up.
    487      const groups = getGroupsById(getState());
    488      expect(groups.size).toBe(0);
    489    });
    490 
    491    it("properly limits number of groups", () => {
    492      const logLimit = 100;
    493      const { dispatch, getState } = setupStore([], {
    494        storeOptions: { logLimit },
    495      });
    496 
    497      const packet = clonePacket(stubPackets.get("console.log(undefined)"));
    498      const packetGroup = clonePacket(stubPackets.get("console.group('bar')"));
    499      const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
    500 
    501      for (let i = 0; i < logLimit + 2; i++) {
    502        dispatch(actions.messagesAdd([packetGroup]));
    503        packet.arguments = [`message-${i}-a`];
    504        dispatch(actions.messagesAdd([packet]));
    505        packet.arguments = [`message-${i}-b`];
    506        dispatch(actions.messagesAdd([packet]));
    507        dispatch(actions.messagesAdd([packetGroupEnd]));
    508      }
    509 
    510      const visibleMessages = getVisibleMessages(getState());
    511      const messages = getMutableMessagesById(getState());
    512      // We should have three times the logLimit since each group has one message inside.
    513      expect(messages.size).toBe(logLimit * 3);
    514 
    515      // We should have logLimit number of groups
    516      const groups = getGroupsById(getState());
    517      expect(groups.size).toBe(logLimit);
    518 
    519      expect(messages.get(visibleMessages[1]).parameters[0]).toBe(
    520        `message-2-a`
    521      );
    522      expect(getLastMessage(getState()).parameters[0]).toBe(
    523        `message-${logLimit + 1}-b`
    524      );
    525    });
    526 
    527    it("properly limits number of collapsed groups", () => {
    528      const logLimit = 100;
    529      const { dispatch, getState } = setupStore([], {
    530        storeOptions: { logLimit },
    531      });
    532 
    533      const packet = clonePacket(stubPackets.get("console.log(undefined)"));
    534      const packetGroupCollapsed = clonePacket(
    535        stubPackets.get("console.groupCollapsed('foo')")
    536      );
    537      const packetGroupEnd = clonePacket(stubPackets.get("console.groupEnd()"));
    538 
    539      for (let i = 0; i < logLimit + 2; i++) {
    540        packetGroupCollapsed.arguments = [`group-${i}`];
    541        dispatch(actions.messagesAdd([packetGroupCollapsed]));
    542        packet.arguments = [`message-${i}-a`];
    543        dispatch(actions.messagesAdd([packet]));
    544        packet.arguments = [`message-${i}-b`];
    545        dispatch(actions.messagesAdd([packet]));
    546        dispatch(actions.messagesAdd([packetGroupEnd]));
    547      }
    548 
    549      const messages = getMutableMessagesById(getState());
    550      // We should have three times the logLimit since each group has two message inside.
    551      expect(messages.size).toBe(logLimit * 3);
    552 
    553      // We should have logLimit number of groups
    554      const groups = getGroupsById(getState());
    555      expect(groups.size).toBe(logLimit);
    556 
    557      expect(getFirstMessage(getState()).parameters[0]).toBe(`group-2`);
    558      expect(getLastMessage(getState()).parameters[0]).toBe(
    559        `message-${logLimit + 1}-b`
    560      );
    561 
    562      const visibleMessages = getVisibleMessages(getState());
    563      expect(visibleMessages.length).toBe(logLimit);
    564      const lastVisibleMessageId = visibleMessages[visibleMessages.length - 1];
    565      expect(messages.get(lastVisibleMessageId).parameters[0]).toBe(
    566        `group-${logLimit + 1}`
    567      );
    568    });
    569 
    570    it("does not add null messages to the store", () => {
    571      const { dispatch, getState } = setupStore();
    572 
    573      const message = stubPackets.get("console.time('bar')");
    574      dispatch(actions.messagesAdd([message]));
    575 
    576      const messages = getMutableMessagesById(getState());
    577      expect(messages.size).toBe(0);
    578    });
    579 
    580    it("adds console.table call with unsupported type as console.log", () => {
    581      const { dispatch, getState } = setupStore();
    582 
    583      const packet = stubPackets.get("console.table('bar')");
    584      dispatch(actions.messagesAdd([packet]));
    585 
    586      const tableMessage = getLastMessage(getState());
    587      expect(tableMessage.level).toEqual(MESSAGE_TYPE.LOG);
    588    });
    589 
    590    it("adds console.group messages to the store", () => {
    591      const { dispatch, getState } = setupStore();
    592 
    593      const message = stubPackets.get("console.group('bar')");
    594      dispatch(actions.messagesAdd([message]));
    595 
    596      const messages = getMutableMessagesById(getState());
    597      expect(messages.size).toBe(1);
    598    });
    599 
    600    it("adds messages in console.group to the store", () => {
    601      const { dispatch, getState } = setupStore();
    602 
    603      const groupPacket = stubPackets.get("console.group('bar')");
    604      const groupEndPacket = stubPackets.get("console.groupEnd('bar')");
    605      const logPacket = stubPackets.get("console.log('foobar', 'test')");
    606 
    607      const packets = [
    608        groupPacket,
    609        logPacket,
    610        groupPacket,
    611        groupPacket,
    612        logPacket,
    613        groupEndPacket,
    614        logPacket,
    615        groupEndPacket,
    616        logPacket,
    617        groupEndPacket,
    618        logPacket,
    619      ];
    620      dispatch(actions.messagesAdd(packets));
    621 
    622      // Here is what we should have (8 messages)
    623      // ▼ bar
    624      // |  foobar test
    625      // |  ▼ bar
    626      // |  |  ▼ bar
    627      // |  |  |  foobar test
    628      // |  |  foobar test
    629      // |  foobar test
    630      // foobar test
    631 
    632      const isNotGroupEnd = p => p !== groupEndPacket;
    633      const messageCount = packets.filter(isNotGroupEnd).length;
    634 
    635      const messages = getMutableMessagesById(getState());
    636      const visibleMessages = getVisibleMessages(getState());
    637      expect(messages.size).toBe(messageCount);
    638      expect(visibleMessages.length).toBe(messageCount);
    639    });
    640 
    641    it("sets groupId property as expected", () => {
    642      const { dispatch, getState } = setupStore();
    643 
    644      dispatch(
    645        actions.messagesAdd([
    646          stubPackets.get("console.group('bar')"),
    647          stubPackets.get("console.log('foobar', 'test')"),
    648        ])
    649      );
    650 
    651      const messages = getMutableMessagesById(getState());
    652      expect(messages.size).toBe(2);
    653      expect(getLastMessage(getState()).groupId).toBe(
    654        getFirstMessage(getState()).id
    655      );
    656    });
    657 
    658    it("does not display console.groupEnd messages to the store", () => {
    659      const { dispatch, getState } = setupStore();
    660 
    661      const message = stubPackets.get("console.groupEnd('bar')");
    662      dispatch(actions.messagesAdd([message]));
    663 
    664      const messages = getMutableMessagesById(getState());
    665      expect(messages.size).toBe(0);
    666    });
    667 
    668    it("filters out message added after a console.groupCollapsed message", () => {
    669      const { dispatch, getState } = setupStore();
    670 
    671      dispatch(
    672        actions.messagesAdd([
    673          stubPackets.get("console.groupCollapsed('foo')"),
    674          stubPackets.get("console.log('foobar', 'test')"),
    675        ])
    676      );
    677 
    678      const messages = getVisibleMessages(getState());
    679      expect(messages.length).toBe(1);
    680    });
    681 
    682    it("adds console.dirxml call as console.log", () => {
    683      const { dispatch, getState } = setupStore();
    684 
    685      const packet = stubPackets.get("console.dirxml(window)");
    686      dispatch(actions.messagesAdd([packet]));
    687 
    688      const dirxmlMessage = getLastMessage(getState());
    689      expect(dirxmlMessage.level).toEqual(MESSAGE_TYPE.LOG);
    690    });
    691 
    692    it("does not throw when adding incomplete console.count packet", () => {
    693      const { dispatch, getState } = setupStore();
    694      const packet = clonePacket(stubPackets.get(`console.count('bar')`));
    695 
    696      // Remove counter information to mimick packet we receive in the browser console.
    697      delete packet.counter;
    698 
    699      dispatch(actions.messagesAdd([packet]));
    700      // The message should not be added to the state.
    701      expect(getMutableMessagesById(getState()).size).toBe(0);
    702    });
    703  });
    704 
    705  describe("mutableMessagesOrder", () => {
    706    it("adds a message to an empty store", () => {
    707      const { dispatch, getState } = setupStore();
    708 
    709      const packet = stubPackets.get("console.log('foobar', 'test')");
    710      dispatch(actions.messagesAdd([packet]));
    711 
    712      const { mutableMessagesOrder } = getState().messages;
    713      expect(mutableMessagesOrder.length).toBe(1);
    714      expect(mutableMessagesOrder[0]).toBe(
    715        // Don't get getMessageIndexAt/getFirstMessage since it relies on mutableMessagesOrder
    716        [...getMutableMessagesById(getState()).keys()][0]
    717      );
    718    });
    719 
    720    it("reorder messages", () => {
    721      const { dispatch, getState } = setupStore();
    722 
    723      const naNpacket = stubPackets.get("console.log(NaN)");
    724      dispatch(actions.messagesAdd([naNpacket]));
    725 
    726      // Add a message that has a shorter timestamp than the previous one, and thus, should
    727      // be displayed before
    728      const nullPacket = clonePacket(stubPackets.get("console.log(null)"));
    729      nullPacket.timeStamp = naNpacket.timeStamp - 10;
    730      dispatch(actions.messagesAdd([nullPacket]));
    731 
    732      // Add a message that should be display between the 2 previous messages
    733      const undefinedPacket = clonePacket(
    734        stubPackets.get("console.log(undefined)")
    735      );
    736      undefinedPacket.timeStamp = naNpacket.timeStamp - 5;
    737      dispatch(actions.messagesAdd([undefinedPacket]));
    738 
    739      const { mutableMessagesOrder } = getState().messages;
    740      const [nanMessage, nullMessage, undefinedMessage] = [
    741        ...getMutableMessagesById(getState()).values(),
    742      ];
    743      const visibleMessages = getVisibleMessages(getState());
    744 
    745      // Checking that messages in the Map are the expected ones
    746      expect(nanMessage.parameters[0].type).toBe("NaN");
    747      expect(nullMessage.parameters[0].type).toBe("null");
    748      expect(undefinedMessage.parameters[0].type).toBe("undefined");
    749 
    750      // Check that mutableMessagesOrder has the message ids in the chronological order
    751      expect(mutableMessagesOrder).toEqual([
    752        nullMessage.id,
    753        undefinedMessage.id,
    754        nanMessage.id,
    755      ]);
    756 
    757      // Since we didn't filtered anything, visibleMessages should be similar to mutableMessagesOrder
    758      expect(mutableMessagesOrder).toEqual(visibleMessages);
    759 
    760      // Check that visibleMessages is computed from mutableMessagesOrder when filtering
    761      dispatch(actions.filterToggle("log"));
    762      expect(getVisibleMessages(getState())).toEqual([]);
    763      dispatch(actions.filterToggle("log"));
    764      expect(getVisibleMessages(getState())).toEqual([
    765        nullMessage.id,
    766        undefinedMessage.id,
    767        nanMessage.id,
    768      ]);
    769    });
    770  });
    771 
    772  describe("expandedMessageIds", () => {
    773    it("opens console.trace messages when they are added", () => {
    774      const { dispatch, getState } = setupStore();
    775 
    776      const message = stubPackets.get("console.trace()");
    777      dispatch(actions.messagesAdd([message]));
    778 
    779      const expanded = getAllMessagesUiById(getState());
    780      expect(expanded.length).toBe(1);
    781      expect(expanded[0]).toBe(getFirstMessage(getState()).id);
    782    });
    783 
    784    it("clears the messages UI list in response to MESSAGES_CLEAR action", () => {
    785      const { dispatch, getState } = setupStore([
    786        "console.log('foobar', 'test')",
    787        "console.log(undefined)",
    788      ]);
    789 
    790      const traceMessage = stubPackets.get("console.trace()");
    791      dispatch(actions.messagesAdd([traceMessage]));
    792 
    793      dispatch(actions.messagesClear());
    794 
    795      const expanded = getAllMessagesUiById(getState());
    796      expect(expanded.length).toBe(0);
    797    });
    798 
    799    it("cleans the messages UI list when messages are pruned", () => {
    800      const { dispatch, getState } = setupStore(
    801        ["console.trace()", "console.log(undefined)", "console.trace()"],
    802        {
    803          storeOptions: {
    804            logLimit: 3,
    805          },
    806        }
    807      );
    808 
    809      // Check that we have the expected data.
    810      let expanded = getAllMessagesUiById(getState());
    811      expect(expanded.length).toBe(2);
    812      expect(expanded[0]).toBe(getFirstMessage(getState()).id);
    813      const lastMessageId = getLastMessage(getState()).id;
    814      expect(expanded[expanded.length - 1]).toBe(lastMessageId);
    815 
    816      // This addition will prune the first message out of the store.
    817      let packet = stubPackets.get("console.log(undefined)");
    818      dispatch(actions.messagesAdd([packet]));
    819 
    820      expanded = getAllMessagesUiById(getState());
    821 
    822      // There should be only the id of the last console.trace message.
    823      expect(expanded.length).toBe(1);
    824      expect(expanded[0]).toBe(lastMessageId);
    825 
    826      // These additions will prune the last console.trace message out of the store.
    827      packet = stubPackets.get("console.log('foobar', 'test')");
    828      dispatch(actions.messagesAdd([packet]));
    829      packet = stubPackets.get("console.log(undefined)");
    830      dispatch(actions.messagesAdd([packet]));
    831 
    832      // expandedMessageIds should now be empty.
    833      expect(getAllMessagesUiById(getState()).length).toBe(0);
    834    });
    835 
    836    it("opens console.group messages when they are added", () => {
    837      const { dispatch, getState } = setupStore();
    838 
    839      const message = stubPackets.get("console.group('bar')");
    840      dispatch(actions.messagesAdd([message]));
    841 
    842      const expanded = getAllMessagesUiById(getState());
    843      expect(expanded.length).toBe(1);
    844      expect(expanded[0]).toBe(getFirstMessage(getState()).id);
    845    });
    846 
    847    it("does not open console.groupCollapsed messages when they are added", () => {
    848      const { dispatch, getState } = setupStore();
    849 
    850      const message = stubPackets.get("console.groupCollapsed('foo')");
    851      dispatch(actions.messagesAdd([message]));
    852 
    853      const expanded = getAllMessagesUiById(getState());
    854      expect(expanded.length).toBe(0);
    855    });
    856 
    857    it("reacts to messageClose/messageOpen actions on console.group", () => {
    858      const { dispatch, getState } = setupStore(["console.group('bar')"]);
    859      const firstMessageId = getFirstMessage(getState()).id;
    860 
    861      let expanded = getAllMessagesUiById(getState());
    862      expect(expanded.length).toBe(1);
    863      expect(expanded[0]).toBe(firstMessageId);
    864 
    865      dispatch(actions.messageClose(firstMessageId));
    866 
    867      expanded = getAllMessagesUiById(getState());
    868      expect(expanded.length).toBe(0);
    869 
    870      dispatch(actions.messageOpen(firstMessageId));
    871 
    872      expanded = getAllMessagesUiById(getState());
    873      expect(expanded.length).toBe(1);
    874      expect(expanded[0]).toBe(firstMessageId);
    875    });
    876 
    877    it("reacts to messageClose/messageOpen actions on exception", () => {
    878      const { dispatch, getState } = setupStore([
    879        "ReferenceError: asdf is not defined",
    880      ]);
    881      const firstMessageId = getFirstMessage(getState()).id;
    882 
    883      let expanded = getAllMessagesUiById(getState());
    884      expect(expanded.length).toBe(0);
    885 
    886      dispatch(actions.messageOpen(firstMessageId));
    887 
    888      expanded = getAllMessagesUiById(getState());
    889      expect(expanded.length).toBe(1);
    890      expect(expanded[0]).toBe(firstMessageId);
    891 
    892      dispatch(actions.messageClose(firstMessageId));
    893 
    894      expanded = getAllMessagesUiById(getState());
    895      expect(expanded.length).toBe(0);
    896    });
    897  });
    898 
    899  describe("currentGroup", () => {
    900    it("sets the currentGroup when console.group message is added", () => {
    901      const { dispatch, getState } = setupStore();
    902 
    903      const packet = stubPackets.get("console.group('bar')");
    904      dispatch(actions.messagesAdd([packet]));
    905 
    906      const currentGroup = getCurrentGroup(getState());
    907      expect(currentGroup).toBe(getFirstMessage(getState()).id);
    908    });
    909 
    910    it("sets currentGroup to expected value when console.groupEnd is added", () => {
    911      const { dispatch, getState } = setupStore([
    912        "console.group('bar')",
    913        "console.groupCollapsed('foo')",
    914        "console.group('bar')",
    915        "console.groupEnd('bar')",
    916      ]);
    917 
    918      let currentGroup = getCurrentGroup(getState());
    919      expect(currentGroup).toBe(getMessageAt(getState(), 1).id);
    920 
    921      const endFooPacket = stubPackets.get("console.groupEnd('foo')");
    922      dispatch(actions.messagesAdd([endFooPacket]));
    923      currentGroup = getCurrentGroup(getState());
    924      expect(currentGroup).toBe(getFirstMessage(getState()).id);
    925 
    926      const endBarPacket = stubPackets.get("console.groupEnd('bar')");
    927      dispatch(actions.messagesAdd([endBarPacket]));
    928      currentGroup = getCurrentGroup(getState());
    929      expect(currentGroup).toBe(null);
    930    });
    931 
    932    it("resets the currentGroup to null in response to MESSAGES_CLEAR action", () => {
    933      const { dispatch, getState } = setupStore(["console.group('bar')"]);
    934 
    935      dispatch(actions.messagesClear());
    936 
    937      const currentGroup = getCurrentGroup(getState());
    938      expect(currentGroup).toBe(null);
    939    });
    940  });
    941 
    942  describe("groupsById", () => {
    943    it("adds the group with expected array when console.group message is added", () => {
    944      const { dispatch, getState } = setupStore();
    945 
    946      const barPacket = stubPackets.get("console.group('bar')");
    947      dispatch(actions.messagesAdd([barPacket]));
    948 
    949      let groupsById = getGroupsById(getState());
    950      expect(groupsById.size).toBe(1);
    951      expect(groupsById.has(getFirstMessage(getState()).id)).toBe(true);
    952      expect(groupsById.get(getFirstMessage(getState()).id)).toEqual([]);
    953 
    954      const fooPacket = stubPackets.get("console.groupCollapsed('foo')");
    955      dispatch(actions.messagesAdd([fooPacket]));
    956 
    957      groupsById = getGroupsById(getState());
    958      expect(groupsById.size).toBe(2);
    959      expect(groupsById.has(getLastMessage(getState()).id)).toBe(true);
    960      expect(groupsById.get(getLastMessage(getState()).id)).toEqual([
    961        getFirstMessage(getState()).id,
    962      ]);
    963    });
    964 
    965    it("resets groupsById in response to MESSAGES_CLEAR action", () => {
    966      const { dispatch, getState } = setupStore([
    967        "console.group('bar')",
    968        "console.groupCollapsed('foo')",
    969      ]);
    970 
    971      let groupsById = getGroupsById(getState());
    972      expect(groupsById.size).toBe(2);
    973 
    974      dispatch(actions.messagesClear());
    975 
    976      groupsById = getGroupsById(getState());
    977      expect(groupsById.size).toBe(0);
    978    });
    979 
    980    it("cleans the groupsById property when messages are pruned", () => {
    981      const { dispatch, getState } = setupStore(
    982        [
    983          "console.group('bar')",
    984          "console.group()",
    985          "console.groupEnd()",
    986          "console.groupEnd('bar')",
    987          "console.group('bar')",
    988          "console.groupEnd('bar')",
    989          "console.log('foobar', 'test')",
    990        ],
    991        {
    992          actions,
    993          storeOptions: {
    994            logLimit: 3,
    995          },
    996        }
    997      );
    998 
    999      /*
   1000       * Here is the initial state of the console:
   1001       * ▶︎ bar
   1002       *   ▶︎ noLabel
   1003       * ▶︎ bar
   1004       * foobar test
   1005       */
   1006 
   1007      // Check that we have the expected data.
   1008      let groupsById = getGroupsById(getState());
   1009      expect(groupsById.size).toBe(3);
   1010 
   1011      // This addition will prune the first group (and its child group) out of the store.
   1012      /*
   1013       * ▶︎ bar
   1014       * foobar test
   1015       * undefined
   1016       */
   1017      let packet = stubPackets.get("console.log(undefined)");
   1018      dispatch(actions.messagesAdd([packet]));
   1019 
   1020      groupsById = getGroupsById(getState());
   1021 
   1022      // There should be only the id of the last console.group message.
   1023      expect(groupsById.size).toBe(1);
   1024 
   1025      // This additions will prune the last group message out of the store.
   1026      /*
   1027       * foobar test
   1028       * undefined
   1029       * foobar test
   1030       */
   1031      packet = stubPackets.get("console.log('foobar', 'test')");
   1032      dispatch(actions.messagesAdd([packet]));
   1033 
   1034      // groupsById should now be empty.
   1035      expect(getGroupsById(getState()).size).toBe(0);
   1036    });
   1037  });
   1038 
   1039  describe("networkMessagesUpdateById", () => {
   1040    it("adds the network update message when network update action is called", () => {
   1041      const { dispatch, getState } = setupStore();
   1042 
   1043      let packet = clonePacket(stubPackets.get("GET request"));
   1044      let updatePacket = clonePacket(stubPackets.get("GET request update"));
   1045 
   1046      packet.actor = "message1";
   1047      updatePacket.actor = "message1";
   1048      dispatch(actions.messagesAdd([packet]));
   1049      dispatch(actions.networkMessageUpdates([updatePacket]));
   1050 
   1051      let networkUpdates = getAllNetworkMessagesUpdateById(getState());
   1052      expect(Object.keys(networkUpdates)).toEqual(["message1"]);
   1053 
   1054      packet = clonePacket(stubPackets.get("GET request"));
   1055      updatePacket = stubPackets.get("XHR GET request update");
   1056      packet.actor = "message2";
   1057      updatePacket.actor = "message2";
   1058      dispatch(actions.messagesAdd([packet]));
   1059      dispatch(actions.networkMessageUpdates([updatePacket]));
   1060 
   1061      networkUpdates = getAllNetworkMessagesUpdateById(getState());
   1062      expect(Object.keys(networkUpdates)).toEqual(["message1", "message2"]);
   1063    });
   1064 
   1065    it("resets networkMessagesUpdateById in response to MESSAGES_CLEAR action", () => {
   1066      const { dispatch, getState } = setupStore(["XHR GET request"]);
   1067 
   1068      const updatePacket = stubPackets.get("XHR GET request update");
   1069      dispatch(actions.networkMessageUpdates([updatePacket]));
   1070 
   1071      let networkUpdates = getAllNetworkMessagesUpdateById(getState());
   1072      expect(!!Object.keys(networkUpdates).length).toBe(true);
   1073 
   1074      dispatch(actions.messagesClear());
   1075 
   1076      networkUpdates = getAllNetworkMessagesUpdateById(getState());
   1077      expect(Object.keys(networkUpdates).length).toBe(0);
   1078    });
   1079 
   1080    it("cleans the networkMessagesUpdateById property when messages are pruned", () => {
   1081      const { dispatch, getState } = setupStore([], {
   1082        storeOptions: {
   1083          logLimit: 3,
   1084        },
   1085      });
   1086 
   1087      // Add 3 network messages and their updates
   1088      let packet = clonePacket(stubPackets.get("XHR GET request"));
   1089      const updatePacket = clonePacket(
   1090        stubPackets.get("XHR GET request update")
   1091      );
   1092      packet.actor = "message1";
   1093      updatePacket.actor = "message1";
   1094      dispatch(actions.messagesAdd([packet]));
   1095      dispatch(actions.networkMessageUpdates([updatePacket]));
   1096 
   1097      packet.actor = "message2";
   1098      updatePacket.actor = "message2";
   1099      dispatch(actions.messagesAdd([packet]));
   1100      dispatch(actions.networkMessageUpdates([updatePacket]));
   1101 
   1102      packet.actor = "message3";
   1103      updatePacket.actor = "message3";
   1104      dispatch(actions.messagesAdd([packet]));
   1105      dispatch(actions.networkMessageUpdates([updatePacket]));
   1106 
   1107      // Check that we have the expected data.
   1108      const messages = getMutableMessagesById(getState());
   1109      const [
   1110        firstNetworkMessageId,
   1111        secondNetworkMessageId,
   1112        thirdNetworkMessageId,
   1113      ] = [...messages.keys()];
   1114 
   1115      let networkUpdates = getAllNetworkMessagesUpdateById(getState());
   1116      expect(Object.keys(networkUpdates)).toEqual([
   1117        firstNetworkMessageId,
   1118        secondNetworkMessageId,
   1119        thirdNetworkMessageId,
   1120      ]);
   1121 
   1122      // This addition will remove the first network message.
   1123      packet = stubPackets.get("console.log(undefined)");
   1124      dispatch(actions.messagesAdd([packet]));
   1125 
   1126      networkUpdates = getAllNetworkMessagesUpdateById(getState());
   1127      expect(Object.keys(networkUpdates)).toEqual([
   1128        secondNetworkMessageId,
   1129        thirdNetworkMessageId,
   1130      ]);
   1131 
   1132      // This addition will remove the second network message.
   1133      packet = stubPackets.get("console.log('foobar', 'test')");
   1134      dispatch(actions.messagesAdd([packet]));
   1135 
   1136      networkUpdates = getAllNetworkMessagesUpdateById(getState());
   1137      expect(Object.keys(networkUpdates)).toEqual([thirdNetworkMessageId]);
   1138 
   1139      // This addition will remove the last network message.
   1140      packet = stubPackets.get("console.log(undefined)");
   1141      dispatch(actions.messagesAdd([packet]));
   1142 
   1143      // networkMessageUpdateById should now be empty.
   1144      networkUpdates = getAllNetworkMessagesUpdateById(getState());
   1145      expect(Object.keys(networkUpdates)).toEqual([]);
   1146    });
   1147  });
   1148 
   1149  describe("cssMessagesMatchingElements", () => {
   1150    it("resets cssMessagesMatchingElements in response to MESSAGES_CLEAR action", () => {
   1151      const { dispatch, getState } = setupStore([
   1152        `Unknown property ‘such-unknown-property’.  Declaration dropped.`,
   1153      ]);
   1154 
   1155      const data = Symbol();
   1156      dispatch({
   1157        type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
   1158        id: getFirstMessage(getState()).id,
   1159        elements: data,
   1160      });
   1161 
   1162      const matchingElements = getAllCssMessagesMatchingElements(getState());
   1163      expect(matchingElements.size).toBe(1);
   1164      expect(matchingElements.get(getFirstMessage(getState()).id)).toBe(data);
   1165 
   1166      dispatch(actions.messagesClear());
   1167 
   1168      expect(getAllCssMessagesMatchingElements(getState()).size).toBe(0);
   1169    });
   1170 
   1171    it("cleans the cssMessagesMatchingElements property when messages are pruned", () => {
   1172      const { dispatch, getState } = setupStore([], {
   1173        storeOptions: {
   1174          logLimit: 2,
   1175        },
   1176      });
   1177 
   1178      // Add 2 css warnings message and their associated data.
   1179      dispatch(
   1180        actions.messagesAdd([
   1181          stubPackets.get(
   1182            `Unknown property ‘such-unknown-property’.  Declaration dropped.`
   1183          ),
   1184        ])
   1185      );
   1186      dispatch(
   1187        actions.messagesAdd([
   1188          stubPackets.get(
   1189            `Error in parsing value for ‘padding-top’.  Declaration dropped.`
   1190          ),
   1191        ])
   1192      );
   1193 
   1194      const messages = getMutableMessagesById(getState());
   1195 
   1196      const data1 = Symbol();
   1197      const data2 = Symbol();
   1198      const [id1, id2] = [...messages.keys()];
   1199 
   1200      dispatch({
   1201        type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
   1202        id: id1,
   1203        elements: data1,
   1204      });
   1205      dispatch({
   1206        type: CSS_MESSAGE_ADD_MATCHING_ELEMENTS,
   1207        id: id2,
   1208        elements: data2,
   1209      });
   1210 
   1211      let matchingElements = getAllCssMessagesMatchingElements(getState());
   1212      expect(matchingElements.size).toBe(2);
   1213 
   1214      // This addition will remove the first css warning.
   1215      dispatch(
   1216        actions.messagesAdd([stubPackets.get("console.log(undefined)")])
   1217      );
   1218 
   1219      matchingElements = getAllCssMessagesMatchingElements(getState());
   1220      expect(matchingElements.size).toBe(1);
   1221      expect(matchingElements.get(id2)).toBe(data2);
   1222 
   1223      // This addition will remove the second css warning.
   1224      dispatch(
   1225        actions.messagesAdd([stubPackets.get("console.log('foobar', 'test')")])
   1226      );
   1227 
   1228      expect(getAllCssMessagesMatchingElements(getState()).size).toBe(0);
   1229    });
   1230  });
   1231 
   1232  describe("messagesAdd", () => {
   1233    it("still log repeated message over logLimit, but only repeated ones", () => {
   1234      // Log two distinct messages
   1235      const key1 = "console.log('foobar', 'test')";
   1236      const key2 = "console.log(undefined)";
   1237      const { dispatch, getState } = setupStore([key1, key2], {
   1238        storeOptions: {
   1239          logLimit: 2,
   1240        },
   1241      });
   1242 
   1243      // Then repeat the last one two times and log the first one again
   1244      const packet1 = clonePacket(stubPackets.get(key2));
   1245      const packet2 = clonePacket(stubPackets.get(key2));
   1246      const packet3 = clonePacket(stubPackets.get(key1));
   1247 
   1248      // Repeat ID must be the same even if the timestamp is different.
   1249      packet1.timeStamp = packet1.timeStamp + 1;
   1250      packet2.timeStamp = packet2.timeStamp + 2;
   1251      packet3.timeStamp = packet3.timeStamp + 3;
   1252      dispatch(actions.messagesAdd([packet1, packet2, packet3]));
   1253 
   1254      // There is still only two messages being logged,
   1255      const messages = getMutableMessagesById(getState());
   1256      expect(messages.size).toBe(2);
   1257 
   1258      // the second one being repeated 3 times
   1259      const repeat = getAllRepeatById(getState());
   1260      expect(repeat[getFirstMessage(getState()).id]).toBe(3);
   1261      expect(repeat[getLastMessage(getState()).id]).toBe(undefined);
   1262    });
   1263  });
   1264 
   1265  describe("messageRemove", () => {
   1266    it("removes the message from the store", () => {
   1267      const { dispatch, getState } = setupStore([
   1268        "console.trace()",
   1269        "console.log(undefined)",
   1270        "console.trace()",
   1271        "console.log(undefined)",
   1272      ]);
   1273 
   1274      let expanded = getAllMessagesUiById(getState());
   1275      expect(expanded.length).toBe(2);
   1276 
   1277      const secondTraceMessage = getMessageAt(getState(), 2);
   1278      dispatch(actions.messageRemove(secondTraceMessage.id));
   1279 
   1280      const messages = getMutableMessagesById(getState());
   1281      const { mutableMessagesOrder } = getState().messages;
   1282      // The messages was removed
   1283      expect(messages.size).toBe(3);
   1284      expect(mutableMessagesOrder.length).toBe(3);
   1285 
   1286      // Its id was removed from the messagesUI property as well
   1287      expanded = getAllMessagesUiById(getState());
   1288      expect(expanded.length).toBe(1);
   1289      expect(expanded.includes(secondTraceMessage.id)).toBeFalsy();
   1290    });
   1291  });
   1292 
   1293  describe("disabledMessagesById", () => {
   1294    it("adds messages ids to disabledMessagesById when message disable action is called", () => {
   1295      const { dispatch, getState } = setupStore();
   1296 
   1297      dispatch(actions.messagesDisable(["message1", "message2"]));
   1298 
   1299      const disabledMessages = getAllDisabledMessagesById(getState());
   1300      expect(disabledMessages).toEqual(["message1", "message2"]);
   1301    });
   1302 
   1303    it("clears disabledMessagesById in response to MESSAGES_CLEAR action", () => {
   1304      const { dispatch, getState } = setupStore();
   1305 
   1306      dispatch(actions.messagesDisable(["message1", "message2"]));
   1307 
   1308      let disabledMessages = getAllDisabledMessagesById(getState());
   1309      expect(disabledMessages).toEqual(["message1", "message2"]);
   1310 
   1311      dispatch(actions.messagesClear());
   1312 
   1313      disabledMessages = getAllDisabledMessagesById(getState());
   1314      expect(disabledMessages).toEqual([]);
   1315    });
   1316 
   1317    it("remove message id from disabledMessagesById when the message is removed", () => {
   1318      const { dispatch, getState } = setupStore(
   1319        [
   1320          "console.log('foobar', 'test')",
   1321          "XHR GET request update",
   1322          "console.log(undefined)",
   1323        ],
   1324        {
   1325          actions,
   1326          storeOptions: {
   1327            logLimit: 3,
   1328          },
   1329        }
   1330      );
   1331 
   1332      // This is `console.log('foobar', 'test'`
   1333      const firstMessageId = getMessageAt(getState(), 0).id;
   1334      // This is for `XHR GET request update`
   1335      const secondMessageId = getMessageAt(getState(), 1).id;
   1336 
   1337      dispatch(actions.messagesDisable([firstMessageId, secondMessageId]));
   1338 
   1339      let disabledMessages = getAllDisabledMessagesById(getState());
   1340      expect(disabledMessages).toEqual([firstMessageId, secondMessageId]);
   1341 
   1342      // Adding a new message should prune the first(oldest) message and should
   1343      // remove its id from the disabled messages list.
   1344      const packet = stubPackets.get("GET request");
   1345      dispatch(actions.messagesAdd([packet]));
   1346 
   1347      disabledMessages = getAllDisabledMessagesById(getState());
   1348      expect(disabledMessages).toEqual([secondMessageId]);
   1349    });
   1350  });
   1351 });