Merge pull request #847 from poanetwork/775-in-order-blocks-realtime

775 in order blocks realtime
pull/852/head
John Stamates 6 years ago committed by GitHub
commit 1ce4753de4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 112
      apps/block_scout_web/assets/__tests__/pages/block.js
  2. 142
      apps/block_scout_web/assets/__tests__/pages/chain.js
  3. 11
      apps/block_scout_web/assets/__tests__/pages/transaction.js
  4. 44
      apps/block_scout_web/assets/css/components/_animations.scss
  5. 2
      apps/block_scout_web/assets/css/components/_card.scss
  6. 9
      apps/block_scout_web/assets/css/components/_loading-spinner.scss
  7. 83
      apps/block_scout_web/assets/js/pages/block.js
  8. 102
      apps/block_scout_web/assets/js/pages/chain.js
  9. 5
      apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex
  10. 33
      apps/block_scout_web/lib/block_scout_web/templates/chain/_block.html.eex
  11. 4
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  12. 1
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  13. 28
      apps/block_scout_web/priv/gettext/default.pot
  14. 28
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  15. 8
      apps/block_scout_web/test/block_scout_web/features/pages/block_list_page.ex
  16. 31
      apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs

@ -1,13 +1,111 @@
import { reducer, initialState } from '../../js/pages/block'
test('RECEIVED_NEW_BLOCK', () => {
test('CHANNEL_DISCONNECTED', () => {
const state = initialState
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: "test"
}
type: 'CHANNEL_DISCONNECTED'
}
const output = reducer(initialState, action)
const output = reducer(state, action)
expect(output.channelDisconnected).toBe(true)
})
describe('RECEIVED_NEW_BLOCK', () => {
test('receives new block', () => {
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: 'test',
blockNumber: 1
}
}
const output = reducer(initialState, action)
expect(output.newBlock).toBe('test')
expect(output.blockNumbers).toEqual([1])
})
test('on page 2+', () => {
const state = Object.assign({}, initialState, {
beyondPageOne: true
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msgs: [{
blockHtml: 'test'
}]
}
const output = reducer(state, action)
expect(output.newBlock).toBe(null)
expect(output.blockNumbers).toEqual([])
expect(output.skippedBlockNumbers).toEqual([])
})
test('inserts place holders if block received out of order', () => {
const state = Object.assign({}, initialState, {
blockNumbers: [2]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: 'test5',
blockNumber: 5
}
}
const output = reducer(state, action)
expect(output.newBlock).toBe('test5')
expect(output.blockNumbers).toEqual([5, 4, 3, 2])
expect(output.skippedBlockNumbers).toEqual([3, 4])
})
test('replaces skipped block', () => {
const state = Object.assign({}, initialState, {
blockNumbers: [5, 4, 3, 2, 1],
skippedBlockNumbers: [1, 3, 4]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: 'test3',
blockNumber: 3
}
}
const output = reducer(state, action)
expect(output.newBlock).toBe('test3')
expect(output.blockNumbers).toEqual([5, 4, 3, 2, 1])
expect(output.skippedBlockNumbers).toEqual([1, 4])
})
test('replaces duplicated block', () => {
const state = Object.assign({}, initialState, {
blockNumbers: [5, 4]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: 'test5',
blockNumber: 5
}
}
const output = reducer(state, action)
expect(output.newBlock).toBe('test5')
expect(output.blockNumbers).toEqual([5, 4])
})
test('skips if new block height is lower than lowest on page', () => {
const state = Object.assign({}, initialState, {
blockNumbers: [5, 4, 3, 2]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: 'test1',
blockNumber: 1
}
}
const output = reducer(state, action)
expect(output.newBlock).toBe("test")
expect(output.newBlock).toBe(null)
expect(output.blockNumbers).toEqual([5, 4, 3, 2])
})
})

