diff --git a/__tests__/components/common/TimePicker.test.tsx b/__tests__/components/common/TimePicker.test.tsx index bc5c104625..8c259891cb 100644 --- a/__tests__/components/common/TimePicker.test.tsx +++ b/__tests__/components/common/TimePicker.test.tsx @@ -3,25 +3,47 @@ import React from 'react'; import TimePicker from '../../../components/common/TimePicker'; describe('TimePicker', () => { - it('changes hours and minutes via inputs', () => { + it('labels hour and minute inputs for accessibility', () => { const onChange = jest.fn(); render(); - fireEvent.change(screen.getByPlaceholderText('HH'), { target: { value: '10' } }); + + const hoursInput = screen.getByLabelText('Hours'); + const minutesInput = screen.getByLabelText('Minutes'); + + fireEvent.change(hoursInput, { target: { value: '10' } }); expect(onChange).toHaveBeenCalledWith(10, 15); - fireEvent.change(screen.getByPlaceholderText('MM'), { target: { value: '45' } }); + + fireEvent.change(minutesInput, { target: { value: '45' } }); expect(onChange).toHaveBeenCalledWith(9, 45); }); it('toggles am/pm respecting minTime', () => { const onChange = jest.fn(); - render(); - fireEvent.click(screen.getByText('AM')); + render( + + ); + fireEvent.click(screen.getByRole('button', { name: 'Toggle AM/PM' })); expect(onChange).toHaveBeenCalledWith(21, 0); }); - it('disables options before minTime', () => { + it('describes minimum time and disables earlier quick options', () => { const onChange = jest.fn(); - render(); + render( + + ); + + const hoursInput = screen.getByLabelText('Hours'); + const minutesInput = screen.getByLabelText('Minutes'); + + const describedBy = hoursInput.getAttribute('aria-describedby'); + expect(describedBy).toBeTruthy(); + + const description = document.getElementById(describedBy ?? ''); + expect(description).not.toBeNull(); + expect(description).toHaveTextContent('Earliest selectable time is 9:30 AM.'); + + expect(minutesInput.getAttribute('aria-describedby')).toBe(describedBy); + const early = screen.getByText('9 AM') as HTMLButtonElement; const later = screen.getByText('12 PM'); expect(early).toBeDisabled(); diff --git a/__tests__/components/common/TooltipIconButton.test.tsx b/__tests__/components/common/TooltipIconButton.test.tsx index 9097cd0be3..df2f019434 100644 --- a/__tests__/components/common/TooltipIconButton.test.tsx +++ b/__tests__/components/common/TooltipIconButton.test.tsx @@ -1,46 +1,108 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import TooltipIconButton from '../../../components/common/TooltipIconButton'; -import { faCheck } from '@fortawesome/free-solid-svg-icons'; +import TooltipIconButton from "@/components/common/TooltipIconButton"; +import { faCheck } from "@fortawesome/free-solid-svg-icons"; +import { fireEvent, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: (props: any) => , +type FontAwesomeProps = { + readonly [key: string]: unknown; +}; + +jest.mock("@fortawesome/react-fontawesome", () => ({ + FontAwesomeIcon: (props: FontAwesomeProps) => ( + + ), })); -describe('TooltipIconButton', () => { - it('shows tooltip on hover and hides on mouse leave', () => { - render(); - const wrapper = screen.getByTestId('icon').parentElement as HTMLElement; - expect(screen.queryByText('info')).not.toBeInTheDocument(); +describe("TooltipIconButton", () => { + it("shows tooltip on hover and hides on mouse leave", () => { + render( + + ); + const button = screen.getByRole("button"); + expect(button).toHaveAttribute("type", "button"); + expect(screen.queryByRole("tooltip")).not.toBeInTheDocument(); + + fireEvent.mouseEnter(button); + const tooltip = screen.getByRole("tooltip"); + expect(tooltip).toHaveTextContent("info"); + expect(tooltip.className).toContain("tw-bottom-6"); + + const describedBy = button.getAttribute("aria-describedby") ?? ""; + expect(describedBy).toContain("external-id"); + expect(describedBy).toContain(tooltip.getAttribute("id") ?? ""); + + fireEvent.mouseLeave(button); + expect(screen.queryByRole("tooltip")).not.toBeInTheDocument(); + }); + + it("applies bottom position classes", () => { + render( + + ); + const button = screen.getByRole("button"); + fireEvent.mouseEnter(button); + const tooltip = screen.getByRole("tooltip"); + expect(tooltip.className).toContain("tw-top-6"); + }); + + it("shows tooltip on focus and hides on blur triggered by keyboard navigation", async () => { + const user = userEvent.setup(); + const handleFocus = jest.fn(); + const handleBlur = jest.fn(); - fireEvent.mouseEnter(wrapper); - const tooltip = screen.getByText('info'); + render( + <> + + + + ); + + await user.tab(); + const tooltip = screen.getByRole("tooltip"); expect(tooltip).toBeInTheDocument(); - expect(tooltip.className).toContain('tw-bottom-6'); + expect(handleFocus).toHaveBeenCalledTimes(1); - fireEvent.mouseLeave(wrapper); - expect(screen.queryByText('info')).not.toBeInTheDocument(); + await user.tab(); + expect(handleBlur).toHaveBeenCalledTimes(1); + expect(screen.queryByRole("tooltip")).not.toBeInTheDocument(); }); - it('applies bottom position classes', () => { + it("forwards tabIndex to the button", () => { render( - + ); - const wrapper = screen.getByTestId('icon').parentElement as HTMLElement; - fireEvent.mouseEnter(wrapper); - const tooltip = screen.getByText('info'); - expect(tooltip.className).toContain('tw-top-6'); + const button = screen.getByRole("button"); + expect(button).toHaveAttribute("tabindex", "-1"); }); - it('calls onClick and stops propagation', () => { + it("calls onClick and stops propagation", () => { const onClick = jest.fn(); const parentClick = jest.fn(); + render(
- +
); - const wrapper = screen.getByTestId('icon').parentElement as HTMLElement; - fireEvent.click(wrapper); + const button = screen.getByRole("button"); + fireEvent.click(button); expect(onClick).toHaveBeenCalled(); expect(parentClick).not.toHaveBeenCalled(); }); diff --git a/__tests__/components/drops/view/part/DropPartMarkdown.test.tsx b/__tests__/components/drops/view/part/DropPartMarkdown.test.tsx index f7f1caca95..a6bdbe6b27 100644 --- a/__tests__/components/drops/view/part/DropPartMarkdown.test.tsx +++ b/__tests__/components/drops/view/part/DropPartMarkdown.test.tsx @@ -1,4 +1,48 @@ -import DropPartMarkdown from "@/components/drops/view/part/DropPartMarkdown"; +/** @jest-environment jsdom */ +import React from "react"; + +jest.mock("next/dynamic", () => { + let mode: "skeleton" | "eager" = "eager"; + const calls: any[] = []; + const mock = jest.fn((loader: any, options: any) => { + calls.push([loader, options]); + const Comp = (props: any) => { + if (mode === "skeleton") { + const L = options?.loading as React.ComponentType | undefined; + return L ? : null; + } + // Eager mode: immediately render the underlying component. + // We know the loader resolves to react-tweet's Tweet, so just render that mock directly. + const { Tweet } = require("react-tweet"); + const T = Tweet as React.ComponentType; + return ; + }; + (Comp as any).__loader = loader; + (Comp as any).__options = options; + return Comp; + }); + const setMode = (m: "skeleton" | "eager") => { + mode = m; + }; + return { + __esModule: true, + default: mock, + __calls: calls, + __setMode: setMode, + }; +}); + +// Use this in assertions: dynamicSpy.mock.calls[0] => [loader, options] +const { + default: dynamicSpy, + __calls: dynamicCalls, + __setMode: setDynamicMode, +} = require("next/dynamic") as { + default: jest.Mock; + __calls: any[]; + __setMode: (m: "skeleton" | "eager") => void; +}; + import { publicEnv } from "@/config/env"; import { fetchYoutubePreview, @@ -7,6 +51,8 @@ import { import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import DropPartMarkdown from "@/components/drops/view/part/DropPartMarkdown"; + const FALLBACK_BASE_ENDPOINT = "https://6529.io"; const originalBaseEndpoint = publicEnv.BASE_ENDPOINT; const originalArtBlocksFlags = { @@ -82,8 +128,18 @@ jest.mock("@/components/waves/FarcasterCard", () => ({ jest.mock("@/components/waves/ChatItemHrefButtons", () => ({ __esModule: true, - default: ({ href, relativeHref }: { href: string; relativeHref?: string }) => ( -
+ default: ({ + href, + relativeHref, + }: { + href: string; + relativeHref?: string; + }) => ( +
), })); @@ -100,8 +156,7 @@ afterEach(() => { describe("DropPartMarkdown", () => { beforeEach(() => { jest.clearAllMocks(); - publicEnv.BASE_ENDPOINT = - originalBaseEndpoint ?? FALLBACK_BASE_ENDPOINT; + publicEnv.BASE_ENDPOINT = originalBaseEndpoint ?? FALLBACK_BASE_ENDPOINT; publicEnv.VITE_FEATURE_AB_CARD = originalArtBlocksFlags.VITE_FEATURE_AB_CARD; publicEnv.NEXT_PUBLIC_VITE_FEATURE_AB_CARD = @@ -201,7 +256,10 @@ describe("DropPartMarkdown", () => { expect(mockArtBlocksTokenCard).not.toHaveBeenCalled(); expect(mockLinkPreviewCard).not.toHaveBeenCalled(); const link = screen.getByRole("link", { name: "token" }); - expect(link).toHaveAttribute("href", "https://www.artblocks.io/project/662000"); + expect(link).toHaveAttribute( + "href", + "https://www.artblocks.io/project/662000" + ); }); it("falls back to regular link when Art Blocks card disabled", () => { @@ -220,7 +278,10 @@ describe("DropPartMarkdown", () => { expect(mockArtBlocksTokenCard).not.toHaveBeenCalled(); expect(mockLinkPreviewCard).not.toHaveBeenCalled(); const link = screen.getByRole("link", { name: "token" }); - expect(link).toHaveAttribute("href", "https://www.artblocks.io/token/662000"); + expect(link).toHaveAttribute( + "href", + "https://www.artblocks.io/token/662000" + ); }); it("renders a fallback link when tweet data is unavailable", async () => { @@ -424,4 +485,33 @@ describe("DropPartMarkdown", () => { const fallbackLink = await screen.findByRole("link", { name: url }); expect(fallbackLink).toHaveAttribute("href", url); }); + + it("lazy loads tweet embeds with a loading skeleton", async () => { + setDynamicMode("skeleton"); + try { + const content = + "Check this [tweet](https://twitter.com/user/status/1234567890)"; + + render( + + ); + + expect(screen.getByTestId("tweet-embed-loading")).toBeInTheDocument(); + + expect(dynamicCalls.length).toBeGreaterThanOrEqual(1); + const [loader, options] = dynamicCalls[0]; + expect(options?.ssr).toBe(false); + + const TweetComponent = await loader(); + const { getByText } = render(); + expect(getByText("tweet:abc123")).toBeInTheDocument(); + } finally { + setDynamicMode("eager"); + } + }); }); diff --git a/__tests__/components/gas-royalties/GasRoyalties.test.tsx b/__tests__/components/gas-royalties/GasRoyalties.test.tsx index f05ec70396..f8cf641c12 100644 --- a/__tests__/components/gas-royalties/GasRoyalties.test.tsx +++ b/__tests__/components/gas-royalties/GasRoyalties.test.tsx @@ -309,7 +309,7 @@ describe("GasRoyaltiesTokenImage", () => { const link = screen.getByRole("link"); expect(link).toHaveAttribute("href", "/memes/1"); expect(link).toHaveAttribute("target", "_blank"); - expect(link).toHaveAttribute("rel", "noreferrer"); + expect(link).toHaveAttribute("rel", "noopener noreferrer"); const image = screen.getByAltText("Meme1"); expect(image).toBeInTheDocument(); diff --git a/__tests__/components/mintCountdownBox/MintCountdownBox.test.tsx b/__tests__/components/mintCountdownBox/MintCountdownBox.test.tsx index f40e6ee892..76de6b7d60 100644 --- a/__tests__/components/mintCountdownBox/MintCountdownBox.test.tsx +++ b/__tests__/components/mintCountdownBox/MintCountdownBox.test.tsx @@ -113,7 +113,7 @@ describe("MintCountdownBox", () => { const link = screen.getByRole("link"); expect(link).toHaveAttribute("href", "/the-memes/mint"); expect(link).toHaveAttribute("target", "_self"); - expect(link).toHaveAttribute("rel", "noreferrer"); + expect(link).not.toHaveAttribute("rel"); }); it("renders multiple buttons correctly", () => { @@ -134,8 +134,10 @@ describe("MintCountdownBox", () => { const links = screen.getAllByRole("link"); expect(links[0]).toHaveAttribute("href", "/the-memes/mint"); expect(links[0]).toHaveAttribute("target", "_self"); + expect(links[0]).not.toHaveAttribute("rel"); expect(links[1]).toHaveAttribute("href", "https://external.com/mint"); expect(links[1]).toHaveAttribute("target", "_blank"); + expect(links[1]).toHaveAttribute("rel", "noopener noreferrer"); }); it("renders JSX Element as button label", () => { @@ -452,7 +454,12 @@ describe("MintCountdownBox", () => { links.forEach(link => { expect(link).toBeVisible(); expect(link).toHaveAttribute("href"); - expect(link).toHaveAttribute("rel", "noreferrer"); + const target = link.getAttribute("target"); + if (target === "_blank") { + expect(link).toHaveAttribute("rel", "noopener noreferrer"); + } else { + expect(link).not.toHaveAttribute("rel"); + } }); }); diff --git a/__tests__/components/nft-image/renderers/NFTImageRenderer.test.tsx b/__tests__/components/nft-image/renderers/NFTImageRenderer.test.tsx index 771c72dbac..4d47ee8384 100644 --- a/__tests__/components/nft-image/renderers/NFTImageRenderer.test.tsx +++ b/__tests__/components/nft-image/renderers/NFTImageRenderer.test.tsx @@ -88,11 +88,28 @@ jest.mock("@/components/nft-image/NFTImageBalance", () => { const createMockNFT = (overrides: Partial = {}): BaseNFT => ({ id: 1, contract: "0x123", - token_id: "1", - name: "Test NFT", - image: "https://example.com/image.png", + collection: "Test Collection", + token_type: "ERC721", + description: "Test description", + artist: "Test Artist", + artist_seize_handle: "testartist", + uri: "https://example.com/token/1", + icon: "https://example.com/icon.png", thumbnail: "https://example.com/thumb.png", scaled: "https://example.com/scaled.png", + image: "https://example.com/image.png", + animation: "https://example.com/animation.mp4", + market_cap: 0, + floor_price: 0.05, + total_volume_last_24_hours: 50, + total_volume_last_7_days: 200, + total_volume_last_1_month: 500, + total_volume: 1000, + highest_offer: 0.08, + mint_price: 0.06529, + supply: 10, + name: "Test NFT", + created_at: new Date(), metadata: { image: "https://example.com/metadata-image.png", name: "Test NFT", @@ -107,8 +124,6 @@ const createDefaultProps = ( ): BaseRendererProps => ({ nft: createMockNFT(), height: 300, - showOwnedIfLoggedIn: false, - showUnseizedIfLoggedIn: false, heightStyle: "height-300", imageStyle: "image-style", bgStyle: "bg-style", @@ -389,10 +404,23 @@ describe("NFTImageRenderer", () => { }); describe("Performance Attributes", () => { - it("sets correct loading and priority attributes", () => { + it("uses lazy loading for grid-sized images", () => { const props = createDefaultProps(); render(); + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("data-loading", "lazy"); + expect(image).toHaveAttribute("data-priority", "false"); + expect(image).toHaveAttribute("data-fetch-priority", "auto"); + }); + + it("prioritizes larger feature images", () => { + const props = createDefaultProps({ + height: 650, + heightStyle: "height-650", + }); + render(); + const image = screen.getByRole("img"); expect(image).toHaveAttribute("data-loading", "eager"); expect(image).toHaveAttribute("data-priority", "true"); diff --git a/__tests__/components/the-memes/MemePage.test.tsx b/__tests__/components/the-memes/MemePage.test.tsx index 01c0824ebc..6add788c5f 100644 --- a/__tests__/components/the-memes/MemePage.test.tsx +++ b/__tests__/components/the-memes/MemePage.test.tsx @@ -10,6 +10,7 @@ import React from "react"; jest.mock("next/navigation", () => ({ useRouter: jest.fn(), useSearchParams: jest.fn(), + usePathname: jest.fn(), })); jest.mock("@/services/6529api", () => ({ @@ -62,31 +63,57 @@ jest.mock("@/components/the-memes/MemePageTimeline", () => ({ show ?
Timeline
: null, })); -jest.mock("@/components/the-memes/MemePageMintCountdown", () => () => ( -
-)); +jest.mock("@/components/the-memes/MemePageMintCountdown", () => { + const MockMemePageMintCountdown = () =>
; + MockMemePageMintCountdown.displayName = "MockMemePageMintCountdown"; + return MockMemePageMintCountdown; +}); -jest.mock("@/components/nft-image/NFTImage", () => () => ( -
-)); +jest.mock("@/components/nft-image/NFTImage", () => { + const MockNFTImage = () =>
; + MockNFTImage.displayName = "MockNFTImage"; + return MockNFTImage; +}); -jest.mock("@/components/nft-navigation/NftNavigation", () => () => ( -
-)); +jest.mock("@/components/nft-navigation/NftNavigation", () => { + const MockNftNavigation = () =>
; + MockNftNavigation.displayName = "MockNftNavigation"; + return MockNftNavigation; +}); -const mockPush = jest.fn(); +const mockReplace = jest.fn((url: string, _options?: { scroll?: boolean }) => { + const parsedUrl = new URL(url, "https://example.com"); + currentFocus = parsedUrl.searchParams.get("focus"); +}); +let currentFocus: string | null = null; const mockSearchParams = { - get: jest.fn().mockReturnValue(null), + get: jest.fn((key: string) => (key === "focus" ? currentFocus : null)), + toString: jest.fn(() => { + const params = new URLSearchParams(); + if (currentFocus) { + params.set("focus", currentFocus); + } + return params.toString(); + }), }; const useSearchParamsMock = require("next/navigation").useSearchParams; useSearchParamsMock.mockReturnValue(mockSearchParams); +const usePathnameMock = require("next/navigation").usePathname; +usePathnameMock.mockReturnValue("/the-memes/1"); (useRouter as jest.Mock).mockReturnValue({ query: { id: "1" }, isReady: true, - push: mockPush, - replace: jest.fn(), + push: jest.fn(), + replace: mockReplace, +}); + +beforeEach(() => { + currentFocus = null; + mockReplace.mockClear(); + mockSearchParams.get.mockClear(); + mockSearchParams.toString.mockClear(); }); const nftMeta = { @@ -189,7 +216,7 @@ function renderPage() { }; return render( - + ); @@ -214,8 +241,8 @@ jest.mock("@/contexts/TitleContext", () => ({ describe("MemePage tab navigation", () => { beforeEach(() => { - mockPush.mockClear(); - mockSearchParams.get.mockReturnValue(null); + currentFocus = null; + mockReplace.mockClear(); }); it.each([ @@ -233,7 +260,7 @@ describe("MemePage tab navigation", () => { expect(screen.getByTestId("mint-countdown")).toBeInTheDocument() ); - mockPush.mockClear(); + mockReplace.mockClear(); const btn = screen.getByRole("button", { name: label }); await userEvent.click(btn); @@ -242,8 +269,9 @@ describe("MemePage tab navigation", () => { }); if (label !== "Live") { - expect(mockPush).toHaveBeenLastCalledWith( - `/the-memes/1?focus=${focus}` + expect(mockReplace).toHaveBeenLastCalledWith( + `/the-memes/1?focus=${focus}`, + { scroll: false } ); } } @@ -252,12 +280,13 @@ describe("MemePage tab navigation", () => { describe("MemePage search params handling", () => { beforeEach(() => { - mockPush.mockClear(); + currentFocus = null; + mockReplace.mockClear(); mockSearchParams.get.mockClear(); }); it("defaults to LIVE focus when no focus param", async () => { - mockSearchParams.get.mockReturnValue(null); + currentFocus = null; renderPage(); await waitFor(() => { expect(screen.getByTestId("live-right")).toBeInTheDocument(); @@ -265,7 +294,7 @@ describe("MemePage search params handling", () => { }); it("sets focus from valid search param", async () => { - mockSearchParams.get.mockReturnValue(MEME_FOCUS.ACTIVITY); + currentFocus = MEME_FOCUS.ACTIVITY; renderPage(); await waitFor(() => { expect(screen.getByTestId("activity")).toBeInTheDocument(); @@ -273,7 +302,7 @@ describe("MemePage search params handling", () => { }); it("ignores invalid focus param and defaults to LIVE", async () => { - mockSearchParams.get.mockReturnValue("invalid-focus"); + currentFocus = "invalid-focus"; renderPage(); await waitFor(() => { expect(screen.getByTestId("live-right")).toBeInTheDocument(); @@ -290,8 +319,9 @@ describe("MemePage search params handling", () => { const artButton = screen.getByRole("button", { name: "The Art" }); await userEvent.click(artButton); - expect(mockPush).toHaveBeenCalledWith( - `/the-memes/1?focus=${MEME_FOCUS.THE_ART}` + expect(mockReplace).toHaveBeenCalledWith( + `/the-memes/1?focus=${MEME_FOCUS.THE_ART}`, + { scroll: false } ); }); }); @@ -329,12 +359,13 @@ describe("MemePage API interactions", () => { renderPage(); - // Should not make the second NFT call when metadata is empty await waitFor(() => { const calls = (fetchUrl as jest.Mock).mock.calls; const nftCalls = calls.filter((call) => call[0].includes("/api/nfts?")); - expect(nftCalls).toHaveLength(0); + expect(nftCalls).toHaveLength(1); }); + + expect(screen.queryByTestId("nft-navigation")).not.toBeInTheDocument(); }); }); @@ -391,7 +422,7 @@ describe("MemePage wallet integration", () => { }; render( - + ); @@ -441,7 +472,7 @@ describe("MemePage wallet integration", () => { }; render( - + ); diff --git a/__tests__/components/the-memes/MemePageActivity.test.tsx b/__tests__/components/the-memes/MemePageActivity.test.tsx index 084b0c474d..02ad75c036 100644 --- a/__tests__/components/the-memes/MemePageActivity.test.tsx +++ b/__tests__/components/the-memes/MemePageActivity.test.tsx @@ -10,9 +10,11 @@ jest.mock("@/services/6529api", () => ({ fetchUrl: (...args: any[]) => fetchUrlMock(...args), })); -jest.mock("@/components/latest-activity/LatestActivityRow", () => () => ( - -)); +jest.mock("@/components/latest-activity/LatestActivityRow", () => { + const MockLatestActivityRow = () => ; + MockLatestActivityRow.displayName = "MockLatestActivityRow"; + return MockLatestActivityRow; +}); // Utility NFT object with required fields only const nft = { @@ -101,6 +103,12 @@ describe("MemePageActivity", () => { }); describe("Activity Fetching", () => { + it("skips fetching when tab is hidden", () => { + render(); + + expect(fetchUrlMock).not.toHaveBeenCalled(); + }); + it("fetches activity with correct base url", async () => { render(); diff --git a/__tests__/components/utils/CommonFilterTargetSelect.test.tsx b/__tests__/components/utils/CommonFilterTargetSelect.test.tsx index 80ab75d296..8d4082f170 100644 --- a/__tests__/components/utils/CommonFilterTargetSelect.test.tsx +++ b/__tests__/components/utils/CommonFilterTargetSelect.test.tsx @@ -1,16 +1,67 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import CommonFilterTargetSelect, { FilterTargetType } from '../../../components/utils/CommonFilterTargetSelect'; +import CommonFilterTargetSelect, { + FilterTargetType, +} from "@/components/utils/CommonFilterTargetSelect"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; -describe('CommonFilterTargetSelect', () => { - it('renders radio buttons and triggers change', async () => { +describe("CommonFilterTargetSelect", () => { + it("renders the filter target options and triggers change on click", async () => { const user = userEvent.setup(); const onChange = jest.fn(); - render(); - const buttons = screen.getAllByRole('radio'); + + render( + + ); + + const group = screen.getByRole("group", { name: /filter target/i }); + expect(group).toBeInTheDocument(); + + const buttons = screen.getAllByRole("radio"); expect(buttons).toHaveLength(3); - await user.click(screen.getByLabelText('Outgoing')); + + await user.click(screen.getByLabelText("Outgoing")); + + expect(onChange).toHaveBeenCalledWith(FilterTargetType.OUTGOING); + }); + + it("supports keyboard navigation between targets", async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + const { rerender } = render( + + ); + + await user.tab(); + + const allRadio = screen.getByRole("radio", { name: "All" }); + expect(allRadio).toHaveFocus(); + + await user.keyboard("{ArrowRight}"); + expect(onChange).toHaveBeenCalledWith(FilterTargetType.OUTGOING); + + onChange.mockClear(); + + rerender( + + ); + + const outgoingRadio = screen.getByRole("radio", { name: "Outgoing" }); + expect(outgoingRadio).toHaveFocus(); + expect(outgoingRadio).toBeChecked(); + + await user.keyboard("{ArrowLeft}"); + + expect(onChange).toHaveBeenCalledWith(FilterTargetType.ALL); }); }); diff --git a/__tests__/helpers/Helpers.test.ts b/__tests__/helpers/Helpers.test.ts index c51b76a306..3482593e90 100644 --- a/__tests__/helpers/Helpers.test.ts +++ b/__tests__/helpers/Helpers.test.ts @@ -74,7 +74,7 @@ describe("additional helper functions", () => { test("parseNftDescriptionToHtml replaces newlines and links", () => { const input = "line1\nhttps://example.com"; expect(parseNftDescriptionToHtml(input)).toBe( - 'line1
https://example.com' + 'line1
https://example.com' ); }); diff --git a/__tests__/moreStaticPages.test.tsx b/__tests__/moreStaticPages.test.tsx index 3a184157e0..df6643d48a 100644 --- a/__tests__/moreStaticPages.test.tsx +++ b/__tests__/moreStaticPages.test.tsx @@ -6,6 +6,8 @@ import { AuthContext } from "@/components/auth/Auth"; import MemeLabCollection from "@/components/memelab/MemeLabCollection"; import NotFoundPage from "@/app/not-found"; import { render, screen, cleanup } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { useRouter } from "next/navigation"; import React, { useMemo } from "react"; jest.mock("next/dynamic", () => () => () =>
); @@ -30,6 +32,9 @@ jest.mock( "@/components/distribution-plan-tool/create-plan/DistributionPlanToolCreatePlan", () => () =>
); +jest.mock("next/navigation", () => ({ + useRouter: jest.fn(), +})); const TestProvider: React.FC<{ children: React.ReactNode }> = ({ children, @@ -61,11 +66,21 @@ jest.mock("@/contexts/TitleContext", () => ({ })); describe("additional static pages", () => { + beforeEach(() => { + (useRouter as jest.Mock).mockReturnValue({ + back: jest.fn(), + push: jest.fn(), + }); + }); afterEach(() => { cleanup(); jest.clearAllMocks(); }); - it("renders 404 not-found page with proper content and navigation", () => { + it("renders 404 not-found page with proper content and navigation", async () => { + const routerMock = { back: jest.fn(), push: jest.fn() }; + (useRouter as jest.Mock).mockReturnValue(routerMock); + const user = userEvent.setup(); + render( @@ -79,7 +94,13 @@ describe("additional static pages", () => { const homeLink = screen.getByRole('link', { name: /take me home/i }); expect(homeLink).toBeInTheDocument(); expect(homeLink).toHaveAttribute('href', '/'); - + + const backButton = screen.getByRole('button', { name: /back to previous page/i }); + expect(backButton).toBeInTheDocument(); + + await user.click(backButton); + expect(routerMock.back).toHaveBeenCalledTimes(1); + // Check for visual elements expect(screen.getByAltText('SummerGlasses')).toBeInTheDocument(); expect(screen.getByAltText('sgt_flushed')).toBeInTheDocument(); diff --git a/app/about/100m-project/page.tsx b/app/about/100m-project/page.tsx index 5b515abcf7..0a39760aba 100644 --- a/app/about/100m-project/page.tsx +++ b/app/about/100m-project/page.tsx @@ -6,7 +6,6 @@ export default function About100mProjectPage() {
- diff --git a/app/about/media/page.tsx b/app/about/media/page.tsx index 008670467c..a9e8e8ada0 100644 --- a/app/about/media/page.tsx +++ b/app/about/media/page.tsx @@ -6,7 +6,6 @@ export default function AboutMediaPage() {
- + rel="noopener noreferrer"> Punk with background @@ -433,7 +432,7 @@ Punk no background" + rel="noopener noreferrer"> Punk no background @@ -477,28 +476,28 @@ Punk no background" + rel="noopener noreferrer"> Black on white
+ rel="noopener noreferrer"> White on black
+ rel="noopener noreferrer"> Black no background
+ rel="noopener noreferrer"> White no background
@@ -544,28 +543,28 @@ Punk no background" + rel="noopener noreferrer"> Black on white
+ rel="noopener noreferrer"> White on black
+ rel="noopener noreferrer"> Black no background
+ rel="noopener noreferrer"> White no background
@@ -939,6 +938,7 @@ Punk no background" diff --git a/app/about/mission/page.tsx b/app/about/mission/page.tsx index c307bfc322..a9335f8630 100644 --- a/app/about/mission/page.tsx +++ b/app/about/mission/page.tsx @@ -6,7 +6,6 @@ export default function AboutMissionPage() {
- diff --git a/app/about/open-metaverse/page.tsx b/app/about/open-metaverse/page.tsx index 0c51be58dc..e719b48eb1 100644 --- a/app/about/open-metaverse/page.tsx +++ b/app/about/open-metaverse/page.tsx @@ -6,7 +6,6 @@ export default function AboutOpenMetaversePage() {
- diff --git a/app/about/press/page.tsx b/app/about/press/page.tsx index 39d002d2bf..5c58460df9 100644 --- a/app/about/press/page.tsx +++ b/app/about/press/page.tsx @@ -6,7 +6,6 @@ export default function AboutPressPage() {
- + rel="noopener noreferrer"> NFT100 LIST 2022 @@ -404,7 +403,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> nft now {" "} |{" "} @@ -512,7 +511,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> HOW TO MINT AN NFT AND SELL IT (ACCORDING TO SOMEONE WHO HAS DONE IT) @@ -540,7 +539,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> GQ India {" "} |{" "} @@ -648,7 +647,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> VENTURE CAPITALISTS SEEK BIG RETURNS WITH NFTs @@ -678,7 +677,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> Financial Times {" "} |{" "} @@ -786,7 +785,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> PUNK6529 LAUNCHES THE FIRST DISTRICT OF OPEN METAVERSE @@ -825,7 +824,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> coinpaper.com {" "} |{" "} @@ -933,7 +932,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> WEEKLY WRAP: WHAT'S HAPPENING IN THE METAVERSE THIS WEEK? @@ -964,7 +963,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> CoinMarketCap.com {" "} |{" "} @@ -1072,7 +1071,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> PUNK 6529 UNVEILS METAVERSE MUSEUM DISTRICT: “MOST HIGH-END ART NFTS EVER” @@ -1105,7 +1104,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> CoinTelegraph.com {" "} |{" "} @@ -1213,7 +1212,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> BITCOIN GOES TO WAR @@ -1246,7 +1245,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> NewRepublic.com {" "} |{" "} @@ -1354,7 +1353,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> 15 BIGGEST NFT INFLUENCERS OF 2021 @@ -1387,7 +1386,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> Decrypt.co {" "} |{" "} @@ -1495,7 +1494,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> A NORMIE'S GUIDE TO BECOMING A CRYPTO PERSON @@ -1526,7 +1525,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> NYMag.com {" "} |{" "} @@ -1634,7 +1633,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> WHY A CRYPTOPUNKS OWNER TURNED DOWN $9.5M IN ETHEREUM FOR HIS NFT @@ -1662,7 +1661,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> Decrypt.co {" "} |{" "} @@ -1770,7 +1769,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> CRYPTOPUNK WHALE BOUGHT $1.67M CHROMIE SQUIGGLE @@ -1798,7 +1797,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> DappRadar.com {" "} |{" "} @@ -1906,7 +1905,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> A DICTIONARY FOR DEGENS @@ -1939,7 +1938,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps + rel="noopener noreferrer"> Coindesk.com {" "} |{" "} @@ -1980,6 +1979,7 @@ Known for his philosophical Twitter threads and love of decentralization, the ps diff --git a/app/about/rules/page.tsx b/app/about/rules/page.tsx index 296905c7c8..4bcbfebfeb 100644 --- a/app/about/rules/page.tsx +++ b/app/about/rules/page.tsx @@ -6,7 +6,6 @@ export default function AboutRulesPage() {
- diff --git a/app/author/6529er6529-io/page.tsx b/app/author/6529er6529-io/page.tsx index 7d74c63c9a..80331a4b42 100644 --- a/app/author/6529er6529-io/page.tsx +++ b/app/author/6529er6529-io/page.tsx @@ -1,14 +1,11 @@ import { getAppMetadata } from "@/components/providers/metadata"; import { Metadata } from "next"; - - export default function Author6529er6529IoPage() { return (
- {" "} {/* #boxed-wrapper */} - +
@@ -446,17 +448,16 @@ export default function Author6529er6529IoPage() {
- +
-); + ); } - export async function generateMetadata(): Promise { return getAppMetadata({ title: "6529er, Author at 6529.io" }); } diff --git a/app/author/ladysabrina/page.tsx b/app/author/ladysabrina/page.tsx index 5598cc98dc..28e52011c2 100644 --- a/app/author/ladysabrina/page.tsx +++ b/app/author/ladysabrina/page.tsx @@ -1,14 +1,11 @@ import { getAppMetadata } from "@/components/providers/metadata"; import { Metadata } from "next"; - - export default function AuthorLadysabrinaPage() { return (
- + target="_blank" + rel="noopener noreferrer"> Disney and DeeKay: Their Secret to Animation
@@ -347,7 +345,8 @@ export default function AuthorLadysabrinaPage() { + target="_blank" + rel="noopener noreferrer"> Disney and DeeKay: Their Secret to Animation{" "} @@ -360,6 +359,7 @@ export default function AuthorLadysabrinaPage() { className="fusion-link-wrapper" href="/blog/disney-deekay-their-secret-to-animation/" target="_blank" + rel="noopener noreferrer" aria-label="Disney and DeeKay: Their Secret to Animation" />
@@ -444,7 +444,8 @@ export default function AuthorLadysabrinaPage() { + target="_blank" + rel="noopener noreferrer"> FROM FIBONACCI TO FIDENZA
@@ -461,7 +462,8 @@ export default function AuthorLadysabrinaPage() { + target="_blank" + rel="noopener noreferrer"> FROM FIBONACCI TO FIDENZA{" "} @@ -474,6 +476,7 @@ export default function AuthorLadysabrinaPage() { className="fusion-link-wrapper" href="/blog/from-fibonacci-to-fidenza/" target="_blank" + rel="noopener noreferrer" aria-label="FROM FIBONACCI TO FIDENZA" />
@@ -559,7 +562,8 @@ export default function AuthorLadysabrinaPage() { + target="_blank" + rel="noopener noreferrer"> A Tale of Two Artists – Van Gogh and XCOPY
@@ -576,7 +580,8 @@ export default function AuthorLadysabrinaPage() { + target="_blank" + rel="noopener noreferrer"> A Tale of Two Artists – Van Gogh and XCOPY{" "} @@ -589,6 +594,7 @@ export default function AuthorLadysabrinaPage() { className="fusion-link-wrapper" href="/blog/a-tale-of-two-artists/" target="_blank" + rel="noopener noreferrer" aria-label="A Tale of Two Artists – Van Gogh and XCOPY" />
@@ -657,9 +663,13 @@ export default function AuthorLadysabrinaPage() { {/* wrapper */}
{" "} {/* #boxed-wrapper */} - +
@@ -679,17 +690,16 @@ export default function AuthorLadysabrinaPage() {
- +
-); + ); } - export async function generateMetadata(): Promise { return getAppMetadata({ title: "Sabrina Khan, Author at 6529.io" }); } diff --git a/app/author/nft6529/page.tsx b/app/author/nft6529/page.tsx index d1ad05611e..33538784c2 100644 --- a/app/author/nft6529/page.tsx +++ b/app/author/nft6529/page.tsx @@ -1,14 +1,11 @@ import { getAppMetadata } from "@/components/providers/metadata"; import { Metadata } from "next"; - - export default function AuthorNft6529Page() { return (
- {/* This site is optimized with the Yoast SEO plugin v23.9 - https://yoast.com/wordpress/plugins/seo/ */} nft6529, Author at 6529.io @@ -301,9 +298,13 @@ export default function AuthorNft6529Page() { {/* wrapper */}
{" "} {/* #boxed-wrapper */} - +
@@ -323,17 +325,16 @@ export default function AuthorNft6529Page() {
- +
-); + ); } - export async function generateMetadata(): Promise { return getAppMetadata({ title: "nft6529, Author at 6529.io" }); } diff --git a/app/author/teexels/page.tsx b/app/author/teexels/page.tsx index 1d9fd59de0..755ed6581f 100644 --- a/app/author/teexels/page.tsx +++ b/app/author/teexels/page.tsx @@ -5,7 +5,6 @@ export default function AuthorTeexelsPage() {
- {/* This site is optimized with the Yoast SEO plugin v23.9 - https://yoast.com/wordpress/plugins/seo/ */} Teexels, Author at 6529.io @@ -298,9 +297,13 @@ export default function AuthorTeexelsPage() { {/* wrapper */}
{" "} {/* #boxed-wrapper */} - +
@@ -320,19 +324,16 @@ export default function AuthorTeexelsPage() {
- +
-); - - + ); } export async function generateMetadata(): Promise { return getAppMetadata({ title: "Teexels, Author at 6529.io" }); } - diff --git a/app/blog/a-tale-of-two-artists/page.tsx b/app/blog/a-tale-of-two-artists/page.tsx index 691f8e95d7..8789e24fed 100644 --- a/app/blog/a-tale-of-two-artists/page.tsx +++ b/app/blog/a-tale-of-two-artists/page.tsx @@ -5,7 +5,6 @@ export default function BlogATaleOfTwoArtistsPage() {
- + rel="noopener noreferrer"> Sabrina Khan @@ -468,7 +467,7 @@ So, it is startling to imagine that" + rel="noopener noreferrer"> XCOPY , who, like Van Gogh, is highly prolific, @@ -634,7 +633,7 @@ So, it is startling to imagine that" + rel="noopener noreferrer"> letter to his brother {" "} Van Gogh wrote almost prophetically: @@ -933,7 +932,7 @@ So, it is startling to imagine that" + rel="noopener noreferrer"> “Today I sold my first gif for £1. Nothing can stop us tumblr.” @@ -996,7 +995,7 @@ So, it is startling to imagine that" + rel="noopener noreferrer"> “Day 3 trying to give away crypto art on tumblr.”  @@ -1013,7 +1012,7 @@ So, it is startling to imagine that" + rel="noopener noreferrer"> 30% off coupon codes {" "} to try to sell his pieces and gather as many @@ -1041,7 +1040,7 @@ So, it is startling to imagine that" + rel="noopener noreferrer"> gave away {" "} so many of his pieces that may have been @@ -1249,7 +1248,7 @@ So, it is startling to imagine that" + rel="noopener noreferrer"> XCOPY {" "} for answering my questions about his work and @@ -1257,14 +1256,14 @@ So, it is startling to imagine that" + rel="noopener noreferrer"> Fred Wilson {" "} and{" "} + rel="noopener noreferrer"> punk6529 {" "} for their helpful comments and @@ -1301,9 +1300,13 @@ So, it is startling to imagine that" {/* wrapper */}
{" "} {/* #boxed-wrapper */} - +
@@ -1323,18 +1327,18 @@ So, it is startling to imagine that"
- +
-); + ); } - export async function generateMetadata(): Promise { - return getAppMetadata({ title: "A Tale of Two Artists - Van Gogh and XCOPY - 6529.io" }); + return getAppMetadata({ + title: "A Tale of Two Artists - Van Gogh and XCOPY - 6529.io", + }); } - diff --git a/app/blog/disney-deekay-their-secret-to-animation/page.tsx b/app/blog/disney-deekay-their-secret-to-animation/page.tsx index 83a53e3f14..a0d91e725e 100644 --- a/app/blog/disney-deekay-their-secret-to-animation/page.tsx +++ b/app/blog/disney-deekay-their-secret-to-animation/page.tsx @@ -5,7 +5,6 @@ export default function BlogDisneyDeekayTheirSecretToAnimationPage() {
- + rel="noopener noreferrer"> Sabrina Khan @@ -716,7 +715,7 @@ These are the legendary words of DeeKay Kwon, a South Korean artist" + rel="noopener noreferrer"> I♡NY

@@ -948,7 +947,7 @@ These are the legendary words of DeeKay Kwon, a South Korean artist" + rel="noopener noreferrer"> Destiny ”, experiencing the wonder of living and aging @@ -957,18 +956,19 @@ These are the legendary words of DeeKay Kwon, a South Korean artist" - Designer & Client - ”, spreading infectious joy in “Happy Virus”, + rel="noopener noreferrer"> + Designer & Client{""} + + ”, spreading infectious joy in “Happy Virus”, or the existence of one's angel and devil sides in “ + rel="noopener noreferrer"> Yin Yang + {""} .” These pieces have the same power to endure in a way that is timeless and moving.  @@ -1003,7 +1003,7 @@ These are the legendary words of DeeKay Kwon, a South Korean artist" + rel="noopener noreferrer"> Happy Virus

@@ -1037,7 +1037,7 @@ These are the legendary words of DeeKay Kwon, a South Korean artist" + rel="noopener noreferrer"> Life and Death

@@ -1117,9 +1117,13 @@ These are the legendary words of DeeKay Kwon, a South Korean artist" {/* wrapper */}
{" "} {/* #boxed-wrapper */} - +
@@ -1139,18 +1144,18 @@ These are the legendary words of DeeKay Kwon, a South Korean artist"
- +
-); + ); } - export async function generateMetadata(): Promise { - return getAppMetadata({ title: "Disney and DeeKay: Their Secret to Animation - 6529.io" }); + return getAppMetadata({ + title: "Disney and DeeKay: Their Secret to Animation - 6529.io", + }); } - diff --git a/app/blog/from-fibonacci-to-fidenza/page.tsx b/app/blog/from-fibonacci-to-fidenza/page.tsx index 36d76bd6b8..0f29365015 100644 --- a/app/blog/from-fibonacci-to-fidenza/page.tsx +++ b/app/blog/from-fibonacci-to-fidenza/page.tsx @@ -5,7 +5,6 @@ export default function BlogFromFibonacciToFidenzaPage() {
- + rel="noopener noreferrer"> Sabrina Khan @@ -450,14 +449,14 @@ There is an autonomous artist among us, a faceless, soulless creature, who, at t + rel="noopener noreferrer"> Tyler Hobbs , who dazzled the art scene with his{" "} + rel="noopener noreferrer"> Fidenza {" "} collection, we are left in the afterglow with @@ -542,7 +541,7 @@ There is an autonomous artist among us, a faceless, soulless creature, who, at t + rel="noopener noreferrer"> sunflower .{" "} @@ -582,7 +581,7 @@ There is an autonomous artist among us, a faceless, soulless creature, who, at t + rel="noopener noreferrer"> source ) @@ -620,7 +619,7 @@ There is an autonomous artist among us, a faceless, soulless creature, who, at t + rel="noopener noreferrer"> source ) @@ -731,7 +730,7 @@ There is an autonomous artist among us, a faceless, soulless creature, who, at t + rel="noopener noreferrer"> source)

@@ -996,7 +995,7 @@ There is an autonomous artist among us, a faceless, soulless creature, who, at t + rel="noopener noreferrer"> p @@ -1263,7 +1262,7 @@ There is an autonomous artist among us, a faceless, soulless creature, who, at t + rel="noopener noreferrer"> A Tale of Two Artists: Van Gogh and XCOPY @@ -1301,9 +1300,13 @@ There is an autonomous artist among us, a faceless, soulless creature, who, at t {/* wrapper */}
{" "} {/* #boxed-wrapper */} - +
@@ -1323,18 +1327,16 @@ There is an autonomous artist among us, a faceless, soulless creature, who, at t
- +
-); + ); } - export async function generateMetadata(): Promise { return getAppMetadata({ title: "FROM FIBONACCI TO FIDENZA - 6529.io" }); } - diff --git a/app/capital/company-portfolio/page.tsx b/app/capital/company-portfolio/page.tsx index b5c70a5a5a..baeccf3818 100644 --- a/app/capital/company-portfolio/page.tsx +++ b/app/capital/company-portfolio/page.tsx @@ -6,7 +6,6 @@ export default function CapitalCompanyPortfolioPage() {
- diff --git a/app/capital/fund/page.tsx b/app/capital/fund/page.tsx index 9bfdb77c91..f329c3dc68 100644 --- a/app/capital/fund/page.tsx +++ b/app/capital/fund/page.tsx @@ -6,7 +6,6 @@ export default function CapitalFundPage() {
- + rel="noopener noreferrer"> here {"."} @@ -415,7 +414,7 @@ It is targeting approximately $25M to $50M of capital deployed each quarter in a + rel="noopener noreferrer"> 6529 NFT FUND SEASON 1 @@ -425,7 +424,7 @@ It is targeting approximately $25M to $50M of capital deployed each quarter in a + rel="noopener noreferrer"> 6529 NFT FUND SEASON 2 @@ -435,7 +434,7 @@ It is targeting approximately $25M to $50M of capital deployed each quarter in a + rel="noopener noreferrer"> LIVING ARCHITECTURE – CASA BATLLO @@ -474,6 +473,7 @@ It is targeting approximately $25M to $50M of capital deployed each quarter in a diff --git a/app/capital/page.tsx b/app/capital/page.tsx index 60ef98a66d..f8bd718b7e 100644 --- a/app/capital/page.tsx +++ b/app/capital/page.tsx @@ -6,7 +6,6 @@ export default function CapitalPage() {
- + rel="noopener noreferrer"> @punk6529

@@ -472,7 +471,7 @@ NFT investing is difficult specifically" + rel="noopener noreferrer"> @krybharat

@@ -519,7 +518,7 @@ NFT investing is difficult specifically" + rel="noopener noreferrer"> @batsoupyum

@@ -566,7 +565,7 @@ NFT investing is difficult specifically" + rel="noopener noreferrer"> @ACthecollector

@@ -613,7 +612,7 @@ NFT investing is difficult specifically" + rel="noopener noreferrer"> @phon_ro

@@ -660,7 +659,7 @@ NFT investing is difficult specifically" + rel="noopener noreferrer"> @bonafidehan

@@ -730,7 +729,7 @@ NFT investing is difficult specifically" + rel="noopener noreferrer"> @6529Guardian

@@ -777,7 +776,7 @@ NFT investing is difficult specifically" + rel="noopener noreferrer"> @itsjpower

@@ -836,6 +835,7 @@ NFT investing is difficult specifically" diff --git a/app/casabatllo/page.tsx b/app/casabatllo/page.tsx index 469f8104b2..0a970555c7 100644 --- a/app/casabatllo/page.tsx +++ b/app/casabatllo/page.tsx @@ -6,7 +6,6 @@ export default function CasaBatlloPage() {
- {" "} {/* #boxed-wrapper */} - +
@@ -391,16 +395,18 @@ export default function CasaBatlloPage() {
- +
); } export async function generateMetadata(): Promise { - return getAppMetadata({ title: "LIVING ARCHITECTURE - CASA BATLLO - 6529.io" }); + return getAppMetadata({ + title: "LIVING ARCHITECTURE - CASA BATLLO - 6529.io", + }); } diff --git a/app/category/blog/page.tsx b/app/category/blog/page.tsx index 44341013a0..f4a3fb33d8 100644 --- a/app/category/blog/page.tsx +++ b/app/category/blog/page.tsx @@ -6,7 +6,6 @@ export default function CategoryBlogPage() {
- + target="_blank" + rel="noopener noreferrer"> Disney and DeeKay: Their Secret to Animation
@@ -316,7 +316,8 @@ export default function CategoryBlogPage() { + target="_blank" + rel="noopener noreferrer"> Disney and DeeKay: Their Secret to Animation{" "} @@ -329,6 +330,7 @@ export default function CategoryBlogPage() { className="fusion-link-wrapper" href="/blog/disney-deekay-their-secret-to-animation/" target="_blank" + rel="noopener noreferrer" aria-label="Disney and DeeKay: Their Secret to Animation" />
@@ -413,7 +415,8 @@ export default function CategoryBlogPage() { + target="_blank" + rel="noopener noreferrer"> FROM FIBONACCI TO FIDENZA
@@ -430,7 +433,8 @@ export default function CategoryBlogPage() { + target="_blank" + rel="noopener noreferrer"> FROM FIBONACCI TO FIDENZA{" "} @@ -443,6 +447,7 @@ export default function CategoryBlogPage() { className="fusion-link-wrapper" href="/blog/from-fibonacci-to-fidenza/" target="_blank" + rel="noopener noreferrer" aria-label="FROM FIBONACCI TO FIDENZA" />
@@ -528,7 +533,8 @@ export default function CategoryBlogPage() { + target="_blank" + rel="noopener noreferrer"> A Tale of Two Artists – Van Gogh and XCOPY
@@ -545,7 +551,8 @@ export default function CategoryBlogPage() { + target="_blank" + rel="noopener noreferrer"> A Tale of Two Artists – Van Gogh and XCOPY{" "} @@ -558,6 +565,7 @@ export default function CategoryBlogPage() { className="fusion-link-wrapper" href="/blog/a-tale-of-two-artists/" target="_blank" + rel="noopener noreferrer" aria-label="A Tale of Two Artists – Van Gogh and XCOPY" />
@@ -626,9 +634,13 @@ export default function CategoryBlogPage() { {/* wrapper */}
{" "} {/* #boxed-wrapper */} - +
@@ -648,11 +661,11 @@ export default function CategoryBlogPage() {
- +
); diff --git a/app/category/news/page.tsx b/app/category/news/page.tsx index 0a0eba1c80..2e754f0ca4 100644 --- a/app/category/news/page.tsx +++ b/app/category/news/page.tsx @@ -6,7 +6,6 @@ export default function CategoryNewsPage() {
- {" "} {/* #boxed-wrapper */} - +
@@ -416,11 +420,11 @@ export default function CategoryNewsPage() {
- +
); diff --git a/app/cdn-cgi/l/email-protection/page.tsx b/app/cdn-cgi/l/email-protection/page.tsx index 33a1c2bd9e..e7dd8692cb 100644 --- a/app/cdn-cgi/l/email-protection/page.tsx +++ b/app/cdn-cgi/l/email-protection/page.tsx @@ -14,7 +14,7 @@ export default function EmailProtectionPage() { - + - - + - + diff --git a/app/education/page.tsx b/app/education/page.tsx index 8ed6d2f45f..654f50ac26 100644 --- a/app/education/page.tsx +++ b/app/education/page.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { getAppMetadata } from "@/components/providers/metadata"; import type { Metadata } from "next"; @@ -7,7 +6,6 @@ export default function EducationPage() {
- diff --git a/app/education/podcasts/page.tsx b/app/education/podcasts/page.tsx index a0e4e7d80e..cdaa93bdcf 100644 --- a/app/education/podcasts/page.tsx +++ b/app/education/podcasts/page.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { getAppMetadata } from "@/components/providers/metadata"; import type { Metadata } from "next"; @@ -7,7 +6,6 @@ export default function PodcastsPage() {
- + rel="noopener noreferrer"> THE PSEUDONYMOUS PHILOSOPHER: PUNK 6529'S VISION FOR OUR DECENTRALIZED FUTURE @@ -415,7 +413,7 @@ This is without question the Real Vision" + rel="noopener noreferrer"> Coindesk

@@ -518,7 +516,7 @@ This is without question the Real Vision" + rel="noopener noreferrer"> THE WORLD ACCORDING TO PUNK 6529 @@ -549,7 +547,7 @@ This is without question the Real Vision" + rel="noopener noreferrer"> RealVision.com

@@ -651,7 +649,7 @@ This is without question the Real Vision" + rel="noopener noreferrer"> PUNK6529 ON THE SIGNIFICANCE OF BORED APE YACHT CLUB AND CRYPTOPUNKS @@ -693,7 +691,7 @@ This is without question the Real Vision" + rel="noopener noreferrer"> UnchainedPodcast.com {" "} |{" "} @@ -764,6 +762,7 @@ This is without question the Real Vision" diff --git a/app/education/tweetstorms/page.tsx b/app/education/tweetstorms/page.tsx index 1cae1d4fbf..8772e39647 100644 --- a/app/education/tweetstorms/page.tsx +++ b/app/education/tweetstorms/page.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { getAppMetadata } from "@/components/providers/metadata"; import type { Metadata } from "next"; @@ -7,7 +6,6 @@ export default function TweetstormsPage() {
- + rel="noopener noreferrer"> https://twitter.com/punk6529
@@ -368,7 +366,7 @@ Current Pinned Tweet:" role="button" href="https://twitter.com/punk6529/status/1448399827054833668" target="_blank" - rel="noreferrer noopener"> + rel="noopener noreferrer"> https://twitter.com/punk6529/status/1448399827054833668

@@ -394,7 +392,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> Don't let the institutions steal your JPGs @@ -402,7 +400,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On JPGs @@ -410,7 +408,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Decentralization and NFTs @@ -418,7 +416,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Regulation and NFTs @@ -426,7 +424,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Sunshine @@ -434,7 +432,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Metaverse @@ -442,7 +440,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> Consider The Tulip @@ -450,7 +448,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On buying your first NFT @@ -458,7 +456,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Sliding into 6529's DMs @@ -466,7 +464,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On NFT Twitter @@ -474,7 +472,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On institutional traps @@ -482,7 +480,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On 6529 Fam @@ -490,7 +488,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> The 100 Million (100,000,000) Project @@ -498,7 +496,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Making It @@ -506,7 +504,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On the 1st Amendment & SEC @@ -514,7 +512,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Emerging NFT Photographers{" "} @@ -522,7 +520,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On the 6529 Squiggles Collection @@ -530,7 +528,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On NFA (Not Financial Advice) @@ -538,7 +536,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On NFT Rights Experiments @@ -546,7 +544,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On The WIP 6529 Museum Photography Collection @@ -555,7 +553,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Billionaire TradArt Collectors @@ -563,7 +561,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On NFTs 2030 @@ -571,7 +569,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On ETH Gas Prices @@ -579,7 +577,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON A PATHWAY TO AN OPEN METAVERSE @@ -587,7 +585,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On the 6529 Gradient Collection @@ -595,7 +593,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Eyes On Earth and Citizen Science @@ -603,7 +601,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On The City Experiment @@ -611,7 +609,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On What is an NFT? @@ -619,7 +617,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Real Estate @@ -627,7 +625,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On the United States and crypto @@ -635,7 +633,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On the EU and crypto @@ -643,7 +641,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Not Failing Decentralization A Second Time @@ -652,7 +650,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On a Pathway to Decentralization @@ -660,7 +658,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On gm NYC @@ -668,7 +666,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Securing Your NFTs (only 102 tweets 😉) @@ -676,7 +674,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On NFT Photography 1/1s @@ -684,7 +682,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Web 1, Web 2, Web 3 and choices (Artnet guest thread) @@ -693,7 +691,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On How To Make It In Crypto As a Normal Person @@ -702,7 +700,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Preserving A Digital Image For 100 Years @@ -711,7 +709,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On NFT Rights Delegation @@ -719,7 +717,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Corporations and Institutions in NFTs @@ -727,7 +725,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Fixing The Transfer Function On Exchanges. @@ -736,7 +734,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On why NFTs are socially optimal @@ -744,7 +742,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Why NFTs Are The Worst Way To Sell Digital Art @@ -753,7 +751,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On why freedom to transact underpins all other constitutional rights. @@ -762,7 +760,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On SWIFT @@ -770,7 +768,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Punks @@ -778,7 +776,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Safely Dropping NFTs to Your Collectors @@ -786,7 +784,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On Life Is Short @@ -794,7 +792,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> On the EU Giving Up @@ -802,7 +800,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> INTRODUCING OM @@ -810,7 +808,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON THE 6529 COF (CULTURAL OBJECT FRAMEWORK) @@ -819,7 +817,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON BEAR MARKETS @@ -827,7 +825,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON VCS IN WEB3 @@ -835,7 +833,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON THE RIGHT WAY TO WORRY @@ -843,7 +841,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON 6529 RAW @@ -851,7 +849,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON THE MEMES @@ -859,7 +857,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON FIXING NFT RIGHTS DELEGATION @@ -867,7 +865,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> SOME THINGS I AM LEARNING MINTING NFTS (AN ONGOING THREAD) @@ -876,7 +874,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON ONE YEAR OF 6529 @@ -884,7 +882,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON ROYALTIES @@ -892,7 +890,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON SOLVING PROBLEMS ON-CHAIN @@ -900,7 +898,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON BEING EFFICIENT WITH BUSY PEOPLE @@ -908,7 +906,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON IS MONEY JUST A GAME? @@ -916,7 +914,7 @@ Current Pinned Tweet:" + rel="noopener noreferrer"> ON HOW TO EFFECTIVELY END CRYPTO (FOR DECADES) @@ -956,6 +954,7 @@ Current Pinned Tweet:" diff --git a/app/email-signatures/page.tsx b/app/email-signatures/page.tsx index dd088bfa82..2a9818a303 100644 --- a/app/email-signatures/page.tsx +++ b/app/email-signatures/page.tsx @@ -6,7 +6,6 @@ export default function EmailSignaturesPage() {
- diff --git a/app/emma/page.tsx b/app/emma/page.tsx index d3bb416ec0..a61e6eb864 100644 --- a/app/emma/page.tsx +++ b/app/emma/page.tsx @@ -24,7 +24,7 @@ export default function DistributionPlanTool() { className="tw-font-semibold hover:tw-text-neutral-300 tw-transition tw-duration-300 tw-ease-out" href="https://github.com/6529-Collections/Janus" target="_blank" - rel="noreferrer"> + rel="noopener noreferrer"> Janus {""}, a domain-specific language for deeply customized diff --git a/app/layout.tsx b/app/layout.tsx index 791cbfee78..34cb8e2fe5 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -21,10 +21,10 @@ import ErrorPage from "./error-page"; export const metadata = getAppMetadata(); export const viewport: Viewport = { width: "device-width", - initialScale: 1, - maximumScale: 1, - userScalable: false, viewportFit: "cover", + userScalable: true, + initialScale: 1, + maximumScale: 10, }; export default function RootLayout({ diff --git a/app/museum/1-of-1-art/page.tsx b/app/museum/1-of-1-art/page.tsx index 5fd3dbf2cf..e3cafb94ae 100644 --- a/app/museum/1-of-1-art/page.tsx +++ b/app/museum/1-of-1-art/page.tsx @@ -1,330 +1,329 @@ import { getAppMetadata } from "@/components/providers/metadata"; const IndexPage = () => (
- - - - - {/* This site is optimized with the Yoast SEO plugin v23.9 - https://yoast.com/wordpress/plugins/seo/ */} - 1 OF 1 ART - 6529.io - - - - - - - - - {/* / Yoast SEO plugin. */} - - - - - - - - - - - - - - -