thirdweb 5.80.0 → 5.80.1-nightly-ce3e850fdbf34911e20919ecc2674e4a63f08fa3-20241225000333

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/cjs/adapters/ethers6.js +1 -1
  2. package/dist/cjs/contract/deployment/utils/bootstrap.js +29 -11
  3. package/dist/cjs/contract/deployment/utils/bootstrap.js.map +1 -1
  4. package/dist/cjs/contract/deployment/zksync/implementations.js +27 -0
  5. package/dist/cjs/contract/deployment/zksync/implementations.js.map +1 -0
  6. package/dist/cjs/extensions/prebuilts/get-required-transactions.js +5 -2
  7. package/dist/cjs/extensions/prebuilts/get-required-transactions.js.map +1 -1
  8. package/dist/cjs/react/core/utils/walletIcon.js +3 -0
  9. package/dist/cjs/react/core/utils/walletIcon.js.map +1 -1
  10. package/dist/cjs/react/web/ui/MediaRenderer/MediaRenderer.js +18 -8
  11. package/dist/cjs/react/web/ui/MediaRenderer/MediaRenderer.js.map +1 -1
  12. package/dist/cjs/react/web/ui/MediaRenderer/useResolvedMediaType.js +25 -0
  13. package/dist/cjs/react/web/ui/MediaRenderer/useResolvedMediaType.js.map +1 -1
  14. package/dist/cjs/react/web/ui/prebuilt/NFT/media.js +24 -12
  15. package/dist/cjs/react/web/ui/prebuilt/NFT/media.js.map +1 -1
  16. package/dist/cjs/react/web/ui/prebuilt/NFT/name.js +24 -12
  17. package/dist/cjs/react/web/ui/prebuilt/NFT/name.js.map +1 -1
  18. package/dist/cjs/version.js +1 -1
  19. package/dist/cjs/version.js.map +1 -1
  20. package/dist/cjs/wallets/manager/index.js +2 -2
  21. package/dist/cjs/wallets/manager/index.js.map +1 -1
  22. package/dist/esm/adapters/ethers6.js +1 -1
  23. package/dist/esm/contract/deployment/utils/bootstrap.js +29 -11
  24. package/dist/esm/contract/deployment/utils/bootstrap.js.map +1 -1
  25. package/dist/esm/contract/deployment/zksync/implementations.js +24 -0
  26. package/dist/esm/contract/deployment/zksync/implementations.js.map +1 -0
  27. package/dist/esm/extensions/prebuilts/get-required-transactions.js +5 -2
  28. package/dist/esm/extensions/prebuilts/get-required-transactions.js.map +1 -1
  29. package/dist/esm/react/core/utils/walletIcon.js +3 -0
  30. package/dist/esm/react/core/utils/walletIcon.js.map +1 -1
  31. package/dist/esm/react/web/ui/MediaRenderer/MediaRenderer.js +12 -3
  32. package/dist/esm/react/web/ui/MediaRenderer/MediaRenderer.js.map +1 -1
  33. package/dist/esm/react/web/ui/MediaRenderer/useResolvedMediaType.js +24 -0
  34. package/dist/esm/react/web/ui/MediaRenderer/useResolvedMediaType.js.map +1 -1
  35. package/dist/esm/react/web/ui/prebuilt/NFT/media.js +23 -12
  36. package/dist/esm/react/web/ui/prebuilt/NFT/media.js.map +1 -1
  37. package/dist/esm/react/web/ui/prebuilt/NFT/name.js +23 -12
  38. package/dist/esm/react/web/ui/prebuilt/NFT/name.js.map +1 -1
  39. package/dist/esm/version.js +1 -1
  40. package/dist/esm/version.js.map +1 -1
  41. package/dist/esm/wallets/manager/index.js +2 -2
  42. package/dist/esm/wallets/manager/index.js.map +1 -1
  43. package/dist/types/adapters/ethers6.d.ts +1 -1
  44. package/dist/types/contract/deployment/utils/bootstrap.d.ts.map +1 -1
  45. package/dist/types/contract/deployment/zksync/implementations.d.ts +3 -0
  46. package/dist/types/contract/deployment/zksync/implementations.d.ts.map +1 -0
  47. package/dist/types/extensions/prebuilts/get-required-transactions.d.ts +1 -1
  48. package/dist/types/extensions/prebuilts/get-required-transactions.d.ts.map +1 -1
  49. package/dist/types/react/core/utils/walletIcon.d.ts +3 -0
  50. package/dist/types/react/core/utils/walletIcon.d.ts.map +1 -1
  51. package/dist/types/react/web/ui/MediaRenderer/MediaRenderer.d.ts +12 -0
  52. package/dist/types/react/web/ui/MediaRenderer/MediaRenderer.d.ts.map +1 -1
  53. package/dist/types/react/web/ui/MediaRenderer/useResolvedMediaType.d.ts +8 -0
  54. package/dist/types/react/web/ui/MediaRenderer/useResolvedMediaType.d.ts.map +1 -1
  55. package/dist/types/react/web/ui/prebuilt/NFT/media.d.ts +10 -0
  56. package/dist/types/react/web/ui/prebuilt/NFT/media.d.ts.map +1 -1
  57. package/dist/types/react/web/ui/prebuilt/NFT/name.d.ts +10 -0
  58. package/dist/types/react/web/ui/prebuilt/NFT/name.d.ts.map +1 -1
  59. package/dist/types/version.d.ts +1 -1
  60. package/dist/types/version.d.ts.map +1 -1
  61. package/package.json +2 -2
  62. package/src/adapters/ethers6.ts +1 -1
  63. package/src/contract/deployment/utils/bootstrap.test.ts +44 -1
  64. package/src/contract/deployment/utils/bootstrap.ts +38 -11
  65. package/src/contract/deployment/zksync/implementations.ts +24 -0
  66. package/src/extensions/prebuilts/get-required-transactions.test.ts +14 -1
  67. package/src/extensions/prebuilts/get-required-transactions.ts +6 -2
  68. package/src/react/core/hooks/wallets/useSwitchActiveWalletChain.test.tsx +51 -0
  69. package/src/react/core/providers/connection-manager.test.tsx +11 -0
  70. package/src/react/core/utils/isSmartWallet.test.ts +19 -0
  71. package/src/react/core/utils/storage.test.ts +57 -0
  72. package/src/react/core/utils/structuralSharing.test.ts +55 -0
  73. package/src/react/core/utils/walletIcon.test.ts +81 -0
  74. package/src/react/core/utils/walletIcon.ts +3 -0
  75. package/src/react/web/ui/MediaRenderer/MediaRenderer.test.tsx +193 -2
  76. package/src/react/web/ui/MediaRenderer/MediaRenderer.tsx +12 -3
  77. package/src/react/web/ui/MediaRenderer/icons.test.tsx +80 -0
  78. package/src/react/web/ui/MediaRenderer/mime/mime.test.ts +66 -0
  79. package/src/react/web/ui/MediaRenderer/useResolvedMediaType.test.tsx +27 -0
  80. package/src/react/web/ui/MediaRenderer/useResolvedMediaType.ts +28 -0
  81. package/src/react/web/ui/hooks/useCopyClipboard.test.tsx +41 -0
  82. package/src/react/web/ui/hooks/useDebouncedValue.test.tsx +107 -0
  83. package/src/react/web/ui/hooks/useShowMore.test.tsx +83 -0
  84. package/src/react/web/ui/prebuilt/Chain/name.test.tsx +36 -28
  85. package/src/react/web/ui/prebuilt/NFT/media.test.tsx +58 -1
  86. package/src/react/web/ui/prebuilt/NFT/media.tsx +32 -13
  87. package/src/react/web/ui/prebuilt/NFT/name.test.tsx +37 -1
  88. package/src/react/web/ui/prebuilt/NFT/name.tsx +29 -13
  89. package/src/react/web/utils/resolveMimeType.test.ts +63 -0
  90. package/src/version.ts +1 -1
  91. package/src/wallets/manager/index.ts +2 -2
  92. package/src/wallets/smart/smart.test.ts +1 -1