@ -15,22 +15,140 @@ test('RECEIVED_NEW_ADDRESS_COUNT', () => {
expect(output.addressCount).toEqual('1,000,000')
})
test('RECEIVED_NEW_BLOCK', () => {
const state = Object.assign({}, initialState, {
averageBlockTime: '6 seconds',
newBlock: 'last new block'
describe('RECEIVED_NEW_BLOCK', () => {
test('receives new block', () => {
const state = Object.assign({}, initialState, {
averageBlockTime: '6 seconds',
blockNumbers: [1],
newBlock: 'last new block'
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
averageBlockTime: '5 seconds',
blockNumber: 2,
chainBlockHtml: 'new block'
}
}
const output = reducer(state, action)
expect(output.averageBlockTime).toEqual('5 seconds')
expect(output.newBlock).toEqual('new block')
expect(output.blockNumbers).toEqual([2, 1])
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
test('inserts place holders if block received out of order', () => {
const state = Object.assign({}, initialState, {
blockNumbers: [2]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
averageBlockTime: '5 seconds',
chainBlockHtml: 'test5',
blockNumber: 5
}
}
const output = reducer(state, action)
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])
})
test('replaces skipped block', () => {
const state = Object.assign({}, initialState, {
blockNumbers: [4, 3, 2, 1],
skippedBlockNumbers: [1, 2, 3]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
averageBlockTime: '5 seconds',
chainBlockHtml: 'test2',
blockNumber: 2
}
}
const output = reducer(state, action)
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])
})
test('replaces duplicated block', () => {
const state = Object.assign({}, initialState, {
blockNumbers: [5, 4]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
averageBlockTime: '5 seconds',
chainBlockHtml: 'test5',
blockNumber: 5
}
}
const output = reducer(state, action)
expect(output.averageBlockTime).toEqual('5 seconds')
expect(output.newBlock).toBe('test5')
expect(output.blockNumbers).toEqual([5, 4])
})
test('skips if new block height is lower than lowest on page', () => {
const state = Object.assign({}, initialState, {
averageBlockTime: '5 seconds',
chainBlockHtml: 'new block'
blockNumbers: [5, 4, 3, 2]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
averageBlockTime: '9 seconds',
chainBlockHtml: 'test1',
blockNumber: 1
}
}
}
const output = reducer(state, action)
const output = reducer(state, action)
expect(output.averageBlockTime).toEqual('5 seconds')
expect(output.newBlock).toBe(null)
expect(output.blockNumbers).toEqual([5, 4, 3, 2])
})
test('only tracks 4 blocks based on page display limit', () => {
const state = Object.assign({}, initialState, {
blockNumbers: [5, 4, 3, 2],
skippedBlockNumbers: [2, 3, 4]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
chainBlockHtml: 'test6',
blockNumber: 6
}
}
const output = reducer(state, action)
expect(output.averageBlockTime).toEqual('5 seconds')
expect(output.newBlock).toEqual('new block')
expect(output.newBlock).toBe('test6')
expect(output.blockNumbers).toEqual([6, 5, 4, 3])
expect(output.skippedBlockNumbers).toEqual([3, 4])
})
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]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
chainBlockHtml: 'test10',
blockNumber: 10
}
}
const output = reducer(state, action)
expect(output.newBlock).toBe('test10')
expect(output.blockNumbers).toEqual([10, 9, 8, 7])
expect(output.skippedBlockNumbers).toEqual([7, 8, 9])
})
})
test('RECEIVED_NEW_EXCHANGE_RATE', () => {

@ -1,5 +1,16 @@
import { reducer, initialState } from '../../js/pages/transaction'
test('CHANNEL_DISCONNECTED', () => {
const state = initialState
const action = {
type: 'CHANNEL_DISCONNECTED'
}
const output = reducer(state, action)
expect(output.channelDisconnected).toBe(true)
expect(output.batchCountAccumulator).toBe(0)
})
test('RECEIVED_NEW_BLOCK', () => {
const state = { ...initialState, blockNumber: 1 }
const action = {

@ -8,20 +8,11 @@
0% {
flex-basis: 0%;
width: 0%;
opacity: 0;
}
25% {
opacity: 0;
transform: translateY(10px) scale(0.97);
}
50% {
flex-basis: 25%;
width: 25%;
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes fade-up {
@ -34,25 +25,7 @@
transform: translateY(10px) scale(0.97);
}
50% {
height: 98px;
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes fade-up--mobile {
0% {
height: 0;
opacity: 0;
}
25% {
opacity: 0;
transform: translateY(10px) scale(0.97);
}
50% {
height: 202px;
height: 100px;
}
100% {
opacity: 1;
@ -67,7 +40,7 @@
}
100% {
opacity: 0;
transform: scale(0.5);
transform: scale(0.75);
}
}
@ -80,22 +53,17 @@
max-height: 98px;
animation: fade-up-blocks-chain 0.6s cubic-bezier(0.455, 0.03, 0.515, 0.955);
@media (max-width: 767px) {
animation: fade-up--mobile 0.6s cubic-bezier(0.455, 0.03, 0.515, 0.955);
@include media-breakpoint-down(md) {
animation: none;
}
}
.fade-up {
will-change: transform, opacity, height;
max-height: 98px;
animation: fade-up 0.6s cubic-bezier(0.455, 0.03, 0.515, 0.955);
@media (max-width: 767px) {
max-height: 202px;
animation: fade-up--mobile 0.6s cubic-bezier(0.455, 0.03, 0.515, 0.955);
}
}
.shrink-out {
animation: shrink-out 0.45s cubic-bezier(0.55, 0.055, 0.675, 0.19) forwards;
transform-origin: bottom center;
animation: shrink-out 0.3s cubic-bezier(0.55, 0.055, 0.675, 0.19) forwards;
}

@ -34,5 +34,5 @@
.card-chain-blocks {
height: auto;
@media (max-width: 767px) { height: 595px; }
@include media-breakpoint-down(md) { height: 595px; }
}

@ -1,3 +1,10 @@
// ATTRIBUTION
//
// Author: Tobias Ahlin (tobiasahlin)
//
// SpinKit - Simple loading spinners animated with CSS.
// https://github.com/tobiasahlin/SpinKit
.loading-spinner {
margin: auto 1rem;
width: 40px;
@ -38,7 +45,7 @@
.loading-spinner-small {
display: inline-block;
position: relative;
top: -0.05em;
top: -0.125em;
margin: auto 0.5em auto 0;
width: 1em;
height: 1em;

@ -1,4 +1,5 @@
import $ from 'jquery'
import _ from 'lodash'
import URI from 'urijs'
import humps from 'humps'
import socket from '../socket'
@ -6,16 +7,20 @@ import { updateAllAges } from '../lib/from_now'
import { initRedux, prependWithClingBottom } from '../utils'
export const initialState = {
blockNumbers: [],
beyondPageOne: null,
channelDisconnected: false,
newBlock: null
newBlock: null,
replaceBlock: null,
skippedBlockNumbers: []
}
export function reducer (state = initialState, action) {
switch (action.type) {
case 'PAGE_LOAD': {
return Object.assign({}, state, {
beyondPageOne: action.beyondPageOne
beyondPageOne: action.beyondPageOne,
blockNumbers: action.blockNumbers
})
}
case 'CHANNEL_DISCONNECTED': {
@ -24,11 +29,36 @@ export function reducer (state = initialState, action) {
})
}
case 'RECEIVED_NEW_BLOCK': {
if (state.channelDisconnected) return state
if (state.channelDisconnected || state.beyondPageOne) return state
return Object.assign({}, state, {
newBlock: action.msg.blockHtml
})
const blockNumber = parseInt(action.msg.blockNumber)
if (_.includes(state.blockNumbers, blockNumber)) {
return Object.assign({}, state, {
newBlock: action.msg.blockHtml,
replaceBlock: blockNumber,
skippedBlockNumbers: _.without(state.skippedBlockNumbers, blockNumber)
})
} else if (blockNumber < _.last(state.blockNumbers)) {
return state
} 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)
}
}
const newBlockNumbers = _.chain([blockNumber])
.union(skippedBlockNumbers, state.blockNumbers)
.orderBy([], ['desc'])
.value()
return Object.assign({}, state, {
blockNumbers: newBlockNumbers,
newBlock: action.msg.blockHtml,
replaceBlock: null,
skippedBlockNumbers
})
}
}
default:
return state
@ -41,7 +71,8 @@ if ($blockListPage.length) {
main (store) {
const state = store.dispatch({
type: 'PAGE_LOAD',
beyondPageOne: !!humps.camelizeKeys(URI(window.location).query(true)).blockNumber
beyondPageOne: !!humps.camelizeKeys(URI(window.location).query(true)).blockNumber,
blockNumbers: $('[data-selector="block-number"]').map((index, el) => parseInt(el.innerText)).toArray()
})
if (!state.beyondPageOne) {
const blocksChannel = socket.channel(`blocks:new_block`, {})
@ -57,10 +88,44 @@ if ($blockListPage.length) {
const $blocksList = $('[data-selector="blocks-list"]')
if (state.channelDisconnected) $channelDisconnected.show()
if (oldState.newBlock !== state.newBlock) {
prependWithClingBottom($blocksList, state.newBlock)
if (oldState.newBlock !== state.newBlock || (state.replaceBlock && oldState.replaceBlock !== state.replaceBlock)) {
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))
})
}
prependWithClingBottom($blocksList, state.newBlock)
}
updateAllAges()
}
}
})
}
function placeHolderBlock (blockNumber) {
return `
<div class="my-3" style="height: 98px;">
<div
class="tile tile-type-block d-flex align-items-center fade-up"
data-selector="place-holder"
data-block-number="${blockNumber}"
style="height: 98px;"
>
<span class="loading-spinner-small ml-1 mr-4">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<div>
<div class="tile-title">${blockNumber}</div>
<div>${window.localized['Block Processing']}</div>
</div>
</div>
</div>
`
}

@ -1,4 +1,5 @@
import $ from 'jquery'
import _ from 'lodash'
import humps from 'humps'
import numeral from 'numeral'
import socket from '../socket'
@ -14,9 +15,12 @@ export const initialState = {
availableSupply: null,
averageBlockTime: null,
batchCountAccumulator: 0,
blockNumbers: [],
marketHistoryData: null,
newBlock: null,
newTransactions: [],
replaceBlock: null,
skippedBlockNumbers: [],
transactionCount: null,
usdMarketCap: null
}
@ -25,6 +29,7 @@ export function reducer (state = initialState, action) {
switch (action.type) {
case 'PAGE_LOAD': {
return Object.assign({}, state, {
blockNumbers: action.blockNumbers,
transactionCount: numeral(action.transactionCount).value()
})
}
@ -34,10 +39,43 @@ export function reducer (state = initialState, action) {
})
}
case 'RECEIVED_NEW_BLOCK': {
return Object.assign({}, state, {
averageBlockTime: action.msg.averageBlockTime,
newBlock: action.msg.chainBlockHtml
})
const blockNumber = parseInt(action.msg.blockNumber)
if (_.includes(state.blockNumbers, blockNumber)) {
return Object.assign({}, state, {
averageBlockTime: action.msg.averageBlockTime,
newBlock: action.msg.chainBlockHtml,
replaceBlock: blockNumber,
skippedBlockNumbers: _.without(state.skippedBlockNumbers, blockNumber)
})
} else if (blockNumber < _.last(state.blockNumbers)) {
return Object.assign({}, state, { newBlock: null })
} 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
skippedBlockNumbers = []
}
for (let i = lastPlaceholder; i < blockNumber; i++) {
skippedBlockNumbers.push(i)
}
}
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,
blockNumbers: newBlockNumbers,
newBlock: action.msg.chainBlockHtml,
replaceBlock: null,
skippedBlockNumbers: newSkippedBlockNumbers
})
}
}
case 'RECEIVED_NEW_EXCHANGE_RATE': {
return Object.assign({}, state, {
@ -79,6 +117,7 @@ if ($chainDetailsPage.length) {
const blocksChannel = socket.channel(`blocks:new_block`)
store.dispatch({
type: 'PAGE_LOAD',
blockNumbers: $('[data-selector="block-number"]').map((index, el) => parseInt(el.innerText)).toArray(),
transactionCount: $('[data-selector="transaction-count"]').text()
})
blocksChannel.join()
@ -113,9 +152,25 @@ if ($chainDetailsPage.length) {
if (oldState.usdMarketCap !== state.usdMarketCap) {
$marketCap.empty().append(formatUsdValue(state.usdMarketCap))
}
if (oldState.newBlock !== state.newBlock) {
$blockList.children().last().remove()
$blockList.prepend(state.newBlock)
if (state.newBlock && oldState.newBlock !== state.newBlock) {
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) => {
$blockList.children().last().remove()
$blockList.prepend(placeHolderBlock(skippedBlockNumber))
})
}
$blockList.children().last().remove()
$blockList.prepend(newBlockHtml(state.newBlock))
}
updateAllAges()
}
if (oldState.transactionCount !== state.transactionCount) $transactionCount.empty().append(numeral(state.transactionCount).format())
@ -142,3 +197,36 @@ if ($chainDetailsPage.length) {
}
})
}
function newBlockHtml (blockHtml) {
return `
<div class="col-lg-3 fade-up-blocks-chain mb-3 mb-lg-0">
${blockHtml}
</div>
`
}
function placeHolderBlock (blockNumber) {
return `
<div
class="col-lg-3 fade-up-blocks-chain mb-3 mb-lg-0"
style="min-height: 98px;"
>
<div
class="tile tile-type-block d-flex align-items-center fade-up"
data-selector="place-holder"
data-block-number="${blockNumber}"
style="height: 98px;"
>
<span class="loading-spinner-small ml-1 mr-4">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<div>
<div class="tile-title">${blockNumber}</div>
<div>${window.localized['Block Processing']}</div>
</div>
</div>
</div>
`
}

@ -1,4 +1,4 @@
<div class="tile tile-type-block fade-up">
<div class="tile tile-type-block fade-up" data-block-number="<%= to_string(@block.number) %>">
<div class="row">
<div class="col-md-8 col-lg-9">
<!-- block height -->
@ -6,8 +6,7 @@
@block,
class: "tile-title",
to: block_path(BlockScoutWeb.Endpoint, :show, @block),
"data-test": "block_number",
"data-block-number": to_string(@block.number)
"data-selector": "block-number"
) %>
<div>
<!-- transactions -->

@ -1,17 +1,20 @@
<div class="col-sm-3 fade-up-blocks-chain mb-3 mb-sm-0" data-selector="chain-block" data-block-number="<%= @block.number %>">
<div class="tile tile-type-block d-flex flex-column">
<%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block), class: "tile-title") %>
<div>
<span class="mr-2"> <%= Enum.count(@block.transactions) %> Transactions </span>
<span class="text-nowrap" data-from-now="<%= @block.timestamp %>"> </span>
</div>
<span class="text-truncate">
<%= gettext "Miner" %>
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @block.miner,
contract: false %>
</span>
<div class="tile tile-type-block d-flex flex-column" data-selector="chain-block" data-block-number="<%= @block.number %>">
<%= link(
@block,
class: "tile-title",
to: block_path(BlockScoutWeb.Endpoint, :show, @block),
"data-selector": "block-number"
) %>
<div>
<span class="mr-2"> <%= gettext("%{count} Transactions", count: Enum.count(@block.transactions)) %> </span>
<span class="text-nowrap" data-from-now="<%= @block.timestamp %>"> </span>
</div>
<span class="text-truncate">
<%= gettext "Miner" %>
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @block.miner,
contract: false %>
</span>
</div>

@ -56,7 +56,9 @@
<h2 class="card-title"><%= gettext "Blocks" %></h2>
<div class="row" data-selector="chain-block-list">
<%= for block <- @blocks do %>
<%= render BlockScoutWeb.ChainView, "_block.html", block: block %>
<div class="col-lg-3 fade-up-blocks-chain mb-3 mb-lg-0">
<%= render BlockScoutWeb.ChainView, "_block.html", block: block %>
</div>
<% end %>
</div>
</div>

@ -30,6 +30,7 @@
</div>
<script>
window.localized = {
'Block Processing': '<%= gettext("Block Mined, awaiting processing...") %>',
'Less than': '<%= gettext("Less than") %>',
'Market Cap': '<%= gettext("Market Cap") %>',
'Price': '<%= gettext("Price") %>',

@ -6,7 +6,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:15
#: lib/block_scout_web/templates/block/_tile.html.eex:14
msgid "%{count} transaction"
msgid_plural "%{count} transactions"
msgstr[0] ""
@ -21,6 +21,7 @@ msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:12
#: lib/block_scout_web/templates/chain/_block.html.eex:9
msgid "%{count} Transactions"
msgstr ""
@ -380,13 +381,13 @@ msgid "Gas"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:35
#: lib/block_scout_web/templates/block/_tile.html.eex:34
#: lib/block_scout_web/templates/block/overview.html.eex:97
msgid "Gas Limit"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:40
#: lib/block_scout_web/templates/block/_tile.html.eex:39
#: lib/block_scout_web/templates/block/overview.html.eex:89
msgid "Gas Used"
msgstr ""
@ -446,7 +447,7 @@ msgid "Inventory"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:33
#: lib/block_scout_web/templates/layout/app.html.eex:34
msgid "Less than"
msgstr ""
@ -465,7 +466,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:17
#: lib/block_scout_web/templates/layout/app.html.eex:34
#: lib/block_scout_web/templates/layout/app.html.eex:35
#: lib/block_scout_web/views/address_view.ex:101
msgid "Market Cap"
msgstr ""
@ -476,9 +477,9 @@ msgid "Max of"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:24
#: lib/block_scout_web/templates/block/_tile.html.eex:23
#: lib/block_scout_web/templates/block/overview.html.eex:70
#: lib/block_scout_web/templates/chain/_block.html.eex:9
#: lib/block_scout_web/templates/chain/_block.html.eex:13
msgid "Miner"
msgstr ""
@ -499,7 +500,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:19
#: lib/block_scout_web/templates/chain/show.html.eex:69
#: lib/block_scout_web/templates/chain/show.html.eex:71
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:48
#: lib/block_scout_web/templates/transaction/index.html.eex:48
msgid "More transactions have come in"
@ -624,7 +625,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:10
#: lib/block_scout_web/templates/layout/app.html.eex:35
#: lib/block_scout_web/templates/layout/app.html.eex:36
msgid "Price"
msgstr ""
@ -895,7 +896,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:23
#: lib/block_scout_web/templates/block_transaction/index.html.eex:26
#: lib/block_scout_web/templates/block_transaction/index.html.eex:35
#: lib/block_scout_web/templates/chain/show.html.eex:73
#: lib/block_scout_web/templates/chain/show.html.eex:75
#: lib/block_scout_web/templates/layout/_topnav.html.eex:24
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:56
#: lib/block_scout_web/templates/transaction/index.html.eex:56
@ -963,7 +964,7 @@ msgid "View All Blocks →"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:72
#: lib/block_scout_web/templates/chain/show.html.eex:74
msgid "View All Transactions →"
msgstr ""
@ -1070,3 +1071,8 @@ msgstr ""
#: lib/block_scout_web/views/layout_view.ex:21
msgid "%{subnetwork} %{network} Explorer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:33
msgid "Block Mined, awaiting processing..."
msgstr ""

@ -6,7 +6,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:15
#: lib/block_scout_web/templates/block/_tile.html.eex:14
msgid "%{count} transaction"
msgid_plural "%{count} transactions"
msgstr[0] ""
@ -21,6 +21,7 @@ msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:12
#: lib/block_scout_web/templates/chain/_block.html.eex:9
msgid "%{count} Transactions"
msgstr ""
@ -380,13 +381,13 @@ msgid "Gas"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:35
#: lib/block_scout_web/templates/block/_tile.html.eex:34
#: lib/block_scout_web/templates/block/overview.html.eex:97
msgid "Gas Limit"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:40
#: lib/block_scout_web/templates/block/_tile.html.eex:39
#: lib/block_scout_web/templates/block/overview.html.eex:89
msgid "Gas Used"
msgstr ""
@ -446,7 +447,7 @@ msgid "Inventory"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:33
#: lib/block_scout_web/templates/layout/app.html.eex:34
msgid "Less than"
msgstr ""
@ -465,7 +466,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:17
#: lib/block_scout_web/templates/layout/app.html.eex:34
#: lib/block_scout_web/templates/layout/app.html.eex:35
#: lib/block_scout_web/views/address_view.ex:101
msgid "Market Cap"
msgstr ""
@ -476,9 +477,9 @@ msgid "Max of"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/_tile.html.eex:24
#: lib/block_scout_web/templates/block/_tile.html.eex:23
#: lib/block_scout_web/templates/block/overview.html.eex:70
#: lib/block_scout_web/templates/chain/_block.html.eex:9
#: lib/block_scout_web/templates/chain/_block.html.eex:13
msgid "Miner"
msgstr ""
@ -499,7 +500,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:19
#: lib/block_scout_web/templates/chain/show.html.eex:69
#: lib/block_scout_web/templates/chain/show.html.eex:71
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:48
#: lib/block_scout_web/templates/transaction/index.html.eex:48
msgid "More transactions have come in"
@ -624,7 +625,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:10
#: lib/block_scout_web/templates/layout/app.html.eex:35
#: lib/block_scout_web/templates/layout/app.html.eex:36
msgid "Price"
msgstr ""
@ -895,7 +896,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:23
#: lib/block_scout_web/templates/block_transaction/index.html.eex:26
#: lib/block_scout_web/templates/block_transaction/index.html.eex:35
#: lib/block_scout_web/templates/chain/show.html.eex:73
#: lib/block_scout_web/templates/chain/show.html.eex:75
#: lib/block_scout_web/templates/layout/_topnav.html.eex:24
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:56
#: lib/block_scout_web/templates/transaction/index.html.eex:56
@ -963,7 +964,7 @@ msgid "View All Blocks →"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:72
#: lib/block_scout_web/templates/chain/show.html.eex:74
msgid "View All Transactions →"
msgstr ""
@ -1070,3 +1071,8 @@ msgstr ""
#: lib/block_scout_web/views/layout_view.ex:21
msgid "%{subnetwork} %{network} Explorer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/app.html.eex:33
msgid "Block Mined, awaiting processing..."
msgstr ""

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.BlockListPage do
use Wallaby.DSL
import Wallaby.Query, only: [css: 1]
import Wallaby.Query, only: [css: 1, css: 2]
alias Explorer.Chain.Block
@ -12,6 +12,10 @@ defmodule BlockScoutWeb.BlockListPage do
end
def block(%Block{number: block_number}) do
css("[data-test='block_number'][data-block-number='#{block_number}']")
css("[data-block-number='#{block_number}']")
end
def place_holder_blocks(count) do
css("[data-selector='place-holder']", count: count)
end
end

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.ViewingBlocksTest do
use BlockScoutWeb.FeatureCase, async: true
alias BlockScoutWeb.{BlockListPage, BlockPage}
alias BlockScoutWeb.{BlockListPage, BlockPage, Notifier}
setup do
timestamp = Timex.now() |> Timex.shift(hours: -1)
@ -35,6 +35,35 @@ defmodule BlockScoutWeb.ViewingBlocksTest do
|> assert_has(BlockPage.detail_number(block))
end
test "inserts place holder blocks if out of order block received", %{session: session} do
BlockListPage.visit_page(session)
block = insert(:block, number: 315)
Notifier.handle_event({:chain_event, :blocks, [block]})
session
|> assert_has(BlockListPage.block(block))
|> assert_has(BlockListPage.place_holder_blocks(3))
end
test "replaces place holder block if skipped block received", %{session: session} do
BlockListPage.visit_page(session)
block = insert(:block, number: 315)
Notifier.handle_event({:chain_event, :blocks, [block]})
session
|> assert_has(BlockListPage.block(block))
|> assert_has(BlockListPage.place_holder_blocks(3))
skipped_block = insert(:block, number: 314)
Notifier.handle_event({:chain_event, :blocks, [skipped_block]})
session
|> assert_has(BlockListPage.block(skipped_block))
|> assert_has(BlockListPage.place_holder_blocks(2))
end
test "block detail page has transactions", %{session: session} do
block = insert(:block, number: 42)

Loading…
Cancel
Save