mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
62
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5983edd21b | ||
|
|
bf13ecba7f | ||
|
|
5f8807acc2 | ||
|
|
026e22bb8d | ||
|
|
f1894393ca | ||
|
|
77ee24ddce | ||
|
|
327057fad5 | ||
|
|
cd71c9620c | ||
|
|
9bb53c02dd | ||
|
|
f67078df07 | ||
|
|
3b185e4bce | ||
|
|
8b2e309479 | ||
|
|
d4bf644e47 | ||
|
|
2697e8aaab | ||
|
|
c861804325 | ||
|
|
2768c84445 | ||
|
|
be6f3c6f77 | ||
|
|
54770cecc4 | ||
|
|
457190cc4b | ||
|
|
6965d57e75 | ||
|
|
bf2c3af93b | ||
|
|
a4b958099c | ||
|
|
b4164cd97c | ||
|
|
f526e91fda | ||
|
|
bb5d0df0e3 | ||
|
|
a6ef65e06b | ||
|
|
93694e3f5d | ||
|
|
96ebf5e969 | ||
|
|
7bd1254eda | ||
|
|
4b8dbe7642 | ||
|
|
99f1c30409 | ||
|
|
288f6d9f48 | ||
|
|
421a0c7077 | ||
|
|
593bdc6762 | ||
|
|
b358404f44 | ||
|
|
f66ef53962 | ||
|
|
989579c533 | ||
|
|
8e97de473e | ||
|
|
f618ca4872 | ||
|
|
b764966c9a | ||
|
|
b954fbf8b7 | ||
|
|
c8f260658d | ||
|
|
fa9b195661 | ||
|
|
3c637b09e6 | ||
|
|
5144062937 | ||
|
|
a9da64fbf7 | ||
|
|
fce9f68f29 | ||
|
|
0090333296 | ||
|
|
789fc57254 | ||
|
|
2187f653f6 | ||
|
|
743db63570 | ||
|
|
93a052c611 | ||
|
|
7d0bef2f25 | ||
|
|
aa25ad22a2 | ||
|
|
00175bd096 | ||
|
|
f806851875 | ||
|
|
aa4555eaf1 | ||
|
|
138d0eb01d | ||
|
|
4503068117 | ||
|
|
af1bcb6d44 | ||
|
|
5936f29d6a | ||
|
|
527e308a90 |
@@ -0,0 +1,897 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
FirebaseClient,
|
||||
compareResults,
|
||||
getYesterdayDate,
|
||||
getTodayDate,
|
||||
} = require('../firebaseUtils');
|
||||
|
||||
describe('FirebaseClient', () => {
|
||||
const originalFetch = global.fetch;
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = jest.fn();
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
FIREBASE_APP_EMAIL: 'test@example.com',
|
||||
FIREBASE_APP_PASS: 'testpassword',
|
||||
FIREBASE_APP_APIKEY: 'test-api-key',
|
||||
FIREBASE_APP_PROJECTNAME: 'test-project',
|
||||
};
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
process.env = originalEnv;
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with environment variables', () => {
|
||||
const client = new FirebaseClient();
|
||||
expect(client.email).toBe('test@example.com');
|
||||
expect(client.password).toBe('testpassword');
|
||||
expect(client.apiKey).toBe('test-api-key');
|
||||
expect(client.projectId).toBe('test-project');
|
||||
expect(client.databaseUrl).toBe(
|
||||
'test-project-default-rtdb.firebaseio.com',
|
||||
);
|
||||
expect(client.idToken).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('authenticate', () => {
|
||||
it('should authenticate successfully', async () => {
|
||||
const mockResponse = {
|
||||
idToken: 'mock-id-token',
|
||||
refreshToken: 'mock-refresh-token',
|
||||
};
|
||||
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResponse)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
await client.authenticate();
|
||||
|
||||
expect(client.idToken).toBe('mock-id-token');
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=test-api-key',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: 'test@example.com',
|
||||
password: 'testpassword',
|
||||
returnSecureToken: true,
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when email is missing', async () => {
|
||||
delete process.env.FIREBASE_APP_EMAIL;
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(client.authenticate()).rejects.toThrow(
|
||||
'Firebase credentials not found in environment variables',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when password is missing', async () => {
|
||||
delete process.env.FIREBASE_APP_PASS;
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(client.authenticate()).rejects.toThrow(
|
||||
'Firebase credentials not found in environment variables',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle authentication failure', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 400,
|
||||
text: jest.fn().mockResolvedValueOnce(
|
||||
JSON.stringify({
|
||||
error: {message: 'Invalid credentials'},
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(client.authenticate()).rejects.toThrow(
|
||||
'HTTP 400: Invalid credentials',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeRequest', () => {
|
||||
it('should make successful GET request', async () => {
|
||||
const mockData = {test: 'data'};
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.makeRequest('example.com', '/test', 'GET');
|
||||
|
||||
expect(result).toEqual(mockData);
|
||||
expect(global.fetch).toHaveBeenCalledWith('https://example.com/test', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should make successful POST request with data', async () => {
|
||||
const mockData = {success: true};
|
||||
const postData = {test: 'post data'};
|
||||
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.makeRequest(
|
||||
'example.com',
|
||||
'/test',
|
||||
'POST',
|
||||
postData,
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockData);
|
||||
expect(global.fetch).toHaveBeenCalledWith('https://example.com/test', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle non-JSON response', async () => {
|
||||
const textResponse = 'plain text response';
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(textResponse),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.makeRequest('example.com', '/test', 'GET');
|
||||
|
||||
expect(result).toBe(textResponse);
|
||||
});
|
||||
|
||||
it('should handle HTTP error with JSON error message', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValueOnce(
|
||||
JSON.stringify({
|
||||
error: {message: 'Not found'},
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(
|
||||
client.makeRequest('example.com', '/test', 'GET'),
|
||||
).rejects.toThrow('HTTP 404: Not found');
|
||||
});
|
||||
|
||||
it('should handle HTTP error with plain text error message', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
|
||||
await expect(
|
||||
client.makeRequest('example.com', '/test', 'GET'),
|
||||
).rejects.toThrow('HTTP 500: Internal Server Error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeDatabaseRequest', () => {
|
||||
it('should make database request with existing token', async () => {
|
||||
const mockData = {test: 'data'};
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.makeDatabaseRequest('2023-12-01', 'GET');
|
||||
|
||||
expect(result).toEqual(mockData);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://test-project-default-rtdb.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should authenticate before making request if no token exists', async () => {
|
||||
const authResponse = {idToken: 'new-token'};
|
||||
const dataResponse = {test: 'data'};
|
||||
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(authResponse)),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(dataResponse)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.makeDatabaseRequest('2023-12-01', 'GET');
|
||||
|
||||
expect(result).toEqual(dataResponse);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(client.idToken).toBe('new-token');
|
||||
});
|
||||
|
||||
it('should make PUT request with data', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce('null'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
const testData = [{library: 'test', status: 'success'}];
|
||||
|
||||
await client.makeDatabaseRequest('2023-12-01', 'PUT', testData);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://test-project-default-rtdb.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(testData),
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('storeResults', () => {
|
||||
it('should store results successfully', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce('null'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
const results = [{library: 'test', status: 'success'}];
|
||||
|
||||
await client.storeResults('2023-12-01', results);
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://test-project-default-rtdb.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(results),
|
||||
},
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Successfully stored results for 2023-12-01',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getResults', () => {
|
||||
it('should retrieve results successfully', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const results = await client.getResults('2023-12-01');
|
||||
|
||||
expect(results).toEqual(mockResults);
|
||||
});
|
||||
|
||||
it('should return null for 404 errors', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValueOnce('Not Found'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const results = await client.getResults('2023-12-01');
|
||||
|
||||
expect(results).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw error for non-404 HTTP errors', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
await expect(client.getResults('2023-12-01')).rejects.toThrow(
|
||||
'HTTP 500: Internal Server Error',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLatestResults', () => {
|
||||
it('should authenticate before making requests if no token exists', async () => {
|
||||
const authResponse = {idToken: 'new-token'};
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(authResponse)),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
const result = await client.getLatestResults('2023-12-15', 1);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-14',
|
||||
});
|
||||
expect(client.idToken).toBe('new-token');
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-14 (1 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-14 (1 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should find results from the previous day', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-14',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-14 (1 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-14 (1 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should find results from several days back', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
// Mock 404 responses for first 2 days, then success on 3rd day
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValueOnce('Not Found'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValueOnce('Not Found'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-12',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-14 (1 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-13 (2 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-12 (3 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-12 (3 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip empty results and continue searching', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
// Mock empty array for first day, then valid results on second day
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify([])),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-13',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-14 (1 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-13 (2 days back)...',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-13 (2 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return null when no results found within maxDaysBack', async () => {
|
||||
// Mock 404 responses for all days
|
||||
global.fetch.mockResolvedValue({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValue('Not Found'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 3);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: null,
|
||||
date: null,
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'No previous results found within the last 3 days',
|
||||
);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should use default maxDaysBack of 7 when not specified', async () => {
|
||||
// Mock 404 responses for all days
|
||||
global.fetch.mockResolvedValue({
|
||||
ok: false,
|
||||
status: 404,
|
||||
text: jest.fn().mockResolvedValue('Not Found'),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15');
|
||||
|
||||
expect(result).toEqual({
|
||||
results: null,
|
||||
date: null,
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'No previous results found within the last 7 days',
|
||||
);
|
||||
expect(global.fetch).toHaveBeenCalledTimes(7);
|
||||
});
|
||||
|
||||
it('should handle non-404 errors and continue searching', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
// Mock 500 error for first day, then success on second day
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
status: 500,
|
||||
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-13',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'No results found for 2023-12-14: HTTP 500: Internal Server Error',
|
||||
);
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Found results from 2023-12-13 (2 days back)',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle date boundaries correctly', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
// Test month boundary
|
||||
const result = await client.getLatestResults('2023-12-01', 1);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-11-30',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-11-30 (1 days back)...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle year boundary correctly', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
// Test year boundary
|
||||
const result = await client.getLatestResults('2024-01-01', 1);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-31',
|
||||
});
|
||||
expect(console.log).toHaveBeenCalledWith(
|
||||
'Checking for results on 2023-12-31 (1 days back)...',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle null results and continue searching', async () => {
|
||||
const mockResults = [{library: 'test', status: 'success'}];
|
||||
|
||||
// Mock null for first day, then valid results on second day
|
||||
global.fetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce('null'),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
|
||||
});
|
||||
|
||||
const client = new FirebaseClient();
|
||||
client.idToken = 'existing-token';
|
||||
|
||||
const result = await client.getLatestResults('2023-12-15', 7);
|
||||
|
||||
expect(result).toEqual({
|
||||
results: mockResults,
|
||||
date: '2023-12-13',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('compareResults', () => {
|
||||
it('should handle null previous results', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, null);
|
||||
|
||||
expect(result).toEqual({
|
||||
broken: [],
|
||||
recovered: [],
|
||||
newFailures: [{library: 'lib1', platform: 'iOS', status: 'failed'}],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle undefined previous results', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, undefined);
|
||||
|
||||
expect(result).toEqual({
|
||||
broken: [],
|
||||
recovered: [],
|
||||
newFailures: [{library: 'lib1', platform: 'iOS', status: 'failed'}],
|
||||
});
|
||||
});
|
||||
|
||||
it('should identify broken tests', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'success',
|
||||
currentStatus: 'failed',
|
||||
},
|
||||
]);
|
||||
expect(result.recovered).toEqual([]);
|
||||
});
|
||||
|
||||
it('should identify recovered tests', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([]);
|
||||
expect(result.recovered).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'failed',
|
||||
currentStatus: 'success',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should identify both broken and recovered tests', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
{library: 'lib3', platform: 'iOS', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
{library: 'lib2', platform: 'Android', status: 'failed'},
|
||||
{library: 'lib3', platform: 'iOS', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'success',
|
||||
currentStatus: 'failed',
|
||||
},
|
||||
]);
|
||||
expect(result.recovered).toEqual([
|
||||
{
|
||||
library: 'lib2',
|
||||
platform: 'Android',
|
||||
previousStatus: 'failed',
|
||||
currentStatus: 'success',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle tests that are not in previous results', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'success',
|
||||
currentStatus: 'failed',
|
||||
},
|
||||
]);
|
||||
expect(result.recovered).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty current results', () => {
|
||||
const currentResults = [];
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([]);
|
||||
expect(result.recovered).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty previous results', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'failed'},
|
||||
];
|
||||
const previousResults = [];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([]);
|
||||
expect(result.recovered).toEqual([]);
|
||||
// When previousResults is an empty array (not null/undefined),
|
||||
// the function doesn't return newFailures property
|
||||
expect(result.newFailures).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle different status values', () => {
|
||||
const currentResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'timeout'},
|
||||
{library: 'lib2', platform: 'Android', status: 'success'},
|
||||
];
|
||||
|
||||
const previousResults = [
|
||||
{library: 'lib1', platform: 'iOS', status: 'success'},
|
||||
{library: 'lib2', platform: 'Android', status: 'error'},
|
||||
];
|
||||
|
||||
const result = compareResults(currentResults, previousResults);
|
||||
|
||||
expect(result.broken).toEqual([
|
||||
{
|
||||
library: 'lib1',
|
||||
platform: 'iOS',
|
||||
previousStatus: 'success',
|
||||
currentStatus: 'timeout',
|
||||
},
|
||||
]);
|
||||
expect(result.recovered).toEqual([
|
||||
{
|
||||
library: 'lib2',
|
||||
platform: 'Android',
|
||||
previousStatus: 'error',
|
||||
currentStatus: 'success',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getYesterdayDate', () => {
|
||||
it("should return yesterday's date in YYYY-MM-DD format", () => {
|
||||
const mockDate = new Date('2023-12-15T10:30:00Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getYesterdayDate();
|
||||
|
||||
expect(result).toBe('2023-12-14');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle month boundary correctly', () => {
|
||||
const mockDate = new Date('2023-12-01T10:30:00Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getYesterdayDate();
|
||||
|
||||
expect(result).toBe('2023-11-30');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle year boundary correctly', () => {
|
||||
const mockDate = new Date('2024-01-01T10:30:00Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getYesterdayDate();
|
||||
|
||||
expect(result).toBe('2023-12-31');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTodayDate', () => {
|
||||
it("should return today's date in YYYY-MM-DD format", () => {
|
||||
const mockDate = new Date('2023-12-15T10:30:00Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getTodayDate();
|
||||
|
||||
expect(result).toBe('2023-12-15');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle different times of day correctly', () => {
|
||||
const mockDate = new Date('2023-12-15T23:59:59Z');
|
||||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
|
||||
|
||||
const result = getTodayDate();
|
||||
|
||||
expect(result).toBe('2023-12-15');
|
||||
|
||||
global.Date.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -11,8 +11,15 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const {
|
||||
prepareFailurePayload,
|
||||
prepareComparisonPayload,
|
||||
sendMessageToDiscord,
|
||||
} = require('./notifyDiscord');
|
||||
const {
|
||||
FirebaseClient,
|
||||
compareResults,
|
||||
getYesterdayDate,
|
||||
getTodayDate,
|
||||
} = require('./firebaseUtils');
|
||||
|
||||
function readOutcomes() {
|
||||
const baseDir = '/tmp';
|
||||
@@ -103,16 +110,62 @@ async function collectResults(discordWebHook) {
|
||||
const outcomes = readOutcomes();
|
||||
const failures = printFailures(outcomes);
|
||||
|
||||
// Send failure notification if there are current failures
|
||||
if (failures.length > 0) {
|
||||
if (discordWebHook) {
|
||||
console.log('Sending to discord');
|
||||
console.log('Sending current failures to Discord...');
|
||||
await notifyDiscord(discordWebHook, failures);
|
||||
} else {
|
||||
console.log('Web hook not set');
|
||||
console.log('Discord webhook not set');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('✅ All tests passed!');
|
||||
|
||||
// Initialize Firebase client
|
||||
const firebaseClient = new FirebaseClient();
|
||||
const today = getTodayDate();
|
||||
|
||||
try {
|
||||
// Store today's results in Firebase
|
||||
console.log(`Storing results for ${today} in Firebase...`);
|
||||
await firebaseClient.storeResults(today, outcomes);
|
||||
|
||||
// Get the most recent previous results for comparison
|
||||
console.log(`Looking for most recent previous results before ${today}...`);
|
||||
const {results: previousResults, date: previousDate} =
|
||||
await firebaseClient.getLatestResults(today);
|
||||
|
||||
let broken = [];
|
||||
let recovered = [];
|
||||
|
||||
if (previousResults) {
|
||||
console.log(`Comparing with results from ${previousDate}`);
|
||||
// Compare results and identify broken/recovered jobs
|
||||
const comparison = compareResults(outcomes, previousResults);
|
||||
broken = comparison.broken;
|
||||
recovered = comparison.recovered;
|
||||
|
||||
console.log(
|
||||
`Found ${broken.length} newly broken jobs and ${recovered.length} recovered jobs compared to ${previousDate}`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
'No previous results found for comparison - this might be the first run or no recent data available',
|
||||
);
|
||||
}
|
||||
|
||||
// Send comparison message to Discord if there are changes
|
||||
if (discordWebHook && (broken.length > 0 || recovered.length > 0)) {
|
||||
console.log('Sending comparison results to Discord...');
|
||||
const comparisonMessage = prepareComparisonPayload(broken, recovered);
|
||||
await sendMessageToDiscord(discordWebHook, comparisonMessage);
|
||||
}
|
||||
|
||||
console.log('✅ All tests passed!');
|
||||
} catch (error) {
|
||||
console.error('Error in collectResults:', error);
|
||||
// If Firebase fails but there are no test failures, don't fail the workflow
|
||||
console.log('⚠️ Firebase operations failed, but all tests passed');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
// We connect to firebase using a plain HTTP request because we don't want to
|
||||
// add yet another devDependency to the react-native monorepo.
|
||||
class FirebaseClient {
|
||||
constructor() {
|
||||
this.email = process.env.FIREBASE_APP_EMAIL;
|
||||
this.password = process.env.FIREBASE_APP_PASS;
|
||||
this.apiKey = process.env.FIREBASE_APP_APIKEY;
|
||||
this.projectId = process.env.FIREBASE_APP_PROJECTNAME;
|
||||
this.databaseUrl = `${this.projectId}-default-rtdb.firebaseio.com`;
|
||||
this.idToken = null;
|
||||
}
|
||||
|
||||
async authenticate() {
|
||||
if (!this.email || !this.password) {
|
||||
throw new Error(
|
||||
'Firebase credentials not found in environment variables',
|
||||
);
|
||||
}
|
||||
|
||||
const authData = {
|
||||
email: this.email,
|
||||
password: this.password,
|
||||
returnSecureToken: true,
|
||||
};
|
||||
|
||||
const response = await this.makeRequest(
|
||||
'identitytoolkit.googleapis.com',
|
||||
`/v1/accounts:signInWithPassword?key=${this.apiKey}`,
|
||||
'POST',
|
||||
authData,
|
||||
);
|
||||
|
||||
this.idToken = response.idToken;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a database request for a specific date
|
||||
* @param {string} date - Date in YYYY-MM-DD format
|
||||
* @param {string} method - HTTP method
|
||||
* @param {*} data - Data to send (optional)
|
||||
* @returns {Promise<*>} - Response data
|
||||
*/
|
||||
async makeDatabaseRequest(date, method, data = null) {
|
||||
if (!this.idToken) {
|
||||
await this.authenticate();
|
||||
}
|
||||
|
||||
const path = `/nightly-results/${date}.json?auth=${this.idToken}`;
|
||||
return this.makeRequest(this.databaseUrl, path, method, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store test results for a specific date
|
||||
* @param {string} date - Date in YYYY-MM-DD format
|
||||
* @param {Array<Object>} results - Array of test results
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async storeResults(date, results) {
|
||||
await this.makeDatabaseRequest(date, 'PUT', results);
|
||||
console.log(`Successfully stored results for ${date}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve test results for a specific date
|
||||
* @param {string} date - Date in YYYY-MM-DD format
|
||||
* @returns {Promise<Array<Object>|null>} - Array of test results or null if not found
|
||||
*/
|
||||
async getResults(date) {
|
||||
try {
|
||||
return await this.makeDatabaseRequest(date, 'GET');
|
||||
} catch (error) {
|
||||
if (error.message.includes('404')) {
|
||||
return null; // No results found for this specific date.
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the most recent available job results before the given date
|
||||
* @param {string} currentDate - Current date in YYYY-MM-DD format
|
||||
* @param {number} maxDaysBack - Maximum number of days to look back (default: 7)
|
||||
* @returns {Promise<{results: Array<Object>|null, date: string|null}>} - Most recent results and their date
|
||||
*/
|
||||
async getLatestResults(currentDate, maxDaysBack = 7) {
|
||||
if (!this.idToken) {
|
||||
await this.authenticate();
|
||||
}
|
||||
|
||||
const currentDateObj = new Date(currentDate);
|
||||
|
||||
for (let daysBack = 1; daysBack <= maxDaysBack; daysBack++) {
|
||||
const checkDate = new Date(currentDateObj);
|
||||
checkDate.setDate(checkDate.getDate() - daysBack);
|
||||
const checkDateStr = checkDate.toISOString().split('T')[0];
|
||||
|
||||
console.log(
|
||||
`Checking for results on ${checkDateStr} (${daysBack} days back)...`,
|
||||
);
|
||||
|
||||
try {
|
||||
const results = await this.getResults(checkDateStr);
|
||||
if (results && results.length > 0) {
|
||||
console.log(
|
||||
`Found results from ${checkDateStr} (${daysBack} days back)`,
|
||||
);
|
||||
return {results, date: checkDateStr};
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`No results found for ${checkDateStr}: ${error.message}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`No previous results found within the last ${maxDaysBack} days`,
|
||||
);
|
||||
return {results: null, date: null};
|
||||
}
|
||||
|
||||
async makeRequest(hostname, path, method, data = null) {
|
||||
const url = `https://${hostname}${path}`;
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
const response = await fetch(url, options);
|
||||
const responseText = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMessage;
|
||||
try {
|
||||
const parsedError = JSON.parse(responseText);
|
||||
errorMessage = parsedError.error?.message || responseText;
|
||||
} catch {
|
||||
errorMessage = responseText;
|
||||
}
|
||||
throw new Error(`HTTP ${response.status}: ${errorMessage}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(responseText);
|
||||
} catch {
|
||||
return responseText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare current results with previous day's results
|
||||
* @param {Array<Object>} currentResults - Today's test results
|
||||
* @param {Array<Object>} previousResults - Yesterday's test results
|
||||
* @returns {Object} - Object containing broken and recovered tests
|
||||
*/
|
||||
function compareResults(currentResults, previousResults) {
|
||||
if (!previousResults) {
|
||||
return {
|
||||
broken: [],
|
||||
recovered: [],
|
||||
newFailures: currentResults.filter(result => result.status !== 'success'),
|
||||
};
|
||||
}
|
||||
|
||||
// Create maps for easier lookup
|
||||
const currentMap = new Map();
|
||||
const previousMap = new Map();
|
||||
|
||||
currentResults.forEach(result => {
|
||||
const key = `${result.library}-${result.platform}`;
|
||||
currentMap.set(key, result);
|
||||
});
|
||||
|
||||
previousResults.forEach(result => {
|
||||
const key = `${result.library}-${result.platform}`;
|
||||
previousMap.set(key, result);
|
||||
});
|
||||
|
||||
const broken = [];
|
||||
const recovered = [];
|
||||
|
||||
// Check for broken tests (was success, now failed)
|
||||
for (const [key, currentResult] of currentMap) {
|
||||
const previousResult = previousMap.get(key);
|
||||
if (previousResult) {
|
||||
if (
|
||||
previousResult.status === 'success' &&
|
||||
currentResult.status !== 'success'
|
||||
) {
|
||||
broken.push({
|
||||
library: currentResult.library,
|
||||
platform: currentResult.platform,
|
||||
previousStatus: previousResult.status,
|
||||
currentStatus: currentResult.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for recovered tests (was failed, now success)
|
||||
for (const [key, currentResult] of currentMap) {
|
||||
const previousResult = previousMap.get(key);
|
||||
if (previousResult) {
|
||||
if (
|
||||
previousResult.status !== 'success' &&
|
||||
currentResult.status === 'success'
|
||||
) {
|
||||
recovered.push({
|
||||
library: currentResult.library,
|
||||
platform: currentResult.platform,
|
||||
previousStatus: previousResult.status,
|
||||
currentStatus: currentResult.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {broken, recovered};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get yesterday's date in YYYY-MM-DD format
|
||||
* @returns {string} - Yesterday's date
|
||||
*/
|
||||
function getYesterdayDate() {
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
return yesterday.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get today's date in YYYY-MM-DD format
|
||||
* @returns {string} - Today's date
|
||||
*/
|
||||
function getTodayDate() {
|
||||
const today = new Date();
|
||||
return today.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FirebaseClient,
|
||||
compareResults,
|
||||
getYesterdayDate,
|
||||
getTodayDate,
|
||||
};
|
||||
@@ -41,20 +41,12 @@ async function sendMessageToDiscord(webHook, message) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a formatted Discord message payload from a list of failures.
|
||||
* @param {Array<Object>} failures - List of failures to format
|
||||
* @returns {Object} - The formatted Discord message payload
|
||||
* Sorts jobs by platform first, then by library name.
|
||||
* @param {Array<Object>} jobs - Array of jobs with platform and library properties
|
||||
* @returns {Array<Object>} - Sorted array of jobs
|
||||
*/
|
||||
function prepareFailurePayload(failures) {
|
||||
if (!failures || failures.length === 0) {
|
||||
return {
|
||||
content:
|
||||
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nNo failures to report.',
|
||||
};
|
||||
}
|
||||
|
||||
// Sort failures by platform and then by library name
|
||||
const sortedFailures = [...failures].sort((a, b) => {
|
||||
function sortResultsByPlatformAndLibrary(jobs) {
|
||||
return [...jobs].sort((a, b) => {
|
||||
// First sort by platform
|
||||
const platformA = a.platform || 'Unknown';
|
||||
const platformB = b.platform || 'Unknown';
|
||||
@@ -68,6 +60,23 @@ function prepareFailurePayload(failures) {
|
||||
const libraryB = b.library || 'Unknown';
|
||||
return libraryA.localeCompare(libraryB);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a formatted Discord message payload from a list of failures.
|
||||
* @param {Array<Object>} failures - List of failures to format
|
||||
* @returns {Object} - The formatted Discord message payload
|
||||
*/
|
||||
function prepareFailurePayload(failures) {
|
||||
if (!failures || failures.length === 0) {
|
||||
return {
|
||||
content:
|
||||
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nNo failures to report.',
|
||||
};
|
||||
}
|
||||
|
||||
// Sort failures by platform and then by library name
|
||||
const sortedFailures = sortResultsByPlatformAndLibrary(failures);
|
||||
|
||||
// Format the failures into a message
|
||||
const formattedFailures = sortedFailures
|
||||
@@ -83,8 +92,45 @@ function prepareFailurePayload(failures) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a formatted Discord message payload for broken and recovered nightly jobs.
|
||||
* @param {Array<Object>} broken - List of newly broken jobs
|
||||
* @param {Array<Object>} recovered - List of recovered jobs
|
||||
* @returns {Object} - The formatted Discord message payload
|
||||
*/
|
||||
function prepareComparisonPayload(broken, recovered) {
|
||||
let content = '📊 **React Native Nightly Integration Status Update** 📊\n\n';
|
||||
|
||||
if (broken.length === 0 && recovered.length === 0) {
|
||||
content +=
|
||||
'No changes from yesterday - all nightly jobs maintained their previous status.';
|
||||
} else {
|
||||
if (broken.length > 0) {
|
||||
content += '🔴 **Newly Broken Jobs:**\n';
|
||||
const sortedBroken = sortResultsByPlatformAndLibrary(broken);
|
||||
|
||||
sortedBroken.forEach(job => {
|
||||
content += `❌ [${job.platform}] ${job.library} (was ${job.previousStatus}, now ${job.currentStatus})\n`;
|
||||
});
|
||||
content += '\n';
|
||||
}
|
||||
|
||||
if (recovered.length > 0) {
|
||||
content += '🟢 **Recovered Jobs:**\n';
|
||||
const sortedRecovered = sortResultsByPlatformAndLibrary(recovered);
|
||||
|
||||
sortedRecovered.forEach(job => {
|
||||
content += `✅ [${job.platform}] ${job.library} (was ${job.previousStatus}, now ${job.currentStatus})\n`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {content};
|
||||
}
|
||||
|
||||
// Export the functions using CommonJS syntax
|
||||
module.exports = {
|
||||
prepareFailurePayload,
|
||||
prepareComparisonPayload,
|
||||
sendMessageToDiscord,
|
||||
};
|
||||
|
||||
@@ -32,3 +32,7 @@ jobs:
|
||||
needs: check-nightly
|
||||
secrets:
|
||||
discord_webhook_url: ${{ secrets.NIGHTLY_DISCORD_WEBHOOK }}
|
||||
firebase_app_email: ${{ secrets.FIREBASE_APP_EMAIL }}
|
||||
firebase_app_pass: ${{ secrets.FIREBASE_APP_PASS }}
|
||||
firebase_app_projectname: ${{ secrets.FIREBASE_APP_PROJECTNAME }}
|
||||
firebase_app_apikey: ${{ secrets.FIREBASE_APP_APIKEY }}
|
||||
|
||||
@@ -5,6 +5,14 @@ on:
|
||||
secrets:
|
||||
discord_webhook_url:
|
||||
required: true
|
||||
firebase_app_email:
|
||||
required: true
|
||||
firebase_app_pass:
|
||||
required: true
|
||||
firebase_app_apikey:
|
||||
required: true
|
||||
firebase_app_projectname:
|
||||
required: true
|
||||
|
||||
|
||||
# We use the matrix.library entry to specify the dependency we want to use
|
||||
@@ -94,6 +102,11 @@ jobs:
|
||||
path: /tmp
|
||||
- name: Collect failures
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
FIREBASE_APP_EMAIL: ${{ secrets.firebase_app_email }}
|
||||
FIREBASE_APP_PASS: ${{ secrets.firebase_app_pass }}
|
||||
FIREBASE_APP_APIKEY: ${{ secrets.firebase_app_apikey }}
|
||||
FIREBASE_APP_PROJECTNAME: ${{ secrets.firebase_app_projectname }}
|
||||
with:
|
||||
script: |
|
||||
const {collectResults} = require('./.github/workflow-scripts/collectNightlyOutcomes.js');
|
||||
|
||||
+26
-206
@@ -1,218 +1,40 @@
|
||||
# Changelog
|
||||
|
||||
## v0.81.0-rc.3
|
||||
|
||||
### Breaking
|
||||
|
||||
- Metro to ^0.83.1 ([e247be793c](https://github.com/facebook/react-native/commit/e247be793c70a374955d798d8cbbc6eba58080ec) by [@motiz88](https://github.com/motiz88))
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Removed
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
## v0.81.0-rc.5
|
||||
|
||||
### Fixed
|
||||
|
||||
#### Android specific
|
||||
- **Runtime:** Fixed `ReactHostImpl.nativeModules` always returning an empty list ([2f46a49](https://github.com/facebook/react-native/commit/2f46a49b8d8a11d5cf4342eee83c469b545c6779) by [@lukmccall](https://github.com/lukmccall))
|
||||
|
||||
## v0.81.0-rc.4 - Burned
|
||||
|
||||
## v0.81.0-rc.3
|
||||
|
||||
### Changed
|
||||
|
||||
- **Metro:** Metro to ^0.83.1 ([e247be793c](https://github.com/facebook/react-native/commit/e247be793c70a374955d798d8cbbc6eba58080ec) by [@motiz88](https://github.com/motiz88))
|
||||
|
||||
### Fixed
|
||||
|
||||
#### Android specific
|
||||
|
||||
- **rngp:** Fix a race condition with codegen libraries missing sources ([9013a9e666](https://github.com/facebook/react-native/commit/9013a9e66629677c47e1b69703f9fc8f4cbc1c2c) by [@cortinico](https://github.com/cortinico))
|
||||
- Make accessors inside HeadlessJsTaskService open again ([7ef57163cb](https://github.com/facebook/react-native/commit/7ef57163cb016317e43e563da7ea181989f6abca) by [@cortinico](https://github.com/cortinico))
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Security
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Unknown
|
||||
|
||||
- Release 0.81.0-rc.3 ([0e6009eecf](https://github.com/facebook/react-native/commit/0e6009eecfeac71121a045f0f7e6bae94ffc11b0) by [@react-native-bot](https://github.com/react-native-bot))
|
||||
- Bump Podfile.lock ([3695258eed](https://github.com/facebook/react-native/commit/3695258eed45fed5fbff5dd6128c88275f91083c) by [@react-native-bot](https://github.com/react-native-bot))
|
||||
|
||||
#### Android Unknown
|
||||
|
||||
|
||||
|
||||
#### iOS Unknown
|
||||
|
||||
|
||||
|
||||
#### Failed to parse
|
||||
|
||||
|
||||
|
||||
- **API:** Make accessors inside HeadlessJsTaskService open again ([7ef57163cb](https://github.com/facebook/react-native/commit/7ef57163cb016317e43e563da7ea181989f6abca) by [@cortinico](https://github.com/cortinico))
|
||||
|
||||
## v0.81.0-rc.2
|
||||
|
||||
### Breaking
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Added support to `react-native/babel-preset` for a `hermesParserOptions` option, that expects an object that enables overriding `hermes-parser` options. ([0508eddfe6](https://github.com/facebook/react-native/commit/0508eddfe60df60cb3bfa4074ae199bd0e492d5f) by [@yungsters](https://github.com/yungsters))
|
||||
- `NewAppScreen` no longer internally handles device safe area, use optional `safeAreaInsets` prop (aligned in 0.81 template) ([732bd12dc2](https://github.com/facebook/react-native/commit/732bd12dc21460641ef01b23f2eb722f26b060d5) by [@huntie](https://github.com/huntie))
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Removed
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
- **API:** `NewAppScreen` no longer internally handles device safe area, use optional `safeAreaInsets` prop (aligned in 0.81 template) ([732bd12dc2](https://github.com/facebook/react-native/commit/732bd12dc21460641ef01b23f2eb722f26b060d5) by [@huntie](https://github.com/huntie))
|
||||
- **Babel:** Added support to `react-native/babel-preset` for a `hermesParserOptions` option, that expects an object that enables overriding `hermes-parser` options. ([0508eddfe6](https://github.com/facebook/react-native/commit/0508eddfe60df60cb3bfa4074ae199bd0e492d5f) by [@yungsters](https://github.com/yungsters))
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
- Fixed issue with RNDeps release/debug switch failing ([4ee2b60a1e](https://github.com/facebook/react-native/commit/4ee2b60a1eacca744d58a7ad336ca9d3714289f6) by [@chrfalch](https://github.com/chrfalch))
|
||||
- Fixed missing script for resolving prebuilt xcframework when switching between release/debug ([2e55241a90](https://github.com/facebook/react-native/commit/2e55241a901b4cd95917de68ce9078928820a208) by [@chrfalch](https://github.com/chrfalch))
|
||||
|
||||
### Security
|
||||
|
||||
|
||||
|
||||
#### Android specific
|
||||
|
||||
|
||||
|
||||
#### iOS specific
|
||||
|
||||
|
||||
|
||||
### Unknown
|
||||
|
||||
- Release 0.81.0-rc.2 ([68ef746ec5](https://github.com/facebook/react-native/commit/68ef746ec5dd7d2874d190733e75bb8197034c5a) by [@react-native-bot](https://github.com/react-native-bot))
|
||||
- Fix E2E test script when the ci flag is not specified ([cdd7f99581](https://github.com/facebook/react-native/commit/cdd7f995813727b630ff38abc8a910a0f8f10b37) by [@cipolleschi](https://github.com/cipolleschi))
|
||||
- Fix E2E script when using CI artifacts ([d8bf94489a](https://github.com/facebook/react-native/commit/d8bf94489ab7498eba7e5f45d09dd2819fe739c3) by [@cipolleschi](https://github.com/cipolleschi))
|
||||
- Bump Podfile.lock ([10b63c15b6](https://github.com/facebook/react-native/commit/10b63c15b6faaf54d555ede879f82ac565f030a9) by [@react-native-bot](https://github.com/react-native-bot))
|
||||
- Release 0.81.0-rc.1 ([b06bb89ddd](https://github.com/facebook/react-native/commit/b06bb89ddd3cebddea4716036a4368b87a65f492) by [@react-native-bot](https://github.com/react-native-bot))
|
||||
|
||||
#### Android Unknown
|
||||
|
||||
|
||||
|
||||
#### iOS Unknown
|
||||
|
||||
|
||||
|
||||
#### Failed to parse
|
||||
|
||||
|
||||
|
||||
- **Podspec:** Fixed issue with RNDeps release/debug switch failing ([4ee2b60a1e](https://github.com/facebook/react-native/commit/4ee2b60a1eacca744d58a7ad336ca9d3714289f6) by [@chrfalch](https://github.com/chrfalch))
|
||||
- **Podspec:** Fixed missing script for resolving prebuilt xcframework when switching between release/debug ([2e55241a90](https://github.com/facebook/react-native/commit/2e55241a901b4cd95917de68ce9078928820a208) by [@chrfalch](https://github.com/chrfalch))
|
||||
|
||||
## v0.81.0-rc.1
|
||||
|
||||
@@ -220,28 +42,27 @@
|
||||
|
||||
#### iOS specific
|
||||
|
||||
- Add release/debug switch script for React-Core-prebuilt ([42d1a7934c](https://github.com/facebook/react-native/commit/42d1a7934cad4b2c92653e3fa7781c2af8f44df4) by [@chrfalch](https://github.com/chrfalch))
|
||||
- Added support for using USE_FRAMEWORKS with prebuilt React Native Core ([40e45f5366](https://github.com/facebook/react-native/commit/40e45f53661ce80c3a6fbbf07f52dc900afcad52) by [@chrfalch](https://github.com/chrfalch))
|
||||
- Add the `ENTERPRISE_REPOSITORY` env variable to cocopaods infra ([23f3bf9239](https://github.com/facebook/react-native/commit/23f3bf9239a849590f1c72b25732d0090780128c) by [@cipolleschi](https://github.com/cipolleschi))
|
||||
- **CocoaPods** Add the `ENTERPRISE_REPOSITORY` env variable to cocoapods infra ([23f3bf9239](https://github.com/facebook/react-native/commit/23f3bf9239a849590f1c72b25732d0090780128c) by [@cipolleschi](https://github.com/cipolleschi))
|
||||
- **Prebuild:** Add release/debug switch script for React-Core-prebuilt ([42d1a7934c](https://github.com/facebook/react-native/commit/42d1a7934cad4b2c92653e3fa7781c2af8f44df4) by [@chrfalch](https://github.com/chrfalch))
|
||||
- **Prebuild:** Added support for using USE_FRAMEWORKS with prebuilt React Native Core ([40e45f5366](https://github.com/facebook/react-native/commit/40e45f53661ce80c3a6fbbf07f52dc900afcad52) by [@chrfalch](https://github.com/chrfalch))
|
||||
|
||||
### Changed
|
||||
|
||||
- Bump Metro to 0.83.0 ([6b9f5d622f](https://github.com/facebook/react-native/commit/6b9f5d622ffbe79da8f4e7b7d8094504a480425e) by [@robhogan](https://github.com/robhogan))
|
||||
- **Metro:** Bump Metro to 0.83.0 ([6b9f5d622f](https://github.com/facebook/react-native/commit/6b9f5d622ffbe79da8f4e7b7d8094504a480425e) by [@robhogan](https://github.com/robhogan))
|
||||
|
||||
#### Android specific
|
||||
|
||||
- Gradle to 8.14.3 ([6892dde363](https://github.com/facebook/react-native/commit/6892dde36373bbef2d0afe535ae818b1a7164f08) by [@cortinico](https://github.com/cortinico))
|
||||
- Expose `react_renderer_bridging` headers via prefab ([d1730ff960](https://github.com/facebook/react-native/commit/d1730ff960fcb9a01ee94b9e46e5a9fbb7d73f4a) by [@tomekzaw](https://github.com/tomekzaw))
|
||||
- Introduce more deprecation warnings for Legacy Arch classes ([625f69f284](https://github.com/facebook/react-native/commit/625f69f284ddfd9c6beecaa4052a871d092053ef) by [@cortinico](https://github.com/cortinico))
|
||||
|
||||
- **Gradle:** Gradle to 8.14.3 ([6892dde363](https://github.com/facebook/react-native/commit/6892dde36373bbef2d0afe535ae818b1a7164f08) by [@cortinico](https://github.com/cortinico))
|
||||
- **Gradle:** Expose `react_renderer_bridging` headers via prefab ([d1730ff960](https://github.com/facebook/react-native/commit/d1730ff960fcb9a01ee94b9e46e5a9fbb7d73f4a) by [@tomekzaw](https://github.com/tomekzaw))
|
||||
- **Legacy Arch:** Introduce more deprecation warnings for Legacy Arch classes ([625f69f284](https://github.com/facebook/react-native/commit/625f69f284ddfd9c6beecaa4052a871d092053ef) by [@cortinico](https://github.com/cortinico))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed nodes with `display: contents` set being cloned with the wrong owner ([d4b36b0300](https://github.com/facebook/react-native/commit/d4b36b03003eb2de9eaf5b57bb639bae8cc12f20) by [@j-piasecki](https://github.com/j-piasecki))
|
||||
- **Yoga:** Fixed nodes with `display: contents` set being cloned with the wrong owner ([d4b36b0300](https://github.com/facebook/react-native/commit/d4b36b03003eb2de9eaf5b57bb639bae8cc12f20) by [@j-piasecki](https://github.com/j-piasecki))
|
||||
|
||||
#### iOS specific
|
||||
|
||||
- Fixed premature return in header file generation from podspec globs ([f2b064c2d4](https://github.com/facebook/react-native/commit/f2b064c2d40c39017ac2a31bf3caf8acef23038c) by [@chrfalch](https://github.com/chrfalch))
|
||||
- **Podspec:** Fixed premature return in header file generation from podspec globs ([f2b064c2d4](https://github.com/facebook/react-native/commit/f2b064c2d40c39017ac2a31bf3caf8acef23038c) by [@chrfalch](https://github.com/chrfalch))
|
||||
|
||||
|
||||
## v0.81.0-rc.0
|
||||
@@ -1586,4 +1407,3 @@ See [CHANGELOG-0.5x](./CHANGELOG-0.5x.md#v0530)
|
||||
## v0.52.0
|
||||
|
||||
See [CHANGELOG-0.5x](./CHANGELOG-0.5x.md#v0520)
|
||||
|
||||
|
||||
@@ -24,12 +24,12 @@ try {
|
||||
} catch (e) {
|
||||
// Fallback to lib when source doesn't exit (e.g. when installed as a dev dependency)
|
||||
FlowParser =
|
||||
// $FlowIgnore[cannot-resolve-module]
|
||||
// $FlowFixMe[cannot-resolve-module]
|
||||
require('@react-native/codegen/lib/parsers/flow/parser').FlowParser;
|
||||
TypeScriptParser =
|
||||
// $FlowIgnore[cannot-resolve-module]
|
||||
// $FlowFixMe[cannot-resolve-module]
|
||||
require('@react-native/codegen/lib/parsers/typescript/parser').TypeScriptParser;
|
||||
// $FlowIgnore[cannot-resolve-module]
|
||||
// $FlowFixMe[cannot-resolve-module]
|
||||
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen');
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ try {
|
||||
'@react-native-community/cli-server-api',
|
||||
{paths: [communityCliPath]},
|
||||
);
|
||||
// $FlowIgnore[unsupported-syntax] dynamic import
|
||||
// $FlowFixMe[unsupported-syntax] dynamic import
|
||||
communityMiddlewareFallback.createDevServerMiddleware = require(
|
||||
communityCliServerApiPath,
|
||||
).createDevServerMiddleware as CreateDevServerMiddleware;
|
||||
|
||||
@@ -92,14 +92,14 @@ async function runServer(
|
||||
console.info(`Starting dev server on ${devServerUrl}\n`);
|
||||
|
||||
if (args.assetPlugins) {
|
||||
// $FlowIgnore[cannot-write] Assigning to readonly property
|
||||
// $FlowFixMe[cannot-write] Assigning to readonly property
|
||||
metroConfig.transformer.assetPlugins = args.assetPlugins.map(plugin =>
|
||||
require.resolve(plugin),
|
||||
);
|
||||
}
|
||||
// TODO(T214991636): Remove legacy Metro log forwarding
|
||||
if (!args.clientLogs) {
|
||||
// $FlowIgnore[cannot-write] Assigning to readonly property
|
||||
// $FlowFixMe[cannot-write] Assigning to readonly property
|
||||
metroConfig.server.forwardClientLogs = false;
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ async function runServer(
|
||||
}
|
||||
},
|
||||
};
|
||||
// $FlowIgnore[cannot-write] Assigning to readonly property
|
||||
// $FlowFixMe[cannot-write] Assigning to readonly property
|
||||
metroConfig.reporter = reporter;
|
||||
|
||||
await Metro.runServer(metroConfig, {
|
||||
@@ -175,7 +175,7 @@ function getReporterImpl(
|
||||
try {
|
||||
// First we let require resolve it, so we can require packages in node_modules
|
||||
// as expected. eg: require('my-package/reporter');
|
||||
// $FlowIgnore[unsupported-syntax]
|
||||
// $FlowFixMe[unsupported-syntax]
|
||||
return require(customLogReporterPath);
|
||||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') {
|
||||
@@ -183,7 +183,7 @@ function getReporterImpl(
|
||||
}
|
||||
// If that doesn't work, then we next try relative to the cwd, eg:
|
||||
// require('./reporter');
|
||||
// $FlowIgnore[unsupported-syntax]
|
||||
// $FlowFixMe[unsupported-syntax]
|
||||
return require(path.resolve(customLogReporterPath));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ const FIRST = 1,
|
||||
FOURTH = 4;
|
||||
|
||||
function getNodePackagePath(packageName: string): string {
|
||||
// $FlowIgnore[prop-missing] type definition is incomplete
|
||||
// $FlowFixMe[prop-missing] type definition is incomplete
|
||||
return require.resolve(packageName, {cwd: [process.cwd(), ...module.paths]});
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ const FIRST = 1,
|
||||
FIFTH = 5;
|
||||
|
||||
function getNodePackagePath(packageName: string): string {
|
||||
// $FlowIgnore[prop-missing] type definition is incomplete
|
||||
// $FlowFixMe[prop-missing] type definition is incomplete
|
||||
return require.resolve(packageName, {cwd: [process.cwd(), ...module.paths]});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@generated SignedSource<<46a90cb884e816d8836dca52c2e13e50>>
|
||||
Git revision: 7dcbddd636137a9604d69a99cc69221216cd4be6
|
||||
@generated SignedSource<<9252db36d4b1db907a38c08935ceeb38>>
|
||||
Git revision: 921566790e9e16d0ecace6e49b3cfaace205958c
|
||||
Built with --nohooks: false
|
||||
Is local checkout: false
|
||||
Remote URL: https://github.com/facebook/react-native-devtools-frontend
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@ contextBridge.executeInMainWorld({
|
||||
let didDecorateInspectorFrontendHostInstance = false;
|
||||
// reactNativeDecorateInspectorFrontendHostInstance was introduced in
|
||||
// https://github.com/facebook/react-native-devtools-frontend/pull/168
|
||||
// $FlowIgnore[prop-missing]
|
||||
// $FlowFixMe[prop-missing]
|
||||
globalThis.reactNativeDecorateInspectorFrontendHostInstance = (
|
||||
InspectorFrontendHostInstance: $FlowFixMe,
|
||||
) => {
|
||||
|
||||
@@ -18,9 +18,9 @@ declare module.exports: typeof Node;
|
||||
// Because Electron doesn't support package.json `exports`, we need to
|
||||
// switch at runtime.
|
||||
if ('electron' in process.versions) {
|
||||
// $FlowIgnore[invalid-export]
|
||||
// $FlowFixMe[invalid-export]
|
||||
module.exports = require('./electron');
|
||||
} else {
|
||||
// $FlowIgnore[invalid-export]
|
||||
// $FlowFixMe[invalid-export]
|
||||
module.exports = require('./node');
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export class DebuggerAgent {
|
||||
this.#ws = null;
|
||||
}
|
||||
|
||||
// $FlowIgnore[unsafe-getters-setters]
|
||||
// $FlowFixMe[unsafe-getters-setters]
|
||||
get socket(): WebSocket {
|
||||
return nullthrows(this.#ws);
|
||||
}
|
||||
@@ -103,11 +103,11 @@ export class DebuggerMock extends DebuggerAgent {
|
||||
originalHandleCallsArray === this.handle.mock.calls
|
||||
? this.handle.mock.calls.slice(originalHandleCallCount)
|
||||
: this.handle.mock.calls;
|
||||
// $FlowIgnore[incompatible-use]
|
||||
// $FlowIgnore[prop-missing]
|
||||
// $FlowFixMe[incompatible-use]
|
||||
// $FlowFixMe[prop-missing]
|
||||
const [response] = newHandleCalls.find(args => args[0].id === message.id);
|
||||
// $FlowIgnore[incompatible-return]
|
||||
// $FlowIgnore[incompatible-indexer]
|
||||
// $FlowFixMe[incompatible-return]
|
||||
// $FlowFixMe[incompatible-indexer]
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ export class DeviceAgent {
|
||||
});
|
||||
}
|
||||
|
||||
// $FlowIgnore[unsafe-getters-setters]
|
||||
// $FlowFixMe[unsafe-getters-setters]
|
||||
get socket(): WebSocket {
|
||||
return nullthrows(this.#ws);
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@ export async function sendFromTargetToDebugger<Message: CdpMessageFromTarget>(
|
||||
originalHandleCallsArray === debugger_.handle.mock.calls
|
||||
? debugger_.handle.mock.calls.slice(originalHandleCallCount)
|
||||
: debugger_.handle.mock.calls;
|
||||
// $FlowIgnore[incompatible-type]
|
||||
// $FlowFixMe[incompatible-type]
|
||||
const [receivedMessage]: [Message] = newHandleCalls.find(
|
||||
// $FlowIgnore[incompatible-call]
|
||||
// $FlowFixMe[incompatible-call]
|
||||
(call: [Message]) => call[0].method === message.method,
|
||||
);
|
||||
return receivedMessage;
|
||||
@@ -97,13 +97,13 @@ export async function sendFromDebuggerToTarget<Message: CdpMessageToTarget>(
|
||||
originalEventCallsArray === device.wrappedEventParsed.mock.calls
|
||||
? device.wrappedEventParsed.mock.calls.slice(originalEventCallCount)
|
||||
: device.wrappedEventParsed.mock.calls;
|
||||
// $FlowIgnore[incompatible-use]
|
||||
// $FlowFixMe[incompatible-use]
|
||||
const [receivedMessage] = newEventCalls.find(
|
||||
// $FlowIgnore[prop-missing]
|
||||
// $FlowIgnore[incompatible-use]
|
||||
// $FlowFixMe[prop-missing]
|
||||
// $FlowFixMe[incompatible-use]
|
||||
call => call[0].wrappedEvent.id === message.id,
|
||||
);
|
||||
// $FlowIgnore[incompatible-return]
|
||||
// $FlowFixMe[incompatible-return]
|
||||
return receivedMessage.wrappedEvent;
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ export async function createAndConnectTarget(
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toHaveLength(1);
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toHaveLength(1);
|
||||
});
|
||||
@@ -119,7 +119,7 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toHaveLength(1);
|
||||
});
|
||||
@@ -187,7 +187,7 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toHaveLength(1);
|
||||
});
|
||||
@@ -288,7 +288,7 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toHaveLength(1);
|
||||
});
|
||||
@@ -338,7 +338,7 @@ describe.each(['HTTP', 'HTTPS'])(
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toHaveLength(1);
|
||||
});
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('inspector proxy device message middleware', () => {
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -314,7 +314,7 @@ describe('inspector-proxy device socket handoff', () => {
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toEqual(
|
||||
expect.arrayContaining(
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('inspector proxy React Native reloads', () => {
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList.length).toBeGreaterThan(0);
|
||||
});
|
||||
@@ -113,7 +113,7 @@ describe('inspector proxy React Native reloads', () => {
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toContainEqual(
|
||||
expect.objectContaining({
|
||||
@@ -169,7 +169,7 @@ describe('inspector proxy React Native reloads', () => {
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList.length).toBeGreaterThan(0);
|
||||
});
|
||||
@@ -222,7 +222,7 @@ describe('inspector proxy React Native reloads', () => {
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toContainEqual(
|
||||
expect.objectContaining({
|
||||
@@ -273,7 +273,7 @@ describe('inspector proxy React Native reloads', () => {
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList.length).toBeGreaterThan(0);
|
||||
});
|
||||
@@ -423,7 +423,7 @@ describe('inspector proxy React Native reloads', () => {
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList.length).toBeGreaterThan(0);
|
||||
});
|
||||
@@ -459,7 +459,7 @@ describe('inspector proxy React Native reloads', () => {
|
||||
await until(async () => {
|
||||
pageList = (await fetchJson(
|
||||
`${serverRef.serverBaseUrl}/json`,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
): any);
|
||||
expect(pageList).toContainEqual(
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
export function withAbortSignalForEachTest(): $ReadOnly<{signal: AbortSignal}> {
|
||||
const ref: {signal: AbortSignal} = {
|
||||
// $FlowIgnore[unsafe-getters-setters]
|
||||
// $FlowFixMe[unsafe-getters-setters]
|
||||
get signal() {
|
||||
throw new Error(
|
||||
'The return value of withAbortSignalForEachTest is lazily initialized and can only be accessed in tests.',
|
||||
|
||||
@@ -39,19 +39,19 @@ export function withServerForEachTest(options: CreateServerOptions): $ReadOnly<{
|
||||
app: ConnectApp,
|
||||
port: number,
|
||||
} = {
|
||||
// $FlowIgnore[unsafe-getters-setters]
|
||||
// $FlowFixMe[unsafe-getters-setters]
|
||||
get serverBaseUrl() {
|
||||
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
|
||||
},
|
||||
// $FlowIgnore[unsafe-getters-setters]
|
||||
// $FlowFixMe[unsafe-getters-setters]
|
||||
get serverBaseWsUrl() {
|
||||
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
|
||||
},
|
||||
// $FlowIgnore[unsafe-getters-setters]
|
||||
// $FlowFixMe[unsafe-getters-setters]
|
||||
get app() {
|
||||
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
|
||||
},
|
||||
// $FlowIgnore[unsafe-getters-setters]
|
||||
// $FlowFixMe[unsafe-getters-setters]
|
||||
get port() {
|
||||
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
|
||||
},
|
||||
|
||||
@@ -40,7 +40,7 @@ function makeRequest(
|
||||
host: ?string,
|
||||
encrypted: boolean,
|
||||
): http$IncomingMessage<> | http$IncomingMessage<tls$TLSSocket> {
|
||||
// $FlowIgnore[incompatible-return] Partial mock of request
|
||||
// $FlowFixMe[incompatible-return] Partial mock of request
|
||||
return {
|
||||
socket: encrypted ? {encrypted: true} : {},
|
||||
headers: host != null ? {host} : {},
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/parser": "^7.25.3",
|
||||
"glob": "^7.1.1",
|
||||
"hermes-parser": "0.30.0",
|
||||
"invariant": "^2.2.4",
|
||||
|
||||
+1
-1
@@ -487,7 +487,7 @@ describe('buildSchemaFromConfigType', () => {
|
||||
|
||||
describe('when buildModuleSchema returns null', () => {
|
||||
it('throws an error', () => {
|
||||
// $FlowIgnore[incompatible-call] - This is to test an invariant
|
||||
// $FlowFixMe[incompatible-call] - This is to test an invariant
|
||||
buildModuleSchemaMock.mockReturnValueOnce(null);
|
||||
|
||||
expect(() =>
|
||||
|
||||
Vendored
+2
-2
@@ -10,9 +10,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
// $FlowIgnore[cannot-resolve-module]
|
||||
// $FlowFixMe[cannot-resolve-module]
|
||||
const flowSnaps = require('../../../../src/parsers/flow/components/__tests__/__snapshots__/component-parser-test.js.snap');
|
||||
// $FlowIgnore[cannot-resolve-module]
|
||||
// $FlowFixMe[cannot-resolve-module]
|
||||
const tsSnaps = require('../../../../src/parsers/typescript/components/__tests__/__snapshots__/typescript-component-parser-test.js.snap');
|
||||
const flowFixtures = require('../../flow/components/__test_fixtures__/fixtures.js');
|
||||
const tsFixtures = require('../../typescript/components/__test_fixtures__/fixtures.js');
|
||||
|
||||
+2
-2
@@ -10,9 +10,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
// $FlowIgnore[cannot-resolve-module]
|
||||
// $FlowFixMe[cannot-resolve-module]
|
||||
const flowSnaps = require('../../../../src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap');
|
||||
// $FlowIgnore[cannot-resolve-module]
|
||||
// $FlowFixMe[cannot-resolve-module]
|
||||
const tsSnaps = require('../../../../src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap');
|
||||
const flowFixtures = require('../../flow/modules/__test_fixtures__/fixtures.js');
|
||||
const tsFixtures = require('../../typescript/modules/__test_fixtures__/fixtures.js');
|
||||
|
||||
@@ -48,6 +48,7 @@ const SUPPORTED_STYLES: {[string]: true} = {
|
||||
borderStartStartRadius: true,
|
||||
elevation: true,
|
||||
opacity: true,
|
||||
filter: true,
|
||||
transform: true,
|
||||
zIndex: true,
|
||||
/* ios styles */
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {HostInstance} from 'react-native';
|
||||
|
||||
import ensureInstance from '../../../src/private/__tests__/utilities/ensureInstance';
|
||||
import * as Fantom from '@react-native/fantom';
|
||||
import {createRef, useMemo} from 'react';
|
||||
import {createRef} from 'react';
|
||||
import {Animated, View, useAnimatedValue} from 'react-native';
|
||||
import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';
|
||||
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
|
||||
@@ -747,98 +747,3 @@ test('Animated.sequence', () => {
|
||||
|
||||
expect(_isSequenceFinished).toBe(true);
|
||||
});
|
||||
|
||||
test('Props default value is restored when disconnected from animated', () => {
|
||||
let _animatedOpacity;
|
||||
const elementRef = createRef<HostInstance>();
|
||||
|
||||
function MyApp({
|
||||
shouldAnimate,
|
||||
}: $ReadOnly<{
|
||||
shouldAnimate?: boolean,
|
||||
}>) {
|
||||
const animatedOpacity = useAnimatedValue(1, {useNativeDriver: true});
|
||||
|
||||
const opacity = useMemo(
|
||||
() => (shouldAnimate === true ? animatedOpacity : undefined),
|
||||
[shouldAnimate, animatedOpacity],
|
||||
);
|
||||
const scale = useMemo(
|
||||
() =>
|
||||
opacity?.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0.95, 1],
|
||||
}) ?? new Animated.Value(1),
|
||||
[opacity],
|
||||
);
|
||||
_animatedOpacity = animatedOpacity;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
ref={elementRef}
|
||||
style={[
|
||||
{
|
||||
opacity,
|
||||
transform: [{scale}],
|
||||
height: 100,
|
||||
width: 100,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<MyApp shouldAnimate={true} />);
|
||||
});
|
||||
|
||||
const element = ensureInstance(elementRef.current, ReactNativeElement);
|
||||
|
||||
expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual(
|
||||
<rn-view />, // default opacity is 1
|
||||
);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
Animated.timing(_animatedOpacity, {
|
||||
toValue: 0,
|
||||
duration: 500,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
});
|
||||
|
||||
Fantom.unstable_produceFramesForDuration(500);
|
||||
|
||||
Fantom.runWorkLoop();
|
||||
|
||||
expect(Fantom.unstable_getDirectManipulationProps(element)).toEqual({
|
||||
opacity: 0,
|
||||
transform: [{scale: 0.95}],
|
||||
});
|
||||
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['opacity', 'transform']}).toJSX(),
|
||||
).toEqual(<rn-view opacity="0" transform='[{"scale": 0.950000}]' />);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<MyApp shouldAnimate={false} />);
|
||||
});
|
||||
|
||||
expect(Fantom.unstable_getDirectManipulationProps(element)).toEqual({
|
||||
opacity: null,
|
||||
transform: null,
|
||||
});
|
||||
|
||||
if (ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()) {
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['opacity', 'transform']}).toJSX(),
|
||||
).toEqual(<rn-view transform='[{"scale": 0.950000}]' />); // TODO: T223344928 scale should be 1
|
||||
} else {
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['opacity', 'transform']}).toJSX(),
|
||||
).toEqual(<rn-view transform='[{"scale": 1.000000}]' />);
|
||||
}
|
||||
|
||||
Fantom.runWorkLoop();
|
||||
});
|
||||
|
||||
@@ -21,11 +21,11 @@ function mockQueueMicrotask() {
|
||||
let queueMicrotask;
|
||||
beforeEach(() => {
|
||||
queueMicrotask = global.queueMicrotask;
|
||||
// $FlowIgnore[cannot-write]
|
||||
// $FlowFixMe[cannot-write]
|
||||
global.queueMicrotask = process.nextTick;
|
||||
});
|
||||
afterEach(() => {
|
||||
// $FlowIgnore[cannot-write]
|
||||
// $FlowFixMe[cannot-write]
|
||||
global.queueMicrotask = queueMicrotask;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,12 +60,12 @@ function processColor(
|
||||
}
|
||||
|
||||
if (isRgbaValue(color)) {
|
||||
// $FlowIgnore[incompatible-cast] - Type is verified above
|
||||
// $FlowFixMe[incompatible-cast] - Type is verified above
|
||||
return (color: RgbaValue);
|
||||
}
|
||||
|
||||
let normalizedColor: ?ProcessedColorValue = normalizeColor(
|
||||
// $FlowIgnore[incompatible-cast] - Type is verified above
|
||||
// $FlowFixMe[incompatible-cast] - Type is verified above
|
||||
(color: ColorValue),
|
||||
);
|
||||
if (normalizedColor === undefined || normalizedColor === null) {
|
||||
@@ -125,7 +125,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
|
||||
let value: RgbaValue | RgbaAnimatedValue | ColorValue =
|
||||
valueIn ?? defaultColor;
|
||||
if (isRgbaAnimatedValue(value)) {
|
||||
// $FlowIgnore[incompatible-cast] - Type is verified above
|
||||
// $FlowFixMe[incompatible-cast] - Type is verified above
|
||||
const rgbaAnimatedValue: RgbaAnimatedValue = (value: RgbaAnimatedValue);
|
||||
this.r = rgbaAnimatedValue.r;
|
||||
this.g = rgbaAnimatedValue.g;
|
||||
@@ -133,14 +133,14 @@ export default class AnimatedColor extends AnimatedWithChildren {
|
||||
this.a = rgbaAnimatedValue.a;
|
||||
} else {
|
||||
const processedColor: RgbaValue | NativeColorValue =
|
||||
// $FlowIgnore[incompatible-cast] - Type is verified above
|
||||
// $FlowFixMe[incompatible-cast] - Type is verified above
|
||||
processColor((value: ColorValue | RgbaValue)) ?? defaultColor;
|
||||
let initColor: RgbaValue = defaultColor;
|
||||
if (isRgbaValue(processedColor)) {
|
||||
// $FlowIgnore[incompatible-cast] - Type is verified above
|
||||
// $FlowFixMe[incompatible-cast] - Type is verified above
|
||||
initColor = (processedColor: RgbaValue);
|
||||
} else {
|
||||
// $FlowIgnore[incompatible-cast] - Type is verified above
|
||||
// $FlowFixMe[incompatible-cast] - Type is verified above
|
||||
this.nativeColor = (processedColor: NativeColorValue);
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
|
||||
processColor(value) ?? defaultColor;
|
||||
this._withSuspendedCallbacks(() => {
|
||||
if (isRgbaValue(processedColor)) {
|
||||
// $FlowIgnore[incompatible-type] - Type is verified above
|
||||
// $FlowFixMe[incompatible-type] - Type is verified above
|
||||
const rgbaValue: RgbaValue = processedColor;
|
||||
this.r.setValue(rgbaValue.r);
|
||||
this.g.setValue(rgbaValue.g);
|
||||
@@ -181,7 +181,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
|
||||
shouldUpdateNodeConfig = true;
|
||||
}
|
||||
} else {
|
||||
// $FlowIgnore[incompatible-type] - Type is verified above
|
||||
// $FlowFixMe[incompatible-type] - Type is verified above
|
||||
const nativeColor: NativeColorValue = processedColor;
|
||||
if (this.nativeColor !== nativeColor) {
|
||||
this.nativeColor = nativeColor;
|
||||
|
||||
@@ -224,7 +224,7 @@ function createStringInterpolation(
|
||||
outputRange.every(output =>
|
||||
output.components.every(
|
||||
(component, i) =>
|
||||
// $FlowIgnoreMe[invalid-compare]
|
||||
// $FlowFixMe[invalid-compare]
|
||||
typeof component === 'number' || component === firstOutput[i],
|
||||
),
|
||||
),
|
||||
@@ -235,9 +235,9 @@ function createStringInterpolation(
|
||||
const numericComponents: $ReadOnlyArray<$ReadOnlyArray<number>> =
|
||||
outputRange.map(output =>
|
||||
isColor
|
||||
? // $FlowIgnoreMe[incompatible-call]
|
||||
? // $FlowFixMe[incompatible-type]
|
||||
output.components
|
||||
: // $FlowIgnoreMe[incompatible-call]
|
||||
: // $FlowFixMe[incompatible-call]
|
||||
output.components.filter(c => typeof c === 'number'),
|
||||
);
|
||||
const interpolations = numericComponents[0].map((_, i) =>
|
||||
@@ -393,7 +393,7 @@ export default class AnimatedInterpolation<
|
||||
let outputRange = this._config.outputRange;
|
||||
let outputType = null;
|
||||
if (typeof outputRange[0] === 'string') {
|
||||
// $FlowIgnoreMe[incompatible-cast]
|
||||
// $FlowFixMe[incompatible-cast]
|
||||
outputRange = ((outputRange: $ReadOnlyArray<string>).map(value => {
|
||||
const processedColor = processColor(value);
|
||||
if (typeof processedColor === 'number') {
|
||||
|
||||
@@ -325,8 +325,8 @@ export default class AnimatedProps extends AnimatedNode {
|
||||
|
||||
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
|
||||
// this shim when they do.
|
||||
// $FlowIgnore[method-unbinding]
|
||||
// $FlowFixMe[method-unbinding]
|
||||
const _hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
|
||||
// $FlowIgnore[method-unbinding]
|
||||
// $FlowFixMe[method-unbinding]
|
||||
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));
|
||||
|
||||
@@ -123,7 +123,7 @@ export default class AnimatedStyle extends AnimatedWithChildren {
|
||||
this._style = style;
|
||||
|
||||
if ((Platform.OS as string) === 'web') {
|
||||
// $FlowIgnore[cannot-write] - Intentional shadowing.
|
||||
// $FlowFixMe[cannot-write] - Intentional shadowing.
|
||||
this.__getValueForStyle = resultStyle => [
|
||||
originalStyleForWeb,
|
||||
resultStyle,
|
||||
@@ -249,8 +249,8 @@ export default class AnimatedStyle extends AnimatedWithChildren {
|
||||
|
||||
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
|
||||
// this shim when they do.
|
||||
// $FlowIgnore[method-unbinding]
|
||||
// $FlowFixMe[method-unbinding]
|
||||
const _hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
|
||||
// $FlowIgnore[method-unbinding]
|
||||
// $FlowFixMe[method-unbinding]
|
||||
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));
|
||||
|
||||
@@ -618,6 +618,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
|
||||
// so omitting onBlur and onFocus pressability handlers here.
|
||||
const {onBlur, onFocus, ...eventHandlers} = usePressability(config);
|
||||
|
||||
const _accessibilityLabel =
|
||||
props?.['aria-label'] ?? props?.accessibilityLabel;
|
||||
|
||||
let _accessibilityState;
|
||||
if (
|
||||
accessibilityState != null ||
|
||||
@@ -681,6 +684,7 @@ function InternalTextInput(props: TextInputProps): React.Node {
|
||||
{...otherProps}
|
||||
{...eventHandlers}
|
||||
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={accessible}
|
||||
submitBehavior={submitBehavior}
|
||||
@@ -744,8 +748,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
|
||||
{...otherProps}
|
||||
{...colorProps}
|
||||
{...eventHandlers}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityLabelledBy={_accessibilityLabelledBy}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={accessible}
|
||||
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
|
||||
autoCapitalize={autoCapitalize}
|
||||
|
||||
+1
@@ -432,6 +432,7 @@ jest.unmock('../TextInput');
|
||||
|
||||
expect(instance.toJSON()).toMatchInlineSnapshot(`
|
||||
<RCTSinglelineTextInputView
|
||||
accessibilityLabel="label"
|
||||
accessibilityState={
|
||||
Object {
|
||||
"busy": true,
|
||||
|
||||
@@ -477,7 +477,7 @@ function runExceptionsManagerTests() {
|
||||
expect(nativeReportException).not.toBeCalled();
|
||||
expect(logBoxAddConsoleLog).toBeCalledTimes(1);
|
||||
expect(logBoxAddConsoleLog.mock.calls[0][0]).toBe('error');
|
||||
// $FlowIgnore[incompatible-call]
|
||||
// $FlowFixMe[incompatible-call]
|
||||
expect(logBoxAddConsoleLog.mock.calls[0][1]).toBe(...args);
|
||||
} else {
|
||||
expect(logBoxAddException).not.toBeCalled();
|
||||
@@ -554,7 +554,7 @@ function runExceptionsManagerTests() {
|
||||
const object = {
|
||||
toString: () => 'Warning: Some error may have happened',
|
||||
};
|
||||
// $FlowIgnore[prop-missing]
|
||||
// $FlowFixMe[prop-missing]
|
||||
object.cycle = object;
|
||||
|
||||
const args = [object];
|
||||
|
||||
+2
-2
@@ -27,12 +27,12 @@ function _setDevelopmentModeForTests(dev: mixed) {
|
||||
|
||||
beforeAll(() => {
|
||||
originalDev = global.__DEV__;
|
||||
// $FlowIgnore[cannot-write]
|
||||
// $FlowFixMe[cannot-write]
|
||||
global.__DEV__ = dev;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// $FlowIgnore[cannot-write]
|
||||
// $FlowFixMe[cannot-write]
|
||||
global.__DEV__ = originalDev;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ import {getUrlCacheBreaker, setUrlCacheBreaker} from '../AssetUtils';
|
||||
|
||||
describe('AssetUtils', () => {
|
||||
afterEach(() => {
|
||||
// $FlowIgnore[cannot-write]
|
||||
// $FlowFixMe[cannot-write]
|
||||
global.__DEV__ = true;
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return empty string and warn once if no cacheBreaker set (DEV)', () => {
|
||||
const mockWarn = jest.spyOn(console, 'warn').mockReturnValue(undefined);
|
||||
// $FlowIgnore[cannot-write]
|
||||
// $FlowFixMe[cannot-write]
|
||||
global.__DEV__ = true;
|
||||
expect(getUrlCacheBreaker()).toEqual('');
|
||||
expect(getUrlCacheBreaker()).toEqual('');
|
||||
@@ -28,7 +28,7 @@ describe('AssetUtils', () => {
|
||||
|
||||
it('should return empty string if no cacheBreaker set in prod', () => {
|
||||
const mockWarn = jest.spyOn(console, 'warn');
|
||||
// $FlowIgnore[cannot-write]
|
||||
// $FlowFixMe[cannot-write]
|
||||
global.__DEV__ = false;
|
||||
expect(getUrlCacheBreaker()).toEqual('');
|
||||
expect(mockWarn).not.toHaveBeenCalled();
|
||||
|
||||
@@ -169,6 +169,460 @@ describe('<Image>', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultSource', () => {
|
||||
it('can provide a default image to display', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
defaultSource={require('./img/img1.png')}
|
||||
source={LOGO_SOURCE}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['defaultSource']}).toJSX(),
|
||||
).toEqual(
|
||||
<rn-image
|
||||
defaultSource-type="remote"
|
||||
defaultSource-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('height', () => {
|
||||
it('provides height for image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image height={100} source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
|
||||
<rn-image height="100.000000" />,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('width', () => {
|
||||
it('provides width for image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image width={100} source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['width']}).toJSX()).toEqual(
|
||||
<rn-image width="100.000000" />,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loading progress', () => {
|
||||
(
|
||||
[
|
||||
['onError', 'fails to load'],
|
||||
['onLoadStart', 'start loading'],
|
||||
['onProgress', 'is loading'],
|
||||
['onLoad', 'loads successfully'],
|
||||
['onLoadEnd', 'ends loading'],
|
||||
] as const
|
||||
).forEach(([onProp, event]) => {
|
||||
it(`${onProp} is called when image ${event}`, () => {
|
||||
const onPropCallback = jest.fn();
|
||||
const ref = createRef<HostInstance>();
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
ref={ref}
|
||||
source={LOGO_SOURCE}
|
||||
onError={() => {
|
||||
onProp === 'onError' && onPropCallback();
|
||||
}}
|
||||
onLoad={() => {
|
||||
onProp === 'onLoad' && onPropCallback();
|
||||
}}
|
||||
onLoadStart={() => {
|
||||
onProp === 'onLoadStart' && onPropCallback();
|
||||
}}
|
||||
onLoadEnd={() => {
|
||||
onProp === 'onLoadEnd' && onPropCallback();
|
||||
}}
|
||||
onProgress={() => {
|
||||
onProp === 'onProgress' && onPropCallback();
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(onPropCallback).toHaveBeenCalledTimes(0);
|
||||
|
||||
const image = ensureInstance(ref.current, ReactNativeElement);
|
||||
Fantom.dispatchNativeEvent(image, onProp, {});
|
||||
|
||||
expect(onPropCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('referrerPolicy', () => {
|
||||
(
|
||||
[
|
||||
'no-referrer',
|
||||
'no-referrer-when-downgrade',
|
||||
'origin',
|
||||
'origin-when-cross-origin',
|
||||
'same-origin',
|
||||
'strict-origin',
|
||||
'strict-origin-when-cross-origin',
|
||||
'unsafe-url',
|
||||
] as const
|
||||
).forEach(referrerPolicy => {
|
||||
it(`${referrerPolicy} sets correct "Referrer-Policy" header`, () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image referrerPolicy={referrerPolicy} src={LOGO_SOURCE.uri} />,
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['source-header']}).toJSX(),
|
||||
).toEqual(
|
||||
<rn-image source-header-Referrer-Policy={referrerPolicy} />,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resizeMode', () => {
|
||||
it('is set to "cover" by default', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['resizeMode']}).toJSX()).toEqual(
|
||||
<rn-image />,
|
||||
);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image resizeMode="cover" source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['resizeMode']}).toJSX()).toEqual(
|
||||
<rn-image />,
|
||||
);
|
||||
});
|
||||
|
||||
(['stretch', 'contain', 'repeat', 'center'] as const).forEach(
|
||||
resizeMode => {
|
||||
it(`can be set to "${resizeMode}"`, () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image resizeMode={resizeMode} source={LOGO_SOURCE} />,
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['resizeMode']}).toJSX(),
|
||||
).toEqual(<rn-image resizeMode={resizeMode} />);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('source', () => {
|
||||
it('can be set to a local image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image source={require('./img/img1.png')} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-scale="1"
|
||||
source-size="{1, 1}"
|
||||
source-type="local"
|
||||
source-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('can be set to a remote image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
source={{
|
||||
uri: 'https://reactnative.dev/img/tiny_logo.png',
|
||||
width: 100,
|
||||
height: 100,
|
||||
scale: 2,
|
||||
cache: 'only-if-cached',
|
||||
method: 'POST',
|
||||
body: 'name=React+Native',
|
||||
headers: {
|
||||
Authorization: 'Basic RandomString',
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-body="name=React+Native"
|
||||
source-cache="only-if-cached"
|
||||
source-header-Authorization="Basic RandomString"
|
||||
source-method="POST"
|
||||
source-scale="2"
|
||||
source-size="{100, 100}"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('can be set to a list of remote images', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
source={[
|
||||
{
|
||||
uri: 'https://reactnative.dev/img/tiny_logo.png',
|
||||
scale: 1,
|
||||
headers: {
|
||||
Authorization: 'Basic RandomString',
|
||||
},
|
||||
},
|
||||
{
|
||||
uri: 'https://reactnative.dev/img/medium_logo.png',
|
||||
scale: 2,
|
||||
cache: 'only-if-cached',
|
||||
},
|
||||
{
|
||||
uri: 'https://reactnative.dev/img/large_logo.png',
|
||||
scale: 3,
|
||||
method: 'POST',
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-header-Authorization="Basic RandomString"
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-cache="only-if-cached"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/medium_logo.png"
|
||||
source-3x-method="POST"
|
||||
source-3x-type="remote"
|
||||
source-3x-uri="https://reactnative.dev/img/large_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('src', () => {
|
||||
it('can be set to a remote image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image src="https://reactnative.dev/img/tiny_logo.png" />,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-scale="1"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('takes precedence over `source` prop', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
src="https://reactnative.dev/img/tiny_logo.png"
|
||||
source={{uri: 'https://reactnative.dev/img/medium_logo.png'}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-scale="1"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('srcSet', () => {
|
||||
it('can be set to a list of remote images', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
srcSet={
|
||||
'https://reactnative.dev/img/tiny_logo.png 1x, https://reactnative.dev/img/header_logo.svg 2x'
|
||||
}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('defaults to `1x` descriptor', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
srcSet={
|
||||
'https://reactnative.dev/img/tiny_logo.png, https://reactnative.dev/img/header_logo.svg 2x'
|
||||
}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('uses `src` for `1x` descriptor when provided', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
srcSet={
|
||||
'https://reactnative.dev/img/header_logo.svg 2x, https://reactnative.dev/img/large_logo.svg 3x'
|
||||
}
|
||||
src="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
|
||||
source-3x-type="remote"
|
||||
source-3x-uri="https://reactnative.dev/img/large_logo.svg"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('style', () => {
|
||||
it('can be set', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
resizeMode: 'contain',
|
||||
}}
|
||||
source={LOGO_SOURCE}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput().toJSX()).toEqual(
|
||||
<rn-image
|
||||
height="100.000000"
|
||||
overflow="hidden"
|
||||
resizeMode="contain"
|
||||
width="100.000000"
|
||||
source-scale="1"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('testID', () => {
|
||||
it('can be set', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image testID="test" source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['testID']}).toJSX()).toEqual(
|
||||
<rn-image testID="test" />,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tintColor', () => {
|
||||
it('can be set', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image tintColor="red" source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['tintColor']}).toJSX()).toEqual(
|
||||
<rn-image tintColor="rgba(255, 0, 0, 1)" />,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ref', () => {
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
|
||||
|
||||
import type {HostInstance} from 'react-native';
|
||||
|
||||
import * as Fantom from '@react-native/fantom';
|
||||
import * as React from 'react';
|
||||
import {createRef} from 'react';
|
||||
import {ImageBackground} from 'react-native';
|
||||
import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance';
|
||||
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
|
||||
|
||||
describe('<ImageBackground>', () => {
|
||||
describe('props', () => {
|
||||
describe('ImageProps', () => {
|
||||
it('can have local source', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<ImageBackground source={require('./img/img1.png')} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-scale="1"
|
||||
source-size="{1, 1}"
|
||||
source-type="local"
|
||||
source-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('can have remote source', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<ImageBackground
|
||||
source={{
|
||||
uri: 'https://reactnative.dev/img/tiny_logo.png',
|
||||
width: 100,
|
||||
height: 100,
|
||||
scale: 2,
|
||||
cache: 'only-if-cached',
|
||||
method: 'POST',
|
||||
body: 'name=React+Native',
|
||||
headers: {
|
||||
Authorization: 'Basic RandomString',
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-body="name=React+Native"
|
||||
source-cache="only-if-cached"
|
||||
source-header-Authorization="Basic RandomString"
|
||||
source-method="POST"
|
||||
source-scale="2"
|
||||
source-size="{100, 100}"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('can have srcSet', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<ImageBackground srcSet="https://reactnative.dev/img/tiny_logo.png 1x, https://reactnative.dev/img/header_logo.svg 2x" />,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('style', () => {
|
||||
it('can be set', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<ImageBackground style={{width: 100, height: 100}} />);
|
||||
});
|
||||
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['width|height']}).toJSX(),
|
||||
).toEqual(<rn-image width="100.000000" height="100.000000" />);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ref', () => {
|
||||
it('Allows to set a reference to the inner `Image` component', () => {
|
||||
const elementRef = createRef<HostInstance>();
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<ImageBackground imageRef={elementRef} />);
|
||||
});
|
||||
|
||||
const image = ensureInstance(elementRef.current, ReactNativeElement);
|
||||
expect(image.tagName).toBe('RN:Image');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -164,7 +164,7 @@ const InteractionManagerStub = {
|
||||
*/
|
||||
addListener(
|
||||
eventType: string,
|
||||
// $FlowIgnore[unclear-type]
|
||||
// $FlowFixMe[unclear-type]
|
||||
listener: (...args: any) => mixed,
|
||||
context: mixed,
|
||||
): EventSubscription {
|
||||
|
||||
@@ -43,7 +43,7 @@ Pod::Spec.new do |s|
|
||||
s.dependency "RCTTypeSafety"
|
||||
s.dependency "React-jsi"
|
||||
s.dependency "React-Core/RCTNetworkHeaders"
|
||||
|
||||
add_dependency(s, "React-debug")
|
||||
add_dependency(s, "React-RCTFBReactNativeSpec")
|
||||
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
|
||||
add_dependency(s, "React-featureflags")
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow
|
||||
* @generated SignedSource<<16b364e89f43b8a47832b0dfb98af11e>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<cf323fc5ca893bab5669c7d321660412>>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow strict-local
|
||||
* @generated SignedSource<<1dd9e9c3f20e37ae14e485fc6ee3d9e9>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<908f5fb85384725318e261f40e49d9a6>>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow
|
||||
* @generated SignedSource<<e2c46705ed927302dbe9332dafba459d>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<8f46fdc9267fcc4fdc9e76842fe24066>>
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
+1
-3
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow strict-local
|
||||
* @generated SignedSource<<e8dce0e82b831c91465d04b49fb48ab2>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<83073425aa3f71ced2c8c51f25a25938>>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
+1
-3
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow strict-local
|
||||
* @generated SignedSource<<556d1487de0b9e4a09cbc67dd130a884>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<52163887de05f1cff05388145cf85b3b>>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -18,9 +18,9 @@ export default function splitLayoutProps(props: ?____ViewStyle_Internal): {
|
||||
let inner: ?____ViewStyle_Internal = null;
|
||||
|
||||
if (props != null) {
|
||||
// $FlowIgnore[incompatible-exact] Will contain a subset of keys from `props`.
|
||||
// $FlowFixMe[incompatible-exact] Will contain a subset of keys from `props`.
|
||||
outer = {};
|
||||
// $FlowIgnore[incompatible-exact] Will contain a subset of keys from `props`.
|
||||
// $FlowFixMe[incompatible-exact] Will contain a subset of keys from `props`.
|
||||
inner = {};
|
||||
|
||||
for (const prop of Object.keys(props)) {
|
||||
|
||||
+517
-243
@@ -14,6 +14,7 @@ import type {GestureResponderEvent} from '../Types/CoreEventTypes';
|
||||
import type {NativeTextProps} from './TextNativeComponent';
|
||||
import type {PressRetentionOffset, TextProps} from './TextProps';
|
||||
|
||||
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
|
||||
import * as PressabilityDebug from '../Pressability/PressabilityDebug';
|
||||
import usePressability from '../Pressability/usePressability';
|
||||
import flattenStyle from '../StyleSheet/flattenStyle';
|
||||
@@ -35,156 +36,495 @@ type TextForwardRef = React.ElementRef<
|
||||
*
|
||||
* @see https://reactnative.dev/docs/text
|
||||
*/
|
||||
const TextImpl: component(
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...props: TextProps
|
||||
) = ({
|
||||
ref: forwardedRef,
|
||||
accessible,
|
||||
accessibilityLabel,
|
||||
accessibilityState,
|
||||
allowFontScaling,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-selected': ariaSelected,
|
||||
children,
|
||||
ellipsizeMode,
|
||||
disabled,
|
||||
id,
|
||||
nativeID,
|
||||
numberOfLines,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
selectable,
|
||||
selectionColor,
|
||||
suppressHighlighting,
|
||||
style,
|
||||
...restProps
|
||||
}: {
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...TextProps,
|
||||
}) => {
|
||||
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
|
||||
|
||||
let _accessibilityState: ?TextProps['accessibilityState'] =
|
||||
accessibilityState;
|
||||
if (
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
) {
|
||||
if (_accessibilityState != null) {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy ?? _accessibilityState.busy,
|
||||
checked: ariaChecked ?? _accessibilityState.checked,
|
||||
disabled: ariaDisabled ?? _accessibilityState.disabled,
|
||||
expanded: ariaExpanded ?? _accessibilityState.expanded,
|
||||
selected: ariaSelected ?? _accessibilityState.selected,
|
||||
};
|
||||
} else {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy,
|
||||
checked: ariaChecked,
|
||||
disabled: ariaDisabled,
|
||||
expanded: ariaExpanded,
|
||||
selected: ariaSelected,
|
||||
};
|
||||
let _TextImpl;
|
||||
if (ReactNativeFeatureFlags.reduceDefaultPropsInText()) {
|
||||
const TextImplNoDefaultProps: component(
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...props: TextProps
|
||||
) = ({
|
||||
ref: forwardedRef,
|
||||
accessible,
|
||||
accessibilityLabel,
|
||||
accessibilityState,
|
||||
allowFontScaling,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-selected': ariaSelected,
|
||||
children,
|
||||
ellipsizeMode,
|
||||
disabled,
|
||||
id,
|
||||
nativeID,
|
||||
numberOfLines,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
selectable,
|
||||
selectionColor,
|
||||
suppressHighlighting,
|
||||
style,
|
||||
...restProps
|
||||
}: {
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...TextProps,
|
||||
}) => {
|
||||
const processedProps = restProps as {
|
||||
...NativeTextProps,
|
||||
};
|
||||
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
|
||||
let _accessibilityState: ?TextProps['accessibilityState'] =
|
||||
accessibilityState;
|
||||
if (
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
) {
|
||||
if (_accessibilityState != null) {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy ?? _accessibilityState.busy,
|
||||
checked: ariaChecked ?? _accessibilityState.checked,
|
||||
disabled: ariaDisabled ?? _accessibilityState.disabled,
|
||||
expanded: ariaExpanded ?? _accessibilityState.expanded,
|
||||
selected: ariaSelected ?? _accessibilityState.selected,
|
||||
};
|
||||
} else {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy,
|
||||
checked: ariaChecked,
|
||||
disabled: ariaDisabled,
|
||||
expanded: ariaExpanded,
|
||||
selected: ariaSelected,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _accessibilityStateDisabled = _accessibilityState?.disabled;
|
||||
const _disabled = disabled ?? _accessibilityStateDisabled;
|
||||
const _accessibilityStateDisabled = _accessibilityState?.disabled;
|
||||
const _disabled = disabled ?? _accessibilityStateDisabled;
|
||||
|
||||
const isPressable =
|
||||
(onPress != null ||
|
||||
onLongPress != null ||
|
||||
onStartShouldSetResponder != null) &&
|
||||
_disabled !== true;
|
||||
|
||||
// TODO: Move this processing to the view configuration.
|
||||
const _selectionColor =
|
||||
selectionColor != null ? processColor(selectionColor) : undefined;
|
||||
|
||||
let _style = style;
|
||||
if (__DEV__) {
|
||||
if (PressabilityDebug.isEnabled() && onPress != null) {
|
||||
_style = [style, {color: 'magenta'}];
|
||||
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
|
||||
// falsy states we need to update the accessibilityState object to use the disabled prop.
|
||||
if (
|
||||
_accessibilityState != null &&
|
||||
_disabled !== _accessibilityStateDisabled &&
|
||||
((_disabled != null && _disabled !== false) ||
|
||||
(_accessibilityStateDisabled != null &&
|
||||
_accessibilityStateDisabled !== false))
|
||||
) {
|
||||
_accessibilityState.disabled = _disabled;
|
||||
}
|
||||
}
|
||||
|
||||
let _numberOfLines = numberOfLines;
|
||||
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
|
||||
const _accessible = Platform.select({
|
||||
ios: accessible !== false,
|
||||
android:
|
||||
accessible == null
|
||||
? onPress != null || onLongPress != null
|
||||
: accessible,
|
||||
default: accessible,
|
||||
});
|
||||
|
||||
const isPressable =
|
||||
(onPress != null ||
|
||||
onLongPress != null ||
|
||||
onStartShouldSetResponder != null) &&
|
||||
_disabled !== true;
|
||||
|
||||
// TODO: Move this processing to the view configuration.
|
||||
const _selectionColor =
|
||||
selectionColor != null ? processColor(selectionColor) : undefined;
|
||||
|
||||
let _style = style;
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
|
||||
if (PressabilityDebug.isEnabled() && onPress != null) {
|
||||
_style = [style, {color: 'magenta'}];
|
||||
}
|
||||
}
|
||||
|
||||
let _numberOfLines = numberOfLines;
|
||||
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
|
||||
);
|
||||
}
|
||||
_numberOfLines = 0;
|
||||
}
|
||||
|
||||
let _selectable = selectable;
|
||||
|
||||
let processedStyle = flattenStyle<TextStyleProp>(_style);
|
||||
if (processedStyle != null) {
|
||||
let overrides: ?{...TextStyleInternal} = null;
|
||||
if (typeof processedStyle.fontWeight === 'number') {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.fontWeight =
|
||||
// $FlowFixMe[incompatible-cast]
|
||||
(String(processedStyle.fontWeight): TextStyleInternal['fontWeight']);
|
||||
}
|
||||
|
||||
if (processedStyle.userSelect != null) {
|
||||
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.userSelect = undefined;
|
||||
}
|
||||
|
||||
if (processedStyle.verticalAlign != null) {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.textAlignVertical =
|
||||
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
|
||||
overrides.verticalAlign = undefined;
|
||||
}
|
||||
|
||||
if (overrides != null) {
|
||||
// $FlowFixMe[incompatible-type]
|
||||
_style = [_style, overrides];
|
||||
}
|
||||
}
|
||||
|
||||
const _nativeID = id ?? nativeID;
|
||||
|
||||
if (_accessibilityLabel !== undefined) {
|
||||
processedProps.accessibilityLabel = _accessibilityLabel;
|
||||
}
|
||||
if (_accessibilityState !== undefined) {
|
||||
processedProps.accessibilityState = _accessibilityState;
|
||||
}
|
||||
if (_nativeID !== undefined) {
|
||||
processedProps.nativeID = _nativeID;
|
||||
}
|
||||
if (_numberOfLines !== undefined) {
|
||||
processedProps.numberOfLines = _numberOfLines;
|
||||
}
|
||||
if (_selectable !== undefined) {
|
||||
processedProps.selectable = _selectable;
|
||||
}
|
||||
if (_style !== undefined) {
|
||||
processedProps.style = _style;
|
||||
}
|
||||
if (_selectionColor !== undefined) {
|
||||
processedProps.selectionColor = _selectionColor;
|
||||
}
|
||||
|
||||
let textPressabilityProps: ?TextPressabilityProps;
|
||||
if (isPressable) {
|
||||
textPressabilityProps = {
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
suppressHighlighting,
|
||||
};
|
||||
}
|
||||
|
||||
const hasTextAncestor = useContext(TextAncestorContext);
|
||||
if (hasTextAncestor) {
|
||||
processedProps.disabled = disabled;
|
||||
processedProps.children = children;
|
||||
if (isPressable) {
|
||||
return (
|
||||
<NativePressableVirtualText
|
||||
ref={forwardedRef}
|
||||
textProps={processedProps}
|
||||
textPressabilityProps={textPressabilityProps ?? {}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <NativeVirtualText {...processedProps} ref={forwardedRef} />;
|
||||
}
|
||||
|
||||
let nativeText = null;
|
||||
|
||||
processedProps.accessible = _accessible;
|
||||
processedProps.allowFontScaling = allowFontScaling !== false;
|
||||
processedProps.disabled = _disabled;
|
||||
processedProps.ellipsizeMode = ellipsizeMode ?? 'tail';
|
||||
processedProps.children = children;
|
||||
|
||||
if (isPressable) {
|
||||
nativeText = (
|
||||
<NativePressableText
|
||||
ref={forwardedRef}
|
||||
textProps={processedProps}
|
||||
textPressabilityProps={textPressabilityProps ?? {}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
nativeText = <NativeText {...processedProps} ref={forwardedRef} />;
|
||||
}
|
||||
|
||||
if (children == null) {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
// If the children do not contain a JSX element it would not be possible to have a
|
||||
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
|
||||
// which has a performance overhead. Since we do this for performance reasons we need
|
||||
// to keep the check simple to avoid regressing overall perf. For this reason the
|
||||
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
|
||||
// to capture the majority of `Text` uses but also not make this check too expensive.
|
||||
if (Array.isArray(children) && children.length <= 3) {
|
||||
let hasNonTextChild = false;
|
||||
for (let child of children) {
|
||||
if (child != null && typeof child === 'object') {
|
||||
hasNonTextChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNonTextChild) {
|
||||
return nativeText;
|
||||
}
|
||||
} else if (typeof children !== 'object') {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
|
||||
};
|
||||
_TextImpl = TextImplNoDefaultProps;
|
||||
} else {
|
||||
const TextImplLegacy: component(
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...props: TextProps
|
||||
) = ({
|
||||
ref: forwardedRef,
|
||||
accessible,
|
||||
accessibilityLabel,
|
||||
accessibilityState,
|
||||
allowFontScaling,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-selected': ariaSelected,
|
||||
children,
|
||||
ellipsizeMode,
|
||||
disabled,
|
||||
id,
|
||||
nativeID,
|
||||
numberOfLines,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
selectable,
|
||||
selectionColor,
|
||||
suppressHighlighting,
|
||||
style,
|
||||
...restProps
|
||||
}: {
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...TextProps,
|
||||
}) => {
|
||||
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
|
||||
|
||||
let _accessibilityState: ?TextProps['accessibilityState'] =
|
||||
accessibilityState;
|
||||
if (
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
) {
|
||||
if (_accessibilityState != null) {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy ?? _accessibilityState.busy,
|
||||
checked: ariaChecked ?? _accessibilityState.checked,
|
||||
disabled: ariaDisabled ?? _accessibilityState.disabled,
|
||||
expanded: ariaExpanded ?? _accessibilityState.expanded,
|
||||
selected: ariaSelected ?? _accessibilityState.selected,
|
||||
};
|
||||
} else {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy,
|
||||
checked: ariaChecked,
|
||||
disabled: ariaDisabled,
|
||||
expanded: ariaExpanded,
|
||||
selected: ariaSelected,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const _accessibilityStateDisabled = _accessibilityState?.disabled;
|
||||
const _disabled = disabled ?? _accessibilityStateDisabled;
|
||||
|
||||
const isPressable =
|
||||
(onPress != null ||
|
||||
onLongPress != null ||
|
||||
onStartShouldSetResponder != null) &&
|
||||
_disabled !== true;
|
||||
|
||||
// TODO: Move this processing to the view configuration.
|
||||
const _selectionColor =
|
||||
selectionColor != null ? processColor(selectionColor) : undefined;
|
||||
|
||||
let _style = style;
|
||||
if (__DEV__) {
|
||||
if (PressabilityDebug.isEnabled() && onPress != null) {
|
||||
_style = [style, {color: 'magenta'}];
|
||||
}
|
||||
}
|
||||
|
||||
let _numberOfLines = numberOfLines;
|
||||
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
|
||||
);
|
||||
}
|
||||
_numberOfLines = 0;
|
||||
}
|
||||
|
||||
let _selectable = selectable;
|
||||
|
||||
let processedStyle = flattenStyle<TextStyleProp>(_style);
|
||||
if (processedStyle != null) {
|
||||
let overrides: ?{...TextStyleInternal} = null;
|
||||
if (typeof processedStyle.fontWeight === 'number') {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.fontWeight =
|
||||
// $FlowFixMe[incompatible-cast]
|
||||
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
|
||||
}
|
||||
|
||||
if (processedStyle.userSelect != null) {
|
||||
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.userSelect = undefined;
|
||||
}
|
||||
|
||||
if (processedStyle.verticalAlign != null) {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.textAlignVertical =
|
||||
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
|
||||
overrides.verticalAlign = undefined;
|
||||
}
|
||||
|
||||
if (overrides != null) {
|
||||
// $FlowFixMe[incompatible-type]
|
||||
_style = [_style, overrides];
|
||||
}
|
||||
}
|
||||
|
||||
const _nativeID = id ?? nativeID;
|
||||
|
||||
const hasTextAncestor = useContext(TextAncestorContext);
|
||||
if (hasTextAncestor) {
|
||||
if (isPressable) {
|
||||
return (
|
||||
<NativePressableVirtualText
|
||||
ref={forwardedRef}
|
||||
textProps={{
|
||||
...restProps,
|
||||
accessibilityLabel: _accessibilityLabel,
|
||||
accessibilityState: _accessibilityState,
|
||||
nativeID: _nativeID,
|
||||
numberOfLines: _numberOfLines,
|
||||
selectable: _selectable,
|
||||
selectionColor: _selectionColor,
|
||||
style: _style,
|
||||
disabled: disabled,
|
||||
children,
|
||||
}}
|
||||
textPressabilityProps={{
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
suppressHighlighting,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NativeVirtualText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}
|
||||
disabled={disabled}>
|
||||
{children}
|
||||
</NativeVirtualText>
|
||||
);
|
||||
}
|
||||
_numberOfLines = 0;
|
||||
}
|
||||
|
||||
let _selectable = selectable;
|
||||
|
||||
let processedStyle = flattenStyle<TextStyleProp>(_style);
|
||||
if (processedStyle != null) {
|
||||
let overrides: ?{...TextStyleInternal} = null;
|
||||
if (typeof processedStyle.fontWeight === 'number') {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.fontWeight =
|
||||
// $FlowFixMe[incompatible-cast]
|
||||
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
|
||||
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
|
||||
// falsy states we need to update the accessibilityState object to use the disabled prop.
|
||||
if (
|
||||
_disabled !== _accessibilityStateDisabled &&
|
||||
((_disabled != null && _disabled !== false) ||
|
||||
(_accessibilityStateDisabled != null &&
|
||||
_accessibilityStateDisabled !== false))
|
||||
) {
|
||||
_accessibilityState = {..._accessibilityState, disabled: _disabled};
|
||||
}
|
||||
|
||||
if (processedStyle.userSelect != null) {
|
||||
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.userSelect = undefined;
|
||||
}
|
||||
const _accessible = Platform.select({
|
||||
ios: accessible !== false,
|
||||
android:
|
||||
accessible == null
|
||||
? onPress != null || onLongPress != null
|
||||
: accessible,
|
||||
default: accessible,
|
||||
});
|
||||
|
||||
if (processedStyle.verticalAlign != null) {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.textAlignVertical =
|
||||
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
|
||||
overrides.verticalAlign = undefined;
|
||||
}
|
||||
|
||||
if (overrides != null) {
|
||||
// $FlowFixMe[incompatible-type]
|
||||
_style = [_style, overrides];
|
||||
}
|
||||
}
|
||||
|
||||
const _nativeID = id ?? nativeID;
|
||||
|
||||
const hasTextAncestor = useContext(TextAncestorContext);
|
||||
if (hasTextAncestor) {
|
||||
let nativeText = null;
|
||||
if (isPressable) {
|
||||
return (
|
||||
<NativePressableVirtualText
|
||||
nativeText = (
|
||||
<NativePressableText
|
||||
ref={forwardedRef}
|
||||
textProps={{
|
||||
...restProps,
|
||||
accessibilityLabel: _accessibilityLabel,
|
||||
accessibilityState: _accessibilityState,
|
||||
accessible: _accessible,
|
||||
allowFontScaling: allowFontScaling !== false,
|
||||
disabled: _disabled,
|
||||
ellipsizeMode: ellipsizeMode ?? 'tail',
|
||||
nativeID: _nativeID,
|
||||
numberOfLines: _numberOfLines,
|
||||
selectable: _selectable,
|
||||
selectionColor: _selectionColor,
|
||||
style: _style,
|
||||
disabled: disabled,
|
||||
children,
|
||||
}}
|
||||
textPressabilityProps={{
|
||||
@@ -203,127 +543,61 @@ const TextImpl: component(
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
nativeText = (
|
||||
<NativeText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={_accessible}
|
||||
allowFontScaling={allowFontScaling !== false}
|
||||
disabled={_disabled}
|
||||
ellipsizeMode={ellipsizeMode ?? 'tail'}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}>
|
||||
{children}
|
||||
</NativeText>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NativeVirtualText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}
|
||||
disabled={disabled}>
|
||||
{children}
|
||||
</NativeVirtualText>
|
||||
);
|
||||
}
|
||||
|
||||
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
|
||||
// falsy states we need to update the accessibilityState object to use the disabled prop.
|
||||
if (
|
||||
_disabled !== _accessibilityStateDisabled &&
|
||||
((_disabled != null && _disabled !== false) ||
|
||||
(_accessibilityStateDisabled != null &&
|
||||
_accessibilityStateDisabled !== false))
|
||||
) {
|
||||
_accessibilityState = {..._accessibilityState, disabled: _disabled};
|
||||
}
|
||||
|
||||
const _accessible = Platform.select({
|
||||
ios: accessible !== false,
|
||||
android:
|
||||
accessible == null ? onPress != null || onLongPress != null : accessible,
|
||||
default: accessible,
|
||||
});
|
||||
|
||||
let nativeText = null;
|
||||
if (isPressable) {
|
||||
nativeText = (
|
||||
<NativePressableText
|
||||
ref={forwardedRef}
|
||||
textProps={{
|
||||
...restProps,
|
||||
accessibilityLabel: _accessibilityLabel,
|
||||
accessibilityState: _accessibilityState,
|
||||
accessible: _accessible,
|
||||
allowFontScaling: allowFontScaling !== false,
|
||||
disabled: _disabled,
|
||||
ellipsizeMode: ellipsizeMode ?? 'tail',
|
||||
nativeID: _nativeID,
|
||||
numberOfLines: _numberOfLines,
|
||||
selectable: _selectable,
|
||||
selectionColor: _selectionColor,
|
||||
style: _style,
|
||||
children,
|
||||
}}
|
||||
textPressabilityProps={{
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
suppressHighlighting,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
nativeText = (
|
||||
<NativeText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={_accessible}
|
||||
allowFontScaling={allowFontScaling !== false}
|
||||
disabled={_disabled}
|
||||
ellipsizeMode={ellipsizeMode ?? 'tail'}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}>
|
||||
{children}
|
||||
</NativeText>
|
||||
);
|
||||
}
|
||||
|
||||
if (children == null) {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
// If the children do not contain a JSX element it would not be possible to have a
|
||||
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
|
||||
// which has a performance overhead. Since we do this for performance reasons we need
|
||||
// to keep the check simple to avoid regressing overall perf. For this reason the
|
||||
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
|
||||
// to capture the majority of `Text` uses but also not make this check too expensive.
|
||||
if (Array.isArray(children) && children.length <= 3) {
|
||||
let hasNonTextChild = false;
|
||||
for (let child of children) {
|
||||
if (child != null && typeof child === 'object') {
|
||||
hasNonTextChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNonTextChild) {
|
||||
if (children == null) {
|
||||
return nativeText;
|
||||
}
|
||||
} else if (typeof children !== 'object') {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
|
||||
};
|
||||
// If the children do not contain a JSX element it would not be possible to have a
|
||||
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
|
||||
// which has a performance overhead. Since we do this for performance reasons we need
|
||||
// to keep the check simple to avoid regressing overall perf. For this reason the
|
||||
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
|
||||
// to capture the majority of `Text` uses but also not make this check too expensive.
|
||||
if (Array.isArray(children) && children.length <= 3) {
|
||||
let hasNonTextChild = false;
|
||||
for (let child of children) {
|
||||
if (child != null && typeof child === 'object') {
|
||||
hasNonTextChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNonTextChild) {
|
||||
return nativeText;
|
||||
}
|
||||
} else if (typeof children !== 'object') {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
|
||||
};
|
||||
_TextImpl = TextImplLegacy;
|
||||
}
|
||||
|
||||
const TextImpl: component(
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...props: TextProps
|
||||
) = _TextImpl;
|
||||
|
||||
TextImpl.displayName = 'Text';
|
||||
|
||||
|
||||
@@ -128,7 +128,6 @@ const HMRClient: HMRClientNativeInterface = {
|
||||
JSON.stringify({
|
||||
type: 'log',
|
||||
level,
|
||||
mode: global.RN$Bridgeless === true ? 'NOBRIDGE' : 'BRIDGE',
|
||||
data: data.map(item =>
|
||||
typeof item === 'string'
|
||||
? item
|
||||
|
||||
@@ -43,7 +43,7 @@ function TestComponent(
|
||||
}
|
||||
|
||||
function id(instance: HostInstance | null): string | null {
|
||||
// $FlowIgnore[prop-missing] - Intentional.
|
||||
// $FlowFixMe[prop-missing] - Intentional.
|
||||
return instance?.props?.id ?? null;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ test('accepts a ref object', () => {
|
||||
const ledger: Array<{[string]: string | null}> = [];
|
||||
|
||||
const ref = {
|
||||
// $FlowIgnore[unsafe-getters-setters] - Intentional.
|
||||
// $FlowFixMe[unsafe-getters-setters] - Intentional.
|
||||
set current(current: HostInstance | null) {
|
||||
ledger.push({ref: id(current)});
|
||||
},
|
||||
@@ -140,7 +140,7 @@ test('invokes refs in order', () => {
|
||||
ledger.push({refA: id(current)});
|
||||
};
|
||||
const refB = {
|
||||
// $FlowIgnore[unsafe-getters-setters] - Intentional.
|
||||
// $FlowFixMe[unsafe-getters-setters] - Intentional.
|
||||
set current(current: HostInstance | null) {
|
||||
ledger.push({refB: id(current)});
|
||||
},
|
||||
@@ -149,7 +149,7 @@ test('invokes refs in order', () => {
|
||||
ledger.push({refC: id(current)});
|
||||
};
|
||||
const refD = {
|
||||
// $FlowIgnore[unsafe-getters-setters] - Intentional.
|
||||
// $FlowFixMe[unsafe-getters-setters] - Intentional.
|
||||
set current(current: HostInstance | null) {
|
||||
ledger.push({refD: id(current)});
|
||||
},
|
||||
|
||||
@@ -52,7 +52,7 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-RCTImage", version
|
||||
s.dependency "React-jsi", version
|
||||
s.dependency 'React-RCTBlob'
|
||||
|
||||
add_dependency(s, "React-debug")
|
||||
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
|
||||
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
|
||||
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
|
||||
|
||||
+22
@@ -96,6 +96,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
|
||||
@interface RCTModalHostViewComponentView () <RCTFabricModalHostViewControllerDelegate>
|
||||
|
||||
@property (nonatomic, weak) UIView *accessibilityFocusedView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTModalHostViewComponentView {
|
||||
@@ -148,6 +150,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
{
|
||||
BOOL shouldBePresented = !_isPresented && _shouldPresent && self.window;
|
||||
if (shouldBePresented) {
|
||||
[self saveAccessibilityFocusedView];
|
||||
self.viewController.presentationController.delegate = self;
|
||||
|
||||
_isPresented = YES;
|
||||
@@ -179,6 +182,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
if (eventEmitter) {
|
||||
eventEmitter->onDismiss(ModalHostViewEventEmitter::OnDismiss{});
|
||||
}
|
||||
|
||||
[self restoreAccessibilityFocusedView];
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -207,6 +212,23 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
[self ensurePresentedOnlyIfNeeded];
|
||||
}
|
||||
|
||||
- (void)saveAccessibilityFocusedView
|
||||
{
|
||||
id focusedElement = UIAccessibilityFocusedElement(nil);
|
||||
if (focusedElement && [focusedElement isKindOfClass:[UIView class]]) {
|
||||
self.accessibilityFocusedView = (UIView *)focusedElement;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)restoreAccessibilityFocusedView
|
||||
{
|
||||
id viewToFocus = self.accessibilityFocusedView;
|
||||
if (viewToFocus) {
|
||||
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, viewToFocus);
|
||||
self.accessibilityFocusedView = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RCTFabricModalHostViewControllerDelegate
|
||||
|
||||
- (void)boundsDidChange:(CGRect)newBounds
|
||||
|
||||
@@ -92,6 +92,7 @@ Pod::Spec.new do |s|
|
||||
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
|
||||
add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork')
|
||||
add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing')
|
||||
add_dependency(s, "React-performancecdpmetrics", :framework_name => 'React_performancecdpmetrics')
|
||||
add_dependency(s, "React-renderercss")
|
||||
add_dependency(s, "React-RCTFBReactNativeSpec")
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ Pod::Spec.new do |s|
|
||||
|
||||
s.dependency "React-Core"
|
||||
s.dependency "React-jsi"
|
||||
add_dependency(s, "React-debug")
|
||||
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
|
||||
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
|
||||
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
|
||||
|
||||
@@ -3348,8 +3348,10 @@ public final class com/facebook/react/uimanager/DisplayMetricsHolder {
|
||||
public static final fun getDisplayMetricsWritableMap (D)Lcom/facebook/react/bridge/WritableMap;
|
||||
public static final fun getScreenDisplayMetrics ()Landroid/util/DisplayMetrics;
|
||||
public static final fun getWindowDisplayMetrics ()Landroid/util/DisplayMetrics;
|
||||
public static final fun initDisplayMetrics (Landroid/content/Context;)V
|
||||
public static final fun initDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
|
||||
public static final fun initScreenDisplayMetrics (Landroid/content/Context;)V
|
||||
public static final fun initScreenDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
|
||||
public static final fun initWindowDisplayMetrics (Landroid/content/Context;)V
|
||||
public static final fun initWindowDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
|
||||
public static final fun setScreenDisplayMetrics (Landroid/util/DisplayMetrics;)V
|
||||
public static final fun setWindowDisplayMetrics (Landroid/util/DisplayMetrics;)V
|
||||
}
|
||||
|
||||
@@ -630,6 +630,7 @@ dependencies {
|
||||
api(libs.androidx.autofill)
|
||||
api(libs.androidx.swiperefreshlayout)
|
||||
api(libs.androidx.tracing)
|
||||
api(libs.androidx.window)
|
||||
|
||||
api(libs.fbjni)
|
||||
api(libs.fresco)
|
||||
|
||||
+15
-1
@@ -229,6 +229,9 @@ public class ReactInstanceManager {
|
||||
return new ReactInstanceManagerBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection deprecation
|
||||
*/
|
||||
/* package */ ReactInstanceManager(
|
||||
Context applicationContext,
|
||||
@Nullable Activity currentActivity,
|
||||
@@ -259,7 +262,11 @@ public class ReactInstanceManager {
|
||||
FLog.d(TAG, "ReactInstanceManager.ctor()");
|
||||
initializeSoLoaderIfNecessary(applicationContext);
|
||||
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext);
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(applicationContext);
|
||||
|
||||
if (currentActivity != null) {
|
||||
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
|
||||
}
|
||||
|
||||
// See {@code ReactInstanceManagerBuilder} for description of all flags here.
|
||||
mApplicationContext = applicationContext;
|
||||
@@ -924,6 +931,13 @@ public class ReactInstanceManager {
|
||||
|
||||
ReactContext currentReactContext = getCurrentReactContext();
|
||||
if (currentReactContext != null) {
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(currentReactContext);
|
||||
Activity currentActivity = currentReactContext.getCurrentActivity();
|
||||
|
||||
if (currentActivity != null) {
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(currentActivity);
|
||||
}
|
||||
|
||||
AppearanceModule appearanceModule =
|
||||
currentReactContext.getNativeModule(AppearanceModule.class);
|
||||
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ import java.util.List;
|
||||
*
|
||||
* @deprecated This class will be replaced by com.facebook.react.ReactHost in the New Architecture.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(
|
||||
since = "This class is part of Legacy Architecture and will be removed in a future release")
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public abstract class ReactNativeHost {
|
||||
|
||||
+6
-5
@@ -136,9 +136,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
|
||||
setClipChildren(false);
|
||||
|
||||
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
|
||||
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
|
||||
}
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(getContext());
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -883,7 +882,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
private int mDeviceRotation = 0;
|
||||
|
||||
/* package */ CustomGlobalLayoutListener() {
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(getContext());
|
||||
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(getContext());
|
||||
mVisibleViewArea = new Rect();
|
||||
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
|
||||
}
|
||||
@@ -1006,7 +1006,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
return;
|
||||
}
|
||||
mDeviceRotation = rotation;
|
||||
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(getContext());
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(getContext());
|
||||
emitOrientationChanged(rotation);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -7,13 +7,13 @@
|
||||
|
||||
package com.facebook.react.bridge
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
import com.facebook.react.common.annotations.internal.InteropLegacyArchitecture
|
||||
|
||||
/**
|
||||
* This interface includes the methods needed to use a running JS instance, without specifying any
|
||||
* of the bridge-specific initialization or lifecycle management.
|
||||
*/
|
||||
@LegacyArchitecture
|
||||
@InteropLegacyArchitecture
|
||||
public interface JSInstance {
|
||||
public fun invokeCallback(callbackID: Int, arguments: NativeArrayInterface)
|
||||
|
||||
|
||||
+5
@@ -25,6 +25,11 @@ import com.facebook.react.packagerconnection.RequestHandler
|
||||
*/
|
||||
internal class DefaultDevSupportManagerFactory : DevSupportManagerFactory {
|
||||
|
||||
@Deprecated(
|
||||
"Use the other create() method with useDevSupport parameter for New Architecture. This method will be removed in a future release.",
|
||||
replaceWith =
|
||||
ReplaceWith(
|
||||
"create(applicationContext, reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, redBoxHandler, devBundleDownloadListener, minNumShakes, customPackagerCommandHandlers, surfaceDelegateFactory, devLoadingViewManager, pausedInDebuggerOverlayManager)"))
|
||||
override fun create(
|
||||
applicationContext: Context,
|
||||
reactInstanceManagerHelper: ReactInstanceDevHelper,
|
||||
|
||||
+6
@@ -22,6 +22,12 @@ public interface DevSupportManagerFactory {
|
||||
* Factory used by the Old Architecture flow to create a [DevSupportManager] and a
|
||||
* [BridgeDevSupportManager]
|
||||
*/
|
||||
@Deprecated(
|
||||
message =
|
||||
"Use the other create() method with useDevSupport parameter for New Architecture. This method will be removed in a future release.",
|
||||
replaceWith =
|
||||
ReplaceWith(
|
||||
"create(applicationContext, reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, redBoxHandler, devBundleDownloadListener, minNumShakes, customPackagerCommandHandlers, surfaceDelegateFactory, devLoadingViewManager, pausedInDebuggerOverlayManager)"))
|
||||
public fun create(
|
||||
applicationContext: Context,
|
||||
reactInstanceManagerHelper: ReactInstanceDevHelper,
|
||||
|
||||
+4
-2
@@ -15,7 +15,8 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.module.annotations.ReactModule
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.getDisplayMetricsWritableMap
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized
|
||||
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
|
||||
|
||||
/** Module that exposes Android Constants to JS. */
|
||||
@@ -26,7 +27,8 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
|
||||
private var previousDisplayMetrics: ReadableMap? = null
|
||||
|
||||
init {
|
||||
initDisplayMetricsIfNotInitialized(reactContext)
|
||||
initScreenDisplayMetricsIfNotInitialized(reactContext)
|
||||
reactContext.currentActivity?.let { initWindowDisplayMetricsIfNotInitialized(it) }
|
||||
reactContext.addLifecycleEventListener(this)
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -8,6 +8,7 @@
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStripAny
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
/**
|
||||
* [Experimental] An interface for reporting network events to the modern debugger server and Web
|
||||
@@ -19,6 +20,10 @@ import com.facebook.proguard.annotations.DoNotStripAny
|
||||
*/
|
||||
@DoNotStripAny
|
||||
internal object InspectorNetworkReporter {
|
||||
init {
|
||||
SoLoader.loadLibrary("react_devsupportjni")
|
||||
}
|
||||
|
||||
@JvmStatic external fun isDebuggingEnabled(): Boolean
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -625,9 +625,8 @@ public class ReactHostImpl(
|
||||
override fun onConfigurationChanged(context: Context) {
|
||||
val currentReactContext = this.currentReactContext
|
||||
if (currentReactContext != null) {
|
||||
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
|
||||
DisplayMetricsHolder.initDisplayMetrics(currentReactContext)
|
||||
}
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(currentReactContext)
|
||||
currentReactContext.currentActivity?.let { DisplayMetricsHolder.initWindowDisplayMetrics(it) }
|
||||
|
||||
val appearanceModule = currentReactContext.getNativeModule(AppearanceModule::class.java)
|
||||
appearanceModule?.onConfigurationChanged(context)
|
||||
@@ -918,6 +917,7 @@ public class ReactHostImpl(
|
||||
val instance =
|
||||
ReactInstance(
|
||||
reactContext,
|
||||
currentActivity,
|
||||
reactHostDelegate,
|
||||
componentFactory,
|
||||
devSupportManager,
|
||||
|
||||
+4
-1
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.facebook.react.runtime
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.AssetManager
|
||||
import android.view.View
|
||||
import com.facebook.common.logging.FLog
|
||||
@@ -88,6 +89,7 @@ import kotlin.jvm.JvmStatic
|
||||
@UnstableReactNativeAPI
|
||||
internal class ReactInstance(
|
||||
private val context: BridgelessReactContext,
|
||||
private val activity: Activity?,
|
||||
delegate: ReactHostDelegate,
|
||||
componentFactory: ComponentFactory,
|
||||
devSupportManager: DevSupportManager,
|
||||
@@ -240,7 +242,8 @@ internal class ReactInstance(
|
||||
FabricUIManager(context, ViewManagerRegistry(viewManagerResolver), eventBeatManager)
|
||||
|
||||
// Misc initialization that needs to be done before Fabric init
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(context)
|
||||
activity?.let { DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(it) }
|
||||
|
||||
val binding = FabricUIManagerBinding()
|
||||
binding.register(
|
||||
|
||||
+46
-17
@@ -13,16 +13,20 @@ import android.util.DisplayMetrics
|
||||
import android.view.WindowManager
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.bridge.WritableNativeMap
|
||||
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
|
||||
|
||||
/**
|
||||
* Holds an instance of the current DisplayMetrics so we don't have to thread it through all the
|
||||
* classes that need it.
|
||||
*/
|
||||
public object DisplayMetricsHolder {
|
||||
private const val INITIALIZATION_MISSING_MESSAGE =
|
||||
"DisplayMetricsHolder must be initialized with initDisplayMetricsIfNotInitialized or initDisplayMetrics"
|
||||
private const val SCREEN_INITIALIZATION_MISSING_MESSAGE =
|
||||
"DisplayMetricsHolder must be initialized with initScreenDisplayMetricsIfNotInitialized or initScreenDisplayMetrics"
|
||||
private const val WINDOW_INITIALIZATION_MISSING_MESSAGE =
|
||||
"DisplayMetricsHolder must be initialized with initWindowDisplayMetricsIfNotInitialized or initWindowDisplayMetrics"
|
||||
|
||||
@JvmStatic private var windowDisplayMetrics: DisplayMetrics? = null
|
||||
@JvmStatic private var screenDisplayMetrics: DisplayMetrics? = null
|
||||
@@ -30,7 +34,7 @@ public object DisplayMetricsHolder {
|
||||
/** The metrics of the window associated to the Context used to initialize ReactNative */
|
||||
@JvmStatic
|
||||
public fun getWindowDisplayMetrics(): DisplayMetrics {
|
||||
checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(windowDisplayMetrics) { WINDOW_INITIALIZATION_MISSING_MESSAGE }
|
||||
return windowDisplayMetrics as DisplayMetrics
|
||||
}
|
||||
|
||||
@@ -42,7 +46,7 @@ public object DisplayMetricsHolder {
|
||||
/** Screen metrics returns the metrics of the default screen on the device. */
|
||||
@JvmStatic
|
||||
public fun getScreenDisplayMetrics(): DisplayMetrics {
|
||||
checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(screenDisplayMetrics) { SCREEN_INITIALIZATION_MISSING_MESSAGE }
|
||||
return screenDisplayMetrics as DisplayMetrics
|
||||
}
|
||||
|
||||
@@ -52,33 +56,58 @@ public object DisplayMetricsHolder {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun initDisplayMetricsIfNotInitialized(context: Context) {
|
||||
if (screenDisplayMetrics != null) {
|
||||
return
|
||||
public fun initScreenDisplayMetricsIfNotInitialized(context: Context) {
|
||||
if (screenDisplayMetrics == null) {
|
||||
initScreenDisplayMetrics(context)
|
||||
}
|
||||
initDisplayMetrics(context)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun initDisplayMetrics(context: Context) {
|
||||
val displayMetrics = context.resources.displayMetrics
|
||||
windowDisplayMetrics = displayMetrics
|
||||
val screenDisplayMetrics = DisplayMetrics()
|
||||
screenDisplayMetrics.setTo(displayMetrics)
|
||||
public fun initWindowDisplayMetricsIfNotInitialized(context: Context) {
|
||||
if (windowDisplayMetrics == null) {
|
||||
initWindowDisplayMetrics(context)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun initScreenDisplayMetrics(context: Context) {
|
||||
val displayMetrics = DisplayMetrics()
|
||||
displayMetrics.setTo(context.resources.displayMetrics)
|
||||
|
||||
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
// Get the real display metrics if we are using API level 17 or higher.
|
||||
// The real metrics include system decor elements (e.g. soft menu bar).
|
||||
//
|
||||
// See:
|
||||
// http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
|
||||
@Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(screenDisplayMetrics)
|
||||
DisplayMetricsHolder.screenDisplayMetrics = screenDisplayMetrics
|
||||
@Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(displayMetrics)
|
||||
screenDisplayMetrics = displayMetrics
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Unlike [initScreenDisplayMetrics], this method needs a UiContext (Activity of
|
||||
* InputMethodService) else WindowMetircsCalculator will throw an exception.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun initWindowDisplayMetrics(context: Context) {
|
||||
val displayMetrics = DisplayMetrics()
|
||||
displayMetrics.setTo(context.resources.displayMetrics)
|
||||
|
||||
if (isEdgeToEdgeFeatureFlagOn) {
|
||||
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context).let { windowMetrics
|
||||
->
|
||||
displayMetrics.widthPixels = windowMetrics.bounds.width()
|
||||
displayMetrics.heightPixels = windowMetrics.bounds.height()
|
||||
}
|
||||
}
|
||||
|
||||
windowDisplayMetrics = displayMetrics
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun getDisplayMetricsWritableMap(fontScale: Double): WritableMap {
|
||||
checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(windowDisplayMetrics) { WINDOW_INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(screenDisplayMetrics) { SCREEN_INITIALIZATION_MISSING_MESSAGE }
|
||||
|
||||
return WritableNativeMap().apply {
|
||||
putMap(
|
||||
|
||||
+4
-4
@@ -20,7 +20,7 @@ public object PixelUtil {
|
||||
}
|
||||
|
||||
return TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, value, DisplayMetricsHolder.getWindowDisplayMetrics())
|
||||
TypedValue.COMPLEX_UNIT_DIP, value, DisplayMetricsHolder.getScreenDisplayMetrics())
|
||||
}
|
||||
|
||||
/** Convert from DIP to PX */
|
||||
@@ -37,7 +37,7 @@ public object PixelUtil {
|
||||
return Float.NaN
|
||||
}
|
||||
|
||||
val displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics()
|
||||
val displayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics()
|
||||
val scaledValue = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, displayMetrics)
|
||||
|
||||
if (maxFontScale >= 1) {
|
||||
@@ -60,13 +60,13 @@ public object PixelUtil {
|
||||
return Float.NaN
|
||||
}
|
||||
|
||||
return value / DisplayMetricsHolder.getWindowDisplayMetrics().density
|
||||
return value / DisplayMetricsHolder.getScreenDisplayMetrics().density
|
||||
}
|
||||
|
||||
/** @return [Float] that represents the density of the display metrics for device screen. */
|
||||
@JvmStatic
|
||||
public fun getDisplayMetricDensity(): Float =
|
||||
DisplayMetricsHolder.getWindowDisplayMetrics().density
|
||||
DisplayMetricsHolder.getScreenDisplayMetrics().density
|
||||
|
||||
/* Kotlin extensions */
|
||||
public fun Int.dpToPx(): Float = toPixelFromDIP(this.toFloat())
|
||||
|
||||
+25
-13
@@ -7,6 +7,8 @@
|
||||
|
||||
package com.facebook.react.uimanager;
|
||||
|
||||
import static com.facebook.infer.annotation.Assertions.assertNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
@@ -25,6 +27,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.Accessibilit
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat;
|
||||
import androidx.core.view.accessibility.AccessibilityNodeProviderCompat;
|
||||
import androidx.customview.widget.ExploreByTouchHelper;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.R;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
@@ -49,6 +52,7 @@ import java.util.Map;
|
||||
* Utility class that handles the addition of a "role" for accessibility to either a View or
|
||||
* AccessibilityNodeInfo.
|
||||
*/
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
|
||||
|
||||
public static final String TOP_ACCESSIBILITY_ACTION_EVENT = "topAccessibilityAction";
|
||||
@@ -73,12 +77,15 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
|
||||
@Nullable View mAccessibilityLabelledBy;
|
||||
|
||||
static {
|
||||
sActionIdMap.put("activate", AccessibilityActionCompat.ACTION_CLICK.getId());
|
||||
sActionIdMap.put("longpress", AccessibilityActionCompat.ACTION_LONG_CLICK.getId());
|
||||
sActionIdMap.put("increment", AccessibilityActionCompat.ACTION_SCROLL_FORWARD.getId());
|
||||
sActionIdMap.put("decrement", AccessibilityActionCompat.ACTION_SCROLL_BACKWARD.getId());
|
||||
sActionIdMap.put("expand", AccessibilityActionCompat.ACTION_EXPAND.getId());
|
||||
sActionIdMap.put("collapse", AccessibilityActionCompat.ACTION_COLLAPSE.getId());
|
||||
sActionIdMap.put("activate", assertNotNull(AccessibilityActionCompat.ACTION_CLICK).getId());
|
||||
sActionIdMap.put(
|
||||
"longpress", assertNotNull(AccessibilityActionCompat.ACTION_LONG_CLICK).getId());
|
||||
sActionIdMap.put(
|
||||
"increment", assertNotNull(AccessibilityActionCompat.ACTION_SCROLL_FORWARD).getId());
|
||||
sActionIdMap.put(
|
||||
"decrement", assertNotNull(AccessibilityActionCompat.ACTION_SCROLL_BACKWARD).getId());
|
||||
sActionIdMap.put("expand", assertNotNull(AccessibilityActionCompat.ACTION_EXPAND).getId());
|
||||
sActionIdMap.put("collapse", assertNotNull(AccessibilityActionCompat.ACTION_COLLAPSE).getId());
|
||||
}
|
||||
|
||||
public ReactAccessibilityDelegate(
|
||||
@@ -91,7 +98,9 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
View host = (View) msg.obj;
|
||||
host.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
|
||||
if (host != null) {
|
||||
host.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -190,13 +199,14 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
|
||||
|
||||
if (accessibilityActions != null) {
|
||||
for (int i = 0; i < accessibilityActions.size(); i++) {
|
||||
final ReadableMap action = accessibilityActions.getMap(i);
|
||||
if (!action.hasKey("name")) {
|
||||
@Nullable final ReadableMap action = accessibilityActions.getMap(i);
|
||||
if (action == null || !action.hasKey("name")) {
|
||||
throw new IllegalArgumentException("Unknown accessibility action.");
|
||||
}
|
||||
|
||||
String actionName = action.getString("name");
|
||||
String actionLabel = action.hasKey("label") ? action.getString("label") : null;
|
||||
// AccessibilityActionCompat actionLabel must be non-null
|
||||
String actionLabel = action.hasKey("label") ? assertNotNull(action.getString("label")) : "";
|
||||
int actionId;
|
||||
|
||||
if (sActionIdMap.containsKey(actionName)) {
|
||||
@@ -336,8 +346,9 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
|
||||
(AccessibilityRole) host.getTag(R.id.accessibility_role);
|
||||
final ReadableMap accessibilityValue = (ReadableMap) host.getTag(R.id.accessibility_value);
|
||||
if (accessibilityRole == AccessibilityRole.ADJUSTABLE
|
||||
&& (action == AccessibilityActionCompat.ACTION_SCROLL_FORWARD.getId()
|
||||
|| action == AccessibilityActionCompat.ACTION_SCROLL_BACKWARD.getId())) {
|
||||
&& (action == assertNotNull(AccessibilityActionCompat.ACTION_SCROLL_FORWARD).getId()
|
||||
|| action
|
||||
== assertNotNull(AccessibilityActionCompat.ACTION_SCROLL_BACKWARD).getId())) {
|
||||
if (accessibilityValue != null && !accessibilityValue.hasKey("text")) {
|
||||
scheduleAccessibilityEventSender(host);
|
||||
}
|
||||
@@ -631,7 +642,8 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
|
||||
return true;
|
||||
}
|
||||
|
||||
final List actionList = node.getActionList();
|
||||
final List<AccessibilityNodeInfoCompat.AccessibilityActionCompat> actionList =
|
||||
assertNotNull(node.getActionList());
|
||||
return actionList.contains(AccessibilityNodeInfoCompat.ACTION_CLICK)
|
||||
|| actionList.contains(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK)
|
||||
|| actionList.contains(AccessibilityNodeInfoCompat.ACTION_FOCUS);
|
||||
|
||||
+11
-2
@@ -12,6 +12,7 @@ import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_M
|
||||
import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;
|
||||
import static com.facebook.react.uimanager.common.UIManagerType.LEGACY;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.res.Configuration;
|
||||
import android.view.View;
|
||||
@@ -126,7 +127,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule
|
||||
ViewManagerResolver viewManagerResolver,
|
||||
int minTimeLeftInFrameForNonBatchedOperationMs) {
|
||||
super(reactContext);
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext);
|
||||
Activity currentActivity = reactContext.getCurrentActivity();
|
||||
if (currentActivity != null) {
|
||||
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
|
||||
}
|
||||
mEventDispatcher = new EventDispatcherImpl(reactContext);
|
||||
mModuleConstants = createConstants(viewManagerResolver);
|
||||
mCustomDirectEvents = UIManagerModuleConstants.directEventTypeConstants;
|
||||
@@ -146,7 +151,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule
|
||||
List<ViewManager> viewManagersList,
|
||||
int minTimeLeftInFrameForNonBatchedOperationMs) {
|
||||
super(reactContext);
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext);
|
||||
Activity currentActivity = reactContext.getCurrentActivity();
|
||||
if (currentActivity != null) {
|
||||
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
|
||||
}
|
||||
mEventDispatcher = new EventDispatcherImpl(reactContext);
|
||||
mCustomDirectEvents = MapBuilder.newHashMap();
|
||||
mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents);
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -27,6 +29,9 @@ import com.facebook.react.uimanager.IllegalViewOperationException
|
||||
* order to animate layout when a valid configuration has been supplied by the application.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal abstract class AbstractLayoutAnimation {
|
||||
var interpolator: Interpolator? = null
|
||||
var delayMs: Int = 0
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -15,6 +17,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
* creation.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal enum class AnimatedPropertyType {
|
||||
OPACITY,
|
||||
SCALE_X,
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -17,6 +19,9 @@ import com.facebook.react.uimanager.IllegalViewOperationException
|
||||
|
||||
/** Class responsible for default layout animation, i.e animation of view creation and deletion. */
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal abstract class BaseLayoutAnimation : AbstractLayoutAnimation() {
|
||||
abstract fun isReverse(): Boolean
|
||||
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -14,6 +16,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
* Enum representing the different interpolators that can be used in layout animation configuration.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal enum class InterpolatorType {
|
||||
LINEAR,
|
||||
EASE_IN,
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.util.SparseArray
|
||||
@@ -29,6 +31,9 @@ import javax.annotation.concurrent.NotThreadSafe
|
||||
*/
|
||||
@NotThreadSafe
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
public open class LayoutAnimationController {
|
||||
private val layoutCreateAnimation: AbstractLayoutAnimation = LayoutCreateAnimation()
|
||||
private val layoutUpdateAnimation: AbstractLayoutAnimation = LayoutUpdateAnimation()
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -12,6 +14,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
|
||||
/** Listener invoked when a layout animation has completed. */
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
public fun interface LayoutAnimationListener {
|
||||
public fun onAnimationEnd()
|
||||
}
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -15,6 +17,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
* Enum representing the different animation type that can be specified in layout animation config.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal enum class LayoutAnimationType {
|
||||
CREATE,
|
||||
UPDATE,
|
||||
|
||||
+2
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -16,6 +18,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
* config was supplied for the layout animation of DELETE type.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class LayoutDeleteAnimation : BaseLayoutAnimation() {
|
||||
|
||||
override fun isReverse(): Boolean = true
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -12,6 +14,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
|
||||
/** Interface for an animation type that takes care of updating the view layout. */
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal interface LayoutHandlingAnimation {
|
||||
/**
|
||||
* Notifies the animation of a layout update in case one occurs during the animation. This avoids
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -19,6 +21,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
* was supplied for the layout animation of UPDATE type.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class LayoutUpdateAnimation : AbstractLayoutAnimation() {
|
||||
|
||||
override fun isValid(): Boolean = durationMs > 0
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -21,6 +23,9 @@ import java.lang.ref.WeakReference
|
||||
* optimize rendering performances.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class OpacityAnimation(view: View, private val startOpacity: Float, endOpacity: Float) :
|
||||
Animation() {
|
||||
private val viewRef = WeakReference(view)
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -22,6 +24,9 @@ import java.lang.ref.WeakReference
|
||||
* ScaleAnimation and TranslateAnimation.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class PositionAndSizeAnimation(view: View, x: Int, y: Int, width: Int, height: Int) :
|
||||
Animation(), LayoutHandlingAnimation {
|
||||
private val viewRef = WeakReference(view)
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.animation.Interpolator
|
||||
@@ -19,6 +21,9 @@ import kotlin.math.sin
|
||||
/** Simple spring interpolator */
|
||||
// TODO(7613736): Improve spring interpolator with friction and damping variable support
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class SimpleSpringInterpolator @JvmOverloads constructor(springDamping: Float = FACTOR) :
|
||||
Interpolator {
|
||||
private val _springDamping: Float = springDamping
|
||||
|
||||
+11
-7
@@ -50,28 +50,32 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
|
||||
internal class VirtualViewContainerState {
|
||||
|
||||
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
|
||||
private val detectWindowFocus = ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()
|
||||
|
||||
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
|
||||
private val emptyRect: Rect = Rect()
|
||||
private val visibleRect: Rect = Rect()
|
||||
private val prerenderRect: Rect = Rect()
|
||||
private val onWindowFocusChangeListener =
|
||||
ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
debugLog("onWindowFocusChanged")
|
||||
updateModes()
|
||||
if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) {
|
||||
ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
debugLog("onWindowFocusChanged")
|
||||
updateModes()
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private val scrollView: ViewGroup
|
||||
|
||||
constructor(scrollView: ViewGroup) {
|
||||
this.scrollView = scrollView
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
scrollView.viewTreeObserver.addOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
}
|
||||
|
||||
public fun cleanup() {
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
scrollView.viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
}
|
||||
@@ -115,7 +119,7 @@ internal class VirtualViewContainerState {
|
||||
rect.isEmpty -> {}
|
||||
rectsOverlap(rect, visibleRect) -> {
|
||||
thresholdRect = visibleRect
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
if (scrollView.hasWindowFocus()) {
|
||||
mode = VirtualViewMode.Visible
|
||||
} else {
|
||||
|
||||
+9
-6
@@ -40,11 +40,14 @@ public class ReactVirtualView(context: Context) :
|
||||
internal var modeChangeEmitter: VirtualViewModeChangeEmitter? = null
|
||||
internal var prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
|
||||
internal val debugLogEnabled: Boolean = ReactNativeFeatureFlags.enableVirtualViewDebugFeatures()
|
||||
internal val detectWindowFocus = ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()
|
||||
|
||||
private val onWindowFocusChangeListener =
|
||||
ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) {
|
||||
ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private var parentScrollView: View? = null
|
||||
@@ -90,7 +93,7 @@ public class ReactVirtualView(context: Context) :
|
||||
ReactScrollViewHelper.addLayoutChangeListener(this)
|
||||
}
|
||||
debugLog("onAttachedToWindow")
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
viewTreeObserver.addOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
@@ -100,7 +103,7 @@ public class ReactVirtualView(context: Context) :
|
||||
super.onDetachedFromWindow()
|
||||
ReactScrollViewHelper.removeScrollListener(this)
|
||||
ReactScrollViewHelper.removeLayoutChangeListener(this)
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
cleanupLayoutListeners()
|
||||
@@ -202,7 +205,7 @@ public class ReactVirtualView(context: Context) :
|
||||
|
||||
val newMode: VirtualViewMode
|
||||
if (rectsOverlap(targetRect, thresholdRect)) {
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
if (hasWindowFocus()) {
|
||||
newMode = VirtualViewMode.Visible
|
||||
} else {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.yoga;
|
||||
|
||||
public class YogaConstants {
|
||||
|
||||
public static final float UNDEFINED = Float.NaN;
|
||||
|
||||
public static boolean isUndefined(float value) {
|
||||
return Float.compare(value, UNDEFINED) == 0;
|
||||
}
|
||||
|
||||
public static boolean isUndefined(YogaValue value) {
|
||||
return value.unit == YogaUnit.UNDEFINED;
|
||||
}
|
||||
|
||||
public static float getUndefined() {
|
||||
return UNDEFINED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.yoga
|
||||
|
||||
public object YogaConstants {
|
||||
@JvmField public val UNDEFINED: Float = Float.NaN
|
||||
|
||||
@JvmStatic public fun isUndefined(value: Float): Boolean = value.compareTo(UNDEFINED) == 0
|
||||
|
||||
@JvmStatic public fun isUndefined(value: YogaValue): Boolean = value.unit == YogaUnit.UNDEFINED
|
||||
|
||||
@JvmStatic public fun getUndefined(): Float = UNDEFINED
|
||||
}
|
||||
@@ -29,9 +29,6 @@ if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
|
||||
endif(CCACHE_FOUND)
|
||||
|
||||
# Make sure every shared lib includes a .note.gnu.build-id header
|
||||
add_link_options(-Wl,--build-id)
|
||||
|
||||
function(add_react_android_subdir relative_path)
|
||||
add_subdirectory(${REACT_ANDROID_DIR}/${relative_path} ReactAndroid/${relative_path})
|
||||
endfunction()
|
||||
|
||||
@@ -19,6 +19,7 @@ target_include_directories(react_devsupportjni PUBLIC .)
|
||||
|
||||
target_link_libraries(react_devsupportjni
|
||||
fbjni
|
||||
jsinspector)
|
||||
jsinspector
|
||||
jsinspector_network)
|
||||
|
||||
target_compile_reactnative_options(react_devsupportjni PRIVATE)
|
||||
|
||||
+24
-23
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "InspectorNetworkReporter.h"
|
||||
#include "JInspectorNetworkReporter.h"
|
||||
|
||||
#include <jsinspector-modern/network/NetworkReporter.h>
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
#endif
|
||||
|
||||
using namespace facebook::jni;
|
||||
using namespace facebook::react::jsinspector_modern;
|
||||
|
||||
namespace facebook::react {
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -59,12 +58,12 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
|
||||
#endif
|
||||
|
||||
/* static */ jboolean InspectorNetworkReporter::isDebuggingEnabled(
|
||||
/* static */ jboolean JInspectorNetworkReporter::isDebuggingEnabled(
|
||||
jni::alias_ref<jclass> /*unused*/) {
|
||||
return NetworkReporter::getInstance().isDebuggingEnabled();
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportRequestStart(
|
||||
/* static */ void JInspectorNetworkReporter::reportRequestStart(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jstring> requestUrl,
|
||||
@@ -82,7 +81,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
std::to_string(requestId), requestInfo, encodedDataLength, std::nullopt);
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportConnectionTiming(
|
||||
/* static */ void JInspectorNetworkReporter::reportConnectionTiming(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jni::JMap<jstring, jstring>> headers) {
|
||||
@@ -90,7 +89,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
std::to_string(requestId), convertJavaMapToHeaders(headers));
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportResponseStart(
|
||||
/* static */ void JInspectorNetworkReporter::reportResponseStart(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jstring> requestUrl,
|
||||
@@ -108,7 +107,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
static_cast<std::int64_t>(encodedDataLength));
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportDataReceivedImpl(
|
||||
/* static */ void JInspectorNetworkReporter::reportDataReceivedImpl(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jint dataLength) {
|
||||
@@ -116,7 +115,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
std::to_string(requestId), dataLength, std::nullopt);
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportResponseEnd(
|
||||
/* static */ void JInspectorNetworkReporter::reportResponseEnd(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jlong encodedDataLength) {
|
||||
@@ -134,7 +133,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportRequestFailed(
|
||||
/* static */ void JInspectorNetworkReporter::reportRequestFailed(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jboolean cancelled) {
|
||||
@@ -142,7 +141,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
std::to_string(requestId), cancelled);
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::maybeStoreResponseBodyImpl(
|
||||
/* static */ void JInspectorNetworkReporter::maybeStoreResponseBodyImpl(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jstring> body,
|
||||
@@ -160,7 +159,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
InspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl(
|
||||
JInspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jstring> data) {
|
||||
@@ -176,31 +175,33 @@ InspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl(
|
||||
#endif
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::registerNatives() {
|
||||
/* static */ void JInspectorNetworkReporter::registerNatives() {
|
||||
javaClassLocal()->registerNatives({
|
||||
makeNativeMethod(
|
||||
"isDebuggingEnabled", InspectorNetworkReporter::isDebuggingEnabled),
|
||||
"isDebuggingEnabled", JInspectorNetworkReporter::isDebuggingEnabled),
|
||||
makeNativeMethod(
|
||||
"reportRequestStart", InspectorNetworkReporter::reportRequestStart),
|
||||
"reportRequestStart", JInspectorNetworkReporter::reportRequestStart),
|
||||
makeNativeMethod(
|
||||
"reportResponseStart", InspectorNetworkReporter::reportResponseStart),
|
||||
"reportResponseStart",
|
||||
JInspectorNetworkReporter::reportResponseStart),
|
||||
makeNativeMethod(
|
||||
"reportConnectionTiming",
|
||||
InspectorNetworkReporter::reportConnectionTiming),
|
||||
JInspectorNetworkReporter::reportConnectionTiming),
|
||||
makeNativeMethod(
|
||||
"reportDataReceivedImpl",
|
||||
InspectorNetworkReporter::reportDataReceivedImpl),
|
||||
JInspectorNetworkReporter::reportDataReceivedImpl),
|
||||
makeNativeMethod(
|
||||
"reportResponseEnd", InspectorNetworkReporter::reportResponseEnd),
|
||||
"reportResponseEnd", JInspectorNetworkReporter::reportResponseEnd),
|
||||
makeNativeMethod(
|
||||
"reportRequestFailed", InspectorNetworkReporter::reportRequestFailed),
|
||||
"reportRequestFailed",
|
||||
JInspectorNetworkReporter::reportRequestFailed),
|
||||
makeNativeMethod(
|
||||
"maybeStoreResponseBodyImpl",
|
||||
InspectorNetworkReporter::maybeStoreResponseBodyImpl),
|
||||
JInspectorNetworkReporter::maybeStoreResponseBodyImpl),
|
||||
makeNativeMethod(
|
||||
"maybeStoreResponseBodyIncrementalImpl",
|
||||
InspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl),
|
||||
JInspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
+5
-5
@@ -9,10 +9,10 @@
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
|
||||
namespace facebook::react {
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
class InspectorNetworkReporter
|
||||
: public jni::HybridClass<InspectorNetworkReporter> {
|
||||
class JInspectorNetworkReporter
|
||||
: public jni::HybridClass<JInspectorNetworkReporter> {
|
||||
public:
|
||||
static constexpr auto kJavaDescriptor =
|
||||
"Lcom/facebook/react/modules/network/InspectorNetworkReporter;";
|
||||
@@ -70,7 +70,7 @@ class InspectorNetworkReporter
|
||||
static void registerNatives();
|
||||
|
||||
private:
|
||||
InspectorNetworkReporter() = delete;
|
||||
JInspectorNetworkReporter() = delete;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user