diff --git a/packages/app/README.md b/packages/app/README.md index 061fdc096a..1023fc9388 100644 --- a/packages/app/README.md +++ b/packages/app/README.md @@ -44,7 +44,6 @@ const config: EnvironmentConfig = { verificationApiUrl: "https://zksync2-testnet-explorer.zksync.dev", hostnames: ["localhost"], icon: "/images/icons/zksync-arrows.svg", - l1ExplorerUrl: "https://goerli.etherscan.io", l2ChainId: 270, l2NetworkName: "Local", l2WalletUrl: "https://goerli.staging-portal.zksync.dev/", @@ -59,7 +58,6 @@ const config: EnvironmentConfig = { verificationApiUrl: "https://zksync2-testnet-explorer.zksync.dev", hostnames: ["localhost"], icon: "/images/icons/zksync-arrows.svg", - l1ExplorerUrl: "https://goerli.etherscan.io", l2ChainId: 270, l2NetworkName: "Local Hyperchain", l2WalletUrl: "https://goerli.staging-portal.zksync.dev/", diff --git a/packages/app/src/components/AddressLink.vue b/packages/app/src/components/AddressLink.vue index e936f4517e..922573341e 100644 --- a/packages/app/src/components/AddressLink.vue +++ b/packages/app/src/components/AddressLink.vue @@ -1,9 +1,18 @@ diff --git a/packages/app/src/components/transfers/Table.vue b/packages/app/src/components/transfers/Table.vue index bf1278e2d7..446e3becbc 100644 --- a/packages/app/src/components/transfers/Table.vue +++ b/packages/app/src/components/transfers/Table.vue @@ -244,6 +244,9 @@ watch( .transfers-data-link-value { @apply block cursor-pointer text-sm font-medium; } + span.transfers-data-link-value { + @apply cursor-default; + } } } diff --git a/packages/app/src/configs/index.ts b/packages/app/src/configs/index.ts index aaa7da16c4..9c5ac1de48 100644 --- a/packages/app/src/configs/index.ts +++ b/packages/app/src/configs/index.ts @@ -9,7 +9,7 @@ export type NetworkConfig = { l2NetworkName: string; l2WalletUrl: string; l2ChainId: 270 | 280 | 324; - l1ExplorerUrl: string; + l1ExplorerUrl?: string; maintenance: boolean; published: boolean; hostnames: string[]; diff --git a/packages/app/tests/components/AddressLink.spec.ts b/packages/app/tests/components/AddressLink.spec.ts index add4f8a8b0..cde2801f78 100644 --- a/packages/app/tests/components/AddressLink.spec.ts +++ b/packages/app/tests/components/AddressLink.spec.ts @@ -1,9 +1,20 @@ -import { describe, expect, it } from "vitest"; +import { computed } from "vue"; + +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest"; import { mount, RouterLinkStub } from "@vue/test-utils"; import AddressLink from "@/components/AddressLink.vue"; +const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io"); +vi.mock("@/composables/useContext", () => { + return { + default: () => ({ + currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })), + }), + }; +}); + const global = { stubs: { RouterLink: RouterLinkStub, @@ -58,4 +69,26 @@ describe("Address Link", () => { "https://goerli.etherscan.io/address/0x0000000000000000000000000000000000000001" ); }); + describe("when L1 explorer url is not set", () => { + let mock1ExplorerUrl: Mock; + beforeEach(() => { + mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null); + }); + + afterEach(() => { + mock1ExplorerUrl.mockRestore(); + }); + + it("renders L1 address as text instead of link", async () => { + const wrapper = mount(AddressLink, { + global, + props: { + address: "0x0000000000000000000000000000000000000001", + network: "L1", + }, + }); + expect(wrapper.findAll("a").length).toBe(0); + expect(wrapper.find("span").text()).toBe("0x0000000000000000000000000000000000000001"); + }); + }); }); diff --git a/packages/app/tests/components/batches/InfoTable.spec.ts b/packages/app/tests/components/batches/InfoTable.spec.ts index a1fdbe60e0..63fc2948a8 100644 --- a/packages/app/tests/components/batches/InfoTable.spec.ts +++ b/packages/app/tests/components/batches/InfoTable.spec.ts @@ -1,6 +1,7 @@ +import { computed } from "vue"; import { createI18n } from "vue-i18n"; -import { describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest"; import { mount, RouterLinkStub } from "@vue/test-utils"; @@ -13,6 +14,15 @@ import type { BatchDetails } from "@/composables/useBatch"; import { localDateFromISOString } from "@/utils/helpers"; +const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io"); +vi.mock("@/composables/useContext", () => { + return { + default: () => ({ + currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })), + }), + }; +}); + describe("InfoTable:", () => { const i18n = createI18n({ locale: "en", @@ -145,4 +155,42 @@ describe("InfoTable:", () => { ); wrapper.unmount(); }); + describe("when L1 explorer url is not set", () => { + let mock1ExplorerUrl: Mock; + beforeEach(() => { + mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null); + }); + + afterEach(() => { + mock1ExplorerUrl.mockRestore(); + }); + + it("renders L1 hashes as texts instead of links", async () => { + const wrapper = mount(InfoTable, { + global: { + stubs: { + RouterLink: RouterLinkStub, + }, + plugins: [i18n], + }, + props: { + batch: batchItem, + loading: false, + }, + }); + expect(wrapper.findAll(".actual-string")[0].text()).toEqual( + "0x8983f748ff6c2f9038904d65dc63a344db33c29d97f1741a931e90689f86b2be" + ); + expect(wrapper.findAll(".actual-string")[1].text()).toEqual( + "0x0ab34d8523b67f80783305760a2989ffe6ab205621813db5420a3012845f5ac7" + ); + expect(wrapper.findAll(".actual-string")[2].text()).toEqual( + "0x87c5c5bf78100d88766101f13ec78d3b3356929556ee971cfacb6fe2a53b210a" + ); + expect(wrapper.findAll(".actual-string")[3].text()).toEqual( + "0x57c44d7c183633f81bfa155bd30e68a94e3ff12c1e6265a4b5e06b6d4a7a1fa8" + ); + wrapper.unmount(); + }); + }); }); diff --git a/packages/app/tests/components/blocks/InfoTable.spec.ts b/packages/app/tests/components/blocks/InfoTable.spec.ts index c66e685530..17ca8b8227 100644 --- a/packages/app/tests/components/blocks/InfoTable.spec.ts +++ b/packages/app/tests/components/blocks/InfoTable.spec.ts @@ -1,6 +1,7 @@ +import { computed } from "vue"; import { createI18n } from "vue-i18n"; -import { describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest"; import { mount, RouterLinkStub } from "@vue/test-utils"; @@ -14,6 +15,15 @@ import type { Block } from "@/composables/useBlock"; import { localDateFromISOString } from "@/utils/helpers"; +const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io"); +vi.mock("@/composables/useContext", () => { + return { + default: () => ({ + currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })), + }), + }; +}); + describe("InfoTable:", () => { const i18n = createI18n({ locale: "en", @@ -241,4 +251,54 @@ describe("InfoTable:", () => { expect(batch[1].findComponent(Tooltip).find("span").text()).toBe("1"); expect(batch[1].findComponent(RouterLinkStub).exists()).toBeFalsy(); }); + describe("when L1 explorer url is not set", () => { + let mock1ExplorerUrl: Mock; + beforeEach(() => { + mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null); + }); + + afterEach(() => { + mock1ExplorerUrl.mockRestore(); + }); + + it("renders L1 hashes as texts instead of links", async () => { + const wrapper = mount(InfoTable, { + global: { + plugins: [i18n], + stubs: { + RouterLink: RouterLinkStub, + }, + }, + props: { + block: { + number: 1, + timestamp: "2022-04-13T16:48:32.000Z", + l1TxCount: 1, + l2TxCount: 0, + hash: "0xcd7533748f8f0c8f406f366e83d5e92d174845405418745d0f7228b85025cd6e", + status: "verified", + commitTxHash: "0x5b5a05691d974803f5f095c1b918d2dd19152ed0a9de506d545c96df6cb9cac2", + committedAt: "2022-04-13T16:54:37.622380Z", + proveTxHash: "0xfb3532f4c38c2eaf78248da64cf80a354429d58204761d6ea6439391043f6fa9", + provenAt: "2022-04-13T16:54:37.700089Z", + executeTxHash: "0x8d1a78d1da5aba1d0755ec9dbcba938f3920681d2a3d4d374ef265a50858f364", + executedAt: "2022-04-13T16:54:37.784185Z", + }, + loading: false, + }, + }); + expect(wrapper.findAll(".actual-string")[0].text()).toEqual( + "0xcd7533748f8f0c8f406f366e83d5e92d174845405418745d0f7228b85025cd6e" + ); + expect(wrapper.findAll(".actual-string")[1].text()).toEqual( + "0x5b5a05691d974803f5f095c1b918d2dd19152ed0a9de506d545c96df6cb9cac2" + ); + expect(wrapper.findAll(".actual-string")[2].text()).toEqual( + "0xfb3532f4c38c2eaf78248da64cf80a354429d58204761d6ea6439391043f6fa9" + ); + expect(wrapper.findAll(".actual-string")[3].text()).toEqual( + "0x8d1a78d1da5aba1d0755ec9dbcba938f3920681d2a3d4d374ef265a50858f364" + ); + }); + }); }); diff --git a/packages/app/tests/components/transactions/Status.spec.ts b/packages/app/tests/components/transactions/Status.spec.ts index 0dacc3c1c8..82f3c72e77 100644 --- a/packages/app/tests/components/transactions/Status.spec.ts +++ b/packages/app/tests/components/transactions/Status.spec.ts @@ -1,6 +1,7 @@ +import { computed } from "vue"; import { createI18n } from "vue-i18n"; -import { describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest"; import { mount } from "@vue/test-utils"; @@ -17,6 +18,15 @@ import $testId from "@/plugins/testId"; const { currentNetwork } = useContext(); +const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io"); +vi.mock("@/composables/useContext", () => { + return { + default: () => ({ + currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })), + }), + }; +}); + describe("Status", () => { const i18n = createI18n({ locale: "en", @@ -148,6 +158,32 @@ describe("Status", () => { expect(spinnerComponents.length).toBe(1); expect(spinnerComponents[0].props().color).toBe("dark-neutral"); }); + describe("when L1 explorer url is not set", () => { + let mock1ExplorerUrl: Mock; + beforeEach(() => { + mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null); + }); + + afterEach(() => { + mock1ExplorerUrl.mockRestore(); + }); + + it("does not render links for 'committed' status", async () => { + const wrapper = mount(Status, { + global, + props: { + status: "committed", + commitTxHash: "commitTxHash", + proveTxHash: "proveTxHash", + executeTxHash: "executeTxHash", + }, + }); + const badges = wrapper.findAllComponents(Badge); + const l1StatusBadgeValueDesktop = badges[3]; + const [sentLink] = l1StatusBadgeValueDesktop.findAll(".badge-pre-content a"); + expect(sentLink.attributes("href")).toBeUndefined(); + }); + }); it("shows l2 completed badge and l1 validating badge for 'proved' status", async () => { const wrapper = mount(Status, { global, @@ -203,6 +239,33 @@ describe("Status", () => { expect(spinnerComponents.length).toBe(1); expect(spinnerComponents[0].props().color).toBe("dark-neutral"); }); + describe("when L1 explorer url is not set", () => { + let mock1ExplorerUrl: Mock; + beforeEach(() => { + mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null); + }); + + afterEach(() => { + mock1ExplorerUrl.mockRestore(); + }); + + it("does not render links for 'proved' status", async () => { + const wrapper = mount(Status, { + global, + props: { + status: "proved", + commitTxHash: "commitTxHash", + proveTxHash: "proveTxHash", + executeTxHash: "executeTxHash", + }, + }); + const badges = wrapper.findAllComponents(Badge); + const l1StatusBadgeValueDesktop = badges[3]; + const [sentLink, validatedLink] = l1StatusBadgeValueDesktop.findAll(".badge-pre-content a"); + expect(sentLink.attributes("href")).toBeUndefined(); + expect(validatedLink.attributes("href")).toBeUndefined(); + }); + }); it("shows l2 completed badge and l1 executed badge for 'verified' status", async () => { const wrapper = mount(Status, { global, @@ -257,6 +320,36 @@ describe("Status", () => { expect(l1StatusBadgeValueMobile.text()).toBe(i18n.global.t("transactions.statusComponent.executed")); expect(l1StatusBadgeValueMobile.props().color).toBe("dark-success"); }); + describe("when L1 explorer url is not set", () => { + let mock1ExplorerUrl: Mock; + beforeEach(() => { + mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null); + }); + + afterEach(() => { + mock1ExplorerUrl.mockRestore(); + }); + + it("does not render links for 'verified' status", async () => { + const wrapper = mount(Status, { + global, + props: { + status: "verified", + commitTxHash: "commitTxHash", + proveTxHash: "proveTxHash", + executeTxHash: "executeTxHash", + }, + }); + const badges = wrapper.findAllComponents(Badge); + const l1StatusBadgeValueDesktop = badges[3]; + const [sentLink, validatedLink] = l1StatusBadgeValueDesktop.findAll(".badge-pre-content a"); + expect(sentLink.attributes("href")).toBeUndefined(); + expect(validatedLink.attributes("href")).toBeUndefined(); + + const l1ExecutedLink = l1StatusBadgeValueDesktop.find(".badge-content a"); + expect(l1ExecutedLink.attributes("href")).toBeUndefined(); + }); + }); it("shows icon tooltip and single indexing badge for 'indexing' status", async () => { const wrapper = mount(Status, { global, diff --git a/packages/app/tests/components/transactions/TransferInfo.spec.ts b/packages/app/tests/components/transactions/TransferInfo.spec.ts index efa85c9db5..f4830ecb21 100644 --- a/packages/app/tests/components/transactions/TransferInfo.spec.ts +++ b/packages/app/tests/components/transactions/TransferInfo.spec.ts @@ -1,6 +1,7 @@ +import { computed } from "vue"; import { createI18n } from "vue-i18n"; -import { describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest"; import { mount, RouterLinkStub } from "@vue/test-utils"; import { $fetch } from "ohmyfetch"; @@ -15,6 +16,15 @@ vi.mock("ohmyfetch", () => { }; }); +const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io"); +vi.mock("@/composables/useContext", () => { + return { + default: () => ({ + currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })), + }), + }; +}); + describe("TransferInfo:", () => { const i18n = createI18n({ locale: "en", @@ -50,4 +60,58 @@ describe("TransferInfo:", () => { mock.mockRestore(); wrapper.unmount(); }); + it("renders component properly for L1 network", async () => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + const mock = ($fetch as any).mockResolvedValue({ accountType: "eOA" }); + + const wrapper = mount(TransferInfo, { + global, + props: { + label: "From", + address: "0x6c10d9c1744f149d4b17660e14faa247964749c7", + network: "L1", + }, + }); + expect(wrapper.find("span")?.text()).toBe("From"); + expect(wrapper.findAll("a")[0].attributes("href")).toEqual( + "https://goerli.etherscan.io/address/0x6c10d9c1744f149d4b17660e14faa247964749c7" + ); + expect(wrapper.findAll("a")[0].text()).toEqual("0x6c10d9c1744...49c7"); + expect(wrapper.find(".copy-btn")).toBeTruthy(); + expect(wrapper.find(".transactions-data-link-network")?.text()).toBe("L1"); + + mock.mockRestore(); + wrapper.unmount(); + }); + describe("when L1 explorer url is not set", () => { + let mock1ExplorerUrl: Mock; + beforeEach(() => { + mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null); + }); + + afterEach(() => { + mock1ExplorerUrl.mockRestore(); + }); + + it("renders L1 address as text instead of a link", async () => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + const mock = ($fetch as any).mockResolvedValue({ accountType: "eOA" }); + + const wrapper = mount(TransferInfo, { + global, + props: { + label: "From", + address: "0x6c10d9c1744f149d4b17660e14faa247964749c7", + network: "L1", + }, + }); + expect(wrapper.find("span")?.text()).toBe("From"); + expect(wrapper.findAll("span.address")[0].text()).toEqual("0x6c10d9c1744...49c7"); + expect(wrapper.find(".copy-btn")).toBeTruthy(); + expect(wrapper.find(".transactions-data-link-network")?.text()).toBe("L1"); + + mock.mockRestore(); + wrapper.unmount(); + }); + }); }); diff --git a/packages/app/tests/mocks.ts b/packages/app/tests/mocks.ts index e5c590dbb1..a5886be231 100644 --- a/packages/app/tests/mocks.ts +++ b/packages/app/tests/mocks.ts @@ -29,7 +29,7 @@ export const GOERLI_NETWORK: NetworkConfig = { rpcUrl: "", l2NetworkName: "Goerli", l2WalletUrl: "", - l1ExplorerUrl: "", + l1ExplorerUrl: "http://goerli-block-explorer", maintenance: false, published: true, hostnames: [], @@ -44,7 +44,7 @@ export const GOERLI_BETA_NETWORK: NetworkConfig = { rpcUrl: "", l2NetworkName: "Goerli Beta", l2WalletUrl: "", - l1ExplorerUrl: "", + l1ExplorerUrl: "http://goerli-beta-block-explorer", maintenance: false, published: true, hostnames: ["https://goerli-beta.staging-scan-v2.zksync.dev/"],