diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 4fd57d95e6..c3a2bdbc59 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -838,14 +838,16 @@ class CreationGrouper { // events that we include in the group but then eject out and place // above the group. this.ejectedEvents = []; - this.readMarker = panel._readMarkerForEvent(createEvent.getId()); + this.readMarker = panel._readMarkerForEvent( + createEvent.getId(), + createEvent === lastShownEvent, + ); } shouldGroup(ev) { const panel = this.panel; const createEvent = this.createEvent; if (!panel._shouldShowEvent(ev)) { - this.readMarker = this.readMarker || panel._readMarkerForEvent(ev.getId()); return true; } if (panel._wantsDateSeparator(this.createEvent, ev.getDate())) { @@ -863,7 +865,10 @@ class CreationGrouper { add(ev) { const panel = this.panel; - this.readMarker = this.readMarker || panel._readMarkerForEvent(ev.getId()); + this.readMarker = this.readMarker || panel._readMarkerForEvent( + ev.getId(), + ev === this.lastShownEvent, + ); if (!panel._shouldShowEvent(ev)) { return; } @@ -950,7 +955,10 @@ class MemberGrouper { constructor(panel, ev, prevEvent, lastShownEvent) { this.panel = panel; - this.readMarker = panel._readMarkerForEvent(ev.getId()); + this.readMarker = panel._readMarkerForEvent( + ev.getId(), + ev === lastShownEvent, + ); this.events = [ev]; this.prevEvent = prevEvent; this.lastShownEvent = lastShownEvent; @@ -971,7 +979,10 @@ class MemberGrouper { const renderText = textForEvent(ev); if (!renderText || renderText.trim().length === 0) return; // quietly ignore } - this.readMarker = this.readMarker || this.panel._readMarkerForEvent(ev.getId()); + this.readMarker = this.readMarker || this.panel._readMarkerForEvent( + ev.getId(), + ev === this.lastShownEvent, + ); this.events.push(ev); } diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js index e6332cf7f8..3ac70bdac9 100644 --- a/test/components/structures/MessagePanel-test.js +++ b/test/components/structures/MessagePanel-test.js @@ -142,128 +142,42 @@ describe('MessagePanel', function() { return events; } - it('should show the events', function() { - const res = TestUtils.renderIntoDocument( - , - ); + // A list of membership events only with nothing else + function mkMelsEventsOnly() { + const events = []; + const ts0 = Date.now(); - // just check we have the right number of tiles for now - const tiles = TestUtils.scryRenderedComponentsWithType( - res, sdk.getComponent('rooms.EventTile')); - expect(tiles.length).toEqual(10); - }); + let i = 0; - it('should collapse adjacent member events', function() { - const res = TestUtils.renderIntoDocument( - , - ); + for (i = 0; i < 10; i++) { + events.push(test_utils.mkMembership({ + event: true, room: "!room:id", user: "@user:id", + target: { + userId: "@user:id", + name: "Bob", + getAvatarUrl: () => { + return "avatar.jpeg"; + }, + }, + ts: ts0 + i*1000, + mship: 'join', + prevMship: 'join', + name: 'A user', + })); + } - // just check we have the right number of tiles for now - const tiles = TestUtils.scryRenderedComponentsWithType( - res, sdk.getComponent('rooms.EventTile'), - ); - expect(tiles.length).toEqual(2); + return events; + } - const summaryTiles = TestUtils.scryRenderedComponentsWithType( - res, sdk.getComponent('elements.MemberEventListSummary'), - ); - expect(summaryTiles.length).toEqual(1); - }); - - it('should show the read-marker in the right place', function() { - const res = TestUtils.renderIntoDocument( - , - ); - - const tiles = TestUtils.scryRenderedComponentsWithType( - res, sdk.getComponent('rooms.EventTile')); - - // find the
  • which wraps the read marker - const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container'); - - // it should follow the
  • which wraps the event tile for event 4 - const eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode; - expect(rm.previousSibling).toEqual(eventContainer); - }); - - it('should show the read-marker that fall in summarised events after the summary', function() { - const melsEvents = mkMelsEvents(); - const res = TestUtils.renderIntoDocument( - , - ); - - const summary = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_EventListSummary'); - - // find the
  • which wraps the read marker - const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container'); - - expect(rm.previousSibling).toEqual(summary); - }); - - it('shows a ghost read-marker when the read-marker moves', function(done) { - // fake the clock so that we can test the velocity animation. - clock.install(); - clock.mockDate(); - - const parentDiv = document.createElement('div'); - - // first render with the RM in one place - let mp = ReactDOM.render( - , parentDiv); - - const tiles = TestUtils.scryRenderedComponentsWithType( - mp, sdk.getComponent('rooms.EventTile')); - const tileContainers = tiles.map(function(t) { - return ReactDOM.findDOMNode(t).parentNode; - }); - - // find the
  • which wraps the read marker - const rm = TestUtils.findRenderedDOMComponentWithClass(mp, 'mx_RoomView_myReadMarker_container'); - expect(rm.previousSibling).toEqual(tileContainers[4]); - - // now move the RM - mp = ReactDOM.render( - , parentDiv); - - // now there should be two RM containers - const found = TestUtils.scryRenderedDOMComponentsWithClass(mp, 'mx_RoomView_myReadMarker_container'); - expect(found.length).toEqual(2); - - // the first should be the ghost - expect(found[0].previousSibling).toEqual(tileContainers[4]); - const hr = found[0].children[0]; - - // the second should be the real thing - expect(found[1].previousSibling).toEqual(tileContainers[6]); - - // advance the clock, and then let the browser run an animation frame, - // to let the animation start - clock.tick(1500); - - realSetTimeout(() => { - // then advance it again to let it complete - clock.tick(1000); - realSetTimeout(() => { - // the ghost should now have finished - expect(hr.style.opacity).toEqual('0'); - done(); - }, 100); - }, 100); - }); - - it('should collapse creation events', function() { + // A list of room creation, encryption, and invite events. + function mkCreationEvents() { const mkEvent = test_utils.mkEvent; const mkMembership = test_utils.mkMembership; const roomId = "!someroom"; const alice = "@alice:example.org"; const ts0 = Date.now(); - const events = [ + + return [ mkEvent({ event: true, type: "m.room.create", @@ -341,6 +255,150 @@ describe('MessagePanel', function() { name: 'Bob', }), ]; + } + + function isReadMarkerVisible(rmContainer) { + return rmContainer && rmContainer.children.length > 0; + } + + it('should show the events', function() { + const res = TestUtils.renderIntoDocument( + , + ); + + // just check we have the right number of tiles for now + const tiles = TestUtils.scryRenderedComponentsWithType( + res, sdk.getComponent('rooms.EventTile')); + expect(tiles.length).toEqual(10); + }); + + it('should collapse adjacent member events', function() { + const res = TestUtils.renderIntoDocument( + , + ); + + // just check we have the right number of tiles for now + const tiles = TestUtils.scryRenderedComponentsWithType( + res, sdk.getComponent('rooms.EventTile'), + ); + expect(tiles.length).toEqual(2); + + const summaryTiles = TestUtils.scryRenderedComponentsWithType( + res, sdk.getComponent('elements.MemberEventListSummary'), + ); + expect(summaryTiles.length).toEqual(1); + }); + + it('should insert the read-marker in the right place', function() { + const res = TestUtils.renderIntoDocument( + , + ); + + const tiles = TestUtils.scryRenderedComponentsWithType( + res, sdk.getComponent('rooms.EventTile')); + + // find the
  • which wraps the read marker + const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container'); + + // it should follow the
  • which wraps the event tile for event 4 + const eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode; + expect(rm.previousSibling).toEqual(eventContainer); + }); + + it('should show the read-marker that fall in summarised events after the summary', function() { + const melsEvents = mkMelsEvents(); + const res = TestUtils.renderIntoDocument( + , + ); + + const summary = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_EventListSummary'); + + // find the
  • which wraps the read marker + const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container'); + + expect(rm.previousSibling).toEqual(summary); + + // read marker should be visible given props and not at the last event + expect(isReadMarkerVisible(rm)).toBeTruthy(); + }); + + it('should hide the read-marker at the end of summarised events', function() { + const melsEvents = mkMelsEventsOnly(); + const res = TestUtils.renderIntoDocument( + , + ); + + const summary = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_EventListSummary'); + + // find the
  • which wraps the read marker + const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container'); + + expect(rm.previousSibling).toEqual(summary); + + // read marker should be hidden given props and at the last event + expect(isReadMarkerVisible(rm)).toBeFalsy(); + }); + + it('shows a ghost read-marker when the read-marker moves', function(done) { + // fake the clock so that we can test the velocity animation. + clock.install(); + clock.mockDate(); + + const parentDiv = document.createElement('div'); + + // first render with the RM in one place + let mp = ReactDOM.render( + , parentDiv); + + const tiles = TestUtils.scryRenderedComponentsWithType( + mp, sdk.getComponent('rooms.EventTile')); + const tileContainers = tiles.map(function(t) { + return ReactDOM.findDOMNode(t).parentNode; + }); + + // find the
  • which wraps the read marker + const rm = TestUtils.findRenderedDOMComponentWithClass(mp, 'mx_RoomView_myReadMarker_container'); + expect(rm.previousSibling).toEqual(tileContainers[4]); + + // now move the RM + mp = ReactDOM.render( + , parentDiv); + + // now there should be two RM containers + const found = TestUtils.scryRenderedDOMComponentsWithClass(mp, 'mx_RoomView_myReadMarker_container'); + expect(found.length).toEqual(2); + + // the first should be the ghost + expect(found[0].previousSibling).toEqual(tileContainers[4]); + const hr = found[0].children[0]; + + // the second should be the real thing + expect(found[1].previousSibling).toEqual(tileContainers[6]); + + // advance the clock, and then let the browser run an animation frame, + // to let the animation start + clock.tick(1500); + + realSetTimeout(() => { + // then advance it again to let it complete + clock.tick(1000); + realSetTimeout(() => { + // the ghost should now have finished + expect(hr.style.opacity).toEqual('0'); + done(); + }, 100); + }, 100); + }); + + it('should collapse creation events', function() { + const events = mkCreationEvents(); const res = mount( , ); @@ -363,4 +421,26 @@ describe('MessagePanel', function() { // invite event should be in the event summary expect(summaryEventTiles.length).toEqual(tiles.length - 3); }); + + it('should hide read-marker at the end of creation event summary', function() { + const events = mkCreationEvents(); + const res = mount( + , + ); + + // find the
  • which wraps the read marker + const rm = res.find('.mx_RoomView_myReadMarker_container').getDOMNode(); + + const rows = res.find('.mx_RoomView_MessageList').children(); + expect(rows.length).toEqual(6); + expect(rm.previousSibling).toEqual(rows.at(4).getDOMNode()); + + // read marker should be hidden given props and at the last event + expect(isReadMarkerVisible(rm)).toBeFalsy(); + }); });