@@ -38,6 +38,9 @@ export const genericWalletIcon =
38
38
  const passkeyIcon =
39
39
  "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzU2MzlfMzIpIj4KPHBhdGggZD0iTTcuOTk5NTkgNi42NjY1QzcuNjQ1OTYgNi42NjY1IDcuMzA2ODMgNi44MDY5OCA3LjA1Njc4IDcuMDU3MDNDNi44MDY3MyA3LjMwNzA4IDYuNjY2MjUgNy42NDYyMiA2LjY2NjI1IDcuOTk5ODRDNi42NjYyNSA4LjY3OTg0IDYuNTk5NTkgOS42NzMxNyA2LjQ5MjkyIDEwLjY2NjUiIHN0cm9rZT0iIzMzODVGRiIgc3Ryb2tlLXdpZHRoPSIxLjI1NDkiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNOS4zMzI5MyA4Ljc0NjU4QzkuMzMyOTMgMTAuMzMzMiA5LjMzMjkzIDEyLjk5OTkgOC42NjYyNiAxNC42NjY2IiBzdHJva2U9IiMzMzg1RkYiIHN0cm9rZS13aWR0aD0iMS4yNTQ5IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTExLjUyNzMgMTQuMDEzM0MxMS42MDczIDEzLjYxMzMgMTEuODE0IDEyLjQ4IDExLjg2MDcgMTIiIHN0cm9rZT0iIzMzODVGRiIgc3Ryb2tlLXdpZHRoPSIxLjI1NDkiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8cGF0aCBkPSJNMS4zMzM5OCA4LjAwMDE2QzEuMzMzOTggNi42MDA5NSAxLjc3NDIzIDUuMjM3MiAyLjU5MjM3IDQuMTAyMDlDMy40MTA1MSAyLjk2Njk5IDQuNTY1MDUgMi4xMTgwOCA1Ljg5MjQ3IDEuNjc1NjFDNy4yMTk4OCAxLjIzMzE0IDguNjUyODYgMS4yMTk1NCA5Ljk4ODQ0IDEuNjM2NzRDMTEuMzI0IDIuMDUzOTQgMTIuNDk0NSAyLjg4MDc5IDEzLjMzNCA0LjAwMDE2IiBzdHJva2U9IiMzMzg1RkYiIHN0cm9rZS13aWR0aD0iMS4yNTQ5IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEuMzMzOTggMTAuNjY2NUgxLjMzOTE0IiBzdHJva2U9IiMzMzg1RkYiIHN0cm9rZS13aWR0aD0iMS4yNTQ5IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTE0LjUzMjcgMTAuNjY2NUMxNC42NjYgOS4zMzMxNyAxNC42MiA3LjA5NzE3IDE0LjUzMjcgNi42NjY1IiBzdHJva2U9IiMzMzg1RkYiIHN0cm9rZS13aWR0aD0iMS4yNTQ5IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTMuMzMzOTggMTIuOTk5OEMzLjY2NzMyIDExLjk5OTggNC4wMDA2NSA5Ljk5OTg0IDQuMDAwNjUgNy45OTk4NEMzLjk5OTk4IDcuNTQ1NzUgNC4wNzY2MyA3LjA5NDg2IDQuMjI3MzIgNi42NjY1IiBzdHJva2U9IiMzMzg1RkYiIHN0cm9rZS13aWR0aD0iMS4yNTQ5IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTUuNzY3MDkgMTQuNjY2OEM1LjkwNzA5IDE0LjIyNjggNi4wNjcwOSAxMy43ODY4IDYuMTQ3MDkgMTMuMzMzNSIgc3Ryb2tlPSIjMzM4NUZGIiBzdHJva2Utd2lkdGg9IjEuMjU0OSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik02IDQuNTMzNDZDNi42MDgyNyA0LjE4MjI4IDcuMjk4MjggMy45OTc0NSA4LjAwMDY0IDMuOTk3NTZDOC43MDMwMSAzLjk5NzY3IDkuMzkyOTYgNC4xODI3MiAxMC4wMDExIDQuNTM0MUMxMC42MDkzIDQuODg1NDggMTEuMTE0MiA1LjM5MDc5IDExLjQ2NTEgNS45OTkyM0MxMS44MTYgNi42MDc2NiAxMi4wMDA1IDcuMjk3NzYgMTIgOC4wMDAxMlY5LjMzMzQ2IiBzdHJva2U9IiMzMzg1RkYiIHN0cm9rZS13aWR0aD0iMS4yNTQ5IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF81NjM5XzMyIj4KPHJlY3Qgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo=";
40
40
 
41
+ /**
42
+ * @internal
43
+ */
41
44
  export const socialIcons = {
42
45
  google: googleIconUri,
43
46
  apple: appleIconUri,
@@ -1,11 +1,18 @@
1
- import { describe, expect, it } from "vitest";
1
+ import React from "react";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
3
  import {
4
+ fireEvent,
3
5
  render,
4
6
  screen,
5
7
  waitFor,
6
8
  } from "../../../../../test/src/react-render.js";
7
9
  import { TEST_CLIENT } from "../../../../../test/src/test-clients.js";
8
- import { MediaRenderer } from "./MediaRenderer.js";
10
+ import {
11
+ IframePlayer,
12
+ LinkPlayer,
13
+ MediaRenderer,
14
+ mergeRefs,
15
+ } from "./MediaRenderer.js";
9
16
 
10
17
  describe("MediaRenderer", () => {
11
18
  it("should render nothing if no src provided", () => {
@@ -31,4 +38,188 @@ describe("MediaRenderer", () => {
31
38
  expect(screen.getByRole("img")).toBeInTheDocument();
32
39
  });
33
40
  });
41
+
42
+ describe("mergeRefs", () => {
43
+ it("should call all callback refs with the given value", () => {
44
+ const callbackRef1 = vi.fn();
45
+ const callbackRef2 = vi.fn();
46
+
47
+ const mergedRef = mergeRefs([callbackRef1, callbackRef2]);
48
+
49
+ const testValue = { test: "value" };
50
+ mergedRef(testValue);
51
+
52
+ expect(callbackRef1).toHaveBeenCalledWith(testValue);
53
+ expect(callbackRef2).toHaveBeenCalledWith(testValue);
54
+ });
55
+
56
+ it("should assign the value to all mutable object refs", () => {
57
+ const mutableRef1 = React.createRef();
58
+ const mutableRef2 = React.createRef();
59
+
60
+ const mergedRef = mergeRefs([mutableRef1, mutableRef2]);
61
+
62
+ const testValue = { test: "value" };
63
+ mergedRef(testValue);
64
+
65
+ expect(mutableRef1.current).toBe(testValue);
66
+ expect(mutableRef2.current).toBe(testValue);
67
+ });
68
+
69
+ it("should handle a mix of callback and mutable object refs", () => {
70
+ const callbackRef = vi.fn();
71
+ const mutableRef = React.createRef();
72
+
73
+ const mergedRef = mergeRefs([callbackRef, mutableRef]);
74
+
75
+ const testValue = { test: "value" };
76
+ mergedRef(testValue);
77
+
78
+ expect(callbackRef).toHaveBeenCalledWith(testValue);
79
+ expect(mutableRef.current).toBe(testValue);
80
+ });
81
+
82
+ it("should do nothing if refs array is empty", () => {
83
+ const mergedRef = mergeRefs([]);
84
+
85
+ expect(() => mergedRef(null)).not.toThrow();
86
+ });
87
+ });
88
+
89
+ describe("LinkPlayer", () => {
90
+ it("renders with default props", () => {
91
+ render(<LinkPlayer />);
92
+
93
+ const linkElement = screen.getByText("File");
94
+ expect(linkElement).toBeInTheDocument();
95
+ expect(linkElement.tagName).toBe("A");
96
+ expect(linkElement).toHaveAttribute("target", "_blank");
97
+ expect(linkElement).toHaveAttribute("rel", "noopener noreferrer");
98
+ });
99
+
100
+ it("renders with custom src and alt", () => {
101
+ const src = "https://example.com/file";
102
+ const alt = "Custom File Name";
103
+ render(<LinkPlayer src={src} alt={alt} />);
104
+
105
+ const linkElement = screen.getByText(alt);
106
+ expect(linkElement).toBeInTheDocument();
107
+ expect(linkElement).toHaveAttribute("href", src);
108
+ });
109
+
110
+ it("applies custom style and className", () => {
111
+ const customStyle = { backgroundColor: "red" };
112
+ const customClassName = "custom-class";
113
+ const { container } = render(
114
+ <LinkPlayer style={customStyle} className={customClassName} />,
115
+ );
116
+
117
+ const outerDiv = container.querySelector(".custom-class");
118
+ if (!outerDiv) {
119
+ throw new Error("Failed to render LinkPlayer");
120
+ }
121
+ const styles = window.getComputedStyle(outerDiv);
122
+ expect(styles.backgroundColor).toBe("red");
123
+ });
124
+
125
+ it("forwards ref to anchor element", () => {
126
+ const ref = React.createRef<HTMLAnchorElement>();
127
+ render(<LinkPlayer ref={ref} />);
128
+
129
+ expect(ref.current).toBeInstanceOf(HTMLAnchorElement);
130
+ });
131
+ });
132
+
133
+ describe("IframePlayer", () => {
134
+ beforeEach(() => {
135
+ // Reset the body before each test
136
+ document.body.innerHTML = "";
137
+ });
138
+
139
+ it("renders with default props", () => {
140
+ render(<IframePlayer />);
141
+
142
+ const iframe = screen.getByTitle("thirdweb iframe player");
143
+ expect(iframe).toBeInTheDocument();
144
+ expect(iframe.tagName).toBe("IFRAME");
145
+ expect(iframe).toHaveAttribute("sandbox", "allow-scripts");
146
+ expect(iframe).toHaveAttribute(
147
+ "allow",
148
+ "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",
149
+ );
150
+ });
151
+
152
+ it("applies custom src and alt", () => {
153
+ const src = "https://example.com/video";
154
+ const alt = "Custom Video";
155
+ render(<IframePlayer src={src} alt={alt} />);
156
+
157
+ const iframe = screen.getByTitle(alt);
158
+ expect(iframe).toBeInTheDocument();
159
+ expect(iframe).toHaveAttribute("src", src);
160
+ });
161
+
162
+ it("renders poster image when provided", () => {
163
+ const poster = "https://example.com/poster.jpg";
164
+ render(<IframePlayer poster={poster} />);
165
+
166
+ const posterImg = screen.getByRole("img");
167
+ expect(posterImg).toBeInTheDocument();
168
+ expect(posterImg).toHaveAttribute("src", poster);
169
+ });
170
+
171
+ it("toggles play state when PlayButton is clicked", async () => {
172
+ render(<IframePlayer src="https://example.com/video" />);
173
+
174
+ const playButton = screen.getByRole("button");
175
+ expect(playButton).toBeInTheDocument();
176
+
177
+ const iframe = screen.getByTitle("thirdweb iframe player");
178
+ expect(iframe).toHaveAttribute("src", "https://example.com/video");
179
+
180
+ fireEvent.click(playButton);
181
+
182
+ // After clicking, the iframe src should be undefined
183
+ expect(iframe).not.toHaveAttribute("src");
184
+
185
+ fireEvent.click(playButton);
186
+
187
+ // After clicking again, the iframe src should be restored
188
+ expect(iframe).toHaveAttribute("src", "https://example.com/video");
189
+ });
190
+
191
+ it("applies custom style", () => {
192
+ const customStyle = { backgroundColor: "red" };
193
+ const { container } = render(
194
+ <IframePlayer style={customStyle} className="iframe-test" />,
195
+ );
196
+
197
+ const element = container.querySelector(".iframe-test");
198
+ if (!element) {
199
+ throw new Error("no iframe detected");
200
+ }
201
+ expect(element).toHaveStyle("background-color: red");
202
+ });
203
+
204
+ it("forwards ref to iframe element", () => {
205
+ const ref = React.createRef<HTMLIFrameElement>();
206
+ render(<IframePlayer ref={ref} />);
207
+
208
+ expect(ref.current).toBeInstanceOf(HTMLIFrameElement);
209
+ });
210
+
211
+ it("respects requireInteraction prop", () => {
212
+ render(
213
+ <IframePlayer src="https://example.com/video" requireInteraction />,
214
+ );
215
+
216
+ const iframe = screen.getByTitle("thirdweb iframe player");
217
+ expect(iframe).not.toHaveAttribute("src");
218
+
219
+ const playButton = screen.getByRole("button");
220
+ fireEvent.click(playButton);
221
+
222
+ expect(iframe).toHaveAttribute("src", "https://example.com/video");
223
+ });
224
+ });
34
225
  });
@@ -521,7 +521,10 @@ const AudioPlayer = /* @__PURE__ */ (() =>
521
521
  );
522
522
  }))();
