diff --git a/package.json b/package.json
index ffdabe09bc611d..6113aa3b5ebe79 100644
--- a/package.json
+++ b/package.json
@@ -97,7 +97,7 @@
"prettier": "2.8.8",
"prettier-plugin-hermes-parser": "0.25.1",
"react": "19.0.0",
- "react-test-renderer": "19.0.0",
+
"rimraf": "^3.0.2",
"shelljs": "^0.8.5",
"signedsource": "^1.0.0",
diff --git a/packages/react-native/Libraries/Blob/URL.js b/packages/react-native/Libraries/Blob/URL.js
index db4bb2792a1e3a..24b06cf551bb49 100644
--- a/packages/react-native/Libraries/Blob/URL.js
+++ b/packages/react-native/Libraries/Blob/URL.js
@@ -9,29 +9,27 @@
*/
import type Blob from './Blob';
-
import NativeBlobModule from './NativeBlobModule';
let BLOB_URL_PREFIX = null;
+// Initialize BLOB_URL_PREFIX if NativeBlobModule is available and properly configured
if (
NativeBlobModule &&
typeof NativeBlobModule.getConstants().BLOB_URI_SCHEME === 'string'
) {
const constants = NativeBlobModule.getConstants();
- // $FlowFixMe[incompatible-type] asserted above
- // $FlowFixMe[unsafe-addition]
BLOB_URL_PREFIX = constants.BLOB_URI_SCHEME + ':';
if (typeof constants.BLOB_URI_HOST === 'string') {
- BLOB_URL_PREFIX += `//${constants.BLOB_URI_HOST}/`;
+ BLOB_URL_PREFIX += //${constants.BLOB_URI_HOST}/;
}
}
/*
- * To allow Blobs be accessed via `content://` URIs,
- * you need to register `BlobProvider` as a ContentProvider in your app's `AndroidManifest.xml`:
+ * To allow Blobs be accessed via content:// URIs,
+ * you need to register BlobProvider as a ContentProvider in your app's AndroidManifest.xml:
*
- * ```xml
+ * xml
*
*
*
*
*
- * ```
- * And then define the `blob_provider_authority` string in `res/values/strings.xml`.
+ *
+ * And then define the blob_provider_authority string in res/values/strings.xml.
* Use a dotted name that's entirely unique to your app:
*
- * ```xml
+ * xml
*
* your.app.package.blobs
*
- * ```
+ *
*/
export {URLSearchParams} from './URLSearchParams';
-function validateBaseUrl(url: string) {
+// Validate the base URL with a regular expression
+function validateBaseUrl(url: string): boolean {
// from this MIT-licensed gist: https://gist.github.com/dperini/729294
- return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test(
+ return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S)?$/.test(
url,
);
}
@@ -64,21 +63,26 @@ function validateBaseUrl(url: string) {
export class URL {
_url: string;
_searchParamsInstance: ?URLSearchParams = null;
+ _urlObject: URL;
static createObjectURL(blob: Blob): string {
if (BLOB_URL_PREFIX === null) {
- throw new Error('Cannot create URL for blob!');
+ throw new Error('Cannot create URL for blob! Ensure NativeBlobModule is properly configured.');
+ }
+ if (!blob || !blob.data || !blob.data.blobId) {
+ throw new Error('Invalid blob data: Missing blobId or data.');
}
- return `${BLOB_URL_PREFIX}${blob.data.blobId}?offset=${blob.data.offset}&size=${blob.size}`;
+ return ${BLOB_URL_PREFIX}${blob.data.blobId}?offset=${blob.data.offset}&size=${blob.size};
}
static revokeObjectURL(url: string) {
- // Do nothing.
+ // Do nothing, no implementation needed for revoking Blob URLs in this case.
}
- // $FlowFixMe[missing-local-annot]
constructor(url: string, base: string | URL) {
let baseUrl = null;
+
+ // Validate URL format and handle base URL correctly
if (!base || validateBaseUrl(url)) {
this._url = url;
if (!this._url.endsWith('/')) {
@@ -88,34 +92,39 @@ export class URL {
if (typeof base === 'string') {
baseUrl = base;
if (!validateBaseUrl(baseUrl)) {
- throw new TypeError(`Invalid base URL: ${baseUrl}`);
+ throw new TypeError(Invalid base URL: ${baseUrl});
}
} else {
baseUrl = base.toString();
}
+
+ // Ensure correct URL formatting
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
}
if (!url.startsWith('/')) {
- url = `/${url}`;
+ url = /${url};
}
if (baseUrl.endsWith(url)) {
url = '';
}
- this._url = `${baseUrl}${url}`;
+ this._url = ${baseUrl}${url};
}
+
+ // Create a URL object for easier parsing (browser-like behavior)
+ this._urlObject = new globalThis.URL(this._url);
}
get hash(): string {
- throw new Error('URL.hash is not implemented');
+ return this._urlObject.hash || ''; // Extract the fragment part
}
get host(): string {
- throw new Error('URL.host is not implemented');
+ return this._urlObject.host || ''; // Extracts the host and port (if present)
}
get hostname(): string {
- throw new Error('URL.hostname is not implemented');
+ return this._urlObject.hostname || ''; // Extracts just the hostname (without port)
}
get href(): string {
@@ -123,32 +132,33 @@ export class URL {
}
get origin(): string {
- throw new Error('URL.origin is not implemented');
+ return this._urlObject.origin || ''; // Extracts the origin (protocol + hostname + port)
}
get password(): string {
- throw new Error('URL.password is not implemented');
+ const match = this._urlObject.href.match(/^.:\/\/(.):(.*)@/);
+ return match && match[2] ? match[2] : ''; // Extract password from "username:password" part
}
get pathname(): string {
- throw new Error('URL.pathname not implemented');
+ return this._urlObject.pathname || ''; // Extracts the pathname (e.g., "/path/to/resource")
}
get port(): string {
- throw new Error('URL.port is not implemented');
+ return this._urlObject.port || ''; // Extracts the port part
}
get protocol(): string {
- throw new Error('URL.protocol is not implemented');
+ return this._urlObject.protocol || ''; // Extracts the protocol (e.g., "http:" or "https:")
}
get search(): string {
- throw new Error('URL.search is not implemented');
+ return this._urlObject.search || ''; // Extracts the query string (e.g., "?id=123")
}
get searchParams(): URLSearchParams {
if (this._searchParamsInstance == null) {
- this._searchParamsInstance = new URLSearchParams();
+ this._searchParamsInstance = new URLSearchParams(this._urlObject.search);
}
return this._searchParamsInstance;
}
@@ -161,13 +171,13 @@ export class URL {
if (this._searchParamsInstance === null) {
return this._url;
}
- // $FlowFixMe[incompatible-use]
const instanceString = this._searchParamsInstance.toString();
const separator = this._url.indexOf('?') > -1 ? '&' : '?';
return this._url + separator + instanceString;
}
get username(): string {
- throw new Error('URL.username is not implemented');
+ const match = this._urlObject.href.match(/^.:\/\/(.?)(?::(.*))?@/);
+ return match && match[1] ? match[1] : ''; // Extract username from "username:password" part
}
-}
+}
\ No newline at end of file
diff --git a/packages/react-native/Libraries/LogBox/__tests__/LogBox-integration-test.js b/packages/react-native/Libraries/LogBox/__tests__/LogBox-integration-test.js
index 1b39b40ddc10d5..24aca736c3adf3 100644
--- a/packages/react-native/Libraries/LogBox/__tests__/LogBox-integration-test.js
+++ b/packages/react-native/Libraries/LogBox/__tests__/LogBox-integration-test.js
@@ -18,7 +18,7 @@ import * as React from 'react';
const ExceptionsManager = require('../../Core/ExceptionsManager.js');
const LogBoxData = require('../Data/LogBoxData');
-const TestRenderer = require('react-test-renderer');
+import { create } from 'react-native-fantom';
const installLogBox = () => {
const LogBox = require('../LogBox').default;
@@ -41,10 +41,8 @@ describe('LogBox', () => {
jest.resetModules();
jest.restoreAllMocks();
jest.spyOn(console, 'error').mockImplementation(() => {});
-
mockError.mockClear();
mockWarn.mockClear();
- // Reset ExceptionManager patching.
if (console._errorOriginal) {
console._errorOriginal = null;
}
@@ -65,16 +63,14 @@ describe('LogBox', () => {
// as it is.
it.skip('integrates with React and handles a key error in LogBox', () => {
const spy = jest.spyOn(LogBoxData, 'addLog');
- installLogBox();
+
// Spy console.error after LogBox is installed
// so we can assert on what React logs.
jest.spyOn(console, 'error');
-
+ installLogBox();
let output;
- TestRenderer.act(() => {
- output = TestRenderer.create();
- });
+ create();
// The key error should always be the highest severity.
// In LogBox, we expect these errors to:
@@ -126,16 +122,14 @@ describe('LogBox', () => {
// as it is.
it.skip('integrates with React and handles a fragment warning in LogBox', () => {
const spy = jest.spyOn(LogBoxData, 'addLog');
- installLogBox();
+
// Spy console.error after LogBox is installed
// so we can assert on what React logs.
jest.spyOn(console, 'error');
-
+ installLogBox();
let output;
- TestRenderer.act(() => {
- output = TestRenderer.create();
- });
+ create();
// The fragment warning is not as severe. For this warning we don't want to
// pop open a dialog, so we show a collapsed error UI.
@@ -188,9 +182,7 @@ describe('LogBox', () => {
jest.spyOn(console, 'error');
let output;
- TestRenderer.act(() => {
- output = TestRenderer.create();
- });
+ create();
// Manual console errors should show a collapsed error dialog.
// When there is no component stack, we expect these errors to:
@@ -230,9 +222,8 @@ describe('LogBox', () => {
jest.spyOn(console, 'error');
let output;
- TestRenderer.act(() => {
- output = TestRenderer.create();
- });
+ create();
+
// Manual console errors should show a collapsed error dialog.
// When there is a component stack, we expect these errors to: