Commit 400424a0 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into test/hook-for-paginated-queries

parents 109546a4 b2c51065
...@@ -51,6 +51,7 @@ NEXT_PUBLIC_AD_SLISE_ON=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_SLISE_ON__ ...@@ -51,6 +51,7 @@ NEXT_PUBLIC_AD_SLISE_ON=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_SLISE_ON__
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=__PLACEHOLDER_FOR_NEXT_PUBLIC_GRAPHIQL_TRANSACTION__ NEXT_PUBLIC_GRAPHIQL_TRANSACTION=__PLACEHOLDER_FOR_NEXT_PUBLIC_GRAPHIQL_TRANSACTION__
NEXT_PUBLIC_WEB3_DEFAULT_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DEFAULT_WALLET__ NEXT_PUBLIC_WEB3_DEFAULT_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DEFAULT_WALLET__
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET__ NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET__
NEXT_PUBLIC_HIDE_INDEXING_ALERT=__PLACEHOLDER_FOR_NEXT_PUBLIC_HIDE_INDEXING_ALERT__
# api config # api config
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__ NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
......
...@@ -2,15 +2,36 @@ name: Checks ...@@ -2,15 +2,36 @@ name: Checks
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
types: [ opened, synchronize, unlabeled ]
paths-ignore:
- '.github/ISSUE_TEMPLATE/**'
- '.husky/**'
- '.vscode/**'
- 'deploy/**'
- 'docs/**'
- 'public/**'
- 'stub/**'
push: push:
branches: branches:
- main - main
paths-ignore:
- '.github/ISSUE_TEMPLATE/**'
- '.husky/**'
- '.vscode/**'
- 'deploy/**'
- 'docs/**'
- 'public/**'
- 'stub/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
lint: code_quality:
name: ESLint name: Code quality
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: "!contains(github.event.pull_request.labels.*.name, 'WIP')" if: ${{ !contains(github.event.pull_request.labels.*.name, 'WIP') && !(github.event.action == 'unlabeled' && github.event.label.name != 'WIP') }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3
...@@ -21,39 +42,27 @@ jobs: ...@@ -21,39 +42,27 @@ jobs:
node-version: 18 node-version: 18
cache: 'yarn' cache: 'yarn'
- name: Install dependencies - name: Cache node_modules
uses: bahmutov/npm-install@v1 uses: actions/cache@v3
id: cache-node-modules
with: with:
useRollingCache: true path: |
node_modules
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Run ESLint - name: Run ESLint
run: yarn lint:eslint run: yarn lint:eslint
type_check:
name: TypeScript
runs-on: ubuntu-latest
if: "!contains(github.event.pull_request.labels.*.name, 'WIP')"
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'yarn'
- name: Install dependencies
uses: bahmutov/npm-install@v1
with:
useRollingCache: true
- name: Compile TypeScript - name: Compile TypeScript
run: yarn lint:tsc run: yarn lint:tsc
jest_tests: jest_tests:
name: Run unit tests with Jest name: Jest tests
needs: [ lint, type_check ] needs: [ code_quality ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repo - name: Checkout repo
...@@ -65,20 +74,32 @@ jobs: ...@@ -65,20 +74,32 @@ jobs:
node-version: 18 node-version: 18
cache: 'yarn' cache: 'yarn'
- name: Install dependencies - name: Cache node_modules
uses: bahmutov/npm-install@v1 uses: actions/cache@v3
id: cache-node-modules
with: with:
useRollingCache: true path: |
node_modules
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Run Jest - name: Run Jest
run: yarn test:jest run: yarn test:jest
pw_tests: pw_tests:
name: Run components visual tests with PlayWright name: 'Playwright tests - project: ${{ matrix.project }}'
needs: [ lint, type_check ] needs: [ code_quality ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/playwright:v1.28.0-focal image: mcr.microsoft.com/playwright:v1.32.0-focal
strategy:
fail-fast: false
matrix:
project: [ default, mobile, dark-color-mode ]
steps: steps:
- name: Install git-lfs - name: Install git-lfs
...@@ -95,18 +116,28 @@ jobs: ...@@ -95,18 +116,28 @@ jobs:
node-version: 18 node-version: 18
cache: 'yarn' cache: 'yarn'
- name: Install dependencies - name: Cache node_modules
uses: bahmutov/npm-install@v1 uses: actions/cache@v3
id: cache-node-modules
with: with:
useRollingCache: true path: |
node_modules
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: yarn --frozen-lockfile
- name: Run PlayWright - name: Run PlayWright
run: HOME=/root yarn test:pw run: yarn test:pw:ci
env:
HOME: /root
PW_PROJECT: ${{ matrix.project }}
- name: Upload test results - name: Upload test results
if: always() if: always()
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: playwright-report name: playwright-report-${{ matrix.project }}
path: playwright-report path: playwright-report
retention-days: 10 retention-days: 10
\ No newline at end of file
...@@ -5,6 +5,10 @@ on: ...@@ -5,6 +5,10 @@ on:
branches: branches:
- main - main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
push_to_registry: push_to_registry:
name: Push Docker image to registry name: Push Docker image to registry
......
...@@ -181,6 +181,7 @@ const config = Object.freeze({ ...@@ -181,6 +181,7 @@ const config = Object.freeze({
graphQL: { graphQL: {
defaultTxnHash: getEnvValue(process.env.NEXT_PUBLIC_GRAPHIQL_TRANSACTION) || '', defaultTxnHash: getEnvValue(process.env.NEXT_PUBLIC_GRAPHIQL_TRANSACTION) || '',
}, },
hideIndexingAlert: getEnvValue(process.env.NEXT_PUBLIC_HIDE_INDEXING_ALERT),
}); });
export default config; export default config;
...@@ -53,7 +53,7 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -53,7 +53,7 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_GRAPHIQL_TRANSACTION | `string` | Txn hash for default query at GraphQl playground page | - | - | `0x69e3923eef50eada197c3336d546936d0c994211492c9f947a24c02827568f9f` | | NEXT_PUBLIC_GRAPHIQL_TRANSACTION | `string` | Txn hash for default query at GraphQl playground page | - | - | `0x69e3923eef50eada197c3336d546936d0c994211492c9f947a24c02827568f9f` |
| NEXT_PUBLIC_WEB3_DEFAULT_WALLET | `metamask` \| `coinbase`| Type of Web3 wallet which will be used by default to add tokens or chains to | - | `metamask` | `coinbase` | | NEXT_PUBLIC_WEB3_DEFAULT_WALLET | `metamask` \| `coinbase`| Type of Web3 wallet which will be used by default to add tokens or chains to | - | `metamask` | `coinbase` |
| NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET | `boolean`| Set to `true` to hide icon "Add to your wallet" next to token addresses | - | `false` | `true` | | NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET | `boolean`| Set to `true` to hide icon "Add to your wallet" next to token addresses | - | `false` | `true` |
| NEXT_PUBLIC_HIDE_INDEXING_ALERT | `boolean` | Set to `true` to hide indexing alert, if the chain indexing isn't completed | - | `false` | `true` |
### Marketplace app configuration properties ### Marketplace app configuration properties
| Property | Type | Description | Example value | Property | Type | Description | Example value
......
...@@ -479,6 +479,9 @@ export const RESOURCES = { ...@@ -479,6 +479,9 @@ export const RESOURCES = {
csv_export_token_transfers: { csv_export_token_transfers: {
path: '/token-transfers-csv', path: '/token-transfers-csv',
}, },
csv_export_logs: {
path: '/logs-csv',
},
}; };
export type ResourceName = keyof typeof RESOURCES; export type ResourceName = keyof typeof RESOURCES;
......
...@@ -10,6 +10,7 @@ export function monaco(): CspDev.DirectiveDescriptor { ...@@ -10,6 +10,7 @@ export function monaco(): CspDev.DirectiveDescriptor {
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.js', 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.nls.js', 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.nls.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/solidity/solidity.js', 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/solidity/solidity.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/elixir/elixir.js',
'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/base/worker/workerMain.js', 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/base/worker/workerMain.js',
], ],
'style-src': [ 'style-src': [
......
...@@ -30,6 +30,7 @@ export const verified: Partial<SmartContract> = { ...@@ -30,6 +30,7 @@ export const verified: Partial<SmartContract> = {
{ address_hash: '0xa62744BeE8646e237441CDbfdedD3458861748A8', name: 'Sol' }, { address_hash: '0xa62744BeE8646e237441CDbfdedD3458861748A8', name: 'Sol' },
{ address_hash: '0xa62744BeE8646e237441CDbfdedD3458861748A8', name: 'math' }, { address_hash: '0xa62744BeE8646e237441CDbfdedD3458861748A8', name: 'math' },
], ],
language: 'solidity',
}; };
export const withMultiplePaths: Partial<SmartContract> = { export const withMultiplePaths: Partial<SmartContract> = {
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"test:pw": "./playwright/make-envs-script.sh && NODE_OPTIONS=\"--max-old-space-size=4096\" ./node_modules/.bin/dotenv -e ./configs/envs/.env.pw -- playwright test -c playwright-ct.config.ts", "test:pw": "./playwright/make-envs-script.sh && NODE_OPTIONS=\"--max-old-space-size=4096\" ./node_modules/.bin/dotenv -e ./configs/envs/.env.pw -- playwright test -c playwright-ct.config.ts",
"test:pw:local": "export NODE_PATH=$(pwd)/node_modules && rm -rf ./playwright/.cache && yarn test:pw", "test:pw:local": "export NODE_PATH=$(pwd)/node_modules && rm -rf ./playwright/.cache && yarn test:pw",
"test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.32.0-focal ./playwright/run-tests.sh", "test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.32.0-focal ./playwright/run-tests.sh",
"test:pw:ci": "yarn test:pw --project=$PW_PROJECT",
"test:jest": "jest", "test:jest": "jest",
"test:jest:watch": "jest --watch" "test:jest:watch": "jest --watch"
}, },
......
...@@ -63,7 +63,9 @@ const config: PlaywrightTestConfig = defineConfig({ ...@@ -63,7 +63,9 @@ const config: PlaywrightTestConfig = defineConfig({
}, },
}, },
/* Configure projects for major browsers */ // configured projects
// these projects are also used for sharding tests in CI
// when adding or deleting a project, make sure to update github workflow accordingly
projects: [ projects: [
{ {
name: 'default', name: 'default',
...@@ -81,15 +83,7 @@ const config: PlaywrightTestConfig = defineConfig({ ...@@ -81,15 +83,7 @@ const config: PlaywrightTestConfig = defineConfig({
}, },
}, },
{ {
name: 'desktop xl', name: 'dark-color-mode',
grep: /\+@desktop-xl/,
use: {
...devices['Desktop Chrome'],
viewport: { width: 1600, height: 1000 },
},
},
{
name: 'dark color mode',
grep: /\+@dark-mode/, grep: /\+@dark-mode/,
use: { use: {
...devices['Desktop Chrome'], ...devices['Desktop Chrome'],
...@@ -97,23 +91,6 @@ const config: PlaywrightTestConfig = defineConfig({ ...@@ -97,23 +91,6 @@ const config: PlaywrightTestConfig = defineConfig({
colorScheme: 'dark', colorScheme: 'dark',
}, },
}, },
{
name: 'dark color mode mobile',
grep: /\+@dark-mode-mobile/,
use: {
...devices['iPhone 13 Pro'],
colorScheme: 'dark',
},
},
{
name: 'dark color mode desktop xl',
grep: /\+@dark-mode-xl/,
use: {
...devices['Desktop Chrome'],
viewport: { width: 1600, height: 1000 },
colorScheme: 'dark',
},
},
], ],
}); });
......
import { devices } from '@playwright/test';
export const viewport = {
mobile: devices['iPhone 13 Pro'].viewport,
xl: { width: 1600, height: 1000 },
};
...@@ -27,7 +27,6 @@ export interface SmartContract { ...@@ -27,7 +27,6 @@ export interface SmartContract {
constructor_args: string | null; constructor_args: string | null;
decoded_constructor_args: Array<SmartContractDecodedConstructorArg> | null; decoded_constructor_args: Array<SmartContractDecodedConstructorArg> | null;
can_be_visualized_via_sol2uml: boolean | null; can_be_visualized_via_sol2uml: boolean | null;
is_vyper_contract: boolean | null;
file_path: string; file_path: string;
additional_sources: Array<{ file_path: string; source_code: string }>; additional_sources: Array<{ file_path: string; source_code: string }>;
external_libraries: Array<SmartContractExternalLibrary> | null; external_libraries: Array<SmartContractExternalLibrary> | null;
...@@ -37,6 +36,7 @@ export interface SmartContract { ...@@ -37,6 +36,7 @@ export interface SmartContract {
}; };
verified_twin_address_hash: string | null; verified_twin_address_hash: string | null;
minimal_proxy_address_hash: string | null; minimal_proxy_address_hash: string | null;
language: string | null;
} }
export type SmartContractDecodedConstructorArg = [ export type SmartContractDecodedConstructorArg = [
......
...@@ -22,7 +22,7 @@ export interface VerifiedContractsResponse { ...@@ -22,7 +22,7 @@ export interface VerifiedContractsResponse {
export interface VerifiedContractsFilters { export interface VerifiedContractsFilters {
q: string | undefined; q: string | undefined;
filter: 'vyper' | 'solidity' | undefined; filter: 'vyper' | 'solidity' | 'yul' | undefined;
} }
export type VerifiedContractsCounters = { export type VerifiedContractsCounters = {
......
export type CsvExportType = 'transactions' | 'internal-transactions' | 'token-transfers'; export type CsvExportType = 'transactions' | 'internal-transactions' | 'token-transfers' | 'logs';
...@@ -10,6 +10,8 @@ import LogItem from 'ui/shared/logs/LogItem'; ...@@ -10,6 +10,8 @@ import LogItem from 'ui/shared/logs/LogItem';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import AddressCsvExportLink from './AddressCsvExportLink';
const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => { const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => {
const router = useRouter(); const router = useRouter();
...@@ -28,11 +30,12 @@ const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement> ...@@ -28,11 +30,12 @@ const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>
}, },
}); });
const actionBar = pagination.isVisible ? ( const actionBar = (
<ActionBar mt={ -6 } showShadow> <ActionBar mt={ -6 } showShadow justifyContent={{ base: 'space-between', lg: 'end' }}>
<Pagination ml="auto" { ...pagination }/> <AddressCsvExportLink address={ hash } isLoading={ pagination.isLoading } type="logs"/>
<Pagination ml={{ base: 0, lg: 8 }} { ...pagination }/>
</ActionBar> </ActionBar>
) : null; );
const content = data?.items ? data.items.map((item, index) => <LogItem key={ index } { ...item } type="address" isLoading={ isPlaceholderData }/>) : null; const content = data?.items ? data.items.map((item, index) => <LogItem key={ index } { ...item } type="address" isLoading={ isPlaceholderData }/>) : null;
......
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { test as base, expect } from '@playwright/experimental-ct-react'; import { test as base, expect } from '@playwright/experimental-ct-react';
import type { Locator } from '@playwright/test';
import React from 'react'; import React from 'react';
import * as txMock from 'mocks/txs/tx'; import * as txMock from 'mocks/txs/tx';
import * as socketServer from 'playwright/fixtures/socketServer'; import * as socketServer from 'playwright/fixtures/socketServer';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import AddressTxs from './AddressTxs'; import AddressTxs from './AddressTxs';
...@@ -19,32 +21,45 @@ const hooksConfig = { ...@@ -19,32 +21,45 @@ const hooksConfig = {
}, },
}; };
const test = base.extend<socketServer.SocketServerFixture>({ base.describe('base view', () => {
createSocket: socketServer.createSocket, let component: Locator;
});
// FIXME base.beforeEach(async({ page, mount }) => {
// test cases which use socket cannot run in parallel since the socket server always run on the same port
test.describe.configure({ mode: 'serial' });
test('address txs +@mobile +@desktop-xl', async({ mount, page }) => {
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify({ items: [ txMock.base, txMock.base ], next_page_params: { block: 1 } }), body: JSON.stringify({ items: [ txMock.base, txMock.base ], next_page_params: { block: 1 } }),
})); }));
const component = await mount( component = await mount(
<TestApp> <TestApp>
<Box h={{ base: '134px', lg: 6 }}/> <Box h={{ base: '134px', lg: 6 }}/>
<AddressTxs/> <AddressTxs/>
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
});
base('+@mobile', async() => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
});
base.describe('screen xl', () => {
base.use({ viewport: configs.viewport.xl });
base('', async() => {
await expect(component).toHaveScreenshot();
});
});
}); });
test.describe('socket', () => { base.describe('socket', () => {
const test = base.extend<socketServer.SocketServerFixture>({
createSocket: socketServer.createSocket,
});
// FIXME
// test cases which use socket cannot run in parallel since the socket server always run on the same port
test.describe.configure({ mode: 'serial' });
test('without overload', async({ mount, page, createSocket }) => { test('without overload', async({ mount, page, createSocket }) => {
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
......
...@@ -23,9 +23,19 @@ function getEditorData(contractInfo: SmartContract | undefined) { ...@@ -23,9 +23,19 @@ function getEditorData(contractInfo: SmartContract | undefined) {
return undefined; return undefined;
} }
const defaultName = contractInfo.is_vyper_contract ? '/index.vy' : '/index.sol'; const extension = (() => {
switch (contractInfo.language) {
case 'vyper':
return 'vy';
case 'yul':
return 'yul';
default:
return 'sol';
}
})();
return [ return [
{ file_path: formatFilePath(contractInfo.file_path || defaultName), source_code: contractInfo.source_code }, { file_path: formatFilePath(contractInfo.file_path || `index.${ extension }`), source_code: contractInfo.source_code },
...(contractInfo.additional_sources || []).map((source) => ({ ...source, file_path: formatFilePath(source.file_path) })), ...(contractInfo.additional_sources || []).map((source) => ({ ...source, file_path: formatFilePath(source.file_path) })),
]; ];
} }
...@@ -74,7 +84,7 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { ...@@ -74,7 +84,7 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => {
const heading = ( const heading = (
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>
<span>Contract source code</span> <span>Contract source code</span>
<Text whiteSpace="pre" as="span" variant="secondary"> ({ activeContract?.is_vyper_contract ? 'Vyper' : 'Solidity' })</Text> <Text whiteSpace="pre" as="span" variant="secondary" textTransform="capitalize"> ({ activeContract?.language })</Text>
</Skeleton> </Skeleton>
); );
...@@ -132,11 +142,19 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { ...@@ -132,11 +142,19 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => {
return ( return (
<> <>
<Box display={ sourceType === 'primary' ? 'block' : 'none' }> <Box display={ sourceType === 'primary' ? 'block' : 'none' }>
<CodeEditor data={ primaryEditorData } remappings={ primaryContractQuery.data?.compiler_settings?.remappings }/> <CodeEditor
data={ primaryEditorData }
remappings={ primaryContractQuery.data?.compiler_settings?.remappings }
language={ primaryContractQuery.data?.language ?? undefined }
/>
</Box> </Box>
{ secondaryEditorData && ( { secondaryEditorData && (
<Box display={ sourceType === 'secondary' ? 'block' : 'none' }> <Box display={ sourceType === 'secondary' ? 'block' : 'none' }>
<CodeEditor data={ secondaryEditorData } remappings={ secondaryContractQuery.data?.compiler_settings?.remappings }/> <CodeEditor
data={ secondaryEditorData }
remappings={ secondaryContractQuery.data?.compiler_settings?.remappings }
language={ secondaryContractQuery.data?.language ?? undefined }
/>
</Box> </Box>
) } ) }
</> </>
......
import { test, expect } from '@playwright/experimental-ct-react'; import { test, expect } from '@playwright/experimental-ct-react';
import type { Locator } from '@playwright/test';
import React from 'react'; import React from 'react';
import * as statsMock from 'mocks/stats/index'; import * as statsMock from 'mocks/stats/index';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import Stats from './Stats'; import Stats from './Stats';
const API_URL = buildApiUrl('homepage_stats'); const API_URL = buildApiUrl('homepage_stats');
test('all items +@mobile +@dark-mode +@desktop-xl', async({ mount, page }) => { test.describe('all items', () => {
let component: Locator;
test.beforeEach(async({ page, mount }) => {
await page.route(API_URL, (route) => route.fulfill({ await page.route(API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify(statsMock.base), body: JSON.stringify(statsMock.base),
})); }));
const component = await mount( component = await mount(
<TestApp> <TestApp>
<Stats/> <Stats/>
</TestApp>, </TestApp>,
); );
});
test('+@mobile +@dark-mode', async() => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
});
test.describe('screen xl', () => {
test.use({ viewport: configs.viewport.xl });
test('', async() => {
await expect(component).toHaveScreenshot();
});
});
}); });
test.describe('4 items', () => { test.describe('4 items', () => {
......
import { test, expect } from '@playwright/experimental-ct-react'; import { test, expect } from '@playwright/experimental-ct-react';
import type { Locator } from '@playwright/test';
import React from 'react'; import React from 'react';
import * as dailyTxsMock from 'mocks/stats/daily_txs'; import * as dailyTxsMock from 'mocks/stats/daily_txs';
...@@ -11,7 +12,10 @@ import ChainIndicators from './ChainIndicators'; ...@@ -11,7 +12,10 @@ import ChainIndicators from './ChainIndicators';
const STATS_API_URL = buildApiUrl('homepage_stats'); const STATS_API_URL = buildApiUrl('homepage_stats');
const TX_CHART_API_URL = buildApiUrl('homepage_chart_txs'); const TX_CHART_API_URL = buildApiUrl('homepage_chart_txs');
test('daily txs chart +@mobile +@dark-mode +@dark-mode-mobile', async({ mount, page }) => { test.describe('daily txs chart', () => {
let component: Locator;
test.beforeEach(async({ page, mount }) => {
await page.route(STATS_API_URL, (route) => route.fulfill({ await page.route(STATS_API_URL, (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify(statsMock.base), body: JSON.stringify(statsMock.base),
...@@ -21,12 +25,23 @@ test('daily txs chart +@mobile +@dark-mode +@dark-mode-mobile', async({ mount, p ...@@ -21,12 +25,23 @@ test('daily txs chart +@mobile +@dark-mode +@dark-mode-mobile', async({ mount, p
body: JSON.stringify(dailyTxsMock.base), body: JSON.stringify(dailyTxsMock.base),
})); }));
const component = await mount( component = await mount(
<TestApp> <TestApp>
<ChainIndicators/> <ChainIndicators/>
</TestApp>, </TestApp>,
); );
await page.hover('.ChartOverlay', { position: { x: 100, y: 100 } }); await page.hover('.ChartOverlay', { position: { x: 100, y: 100 } });
});
test('+@mobile', async() => {
await expect(component).toHaveScreenshot();
});
test.describe('dark mode', () => {
test.use({ colorScheme: 'dark' });
test('+@mobile', async() => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
});
});
}); });
...@@ -39,6 +39,11 @@ const EXPORT_TYPES: Record<CsvExportType, ExportTypeEntity> = { ...@@ -39,6 +39,11 @@ const EXPORT_TYPES: Record<CsvExportType, ExportTypeEntity> = {
resource: 'csv_export_token_transfers', resource: 'csv_export_token_transfers',
fileNameTemplate: 'token_transfers', fileNameTemplate: 'token_transfers',
}, },
logs: {
text: 'logs',
resource: 'csv_export_logs',
fileNameTemplate: 'logs',
},
}; };
const isCorrectExportType = (type: string): type is CsvExportType => Object.keys(EXPORT_TYPES).includes(type); const isCorrectExportType = (type: string): type is CsvExportType => Object.keys(EXPORT_TYPES).includes(type);
......
import { test, expect, devices } from '@playwright/experimental-ct-react'; import { test, expect, devices } from '@playwright/experimental-ct-react';
import type { Locator } from '@playwright/test';
import React from 'react'; import React from 'react';
import * as blockMock from 'mocks/blocks/block'; import * as blockMock from 'mocks/blocks/block';
...@@ -8,11 +9,15 @@ import * as txMock from 'mocks/txs/tx'; ...@@ -8,11 +9,15 @@ import * as txMock from 'mocks/txs/tx';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl'; import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder'; import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder';
import Home from './Home'; import Home from './Home';
test('default view -@default +@desktop-xl +@dark-mode', async({ mount, page }) => { test.describe('default view', () => {
let component: Locator;
test.beforeEach(async({ page, mount }) => {
await page.route(buildApiUrl('homepage_stats'), (route) => route.fulfill({ await page.route(buildApiUrl('homepage_stats'), (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify(statsMock.base), body: JSON.stringify(statsMock.base),
...@@ -37,15 +42,26 @@ test('default view -@default +@desktop-xl +@dark-mode', async({ mount, page }) = ...@@ -37,15 +42,26 @@ test('default view -@default +@desktop-xl +@dark-mode', async({ mount, page }) =
body: JSON.stringify(dailyTxsMock.base), body: JSON.stringify(dailyTxsMock.base),
})); }));
const component = await mount( component = await mount(
<TestApp> <TestApp>
<Home/> <Home/>
</TestApp>, </TestApp>,
); );
await insertAdPlaceholder(page); await insertAdPlaceholder(page);
});
test('-@default +@dark-mode', async() => {
await expect(component.locator('main')).toHaveScreenshot(); await expect(component.locator('main')).toHaveScreenshot();
});
test.describe('screen xl', () => {
test.use({ viewport: configs.viewport.xl });
test('', async() => {
await expect(component.locator('main')).toHaveScreenshot();
});
});
}); });
test.describe('custom hero plate background', () => { test.describe('custom hero plate background', () => {
......
...@@ -61,14 +61,10 @@ const VerifiedContracts = () => { ...@@ -61,14 +61,10 @@ const VerifiedContracts = () => {
return; return;
} }
if ((value === 'vyper' || value === 'solidity')) { const filter = value === 'all' ? undefined : value as VerifiedContractsFilters['filter'];
onFilterChange({ q: debouncedSearchTerm, filter: value });
setType(value);
return;
}
onFilterChange({ q: debouncedSearchTerm, filter: undefined }); onFilterChange({ q: debouncedSearchTerm, filter });
setType(undefined); setType(filter);
}, [ debouncedSearchTerm, onFilterChange ]); }, [ debouncedSearchTerm, onFilterChange ]);
const handleSortToggle = React.useCallback((field: SortField) => { const handleSortToggle = React.useCallback((field: SortField) => {
......
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import appConfig from 'configs/app/config';
import IndexingAlert from 'ui/home/IndexingAlert'; import IndexingAlert from 'ui/home/IndexingAlert';
interface Props { interface Props {
...@@ -17,7 +18,7 @@ const PageContent = ({ children, isHomePage }: Props) => { ...@@ -17,7 +18,7 @@ const PageContent = ({ children, isHomePage }: Props) => {
paddingBottom={ 10 } paddingBottom={ 10 }
paddingTop={{ base: isHomePage ? '88px' : '138px', lg: 0 }} paddingTop={{ base: isHomePage ? '88px' : '138px', lg: 0 }}
> >
<IndexingAlert display={{ base: 'block', lg: 'none' }}/> { !appConfig.hideIndexingAlert && <IndexingAlert display={{ base: 'block', lg: 'none' }}/> }
{ children } { children }
</Box> </Box>
); );
......
...@@ -80,7 +80,7 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa ...@@ -80,7 +80,7 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa
{ afterTitle } { afterTitle }
</Box> </Box>
{ contentAfter } { contentAfter }
{ withTextAd && <TextAd order={{ base: -1, lg: 100 }} mb={{ base: 6, lg: 0 }} ml="auto"/> } { withTextAd && <TextAd order={{ base: -1, lg: 100 }} mb={{ base: 6, lg: 0 }} ml="auto" w={{ base: '100%', lg: 'auto' }}/> }
</Flex> </Flex>
); );
}; };
......
/* eslint-disable max-len */ /* eslint-disable max-len */
import { Flex, chakra } from '@chakra-ui/react'; import { Flex, chakra } from '@chakra-ui/react';
import { useRouter } from 'next/navigation';
import Script from 'next/script'; import Script from 'next/script';
import React from 'react'; import React from 'react';
import { connectAdbutler, placeAd } from 'ui/shared/ad/adbutlerScript'; import isBrowser from 'lib/isBrowser';
import { connectAdbutler, placeAd, placeAdSPA } from 'ui/shared/ad/adbutlerScript';
const AdbutlerBanner = ({ className }: { className?: string }) => { const AdbutlerBanner = ({ className }: { className?: string }) => {
const router = useRouter();
React.useEffect(() => {
if (isBrowser()) {
eval(placeAdSPA);
}
}, [ router ]);
return ( return (
<Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}> <Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}>
<div id="ad-banner"></div>
<Script id="ad-butler-1">{ connectAdbutler }</Script> <Script id="ad-butler-1">{ connectAdbutler }</Script>
<Script id="ad-butler-2">{ placeAd }</Script> <Script id="ad-butler-2">{ placeAd }</Script>
<div id="ad-banner"></div>
</Flex> </Flex>
); );
}; };
......
...@@ -45,6 +45,7 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => { ...@@ -45,6 +45,7 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => {
} }
}) })
.finally(() => { .finally(() => {
// setAdData(MOCK);
setIsLoading(false); setIsLoading(false);
}); });
} }
...@@ -55,7 +56,16 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => { ...@@ -55,7 +56,16 @@ const CoinzillaTextAd = ({ className }: {className?: string}) => {
} }
if (isLoading) { if (isLoading) {
return <Skeleton className={ className } h={{ base: 12, lg: 6 }} w={{ base: '100%', lg: 'auto' }} flexGrow={ 1 } maxW="1000px" display="inline-block"/>; return (
<Skeleton
className={ className }
h={{ base: 12, lg: 6 }}
w="100%"
flexGrow={ 1 }
maxW="800px"
display="block"
/>
);
} }
if (!adData) { if (!adData) {
......
/* eslint-disable max-len */ /* eslint-disable max-len */
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
const ADBUTLER_ACCOUNT = 182226;
export const connectAdbutler = `if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}`; export const connectAdbutler = `if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}`;
export const placeAd = ` export const placeAd = `
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || []; var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || ''; var abkw = window.abkw || '';
const isMobile = window.matchMedia("only screen and (max-width: 760px)").matches; const isMobile = window.matchMedia("only screen and (max-width: 1000px)").matches;
if (isMobile) {
var plc${ appConfig.ad.adButlerConfigMobile?.id } = window.plc${ appConfig.ad.adButlerConfigMobile?.id } || 0;
document.getElementById('ad-banner').innerHTML = '<'+'div id="placement_${ appConfig.ad.adButlerConfigMobile?.id }_'+plc${ appConfig.ad.adButlerConfigMobile?.id }+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ appConfig.ad.adButlerConfigMobile?.id }, [${ appConfig.ad.adButlerConfigMobile?.width },${ appConfig.ad.adButlerConfigMobile?.height }], 'placement_${ appConfig.ad.adButlerConfigMobile?.id }_'+opt.place, opt); }, opt: { place: plc${ appConfig.ad.adButlerConfigMobile?.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} else {
var plc${ appConfig.ad.adButlerConfigDesktop?.id } = window.plc${ appConfig.ad.adButlerConfigDesktop?.id } || 0;
document.getElementById('ad-banner').innerHTML = '<'+'div id="placement_${ appConfig.ad.adButlerConfigDesktop?.id }_'+plc${ appConfig.ad.adButlerConfigDesktop?.id }+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ appConfig.ad.adButlerConfigDesktop?.id }, [${ appConfig.ad.adButlerConfigDesktop?.width },${ appConfig.ad.adButlerConfigDesktop?.height }], 'placement_${ appConfig.ad.adButlerConfigDesktop?.id }_'+opt.place, opt); }, opt: { place: plc${ appConfig.ad.adButlerConfigDesktop?.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
}
`;
export const placeAdSPA = `
if (!window.AdButler.ads) {window.AdButler.ads = [];}
var abkw = window.abkw || '';
const isMobile = window.matchMedia("only screen and (max-width: 1000px)").matches;
if (isMobile) { if (isMobile) {
var plc${ appConfig.ad.adButlerConfigMobile?.id } = window.plc${ appConfig.ad.adButlerConfigMobile?.id } || 0; var plc${ appConfig.ad.adButlerConfigMobile?.id } = window.plc${ appConfig.ad.adButlerConfigMobile?.id } || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_${ appConfig.ad.adButlerConfigMobile?.id }_'+plc${ appConfig.ad.adButlerConfigMobile?.id }+'"></'+'div>'; document.getElementById('ad-banner').innerHTML = '<'+'div id="placement_${ appConfig.ad.adButlerConfigMobile?.id }_'+plc${ appConfig.ad.adButlerConfigMobile?.id }+'"></'+'div>';
document.getElementById("ad-banner").className = "ad-container mb-3"; window.AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ appConfig.ad.adButlerConfigMobile?.id }, [${ appConfig.ad.adButlerConfigMobile?.width },${ appConfig.ad.adButlerConfigMobile?.height }], 'placement_${ appConfig.ad.adButlerConfigMobile?.id }_'+opt.place, opt); }, opt: { place: plc${ appConfig.ad.adButlerConfigMobile?.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, ${ appConfig.ad.adButlerConfigMobile?.id }, [${ appConfig.ad.adButlerConfigMobile?.width },${ appConfig.ad.adButlerConfigMobile?.height }], 'placement_${ appConfig.ad.adButlerConfigMobile?.id }_'+opt.place, opt); }, opt: { place: plc${ appConfig.ad.adButlerConfigMobile?.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} else { } else {
var plc${ appConfig.ad.adButlerConfigDesktop?.id } = window.plc${ appConfig.ad.adButlerConfigDesktop?.id } || 0; var plc${ appConfig.ad.adButlerConfigDesktop?.id } = window.plc${ appConfig.ad.adButlerConfigDesktop?.id } || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_${ appConfig.ad.adButlerConfigDesktop?.id }_'+plc${ appConfig.ad.adButlerConfigDesktop?.id }+'"></'+'div>'; document.getElementById('ad-banner').innerHTML = '<'+'div id="placement_${ appConfig.ad.adButlerConfigDesktop?.id }_'+plc${ appConfig.ad.adButlerConfigDesktop?.id }+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, ${ appConfig.ad.adButlerConfigDesktop?.id }, [${ appConfig.ad.adButlerConfigDesktop?.width },${ appConfig.ad.adButlerConfigDesktop?.height }], 'placement_${ appConfig.ad.adButlerConfigDesktop?.id }_'+opt.place, opt); }, opt: { place: plc${ appConfig.ad.adButlerConfigDesktop?.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }}); window.AdButler.ads.push({handler: function(opt){ window.AdButler.register(${ ADBUTLER_ACCOUNT }, ${ appConfig.ad.adButlerConfigDesktop?.id }, [${ appConfig.ad.adButlerConfigDesktop?.width },${ appConfig.ad.adButlerConfigDesktop?.height }], 'placement_${ appConfig.ad.adButlerConfigDesktop?.id }_'+opt.place, opt); }, opt: { place: plc${ appConfig.ad.adButlerConfigDesktop?.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} }
`; `;
...@@ -36,9 +36,10 @@ const EDITOR_HEIGHT = 500; ...@@ -36,9 +36,10 @@ const EDITOR_HEIGHT = 500;
interface Props { interface Props {
data: Array<File>; data: Array<File>;
remappings?: Array<string>; remappings?: Array<string>;
language?: string;
} }
const CodeEditor = ({ data, remappings }: Props) => { const CodeEditor = ({ data, remappings, language }: Props) => {
const [ instance, setInstance ] = React.useState<Monaco | undefined>(); const [ instance, setInstance ] = React.useState<Monaco | undefined>();
const [ editor, setEditor ] = React.useState<monaco.editor.IStandaloneCodeEditor | undefined>(); const [ editor, setEditor ] = React.useState<monaco.editor.IStandaloneCodeEditor | undefined>();
const [ index, setIndex ] = React.useState(0); const [ index, setIndex ] = React.useState(0);
...@@ -53,6 +54,8 @@ const CodeEditor = ({ data, remappings }: Props) => { ...@@ -53,6 +54,8 @@ const CodeEditor = ({ data, remappings }: Props) => {
const editorWidth = containerRect ? containerRect.width - (isMobile ? 0 : SIDE_BAR_WIDTH) : 0; const editorWidth = containerRect ? containerRect.width - (isMobile ? 0 : SIDE_BAR_WIDTH) : 0;
const editorLanguage = language === 'vyper' ? 'elixir' : 'sol';
React.useEffect(() => { React.useEffect(() => {
instance?.editor.setTheme(colorMode === 'light' ? 'blockscout-light' : 'blockscout-dark'); instance?.editor.setTheme(colorMode === 'light' ? 'blockscout-light' : 'blockscout-dark');
}, [ colorMode, instance?.editor ]); }, [ colorMode, instance?.editor ]);
...@@ -69,7 +72,7 @@ const CodeEditor = ({ data, remappings }: Props) => { ...@@ -69,7 +72,7 @@ const CodeEditor = ({ data, remappings }: Props) => {
const loadedModelsPaths = loadedModels.map((model) => model.uri.path); const loadedModelsPaths = loadedModels.map((model) => model.uri.path);
const newModels = data.slice(1) const newModels = data.slice(1)
.filter((file) => !loadedModelsPaths.includes(file.file_path)) .filter((file) => !loadedModelsPaths.includes(file.file_path))
.map((file) => monaco.editor.createModel(file.source_code, 'sol', monaco.Uri.parse(file.file_path))); .map((file) => monaco.editor.createModel(file.source_code, editorLanguage, monaco.Uri.parse(file.file_path)));
loadedModels.concat(newModels).forEach(addFileImportDecorations); loadedModels.concat(newModels).forEach(addFileImportDecorations);
...@@ -185,7 +188,7 @@ const CodeEditor = ({ data, remappings }: Props) => { ...@@ -185,7 +188,7 @@ const CodeEditor = ({ data, remappings }: Props) => {
return ( return (
<Box overflow="hidden" borderRadius="md" height={ `${ EDITOR_HEIGHT }px` }> <Box overflow="hidden" borderRadius="md" height={ `${ EDITOR_HEIGHT }px` }>
<MonacoEditor <MonacoEditor
language="sol" language={ editorLanguage }
path={ data[index].file_path } path={ data[index].file_path }
defaultValue={ data[index].source_code } defaultValue={ data[index].source_code }
options={ EDITOR_OPTIONS } options={ EDITOR_OPTIONS }
...@@ -216,7 +219,7 @@ const CodeEditor = ({ data, remappings }: Props) => { ...@@ -216,7 +219,7 @@ const CodeEditor = ({ data, remappings }: Props) => {
<MonacoEditor <MonacoEditor
className="editor-container" className="editor-container"
height={ `${ EDITOR_HEIGHT }px` } height={ `${ EDITOR_HEIGHT }px` }
language="sol" language={ editorLanguage }
path={ data[index].file_path } path={ data[index].file_path }
defaultValue={ data[index].source_code } defaultValue={ data[index].source_code }
options={ EDITOR_OPTIONS } options={ EDITOR_OPTIONS }
......
...@@ -5,6 +5,7 @@ import type { WindowProvider } from 'wagmi'; ...@@ -5,6 +5,7 @@ import type { WindowProvider } from 'wagmi';
import { FOOTER_LINKS } from 'mocks/config/footerLinks'; import { FOOTER_LINKS } from 'mocks/config/footerLinks';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import * as configs from 'playwright/utils/configs';
import Footer from './Footer'; import Footer from './Footer';
...@@ -18,7 +19,7 @@ base.describe('with custom links, 4 cols', () => { ...@@ -18,7 +19,7 @@ base.describe('with custom links, 4 cols', () => {
]) as any, ]) as any,
}); });
test('base view +@dark-mode +@mobile +@desktop-xl', async({ mount, page }) => { test.beforeEach(async({ page, mount }) => {
await page.route(FOOTER_LINKS_URL, (route) => { await page.route(FOOTER_LINKS_URL, (route) => {
return route.fulfill({ return route.fulfill({
body: JSON.stringify(FOOTER_LINKS), body: JSON.stringify(FOOTER_LINKS),
...@@ -30,9 +31,19 @@ base.describe('with custom links, 4 cols', () => { ...@@ -30,9 +31,19 @@ base.describe('with custom links, 4 cols', () => {
<Footer/> <Footer/>
</TestApp>, </TestApp>,
); );
});
test('+@mobile +@dark-mode', async({ page }) => {
await expect(page).toHaveScreenshot();
});
test.describe('screen xl', () => {
test.use({ viewport: configs.viewport.xl });
test('', async({ page }) => {
await expect(page).toHaveScreenshot(); await expect(page).toHaveScreenshot();
}); });
});
}); });
base.describe('with custom links, 2 cols', () => { base.describe('with custom links, 2 cols', () => {
......
...@@ -5,7 +5,7 @@ import TestApp from 'playwright/TestApp'; ...@@ -5,7 +5,7 @@ import TestApp from 'playwright/TestApp';
import Header from './Header'; import Header from './Header';
test('no auth +@mobile +@dark-mode +@dark-mode-mobile', async({ mount, page }) => { test('no auth +@mobile', async({ mount, page }) => {
await mount( await mount(
<TestApp> <TestApp>
<Header/> <Header/>
...@@ -14,3 +14,17 @@ test('no auth +@mobile +@dark-mode +@dark-mode-mobile', async({ mount, page }) = ...@@ -14,3 +14,17 @@ test('no auth +@mobile +@dark-mode +@dark-mode-mobile', async({ mount, page }) =
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 150 } }); await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 150 } });
}); });
test.describe('dark mode', () => {
test.use({ colorScheme: 'dark' });
test('+@mobile', async({ mount, page }) => {
await mount(
<TestApp>
<Header/>
</TestApp>,
);
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 150 } });
});
});
...@@ -52,7 +52,7 @@ const Header = ({ isHomePage, renderSearchBar }: Props) => { ...@@ -52,7 +52,7 @@ const Header = ({ isHomePage, renderSearchBar }: Props) => {
paddingTop={ 9 } paddingTop={ 9 }
display={{ base: 'none', lg: 'block' }} display={{ base: 'none', lg: 'block' }}
> >
<IndexingAlert/> { !appConfig.hideIndexingAlert && <IndexingAlert/> }
{ !isHomePage && ( { !isHomePage && (
<HStack <HStack
as="header" as="header"
......
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { test as base, expect } from '@playwright/experimental-ct-react'; import { test as base, expect } from '@playwright/experimental-ct-react';
import type { Locator } from '@playwright/test';
import React from 'react'; import React from 'react';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
...@@ -7,6 +8,7 @@ import authFixture from 'playwright/fixtures/auth'; ...@@ -7,6 +8,7 @@ import authFixture from 'playwright/fixtures/auth';
import contextWithEnvs, { createContextWithEnvs } from 'playwright/fixtures/contextWithEnvs'; import contextWithEnvs, { createContextWithEnvs } from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import * as app from 'playwright/utils/app'; import * as app from 'playwright/utils/app';
import * as configs from 'playwright/utils/configs';
import NavigationDesktop from './NavigationDesktop'; import NavigationDesktop from './NavigationDesktop';
...@@ -27,8 +29,11 @@ const test = base.extend({ ...@@ -27,8 +29,11 @@ const test = base.extend({
]) as any, ]) as any,
}); });
test('no auth +@desktop-xl +@dark-mode-xl', async({ mount }) => { test.describe('no auth', () => {
const component = await mount( let component: Locator;
test.beforeEach(async({ mount }) => {
component = await mount(
<TestApp> <TestApp>
<Flex w="100%" minH="100vh" alignItems="stretch"> <Flex w="100%" minH="100vh" alignItems="stretch">
<NavigationDesktop/> <NavigationDesktop/>
...@@ -37,8 +42,19 @@ test('no auth +@desktop-xl +@dark-mode-xl', async({ mount }) => { ...@@ -37,8 +42,19 @@ test('no auth +@desktop-xl +@dark-mode-xl', async({ mount }) => {
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
});
test('+@dark-mode', async() => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
});
test.describe('xl screen', () => {
test.use({ viewport: configs.viewport.xl });
test('+@dark-mode', async() => {
await expect(component).toHaveScreenshot();
});
});
}); });
base.describe('auth', () => { base.describe('auth', () => {
...@@ -52,8 +68,10 @@ base.describe('auth', () => { ...@@ -52,8 +68,10 @@ base.describe('auth', () => {
}, },
}); });
test('+@desktop-xl +@dark-mode-xl', async({ mount }) => { let component: Locator;
const component = await mount(
test.beforeEach(async({ mount }) => {
component = await mount(
<TestApp> <TestApp>
<Flex w="100%" minH="100vh" alignItems="stretch"> <Flex w="100%" minH="100vh" alignItems="stretch">
<NavigationDesktop/> <NavigationDesktop/>
...@@ -62,12 +80,25 @@ base.describe('auth', () => { ...@@ -62,12 +80,25 @@ base.describe('auth', () => {
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
});
test('+@dark-mode', async() => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test.describe('xl screen', () => {
test.use({ viewport: configs.viewport.xl });
test('+@dark-mode', async() => {
await expect(component).toHaveScreenshot();
});
});
}); });
test('with tooltips +@desktop-xl -@default', async({ mount, page }) => { test.describe('with tooltips', () => {
test.use({ viewport: configs.viewport.xl });
test('', async({ mount, page }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<Flex w="100%" minH="100vh" alignItems="stretch"> <Flex w="100%" minH="100vh" alignItems="stretch">
...@@ -82,10 +113,14 @@ test('with tooltips +@desktop-xl -@default', async({ mount, page }) => { ...@@ -82,10 +113,14 @@ test('with tooltips +@desktop-xl -@default', async({ mount, page }) => {
await page.locator('a[aria-label="Tokens link"]').hover(); await page.locator('a[aria-label="Tokens link"]').hover();
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
});
}); });
test('with submenu +@desktop-xl +@dark-mode', async({ mount, page }) => { test.describe('with submenu', () => {
const component = await mount( let component: Locator;
test.beforeEach(async({ mount, page }) => {
component = await mount(
<TestApp> <TestApp>
<Flex w="100%" minH="100vh" alignItems="stretch"> <Flex w="100%" minH="100vh" alignItems="stretch">
<NavigationDesktop/> <NavigationDesktop/>
...@@ -95,8 +130,19 @@ test('with submenu +@desktop-xl +@dark-mode', async({ mount, page }) => { ...@@ -95,8 +130,19 @@ test('with submenu +@desktop-xl +@dark-mode', async({ mount, page }) => {
{ hooksConfig }, { hooksConfig },
); );
await page.locator('a[aria-label="Blockchain link group"]').hover(); await page.locator('a[aria-label="Blockchain link group"]').hover();
});
test('', async() => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
});
test.describe('xl screen', () => {
test.use({ viewport: configs.viewport.xl });
test('', async() => {
await expect(component).toHaveScreenshot();
});
});
}); });
base.describe('cookie set to false', () => { base.describe('cookie set to false', () => {
...@@ -110,8 +156,10 @@ base.describe('cookie set to false', () => { ...@@ -110,8 +156,10 @@ base.describe('cookie set to false', () => {
}, },
}); });
test('navbar is opened +@desktop-xl', async({ mount }) => { let component: Locator;
const component = await mount(
test.beforeEach(async({ mount }) => {
component = await mount(
<TestApp> <TestApp>
<Flex w="100%" minH="100vh" alignItems="stretch"> <Flex w="100%" minH="100vh" alignItems="stretch">
<NavigationDesktop/> <NavigationDesktop/>
...@@ -120,9 +168,20 @@ base.describe('cookie set to false', () => { ...@@ -120,9 +168,20 @@ base.describe('cookie set to false', () => {
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
});
test('', async() => {
const networkMenu = component.locator('button[aria-label="Network menu"]'); const networkMenu = component.locator('button[aria-label="Network menu"]');
expect(await networkMenu.isVisible()).toBe(true); await expect(networkMenu).toBeVisible();
});
test.describe('xl screen', () => {
test.use({ viewport: configs.viewport.xl });
test('', async() => {
const networkMenu = component.locator('button[aria-label="Network menu"]');
await expect(networkMenu).toBeVisible();
});
}); });
}); });
......
import { test, expect } from '@playwright/experimental-ct-react'; import { test as base, expect } from '@playwright/experimental-ct-react';
import type { Locator } from '@playwright/test';
import React from 'react'; import React from 'react';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import * as configs from 'playwright/utils/configs';
import NetworkLogo from './NetworkLogo'; import NetworkLogo from './NetworkLogo';
test.describe('placeholder logo', () => { base.describe('placeholder logo', () => {
const extendedTest = test.extend({ const test = base.extend({
context: contextWithEnvs([ context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_NETWORK_LOGO', value: '' }, { name: 'NEXT_PUBLIC_NETWORK_LOGO', value: '' },
{ name: 'NEXT_PUBLIC_NETWORK_ICON', value: '' }, { name: 'NEXT_PUBLIC_NETWORK_ICON', value: '' },
...@@ -15,7 +17,7 @@ test.describe('placeholder logo', () => { ...@@ -15,7 +17,7 @@ test.describe('placeholder logo', () => {
]) as any, ]) as any,
}); });
extendedTest('+@desktop-xl +@dark-mode +@dark-mode-xl', async({ mount }) => { test('+@dark-mode', async({ mount }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<NetworkLogo/> <NetworkLogo/>
...@@ -24,12 +26,26 @@ test.describe('placeholder logo', () => { ...@@ -24,12 +26,26 @@ test.describe('placeholder logo', () => {
await expect(component.locator('a')).toHaveScreenshot(); await expect(component.locator('a')).toHaveScreenshot();
}); });
test.describe('screen xl', () => {
test.use({ viewport: configs.viewport.xl });
test('+@dark-mode', async({ mount }) => {
const component = await mount(
<TestApp>
<NetworkLogo/>
</TestApp>,
);
await expect(component.locator('a')).toHaveScreenshot();
});
});
}); });
test.describe('custom logo', () => { base.describe('custom logo', () => {
const LOGO_URL = 'https://localhost:3000/my-logo.png'; const LOGO_URL = 'https://localhost:3000/my-logo.png';
const ICON_URL = 'https://localhost:3000/my-icon.png'; const ICON_URL = 'https://localhost:3000/my-icon.png';
const extendedTest = test.extend({ const test = base.extend({
context: contextWithEnvs([ context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_NETWORK_LOGO', value: LOGO_URL }, { name: 'NEXT_PUBLIC_NETWORK_LOGO', value: LOGO_URL },
{ name: 'NEXT_PUBLIC_NETWORK_ICON', value: ICON_URL }, { name: 'NEXT_PUBLIC_NETWORK_ICON', value: ICON_URL },
...@@ -37,7 +53,9 @@ test.describe('custom logo', () => { ...@@ -37,7 +53,9 @@ test.describe('custom logo', () => {
]) as any, ]) as any,
}); });
extendedTest('+@desktop-xl +@dark-mode +@dark-mode-xl', async({ mount, page }) => { let component: Locator;
test.beforeEach(async({ page, mount }) => {
await page.route(LOGO_URL, (route) => { await page.route(LOGO_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
...@@ -51,20 +69,30 @@ test.describe('custom logo', () => { ...@@ -51,20 +69,30 @@ test.describe('custom logo', () => {
}); });
}); });
const component = await mount( component = await mount(
<TestApp> <TestApp>
<NetworkLogo/> <NetworkLogo/>
</TestApp>, </TestApp>,
); );
});
test('+@dark-mode', async() => {
await expect(component.locator('a')).toHaveScreenshot(); await expect(component.locator('a')).toHaveScreenshot();
}); });
test.describe('screen xl', () => {
test.use({ viewport: configs.viewport.xl });
test('+@dark-mode', async() => {
await expect(component.locator('a')).toHaveScreenshot();
});
});
}); });
test.describe('custom logo with dark option', () => { base.describe('custom logo with dark option -@default +@dark-mode', () => {
const LOGO_URL = 'https://localhost:3000/my-logo.png'; const LOGO_URL = 'https://localhost:3000/my-logo.png';
const ICON_URL = 'https://localhost:3000/my-icon.png'; const ICON_URL = 'https://localhost:3000/my-icon.png';
const extendedTest = test.extend({ const test = base.extend({
context: contextWithEnvs([ context: contextWithEnvs([
{ name: 'NEXT_PUBLIC_NETWORK_LOGO', value: LOGO_URL }, { name: 'NEXT_PUBLIC_NETWORK_LOGO', value: LOGO_URL },
{ name: 'NEXT_PUBLIC_NETWORK_LOGO_DARK', value: LOGO_URL }, { name: 'NEXT_PUBLIC_NETWORK_LOGO_DARK', value: LOGO_URL },
...@@ -74,7 +102,9 @@ test.describe('custom logo with dark option', () => { ...@@ -74,7 +102,9 @@ test.describe('custom logo with dark option', () => {
]) as any, ]) as any,
}); });
extendedTest('-@default +@dark-mode +@dark-mode-xl', async({ mount, page }) => { let component: Locator;
test.beforeEach(async({ page, mount }) => {
await page.route(LOGO_URL, (route) => { await page.route(LOGO_URL, (route) => {
return route.fulfill({ return route.fulfill({
status: 200, status: 200,
...@@ -88,12 +118,22 @@ test.describe('custom logo with dark option', () => { ...@@ -88,12 +118,22 @@ test.describe('custom logo with dark option', () => {
}); });
}); });
const component = await mount( component = await mount(
<TestApp> <TestApp>
<NetworkLogo/> <NetworkLogo/>
</TestApp>, </TestApp>,
); );
});
test('', async() => {
await expect(component.locator('a')).toHaveScreenshot();
});
test.describe('screen xl', () => {
test.use({ viewport: configs.viewport.xl });
test('', async() => {
await expect(component.locator('a')).toHaveScreenshot(); await expect(component.locator('a')).toHaveScreenshot();
}); });
});
}); });
import { Text } from '@chakra-ui/react'; import { Box, Text } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import _uniqBy from 'lodash/uniqBy'; import _uniqBy from 'lodash/uniqBy';
import React from 'react'; import React from 'react';
...@@ -96,7 +96,11 @@ const SearchBarSuggest = ({ query, redirectCheckQuery, searchTerm, onItemClick } ...@@ -96,7 +96,11 @@ const SearchBarSuggest = ({ query, redirectCheckQuery, searchTerm, onItemClick }
return ( return (
<> <>
{ !isMobile && <TextAd pb={ 4 } mb={ 5 } borderColor="divider" borderBottomWidth="1px"/> } { !isMobile && (
<Box pb={ 4 } mb={ 5 } borderColor="divider" borderBottomWidth="1px" _empty={{ display: 'none' }}>
<TextAd/>
</Box>
) }
{ content } { content }
</> </>
); );
......
...@@ -37,7 +37,7 @@ const TxRawTrace = () => { ...@@ -37,7 +37,7 @@ const TxRawTrace = () => {
const channel = useSocketChannel({ const channel = useSocketChannel({
topic: `transactions:${ hash }`, topic: `transactions:${ hash }`,
isDisabled: !hash || !txInfo.isPlaceholderData || !txInfo.data?.status, isDisabled: !hash || txInfo.isPlaceholderData || !txInfo.data?.status,
onJoin: () => setIsSocketOpen(true), onJoin: () => setIsSocketOpen(true),
}); });
useSocketMessage({ useSocketMessage({
......
...@@ -3,10 +3,27 @@ import React from 'react'; ...@@ -3,10 +3,27 @@ import React from 'react';
import * as txMock from 'mocks/txs/tx'; import * as txMock from 'mocks/txs/tx';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
import * as configs from 'playwright/utils/configs';
import TxsTable from './TxsTable'; import TxsTable from './TxsTable';
test('base view +@dark-mode +@desktop-xl', async({ mount }) => { test.describe('base view', () => {
test('+@dark-mode', async({ mount }) => {
const component = await mount(
<TestApp>
{ /* eslint-disable-next-line react/jsx-no-bind */ }
<TxsTable txs={ [ txMock.base, txMock.base ] } sort={ () => () => {} } top={ 0 } showBlockInfo showSocketInfo={ false }/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
test.describe('screen xl', () => {
test.use({ viewport: configs.viewport.xl });
test('', async({ mount }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
{ /* eslint-disable-next-line react/jsx-no-bind */ } { /* eslint-disable-next-line react/jsx-no-bind */ }
...@@ -15,4 +32,6 @@ test('base view +@dark-mode +@desktop-xl', async({ mount }) => { ...@@ -15,4 +32,6 @@ test('base view +@dark-mode +@desktop-xl', async({ mount }) => {
); );
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
});
});
}); });
...@@ -35,6 +35,7 @@ const VerifiedContractsFilter = ({ onChange, defaultValue, isActive }: Props) => ...@@ -35,6 +35,7 @@ const VerifiedContractsFilter = ({ onChange, defaultValue, isActive }: Props) =>
<MenuItemOption value="all">All</MenuItemOption> <MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="solidity">Solidity</MenuItemOption> <MenuItemOption value="solidity">Solidity</MenuItemOption>
<MenuItemOption value="vyper">Vyper</MenuItemOption> <MenuItemOption value="vyper">Vyper</MenuItemOption>
<MenuItemOption value="yul">Yul</MenuItemOption>
</MenuOptionGroup> </MenuOptionGroup>
</MenuList> </MenuList>
</Menu> </Menu>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment