From 3254ead1ee00453ac93b965c1de1668fcbbb33b7 Mon Sep 17 00:00:00 2001 From: Stamates Date: Thu, 4 Oct 2018 17:16:09 -0400 Subject: [PATCH] Fill-in skipped blocks on page render for blocks list and homepage --- .../assets/__tests__/pages/block.js | 62 +++++++++++++++++-- .../assets/__tests__/pages/chain.js | 39 +++++++++--- .../assets/css/components/_tile.scss | 6 +- apps/block_scout_web/assets/js/pages/block.js | 29 +++++---- apps/block_scout_web/assets/js/pages/chain.js | 47 +++++++------- apps/block_scout_web/assets/js/utils.js | 35 +++++++++-- .../features/pages/block_list_page.ex | 2 +- .../features/pages/chain_page.ex | 10 ++- .../features/viewing_blocks_test.exs | 23 +++++++ .../features/viewing_chain_test.exs | 54 +++++++++++++++- 10 files changed, 247 insertions(+), 60 deletions(-) diff --git a/apps/block_scout_web/assets/__tests__/pages/block.js b/apps/block_scout_web/assets/__tests__/pages/block.js index f6f84976fb..d854fb9746 100644 --- a/apps/block_scout_web/assets/__tests__/pages/block.js +++ b/apps/block_scout_web/assets/__tests__/pages/block.js @@ -1,6 +1,5 @@ import { reducer, initialState } from '../../js/pages/block' - test('CHANNEL_DISCONNECTED', () => { const state = initialState const action = { @@ -11,6 +10,61 @@ test('CHANNEL_DISCONNECTED', () => { expect(output.channelDisconnected).toBe(true) }) +describe('PAGE_LOAD', () => { + test('page 1 loads block numbers', () => { + const state = initialState + const action = { + type: 'PAGE_LOAD', + beyondPageOne: false, + blockNumbers: [2, 1] + } + const output = reducer(state, action) + + expect(output.beyondPageOne).toBe(false) + expect(output.blockNumbers).toEqual([2, 1]) + expect(output.skippedBlockNumbers).toEqual([]) + }) + test('page 2 loads block numbers', () => { + const state = initialState + const action = { + type: 'PAGE_LOAD', + beyondPageOne: true, + blockNumbers: [2, 1] + } + const output = reducer(state, action) + + expect(output.beyondPageOne).toBe(true) + expect(output.blockNumbers).toEqual([2, 1]) + expect(output.skippedBlockNumbers).toEqual([]) + }) + test('page 1 with skipped blocks', () => { + const state = initialState + const action = { + type: 'PAGE_LOAD', + beyondPageOne: false, + blockNumbers: [4, 1] + } + const output = reducer(state, action) + + expect(output.beyondPageOne).toBe(false) + expect(output.blockNumbers).toEqual([4, 3, 2, 1]) + expect(output.skippedBlockNumbers).toEqual([3, 2]) + }) + test('page 2 with skipped blocks', () => { + const state = initialState + const action = { + type: 'PAGE_LOAD', + beyondPageOne: true, + blockNumbers: [4, 1] + } + const output = reducer(state, action) + + expect(output.beyondPageOne).toBe(true) + expect(output.blockNumbers).toEqual([4, 3, 2, 1]) + expect(output.skippedBlockNumbers).toEqual([3, 2]) + }) +}) + describe('RECEIVED_NEW_BLOCK', () => { test('receives new block', () => { const action = { @@ -56,12 +110,12 @@ describe('RECEIVED_NEW_BLOCK', () => { expect(output.newBlock).toBe('test5') expect(output.blockNumbers).toEqual([5, 4, 3, 2]) - expect(output.skippedBlockNumbers).toEqual([3, 4]) + expect(output.skippedBlockNumbers).toEqual([4, 3]) }) test('replaces skipped block', () => { const state = Object.assign({}, initialState, { blockNumbers: [5, 4, 3, 2, 1], - skippedBlockNumbers: [1, 3, 4] + skippedBlockNumbers: [4, 3, 1] }) const action = { type: 'RECEIVED_NEW_BLOCK', @@ -74,7 +128,7 @@ describe('RECEIVED_NEW_BLOCK', () => { expect(output.newBlock).toBe('test3') expect(output.blockNumbers).toEqual([5, 4, 3, 2, 1]) - expect(output.skippedBlockNumbers).toEqual([1, 4]) + expect(output.skippedBlockNumbers).toEqual([4, 1]) }) test('replaces duplicated block', () => { const state = Object.assign({}, initialState, { diff --git a/apps/block_scout_web/assets/__tests__/pages/chain.js b/apps/block_scout_web/assets/__tests__/pages/chain.js index d3bc264f05..4f0cde7106 100644 --- a/apps/block_scout_web/assets/__tests__/pages/chain.js +++ b/apps/block_scout_web/assets/__tests__/pages/chain.js @@ -1,5 +1,30 @@ import { reducer, initialState } from '../../js/pages/chain' +describe('PAGE_LOAD', () => { + test('loads block numbers', () => { + const state = initialState + const action = { + type: 'PAGE_LOAD', + blockNumbers: [2, 1] + } + const output = reducer(state, action) + + expect(output.blockNumbers).toEqual([2, 1]) + expect(output.skippedBlockNumbers).toEqual([]) + }) + test('loads with skipped blocks', () => { + const state = initialState + const action = { + type: 'PAGE_LOAD', + blockNumbers: [4, 1] + } + const output = reducer(state, action) + + expect(output.blockNumbers).toEqual([4, 3, 2, 1]) + expect(output.skippedBlockNumbers).toEqual([3, 2]) + }) +}) + test('RECEIVED_NEW_ADDRESS_COUNT', () => { const state = Object.assign({}, initialState, { addressCount: '1,000' @@ -54,12 +79,12 @@ describe('RECEIVED_NEW_BLOCK', () => { expect(output.averageBlockTime).toEqual('5 seconds') expect(output.newBlock).toBe('test5') expect(output.blockNumbers).toEqual([5, 4, 3, 2]) - expect(output.skippedBlockNumbers).toEqual([3, 4]) + expect(output.skippedBlockNumbers).toEqual([4, 3]) }) test('replaces skipped block', () => { const state = Object.assign({}, initialState, { blockNumbers: [4, 3, 2, 1], - skippedBlockNumbers: [1, 2, 3] + skippedBlockNumbers: [3, 2, 1] }) const action = { type: 'RECEIVED_NEW_BLOCK', @@ -74,7 +99,7 @@ describe('RECEIVED_NEW_BLOCK', () => { expect(output.averageBlockTime).toEqual('5 seconds') expect(output.newBlock).toBe('test2') expect(output.blockNumbers).toEqual([4, 3, 2, 1]) - expect(output.skippedBlockNumbers).toEqual([1, 3]) + expect(output.skippedBlockNumbers).toEqual([3, 1]) }) test('replaces duplicated block', () => { const state = Object.assign({}, initialState, { @@ -116,7 +141,7 @@ describe('RECEIVED_NEW_BLOCK', () => { test('only tracks 4 blocks based on page display limit', () => { const state = Object.assign({}, initialState, { blockNumbers: [5, 4, 3, 2], - skippedBlockNumbers: [2, 3, 4] + skippedBlockNumbers: [4, 3, 2] }) const action = { type: 'RECEIVED_NEW_BLOCK', @@ -129,12 +154,12 @@ describe('RECEIVED_NEW_BLOCK', () => { expect(output.newBlock).toBe('test6') expect(output.blockNumbers).toEqual([6, 5, 4, 3]) - expect(output.skippedBlockNumbers).toEqual([3, 4]) + expect(output.skippedBlockNumbers).toEqual([4, 3]) }) test('skipped blocks list replaced when another block comes in with +3 blockheight', () => { const state = Object.assign({}, initialState, { blockNumbers: [5, 4, 3, 2], - skippedBlockNumbers: [2, 3, 4] + skippedBlockNumbers: [4, 3, 2] }) const action = { type: 'RECEIVED_NEW_BLOCK', @@ -147,7 +172,7 @@ describe('RECEIVED_NEW_BLOCK', () => { expect(output.newBlock).toBe('test10') expect(output.blockNumbers).toEqual([10, 9, 8, 7]) - expect(output.skippedBlockNumbers).toEqual([7, 8, 9]) + expect(output.skippedBlockNumbers).toEqual([9, 8, 7]) }) }) diff --git a/apps/block_scout_web/assets/css/components/_tile.scss b/apps/block_scout_web/assets/css/components/_tile.scss index ebf786d6c6..7013626273 100644 --- a/apps/block_scout_web/assets/css/components/_tile.scss +++ b/apps/block_scout_web/assets/css/components/_tile.scss @@ -17,7 +17,7 @@ &-type { - &-Block { + &-block { border-left: 4px solid $indigo; .tile-label { @@ -25,7 +25,7 @@ } } - &-Uncle { + &-uncle { border-left: 4px solid $cyan; .tile-label { @@ -33,7 +33,7 @@ } } - &-Reorg { + &-reorg { border-left: 4px solid $purple; .tile-label { diff --git a/apps/block_scout_web/assets/js/pages/block.js b/apps/block_scout_web/assets/js/pages/block.js index 9201d738db..4e137984a6 100644 --- a/apps/block_scout_web/assets/js/pages/block.js +++ b/apps/block_scout_web/assets/js/pages/block.js @@ -4,7 +4,7 @@ import URI from 'urijs' import humps from 'humps' import socket from '../socket' import { updateAllAges } from '../lib/from_now' -import { initRedux, prependWithClingBottom } from '../utils' +import { buildFullBlockList, initRedux, beforeWithClingBottom, skippedBlockListBuilder } from '../utils' export const initialState = { blockNumbers: [], @@ -18,9 +18,12 @@ export const initialState = { export function reducer (state = initialState, action) { switch (action.type) { case 'PAGE_LOAD': { + const blockNumbers = buildFullBlockList(action.blockNumbers) + const skippedBlockNumbers = _.difference(blockNumbers, action.blockNumbers) return Object.assign({}, state, { beyondPageOne: action.beyondPageOne, - blockNumbers: action.blockNumbers + blockNumbers, + skippedBlockNumbers }) } case 'CHANNEL_DISCONNECTED': { @@ -43,9 +46,7 @@ export function reducer (state = initialState, action) { } else { let skippedBlockNumbers = state.skippedBlockNumbers.slice(0) if (blockNumber > state.blockNumbers[0] + 1) { - for (let i = state.blockNumbers[0] + 1; i < blockNumber; i++) { - skippedBlockNumbers.push(i) - } + skippedBlockListBuilder(skippedBlockNumbers, blockNumber, state.blockNumbers[0]) } const newBlockNumbers = _.chain([blockNumber]) .union(skippedBlockNumbers, state.blockNumbers) @@ -85,22 +86,21 @@ if ($blockListPage.length) { }, render (state, oldState) { const $channelDisconnected = $('[data-selector="channel-disconnected-message"]') - const $blocksList = $('[data-selector="blocks-list"]') + const skippedBlockNumbers = _.difference(state.skippedBlockNumbers, oldState.skippedBlockNumbers) if (state.channelDisconnected) $channelDisconnected.show() - if (oldState.newBlock !== state.newBlock || (state.replaceBlock && oldState.replaceBlock !== state.replaceBlock)) { + if ((state.newBlock && oldState.newBlock !== state.newBlock) || skippedBlockNumbers.length) { if (state.replaceBlock && oldState.replaceBlock !== state.replaceBlock) { const $replaceBlock = $(`[data-block-number="${state.replaceBlock}"]`) $replaceBlock.addClass('shrink-out') setTimeout(() => $replaceBlock.replaceWith(state.newBlock), 400) } else { - if (oldState.skippedBlockNumbers !== state.skippedBlockNumbers) { - const newSkippedBlockNumbers = _.difference(state.skippedBlockNumbers, oldState.skippedBlockNumbers) - _.map(newSkippedBlockNumbers, (skippedBlockNumber) => { - prependWithClingBottom($blocksList, placeHolderBlock(skippedBlockNumber)) + if (skippedBlockNumbers.length) { + _.forEachRight(skippedBlockNumbers, (skippedBlockNumber) => { + beforeWithClingBottom($(`[data-block-number="${skippedBlockNumber - 1}"]`), placeHolderBlock(skippedBlockNumber)) }) } - prependWithClingBottom($blocksList, state.newBlock) + beforeWithClingBottom($(`[data-block-number="${state.blockNumbers[0] - 1}"]`), state.newBlock) } updateAllAges() } @@ -110,11 +110,10 @@ if ($blockListPage.length) { function placeHolderBlock (blockNumber) { return ` -
+
diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js index 0a7cd067c9..c9755da39e 100644 --- a/apps/block_scout_web/assets/js/pages/chain.js +++ b/apps/block_scout_web/assets/js/pages/chain.js @@ -5,7 +5,7 @@ import numeral from 'numeral' import socket from '../socket' import { updateAllAges } from '../lib/from_now' import { exchangeRateChannel, formatUsdValue } from '../lib/currency' -import { batchChannel, initRedux, slideDownPrepend } from '../utils' +import { batchChannel, buildFullBlockList, initRedux, skippedBlockListBuilder, slideDownPrepend } from '../utils' import { createMarketHistoryChart } from '../lib/market_history_chart' const BATCH_THRESHOLD = 10 @@ -28,9 +28,13 @@ export const initialState = { export function reducer (state = initialState, action) { switch (action.type) { case 'PAGE_LOAD': { + const fullBlockNumberList = buildFullBlockList(action.blockNumbers) + const fullSkippedBlockNumberList = _.difference(fullBlockNumberList, action.blockNumbers) + const blockNumbers = fullBlockNumberList.slice(0, 4) return Object.assign({}, state, { - blockNumbers: action.blockNumbers, - transactionCount: numeral(action.transactionCount).value() + blockNumbers, + transactionCount: numeral(action.transactionCount).value(), + skippedBlockNumbers: _.intersection(fullSkippedBlockNumberList, blockNumbers) }) } case 'RECEIVED_NEW_ADDRESS_COUNT': { @@ -52,21 +56,18 @@ export function reducer (state = initialState, action) { } else { let skippedBlockNumbers = state.skippedBlockNumbers.slice(0) if (blockNumber > state.blockNumbers[0] + 1) { - let lastPlaceholder = state.blockNumbers[0] + 1 - if (blockNumber - lastPlaceholder > 3) { - lastPlaceholder = blockNumber - 3 + let oldestBlock = state.blockNumbers[0] + if (blockNumber - oldestBlock >= 3) { skippedBlockNumbers = [] + if (blockNumber - oldestBlock > 3) oldestBlock = blockNumber - 4 } - for (let i = lastPlaceholder; i < blockNumber; i++) { - skippedBlockNumbers.push(i) - } + skippedBlockListBuilder(skippedBlockNumbers, blockNumber, oldestBlock) } const newBlockNumbers = _.chain([blockNumber]) .union(skippedBlockNumbers, state.blockNumbers) .orderBy([], ['desc']) .slice(0, 4) .value() - const newSkippedBlockNumbers = _.intersection(skippedBlockNumbers, newBlockNumbers) return Object.assign({}, state, { averageBlockTime: action.msg.averageBlockTime, @@ -142,6 +143,8 @@ if ($chainDetailsPage.length) { const $marketCap = $('[data-selector="market-cap"]') const $transactionsList = $('[data-selector="transactions-list"]') const $transactionCount = $('[data-selector="transaction-count"]') + const newTransactions = _.difference(state.newTransactions, oldState.newTransactions) + const skippedBlockNumbers = _.difference(state.skippedBlockNumbers, oldState.skippedBlockNumbers) if (oldState.addressCount !== state.addressCount) { $addressCount.empty().append(state.addressCount) @@ -152,24 +155,23 @@ if ($chainDetailsPage.length) { if (oldState.usdMarketCap !== state.usdMarketCap) { $marketCap.empty().append(formatUsdValue(state.usdMarketCap)) } - if (state.newBlock && oldState.newBlock !== state.newBlock) { + if ((state.newBlock && oldState.newBlock !== state.newBlock) || skippedBlockNumbers.length) { if (state.replaceBlock && oldState.replaceBlock !== state.replaceBlock) { const $replaceBlock = $(`[data-block-number="${state.replaceBlock}"]`) $replaceBlock.addClass('shrink-out') setTimeout(() => $replaceBlock.replaceWith(state.newBlock), 400) } else { - if (oldState.skippedBlockNumbers !== state.skippedBlockNumbers) { - const newSkippedBlockNumbers = _.chain(state.skippedBlockNumbers) - .difference(oldState.skippedBlockNumbers) - .intersection(state.blockNumbers) - .value() - _.map(newSkippedBlockNumbers, (skippedBlockNumber) => { + if (state.newBlock) { + $blockList.children().last().remove() + $blockList.prepend(newBlockHtml(state.newBlock)) + } + if (skippedBlockNumbers.length) { + const newSkippedBlockNumbers = _.intersection(skippedBlockNumbers, state.blockNumbers) + _.each(newSkippedBlockNumbers, (skippedBlockNumber) => { $blockList.children().last().remove() - $blockList.prepend(placeHolderBlock(skippedBlockNumber)) + $(`[data-block-number="${skippedBlockNumber + 1}"]`).parent().after(placeHolderBlock(skippedBlockNumber)) }) } - $blockList.children().last().remove() - $blockList.prepend(newBlockHtml(state.newBlock)) } updateAllAges() } @@ -180,7 +182,7 @@ if ($chainDetailsPage.length) { } else { $channelBatching.hide() } - if (oldState.newTransactions !== state.newTransactions) { + if (newTransactions.length) { const newTransactionsToInsert = state.newTransactions.slice(oldState.newTransactions.length) $transactionsList .children() @@ -190,7 +192,6 @@ if ($chainDetailsPage.length) { updateAllAges() } - if (oldState.availableSupply !== state.availableSupply || oldState.marketHistoryData !== state.marketHistoryData) { chart.update(state.availableSupply, state.marketHistoryData) } @@ -214,9 +215,9 @@ function placeHolderBlock (blockNumber) { >
diff --git a/apps/block_scout_web/assets/js/utils.js b/apps/block_scout_web/assets/js/utils.js index b0a2f89c3c..6544debd3c 100644 --- a/apps/block_scout_web/assets/js/utils.js +++ b/apps/block_scout_web/assets/js/utils.js @@ -14,6 +14,12 @@ export function batchChannel (func) { } } +export function buildFullBlockList (blockNumbers) { + const newestBlock = _.first(blockNumbers) + const oldestBlock = _.last(blockNumbers) + return skippedBlockListBuilder([], newestBlock + 1, oldestBlock - 1) +} + export function initRedux (reducer, { main, render, debug } = {}) { if (!reducer) { console.error('initRedux: You need a reducer to initialize Redux.') @@ -34,16 +40,33 @@ export function initRedux (reducer, { main, render, debug } = {}) { if (main) main(store) } +export function skippedBlockListBuilder (skippedBlockNumbers, newestBlock, oldestBlock) { + for (let i = newestBlock - 1; i > oldestBlock; i--) skippedBlockNumbers.push(i) + return skippedBlockNumbers +} + export function slideDownPrepend ($el, content, callback) { const $content = $(content) $el.prepend($content.hide()) $content.slideDown({ complete: callback }) } +export function slideDownBefore ($el, content, callback) { + const $content = $(content) + $el.before($content.hide()) + $content.slideDown({ complete: callback }) +} export function prependWithClingBottom ($el, content) { + return slideDownPrepend($el, content, clingBottom($el, content)) +} +export function beforeWithClingBottom ($el, content) { + return slideDownBefore($el, content, clingBottom($el, content)) +} + +function clingBottom ($el, content) { function userAtTop () { return window.scrollY < $('[data-selector="navbar"]').outerHeight() } - if (userAtTop()) return slideDownPrepend($el, content) + if (userAtTop()) return true let isAnimating function setIsAnimating () { @@ -73,8 +96,10 @@ export function prependWithClingBottom ($el, content) { $el.off('animationend animationcancel', stopClinging) } - return slideDownPrepend($el, content, () => { - $el.on('animationend animationcancel', stopClinging) - setTimeout(() => !isAnimating && stopClinging(), 100) - }) + return { + function () { + $el.on('animationend animationcancel', stopClinging) + setTimeout(() => !isAnimating && stopClinging(), 100) + } + } } diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex index 9029964385..f59cf4243a 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.BlockListPage do end def block(%Block{number: block_number}) do - css("[data-selector='block-tile'][data-block-number='#{block_number}']") + css("[data-block-number='#{block_number}']") end def place_holder_blocks(count) do diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex index 9ff557eabb..21e5329922 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex @@ -5,7 +5,11 @@ defmodule BlockScoutWeb.ChainPage do import Wallaby.Query, only: [css: 1, css: 2] - alias Explorer.Chain.Transaction + alias Explorer.Chain.{Block, Transaction} + + def block(%Block{number: block_number}) do + css("[data-block-number='#{block_number}']") + end def blocks(count: count) do css("[data-selector='chain-block']", count: count) @@ -15,6 +19,10 @@ defmodule BlockScoutWeb.ChainPage do css("[data-test='contract-creation'] [data-address-hash='#{hash}']") end + def place_holder_blocks(count) do + css("[data-selector='place-holder']", count: count) + end + def search(session, text) do session |> fill_in(css("[data-test='search_input']"), with: text) diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs index 4dff2a36ee..60ccda437e 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.ViewingBlocksTest do use BlockScoutWeb.FeatureCase, async: true alias BlockScoutWeb.{BlockListPage, BlockPage, Notifier} + alias Explorer.Chain.Block setup do timestamp = Timex.now() |> Timex.shift(hours: -1) @@ -159,6 +160,28 @@ defmodule BlockScoutWeb.ViewingBlocksTest do |> assert_has(BlockListPage.block(skipped_block)) |> assert_has(BlockListPage.place_holder_blocks(2)) end + + test "inserts place holder blocks on render for out of order blocks", %{session: session} do + insert(:block, number: 315) + + session + |> BlockListPage.visit_page() + |> assert_has(BlockListPage.block(%Block{number: 314})) + |> assert_has(BlockListPage.place_holder_blocks(3)) + end + + test "replaces rendered place holder block if skipped block received", %{session: session} do + insert(:block, number: 315) + + BlockListPage.visit_page(session) + + block = insert(:block, number: 314) + Notifier.handle_event({:chain_event, :blocks, [block]}) + + session + |> assert_has(BlockListPage.block(block)) + |> assert_has(BlockListPage.place_holder_blocks(2)) + end end describe "viewing uncle blocks list" do diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs index ac24752573..b82e9e82ab 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs @@ -3,7 +3,8 @@ defmodule BlockScoutWeb.ViewingChainTest do use BlockScoutWeb.FeatureCase, async: true - alias BlockScoutWeb.{AddressPage, BlockPage, ChainPage, TransactionPage} + alias BlockScoutWeb.{AddressPage, BlockPage, ChainPage, Notifier, TransactionPage} + alias Explorer.Chain.Block setup do Enum.map(401..404, &insert(:block, number: &1)) @@ -50,6 +51,57 @@ defmodule BlockScoutWeb.ViewingChainTest do |> ChainPage.visit_page() |> assert_has(ChainPage.blocks(count: 4)) end + + test "inserts place holder blocks if out of order block received", %{session: session} do + ChainPage.visit_page(session) + + block = insert(:block, number: 409) + Notifier.handle_event({:chain_event, :blocks, :realtime, [block]}) + + session + |> assert_has(ChainPage.block(block)) + |> assert_has(ChainPage.place_holder_blocks(3)) + end + + test "replaces place holder block if skipped block received", %{session: session} do + ChainPage.visit_page(session) + + block = insert(:block, number: 409) + Notifier.handle_event({:chain_event, :blocks, :realtime, [block]}) + + session + |> assert_has(ChainPage.block(block)) + |> assert_has(ChainPage.place_holder_blocks(3)) + + skipped_block = insert(:block, number: 408) + Notifier.handle_event({:chain_event, :blocks, :realtime, [skipped_block]}) + + session + |> assert_has(ChainPage.block(skipped_block)) + |> assert_has(ChainPage.place_holder_blocks(2)) + end + + test "inserts place holder blocks on render for out of order blocks", %{session: session} do + insert(:block, number: 409) + + session + |> ChainPage.visit_page() + |> assert_has(ChainPage.block(%Block{number: 408})) + |> assert_has(ChainPage.place_holder_blocks(3)) + end + + test "replaces rendered place holder block if skipped block received", %{session: session} do + insert(:block, number: 409) + + ChainPage.visit_page(session) + + block = insert(:block, number: 408) + Notifier.handle_event({:chain_event, :blocks, [block]}) + + session + |> assert_has(ChainPage.block(block)) + |> assert_has(ChainPage.place_holder_blocks(2)) + end end describe "viewing transactions" do