523
523
 
524
- const IframePlayer = /* @__PURE__ */ (() =>
524
+ /**
525
+ * @internal Exported for tests
526
+ */
527
+ export const IframePlayer = /* @__PURE__ */ (() =>
525
528
  React.forwardRef<
526
529
  HTMLIFrameElement,
527
530
  Omit<
@@ -588,7 +591,10 @@ const IframePlayer = /* @__PURE__ */ (() =>
588
591
  );
589
592
  }))();
590
593
 
591
- const LinkPlayer = /* @__PURE__ */ (() =>
594
+ /**
595
+ * @internal Exported for tests
596
+ */
597
+ export const LinkPlayer = /* @__PURE__ */ (() =>
592
598
  React.forwardRef<
593
599
  HTMLAnchorElement,
594
600
  Pick<MediaRendererProps, "src" | "alt" | "style" | "className">
@@ -640,8 +646,11 @@ const LinkPlayer = /* @__PURE__ */ (() =>
640
646
  );
641
647
  }))();
642
648
 
649
+ /**
650
+ * @internal
651
+ */
643
652
  // biome-ignore lint/suspicious/noExplicitAny: TODO: fix any
644
- function mergeRefs<T = any>(
653
+ export function mergeRefs<T = any>(
645
654
  refs: Array<React.MutableRefObject<T> | React.LegacyRef<T>>,
646
655
  ): React.RefCallback<T> {
647
656
  return (value) => {
@@ -0,0 +1,80 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render } from "~test/react-render.js";
3
+ import {
4
+ CarbonDocumentAudio,
5
+ CarbonDocumentUnknown,
6
+ CarbonPauseFilled,
7
+ CarbonPlayFilledAlt,
8
+ } from "./icons.js";
9
+
10
+ describe("MediaRenderer Icons", () => {
11
+ it("renders CarbonDocumentUnknown correctly", () => {
12
+ const { container } = render(<CarbonDocumentUnknown />);
13
+ expect(container.querySelector("svg")).toBeInTheDocument();
14
+ expect(container.querySelector("svg")).toHaveAttribute("width", "16");
15
+ expect(container.querySelector("svg")).toHaveAttribute("height", "16");
16
+ expect(container.querySelector("svg")).toHaveAttribute(
17
+ "viewBox",
18
+ "0 0 32 32",
19
+ );
20
+ expect(container.querySelector("svg")).toHaveAttribute(
21
+ "role",
22
+ "presentation",
23
+ );
24
+ });
25
+
26
+ it("renders CarbonDocumentAudio correctly", () => {
27
+ const { container } = render(<CarbonDocumentAudio />);
28
+ expect(container.querySelector("svg")).toBeInTheDocument();
29
+ expect(container.querySelector("svg")).toHaveAttribute("width", "16");
30
+ expect(container.querySelector("svg")).toHaveAttribute("height", "16");
31
+ expect(container.querySelector("svg")).toHaveAttribute(
32
+ "viewBox",
33
+ "0 0 32 32",
34
+ );
35
+ expect(container.querySelector("svg")).toHaveAttribute(
36
+ "role",
37
+ "presentation",
38
+ );
39
+ });
40
+
41
+ it("renders CarbonPauseFilled correctly", () => {
42
+ const { container } = render(<CarbonPauseFilled />);
43
+ expect(container.querySelector("svg")).toBeInTheDocument();
44
+ expect(container.querySelector("svg")).toHaveAttribute("width", "16");
45
+ expect(container.querySelector("svg")).toHaveAttribute("height", "16");
46
+ expect(container.querySelector("svg")).toHaveAttribute(
47
+ "viewBox",
48
+ "0 0 32 32",
49
+ );
50
+ expect(container.querySelector("svg")).toHaveAttribute(
51
+ "role",
52
+ "presentation",
53
+ );
54
+ });
55
+
56
+ it("renders CarbonPlayFilledAlt correctly", () => {
57
+ const { container } = render(<CarbonPlayFilledAlt />);
58
+ expect(container.querySelector("svg")).toBeInTheDocument();
59
+ expect(container.querySelector("svg")).toHaveAttribute("width", "16");
60
+ expect(container.querySelector("svg")).toHaveAttribute("height", "16");
61
+ expect(container.querySelector("svg")).toHaveAttribute(
62
+ "viewBox",
63
+ "0 0 32 32",
64
+ );
65
+ expect(container.querySelector("svg")).toHaveAttribute(
66
+ "role",
67
+ "presentation",
68
+ );
69
+ });
70
+
71
+ it("applies custom props to the svg element", () => {
72
+ const { container } = render(
73
+ <CarbonDocumentUnknown data-testid="custom-icon" />,
74
+ );
75
+ expect(container.querySelector("svg")).toHaveAttribute(
76
+ "data-testid",
77
+ "custom-icon",
78
+ );
79
+ });
80
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getMimeTypeFromUrl } from "./mime.js";
3
+ import { extensionsToMimeType } from "./types.js";
4
+
5
+ describe("getMimeTypeFromUrl", () => {
6
+ it("should return correct mime type for valid file extensions", () => {
7
+ expect(getMimeTypeFromUrl("https://example.com/image.jpg")).toBe(
8
+ "image/jpeg",
9
+ );
10
+ expect(getMimeTypeFromUrl("https://example.com/audio.mp3")).toBe(
11
+ "audio/mpeg",
12
+ );
13
+ expect(getMimeTypeFromUrl("https://example.com/video.mp4")).toBe(
14
+ "video/mp4",
15
+ );
16
+ expect(getMimeTypeFromUrl("https://example.com/document.html")).toBe(
17
+ "text/html",
18
+ );
19
+ expect(getMimeTypeFromUrl("https://example.com/model.gltf")).toBe(
20
+ "model/gltf+json",
21
+ );
22
+ });
23
+
24
+ it("should be case-insensitive for file extensions", () => {
25
+ expect(getMimeTypeFromUrl("https://example.com/IMAGE.JPG")).toBe(
26
+ "image/jpeg",
27
+ );
28
+ expect(getMimeTypeFromUrl("https://example.com/AUDIO.MP3")).toBe(
29
+ "audio/mpeg",
30
+ );
31
+ });
32
+
33
+ it("should return null for unknown file extensions", () => {
34
+ expect(getMimeTypeFromUrl("https://example.com/unknown.xyz")).toBeNull();
35
+ });
36
+
37
+ it("should handle URLs without file extensions", () => {
38
+ expect(getMimeTypeFromUrl("https://example.com/noextension")).toBeNull();
39
+ expect(getMimeTypeFromUrl("https://example.com/")).toBeNull();
40
+ });
41
+
42
+ it("should handle URLs with only a file name", () => {
43
+ expect(getMimeTypeFromUrl("image.jpg")).toBe("image/jpeg");
44
+ expect(getMimeTypeFromUrl("audio.mp3")).toBe("audio/mpeg");
45
+ });
46
+
47
+ it("should handle URLs with special characters", () => {
48
+ expect(
49
+ getMimeTypeFromUrl("https://example.com/image%20with%20spaces.png"),
50
+ ).toBe("image/png");
51
+ expect(
52
+ getMimeTypeFromUrl(
53
+ "https://example.com/file-name_with-special_chars.mp4",
54
+ ),
55
+ ).toBe("video/mp4");
56
+ });
57
+
58
+ it("should return correct mime type for all defined extensions", () => {
59
+ // biome-ignore lint/complexity/noForEach: Relax just a test
60
+ Object.entries(extensionsToMimeType).forEach(([extension, mimeType]) => {
61
+ expect(getMimeTypeFromUrl(`https://example.com/file.${extension}`)).toBe(
62
+ mimeType,
63
+ );
64
+ });
65
+ });
66
+ });
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { TEST_CLIENT } from "~test/test-clients.js";
3
+ import { resolveMediaTypeFromUri } from "./useResolvedMediaType.js";
4
+
5
+ const client = TEST_CLIENT;
6
+
7
+ describe("useResolvedMediaType", () => {
8
+ it("resolveMediaTypeFromUri should return an empty string if uri is falsy", () => {
9
+ expect(resolveMediaTypeFromUri({ client })).toBe("");
10
+ });
11
+
12
+ it("resolveMediaTypeFromUri should resolve Arweave scheme", () => {
13
+ expect(resolveMediaTypeFromUri({ client, uri: "ar://test" })).toBe(
14
+ "https://arweave.net/test",
15
+ );
16
+ });
17
+
18
+ it("resolveMediaTypeFromUri should resolve IPFS uri with custom gateway", () => {
19
+ expect(
20
+ resolveMediaTypeFromUri({
21
+ client,
22
+ uri: "ipfs://test",
23
+ gatewayUrl: "https://cf-ipfs.com/ipfs/",
24
+ }),
25
+ ).toBe("https://cf-ipfs.com/ipfs/test");
26
+ });
27
+ });
@@ -46,3 +46,31 @@ export function useResolvedMediaType(
46
46
  isFetched: resolvedMimeType.isFetched || !!mimeType,
47
47
  };
48
48
  }
49
+
50
+ /**
51
+ * @internal Exported for tests
52
+ */
53
+ export function resolveMediaTypeFromUri(props: {
54
+ uri?: string;
55
+ client: ThirdwebClient;
56
+ gatewayUrl?: string;
57
+ }) {
58
+ const { uri, client, gatewayUrl } = props;
59
+ if (!uri) {
60
+ return "";
61
+ }
62
+ if (uri.startsWith("ar://")) {
63
+ return resolveArweaveScheme({ uri, gatewayUrl });
64
+ }
65
+ if (gatewayUrl) {
66
+ return uri.replace("ipfs://", gatewayUrl);
67
+ }
68
+ try {
69
+ return resolveScheme({
70
+ client,
71
+ uri,
72
+ });
73
+ } catch {
74
+ return uri.replace("ipfs://", "https://ipfs.io/ipfs/");
75
+ }
76
+ }
@@ -0,0 +1,41 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { act, renderHook } from "~test/react-render.js";
3
+ import { useClipboard } from "./useCopyClipboard.js";
4
+
5
+ describe("useClipboard", () => {
6
+ beforeEach(() => {
7
+ vi.useFakeTimers();
8
+ });
9
+
10
+ afterEach(() => {
11
+ vi.useRealTimers();
12
+ });
13
+
14
+ it("should copy text to clipboard and set hasCopied to true", async () => {
15
+ const { result } = renderHook(() => useClipboard("Hello World"));
16
+
17
+ // Simulate copying text
18
+ await act(async () => {
19
+ await result.current.onCopy();
20
+ });
21
+
22
+ // Check if hasCopied is true after copying
23
+ expect(result.current.hasCopied).toBe(true);
24
+ });
25
+
26
+ it("should reset hasCopied to false after 1.5 seconds", async () => {
27
+ const { result } = renderHook(() => useClipboard("Hello World"));
28
+
29
+ await act(async () => {
30
+ await result.current.onCopy();
31
+ });
32
+
33
+ expect(result.current.hasCopied).toBe(true);
34
+
35
+ act(() => {
36
+ vi.advanceTimersByTime(1500);
37
+ });
38
+
39
+ expect(result.current.hasCopied).toBe(false);
40
+ });
41
+ });
@@ -0,0 +1,107 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { act, renderHook } from "~test/react-render.js";
3
+ import { useDebouncedValue } from "./useDebouncedValue.js";
4
+
5
+ describe("useDebouncedValue", () => {
6
+ beforeEach(() => {
7
+ vi.useFakeTimers();
8
+ });
9
+
10
+ afterEach(() => {
11
+ vi.useRealTimers();
12
+ });
13
+
14
+ it("should return the initial value immediately", () => {
15
+ const { result } = renderHook(() => useDebouncedValue("initial", 1000));
16
+ expect(result.current).toBe("initial");
17
+ });
18
+
19
+ it("should debounce value changes", () => {
20
+ const { result, rerender } = renderHook(
21
+ ({ value, delay }) => useDebouncedValue(value, delay),
22
+ { initialProps: { value: "initial", delay: 1000 } },
23
+ );
24
+
25
+ expect(result.current).toBe("initial");
26
+
27
+ // Change the value
28
+ rerender({ value: "changed", delay: 1000 });
29
+
30
+ // The value should not change immediately
31
+ expect(result.current).toBe("initial");
32
+
33
+ // Fast-forward time by 500ms
34
+ act(() => {
35
+ vi.advanceTimersByTime(500);
36
+ });
37
+
38
+ // The value should still not have changed
39
+ expect(result.current).toBe("initial");
40
+
41
+ // Fast-forward time to just before the delay
42
+ act(() => {
43
+ vi.advanceTimersByTime(499);
44
+ });
45
+
46
+ // The value should still not have changed
47
+ expect(result.current).toBe("initial");
48
+
49
+ // Fast-forward time to the full delay
50
+ act(() => {
51
+ vi.advanceTimersByTime(1);
52
+ });
53
+
54
+ // Now the value should have changed
55
+ expect(result.current).toBe("changed");
56
+ });
57
+
58
+ it("should handle multiple rapid changes", () => {
59
+ const { result, rerender } = renderHook(
60
+ ({ value, delay }) => useDebouncedValue(value, delay),
61
+ { initialProps: { value: "initial", delay: 1000 } },
62
+ );
63
+
64
+ rerender({ value: "change1", delay: 1000 });
65
+ rerender({ value: "change2", delay: 1000 });
66
+ rerender({ value: "change3", delay: 1000 });
67
+
68
+ expect(result.current).toBe("initial");
69
+
70
+ act(() => {
71
+ vi.advanceTimersByTime(1000);
72
+ });
73
+
74
+ expect(result.current).toBe("change3");
75
+ });
76
+
77
+ it("should respect changing delay times", () => {
78
+ const { result, rerender } = renderHook(
79
+ ({ value, delay }) => useDebouncedValue(value, delay),
80
+ { initialProps: { value: "initial", delay: 1000 } },
81
+ );
82
+
83
+ rerender({ value: "changed", delay: 500 });
84
+
85
+ act(() => {
86
+ vi.advanceTimersByTime(600);
87
+ });
88
+
89
+ expect(result.current).toBe("changed");
90
+ });
91
+
92
+ it("should cancel debounce on unmount", () => {
93
+ const { result, unmount } = renderHook(() =>
94
+ useDebouncedValue("initial", 1000),
95
+ );
96
+
97
+ expect(result.current).toBe("initial");
98
+
99
+ act(() => {
100
+ unmount();
101
+ vi.advanceTimersByTime(1000);
102
+ });
103
+
104
+ // The value should remain 'initial' as the effect should have been cleaned up
105
+ expect(result.current).toBe("initial");
106
+ });
107
+ });