mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3293792b1f | ||
|
|
6830ec87ff | ||
|
|
8750eb2538 | ||
|
|
afd35296f2 | ||
|
|
8d4e8a3775 | ||
|
|
245ac945fb | ||
|
|
06c64f5f1b | ||
|
|
a5353c0361 | ||
|
|
930ffa28a3 | ||
|
|
e0d9ceaa79 | ||
|
|
fc6580e2e0 | ||
|
|
a648a7176a | ||
|
|
cf5ca2659a | ||
|
|
231581c256 | ||
|
|
2ec83bec5f | ||
|
|
50d9551f87 | ||
|
|
0356b1fa13 | ||
|
|
b153a05e2f | ||
|
|
dcf56a43fa | ||
|
|
e0e9a1cb4f | ||
|
|
8d02e1723f | ||
|
|
eb0216d8be | ||
|
|
c0fa590a03 | ||
|
|
ba5222f3cc | ||
|
|
c57bfea01e | ||
|
|
fafc58b62d | ||
|
|
eded25d532 | ||
|
|
bc28eee87f | ||
|
|
104b0cc862 | ||
|
|
6d6b532f8f | ||
|
|
1dd1152025 | ||
|
|
2575eb318f | ||
|
|
1dd68819ab | ||
|
|
9296ab1a61 | ||
|
|
612c033918 | ||
|
|
f827ea90b0 | ||
|
|
b356ac7769 | ||
|
|
5485238bb4 |
@@ -0,0 +1,47 @@
|
||||
environment:
|
||||
ANDROID_HOME: "C:\\android-sdk-windows"
|
||||
ANDROID_NDK: "C:\\android-sdk-windows\\android-ndk-r19c"
|
||||
ANDROID_BUILD_VERSION: 28
|
||||
ANDROID_TOOLS_VERSION: 28.0.3
|
||||
|
||||
GRADLE_OPTS: -Dorg.gradle.daemon=false
|
||||
|
||||
SDK_TOOLS_URL: https://dl.google.com/android/repository/sdk-tools-windows-3859397.zip
|
||||
NDK_TOOLS_URL: https://dl.google.com/android/repository/android-ndk-r19c-windows-x86_64.zip
|
||||
|
||||
matrix:
|
||||
- nodejs_version: 8
|
||||
- nodejs_version: 10
|
||||
|
||||
install:
|
||||
# Install Android SDK Tools
|
||||
- mkdir "%ANDROID_HOME%"
|
||||
- appveyor DownloadFile "%SDK_TOOLS_URL%" -FileName "%TMP%/sdk-tools.zip"
|
||||
- 7z x "%TMP%/sdk-tools.zip" -o"%ANDROID_HOME%" > nul
|
||||
- set PATH=%PATH%;"%ANDROID_HOME%\tools\bin"
|
||||
|
||||
- yes 2> nul | sdkmanager --licenses > nul
|
||||
- yes 2> nul | sdkmanager "system-images;android-19;google_apis;armeabi-v7a"
|
||||
- yes 2> nul | sdkmanager "platforms;android-%ANDROID_BUILD_VERSION%"
|
||||
- yes 2> nul | sdkmanager "build-tools;%ANDROID_TOOLS_VERSION%"
|
||||
- yes 2> nul | sdkmanager "add-ons;addon-google_apis-google-23"
|
||||
- yes 2> nul | sdkmanager "extras;android;m2repository"
|
||||
|
||||
- appveyor DownloadFile "%NDK_TOOLS_URL%" -FileName "%TMP%/ndk.zip"
|
||||
- 7z x "%TMP%/ndk.zip" -o"%ANDROID_HOME%" > nul
|
||||
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- npx envinfo@latest
|
||||
- appveyor-retry yarn install
|
||||
|
||||
build_script:
|
||||
- yarn run flow-check-android
|
||||
- yarn run flow-check-ios
|
||||
- yarn run test
|
||||
# - gradlew.bat RNTester:android:app:assembleRelease
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
- "%LOCALAPPDATA%/Yarn"
|
||||
- "%USERPROFILE%/.gradle/caches"
|
||||
- "%USERPROFILE%/.gradle/wrapper"
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
|
||||
[android]
|
||||
target = android-30
|
||||
target = android-28
|
||||
|
||||
[download]
|
||||
max_number_of_retries = 3
|
||||
|
||||
[maven_repositories]
|
||||
central = https://repo1.maven.org/maven2
|
||||
google = https://maven.google.com/
|
||||
google = https://dl.google.com/dl/android/maven2/
|
||||
|
||||
[alias]
|
||||
rntester = //packages/rn-tester/android/app:app
|
||||
rntester = //RNTester/android/app:app
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# and build a Android application that can be used to run the
|
||||
# tests specified in the scripts/ directory.
|
||||
#
|
||||
FROM reactnativecommunity/react-native-android:4.0
|
||||
FROM reactnativecommunity/react-native-android
|
||||
|
||||
LABEL Description="React Native Android Test Image"
|
||||
LABEL maintainer="Héctor Ramos <hector@fb.com>"
|
||||
@@ -25,14 +25,11 @@ ENV JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF8"
|
||||
|
||||
ADD .buckconfig /app/.buckconfig
|
||||
ADD .buckjavaargs /app/.buckjavaargs
|
||||
ADD BUCK /app/BUCK
|
||||
ADD Libraries /app/Libraries
|
||||
ADD tools /app/tools
|
||||
ADD ReactAndroid /app/ReactAndroid
|
||||
ADD ReactCommon /app/ReactCommon
|
||||
ADD React /app/React
|
||||
ADD keystores /app/keystores
|
||||
ADD packages/react-native-codegen /app/packages/react-native-codegen
|
||||
ADD tools /app/tools
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -52,3 +49,4 @@ RUN yarn
|
||||
RUN ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog
|
||||
|
||||
RUN ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -Pjobs=1
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
|
||||
# for buck gen
|
||||
mount -o remount,exec /dev/shm
|
||||
@@ -10,7 +12,7 @@ mount -o remount,exec /dev/shm
|
||||
AVD_UUID=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)
|
||||
|
||||
# create virtual device
|
||||
echo no | android create avd -n "$AVD_UUID" -f -t android-21 --abi default/armeabi-v7a
|
||||
echo no | android create avd -n "$AVD_UUID" -f -t android-19 --abi default/armeabi-v7a
|
||||
|
||||
# emulator setup
|
||||
emulator64-arm -avd $AVD_UUID -no-skin -no-audio -no-window -no-boot-anim &
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
|
||||
# set default environment variables
|
||||
UNIT_TESTS_BUILD_THREADS="${UNIT_TESTS_BUILD_THREADS:-1}"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
|
||||
set -ex
|
||||
|
||||
@@ -67,6 +69,11 @@ while :; do
|
||||
shift
|
||||
;;
|
||||
|
||||
--tvos)
|
||||
RUN_IOS=1
|
||||
shift
|
||||
;;
|
||||
|
||||
*)
|
||||
break
|
||||
esac
|
||||
@@ -109,7 +116,7 @@ function e2e_suite() {
|
||||
|
||||
# create virtual device
|
||||
if ! android list avd | grep "$AVD_UUID" > /dev/null; then
|
||||
echo no | android create avd -n "$AVD_UUID" -f -t android-21 --abi default/armeabi-v7a
|
||||
echo no | android create avd -n "$AVD_UUID" -f -t android-19 --abi default/armeabi-v7a
|
||||
fi
|
||||
|
||||
# newline at end of adb devices call and first line is headers
|
||||
@@ -194,7 +201,7 @@ function e2e_suite() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Starting Metro"
|
||||
echo "Starting packager server"
|
||||
npm start >> /dev/null &
|
||||
SERVER_PID=$!
|
||||
sleep 15
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
|
||||
# shellcheck disable=SC1117
|
||||
# Python script to run instrumentation tests, copied from https://github.com/circleci/circle-dummy-android
|
||||
|
||||
+199
-421
@@ -1,12 +1,5 @@
|
||||
version: 2.1
|
||||
|
||||
# -------------------------
|
||||
# ORBS
|
||||
# -------------------------
|
||||
|
||||
orbs:
|
||||
win: circleci/windows@2.4.0
|
||||
|
||||
# -------------------------
|
||||
# DEFAULTS
|
||||
# -------------------------
|
||||
@@ -14,29 +7,23 @@ defaults: &defaults
|
||||
working_directory: ~/react-native
|
||||
environment:
|
||||
- GIT_COMMIT_DESC: git log --format=oneline -n 1 $CIRCLE_SHA1
|
||||
# The public github tokens are publicly visible by design
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_A: &github_pullbot_token_a "a6edf8e8d40ce4e8b11a"
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_B: &github_pullbot_token_b "150e1341f4dd9c944d2a"
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: &github_analysisbot_token_a "312d354b5c36f082cfe9"
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: &github_analysisbot_token_b "07973d757026bdd9f196"
|
||||
|
||||
# -------------------------
|
||||
# EXECUTORS
|
||||
# -------------------------
|
||||
executors:
|
||||
node8:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
nodelts:
|
||||
<<: *defaults
|
||||
docker:
|
||||
# Note: Version set separately for Windows builds, see below.
|
||||
- image: circleci/node:14
|
||||
nodeprevlts:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:12
|
||||
- image: circleci/node:lts
|
||||
reactnativeandroid:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: reactnativecommunity/react-native-android:4.0
|
||||
- image: reactnativecommunity/react-native-android:2019-6-4
|
||||
resource_class: "large"
|
||||
environment:
|
||||
- TERM: "dumb"
|
||||
@@ -44,15 +31,10 @@ executors:
|
||||
- _JAVA_OPTIONS: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
|
||||
- GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-XX:+HeapDumpOnOutOfMemoryError"'
|
||||
- BUILD_THREADS: 2
|
||||
# Repeated here, as the environment key in this executor will overwrite the one in defaults
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: *github_analysisbot_token_a
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: *github_analysisbot_token_b
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_A: *github_pullbot_token_a
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_B: *github_pullbot_token_b
|
||||
reactnativeios:
|
||||
<<: *defaults
|
||||
macos:
|
||||
xcode: &_XCODE_VERSION "12.5.0"
|
||||
xcode: "10.2.1"
|
||||
|
||||
# -------------------------
|
||||
# COMMANDS
|
||||
@@ -71,15 +53,16 @@ commands:
|
||||
steps:
|
||||
- run:
|
||||
name: Initial Setup
|
||||
command: mkdir -p ./reports/{buck,build,junit,outputs}
|
||||
command: mkdir -p ~/reports/{buck,build,junit,outputs}
|
||||
|
||||
run_yarn:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v4-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- v3-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- v3-yarn-cache-{{ arch }}
|
||||
- run:
|
||||
name: "Yarn: Install Dependencies"
|
||||
name: Run Yarn
|
||||
command: |
|
||||
# Skip yarn install on metro bump commits as the package is not yet
|
||||
# available on npm
|
||||
@@ -89,13 +72,14 @@ commands:
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
key: v4-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
key: v3-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
install_buck_tooling:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v3-buck-v2019.01.10.01-{{ checksum "scripts/circleci/buck_fetch.sh" }}}
|
||||
- v3-buck-v2019.01.10.01-
|
||||
- run:
|
||||
name: Install BUCK
|
||||
command: |
|
||||
@@ -110,21 +94,13 @@ commands:
|
||||
- ~/okbuck
|
||||
key: v3-buck-v2019.01.10.01-{{ checksum "scripts/circleci/buck_fetch.sh" }}
|
||||
|
||||
install_github_bot_deps:
|
||||
steps:
|
||||
- run:
|
||||
name: "Yarn: Install dependencies (GitHub bots)"
|
||||
command: cd bots && yarn install --non-interactive --cache-folder ~/.cache/yarn
|
||||
|
||||
brew_install:
|
||||
parameters:
|
||||
package:
|
||||
description: Homebrew package to install
|
||||
type: string
|
||||
steps:
|
||||
- run:
|
||||
name: "Brew: Install << parameters.package >>"
|
||||
command: HOMEBREW_NO_AUTO_UPDATE=1 brew install << parameters.package >> >/dev/null
|
||||
- run: HOMEBREW_NO_AUTO_UPDATE=1 brew install << parameters.package >> >/dev/null
|
||||
|
||||
with_brew_cache_span:
|
||||
parameters:
|
||||
@@ -133,39 +109,39 @@ commands:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v4-brew
|
||||
- v1-brew
|
||||
- steps: << parameters.steps >>
|
||||
- save_cache:
|
||||
paths:
|
||||
- /usr/local/Homebrew
|
||||
- ~/Library/Caches/Homebrew
|
||||
key: v4-brew
|
||||
key: v1-brew
|
||||
|
||||
with_rntester_pods_cache_span:
|
||||
with_pods_cache_span:
|
||||
parameters:
|
||||
steps:
|
||||
type: steps
|
||||
steps:
|
||||
- run:
|
||||
name: Setup CocoaPods cache
|
||||
# Copy packages/rn-tester/Podfile.lock since it can be changed by pod install
|
||||
command: cp packages/rn-tester/Podfile.lock packages/rn-tester/Podfile.lock.bak
|
||||
# Copy RNTester/Podfile.lock since it can be changed by pod install
|
||||
command: cp RNTester/Podfile.lock RNTester/Podfile.lock.bak
|
||||
- restore_cache:
|
||||
keys:
|
||||
# The committed lockfile is generated using USE_FRAMEWORKS=0 and USE_HERMES=0 so it could load an outdated cache if a change
|
||||
# only affects the frameworks or hermes config. To help prevent this also cache based on the content of Podfile.
|
||||
- v3-pods-{{ .Environment.CIRCLE_JOB }}-{{ checksum "packages/rn-tester/Podfile.lock.bak" }}-{{ checksum "packages/rn-tester/Podfile" }}
|
||||
- v1-pods-{{ .Environment.CIRCLE_JOB }}-{{ checksum "RNTester/Podfile.lock.bak" }}
|
||||
- v1-pods-{{ .Environment.CIRCLE_JOB }}-
|
||||
- steps: << parameters.steps >>
|
||||
- save_cache:
|
||||
paths:
|
||||
- packages/rn-tester/Pods
|
||||
key: v3-pods-{{ .Environment.CIRCLE_JOB }}-{{ checksum "packages/rn-tester/Podfile.lock.bak" }}-{{ checksum "packages/rn-tester/Podfile" }}
|
||||
- RNTester/Pods
|
||||
key: v1-pods-{{ .Environment.CIRCLE_JOB }}-{{ checksum "RNTester/Podfile.lock.bak" }}
|
||||
|
||||
download_gradle_dependencies:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-gradle-{{ checksum "ReactAndroid/build.gradle" }}-{{ checksum "scripts/circleci/gradle_download_deps.sh" }}
|
||||
- v1-gradle-
|
||||
- run:
|
||||
name: Download Dependencies Using Gradle
|
||||
command: ./scripts/circleci/gradle_download_deps.sh
|
||||
@@ -182,33 +158,24 @@ commands:
|
||||
name: Download Dependencies Using Buck
|
||||
command: ./scripts/circleci/buck_fetch.sh
|
||||
|
||||
run_e2e:
|
||||
parameters:
|
||||
platform:
|
||||
description: Target platform
|
||||
type: enum
|
||||
enum: ["android", "ios", "js"]
|
||||
default: "js"
|
||||
retries:
|
||||
description: How many times the job should try to run these tests
|
||||
type: integer
|
||||
default: 3
|
||||
# -------------------------
|
||||
# COMMANDS: Disabled Tests
|
||||
# -------------------------
|
||||
run_podspec_tests:
|
||||
steps:
|
||||
- run:
|
||||
name: "Run Tests: << parameters.platform >> End-to-End Tests"
|
||||
command: node ./scripts/run-ci-e2e-tests.js --<< parameters.platform >> --retries << parameters.retries >>
|
||||
|
||||
report_bundle_size:
|
||||
parameters:
|
||||
platform:
|
||||
description: Target platform
|
||||
type: enum
|
||||
enum: ["android", "ios"]
|
||||
name: Test CocoaPods
|
||||
command: ./scripts/process-podspecs.sh
|
||||
run_e2e_tests:
|
||||
steps:
|
||||
- install_github_bot_deps
|
||||
- run:
|
||||
name: Report size of RNTester.app (analysis-bot)
|
||||
command: GITHUB_TOKEN="$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A""$PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B" scripts/circleci/report-bundle-size.sh << parameters.platform >>
|
||||
name: Full End-to-End Test Suite
|
||||
command: node ./scripts/run-ci-e2e-tests.js --android --ios --js --retries 3;
|
||||
run_android_e2e_tests:
|
||||
steps:
|
||||
- run:
|
||||
name: Android End-to-End Test Suite
|
||||
command: node ./scripts/run-ci-e2e-tests.js --android --retries 3;
|
||||
|
||||
# -------------------------
|
||||
# JOBS
|
||||
@@ -216,12 +183,12 @@ commands:
|
||||
jobs:
|
||||
setup:
|
||||
parameters:
|
||||
executor:
|
||||
type: executor
|
||||
default: nodelts
|
||||
checkout_type:
|
||||
type: string
|
||||
default: node
|
||||
executor:
|
||||
type: executor
|
||||
default: node8
|
||||
checkout_type:
|
||||
type: string
|
||||
default: node
|
||||
executor: << parameters.executor >>
|
||||
steps:
|
||||
- checkout
|
||||
@@ -237,20 +204,24 @@ jobs:
|
||||
# Issues will be posted to the PR itself via GitHub bots.
|
||||
# This workflow should only fail if the bots fail to run.
|
||||
analyze_pr:
|
||||
executor: reactnativeandroid
|
||||
executor: nodelts
|
||||
# The public github tokens are publicly visible by design
|
||||
environment:
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_A: "a6edf8e8d40ce4e8b11a"
|
||||
- PUBLIC_PULLBOT_GITHUB_TOKEN_B: "150e1341f4dd9c944d2a"
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_A: "78a72af35445ca3f8180"
|
||||
- PUBLIC_ANALYSISBOT_GITHUB_TOKEN_B: "b1a98e0bbd56ff1ccba1"
|
||||
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: android
|
||||
checkout_type: node
|
||||
- run_yarn
|
||||
|
||||
- install_github_bot_deps
|
||||
|
||||
# Note: The yarn gpg key needs to be refreshed to work around https://github.com/yarnpkg/yarn/issues/7866
|
||||
- run:
|
||||
name: Install additional GitHub bot dependencies
|
||||
name: Install dependencies
|
||||
command: |
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
||||
apt update && apt install -y shellcheck jq
|
||||
sudo apt update && sudo apt install -y shellcheck jq
|
||||
cd bots && yarn install --non-interactive --cache-folder ~/.cache/yarn
|
||||
|
||||
- run:
|
||||
name: Run linters against modified files (analysis-bot)
|
||||
@@ -264,25 +235,21 @@ jobs:
|
||||
DANGER_GITHUB_API_TOKEN="$PUBLIC_PULLBOT_GITHUB_TOKEN_A""$PUBLIC_PULLBOT_GITHUB_TOKEN_B" yarn danger ci --use-github-checks
|
||||
when: always
|
||||
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Analyze Code
|
||||
# -------------------------
|
||||
analyze_code:
|
||||
executor: reactnativeandroid
|
||||
executor: node8
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: android
|
||||
checkout_type: node
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
|
||||
- run:
|
||||
name: Lint code
|
||||
command: scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ./reports/junit/eslint/results.xml
|
||||
when: always
|
||||
|
||||
- run:
|
||||
name: Lint Java
|
||||
command: scripts/circleci/exec_swallow_error.sh yarn lint-java --check
|
||||
command: scripts/circleci/exec_swallow_error.sh yarn lint --format junit -o ~/reports/junit/eslint/results.xml
|
||||
when: always
|
||||
|
||||
- run:
|
||||
@@ -308,73 +275,42 @@ jobs:
|
||||
when: always
|
||||
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
path: ~/reports/junit
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Test JavaScript
|
||||
# -------------------------
|
||||
# Runs JavaScript tests
|
||||
test_js:
|
||||
parameters:
|
||||
executor:
|
||||
type: executor
|
||||
default: nodelts
|
||||
run_disabled_tests:
|
||||
type: boolean
|
||||
default: false
|
||||
default: node8
|
||||
executor: << parameters.executor >>
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: node
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
- run:
|
||||
name: Install rsync
|
||||
command: sudo apt-get install rsync
|
||||
|
||||
# -------------------------
|
||||
# Run JavaScript tests
|
||||
- run:
|
||||
name: "Run Tests: JavaScript Tests"
|
||||
name: JavaScript Test Suite
|
||||
command: node ./scripts/run-ci-javascript-tests.js --maxWorkers 2
|
||||
- run_e2e:
|
||||
platform: js
|
||||
|
||||
# Optionally, run disabled tests
|
||||
- when:
|
||||
condition: << parameters.run_disabled_tests >>
|
||||
steps:
|
||||
- run: echo "Failing tests may be moved here temporarily."
|
||||
# -------------------------
|
||||
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
|
||||
path: ~/reports/junit
|
||||
# -------------------------
|
||||
# JOBS: Test iOS
|
||||
# -------------------------
|
||||
# Runs unit tests on iOS devices
|
||||
test_ios:
|
||||
executor: reactnativeios
|
||||
parameters:
|
||||
use_frameworks:
|
||||
type: boolean
|
||||
default: false
|
||||
use_hermes:
|
||||
type: boolean
|
||||
default: false
|
||||
run_unit_tests:
|
||||
description: Specifies whether unit tests should run.
|
||||
type: boolean
|
||||
default: false
|
||||
run_detox_tests:
|
||||
description: Specifies whether Detox e2e tests should run.
|
||||
type: boolean
|
||||
default: false
|
||||
run_disabled_tests:
|
||||
description: Specifies whether disabled tests should run. Set this to true to debug failing tests.
|
||||
type: boolean
|
||||
default: false
|
||||
environment:
|
||||
- REPORTS_DIR: "./reports/junit"
|
||||
- REPORTS_DIR: "./reports"
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: ios
|
||||
@@ -382,33 +318,16 @@ jobs:
|
||||
- run_yarn
|
||||
|
||||
- run: |
|
||||
cd packages/rn-tester
|
||||
cd RNTester
|
||||
bundle check || bundle install
|
||||
- run:
|
||||
name: Boot iPhone Simulator
|
||||
command: source scripts/.tests.env && xcrun simctl boot "$IOS_DEVICE" || true
|
||||
|
||||
- run:
|
||||
name: Configure Environment Variables
|
||||
name: Fetch CocoaPods Specs
|
||||
command: |
|
||||
echo 'export PATH=/usr/local/opt/node@12/bin:$PATH' >> $BASH_ENV
|
||||
source $BASH_ENV
|
||||
|
||||
- with_brew_cache_span:
|
||||
steps:
|
||||
- brew_install:
|
||||
package: watchman
|
||||
- brew_install:
|
||||
package: node@12
|
||||
- run:
|
||||
name: "Brew: Tap wix/brew"
|
||||
command: HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
|
||||
- brew_install:
|
||||
package: applesimutils
|
||||
|
||||
- run:
|
||||
name: Configure Watchman
|
||||
command: touch .watchmanconfig
|
||||
curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf
|
||||
|
||||
- when:
|
||||
condition: << parameters.use_frameworks >>
|
||||
@@ -417,73 +336,107 @@ jobs:
|
||||
name: Set USE_FRAMEWORKS=1
|
||||
command: echo "export USE_FRAMEWORKS=1" >> $BASH_ENV
|
||||
|
||||
- when:
|
||||
condition: << parameters.use_hermes >>
|
||||
steps:
|
||||
- run:
|
||||
name: Set USE_HERMES=1
|
||||
command: echo "export USE_HERMES=1" >> $BASH_ENV
|
||||
|
||||
- run:
|
||||
name: Setup the CocoaPods environment
|
||||
command: pod setup
|
||||
|
||||
- with_rntester_pods_cache_span:
|
||||
- with_pods_cache_span:
|
||||
steps:
|
||||
- run:
|
||||
name: Generate RNTesterPods Workspace
|
||||
command: cd packages/rn-tester && USE_FABRIC=1 bundle exec pod install --verbose
|
||||
command: cd RNTester && pod install --verbose
|
||||
|
||||
# -------------------------
|
||||
# Runs iOS unit tests
|
||||
- when:
|
||||
condition: << parameters.run_unit_tests >>
|
||||
- with_brew_cache_span:
|
||||
steps:
|
||||
- run:
|
||||
name: "Run Tests: iOS Unit and Integration Tests"
|
||||
command: yarn test-ios
|
||||
# Runs iOS Detox e2e tests
|
||||
- when:
|
||||
condition: << parameters.run_detox_tests >>
|
||||
steps:
|
||||
- run:
|
||||
name: "Run Tests: Detox iOS End-to-End Tests"
|
||||
command: yarn run build-ios-e2e && yarn run test-ios-e2e
|
||||
- brew_install:
|
||||
package: watchman
|
||||
- run: touch .watchmanconfig
|
||||
|
||||
# Optionally, run disabled tests
|
||||
- when:
|
||||
condition: << parameters.run_disabled_tests >>
|
||||
steps:
|
||||
- run: echo "Failing tests may be moved here temporarily."
|
||||
- run:
|
||||
name: "Run Tests: CocoaPods"
|
||||
command: ./scripts/process-podspecs.sh
|
||||
- run:
|
||||
name: Free up port 8081 for iOS End-to-End Tests
|
||||
command: |
|
||||
# free up port 8081 for the packager before running tests
|
||||
set +eo pipefail
|
||||
lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill
|
||||
set -eo pipefail
|
||||
- run_e2e:
|
||||
platform: ios
|
||||
# -------------------------
|
||||
|
||||
# Collect Results
|
||||
- report_bundle_size:
|
||||
platform: ios
|
||||
- run: yarn test-ios
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
path: ~/reports/junit
|
||||
|
||||
# Runs iOS end-to-end tests
|
||||
test_ios_e2e:
|
||||
executor: reactnativeios
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: ios
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
|
||||
- run:
|
||||
name: Boot iPhone Simulator
|
||||
command: source scripts/.tests.env && xcrun simctl boot "$IOS_DEVICE" || true
|
||||
|
||||
- run:
|
||||
name: Configure Environment Variables
|
||||
command: |
|
||||
echo 'export PATH=/usr/local/opt/node@8/bin:$PATH' >> $BASH_ENV
|
||||
source $BASH_ENV
|
||||
|
||||
# Brew
|
||||
- with_brew_cache_span:
|
||||
steps:
|
||||
- brew_install:
|
||||
package: node@8
|
||||
- run: HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
|
||||
- brew_install:
|
||||
package: applesimutils
|
||||
- brew_install:
|
||||
package: watchman
|
||||
# Configure Watchman
|
||||
- run: touch .watchmanconfig
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-cocoapods-{{ checksum "template/ios/Podfile" }}
|
||||
- v1-cocoapods-
|
||||
|
||||
- run: pod setup
|
||||
|
||||
- run:
|
||||
name: Generate RNTesterPods Workspace
|
||||
command: pushd RNTester && pod install --verbose && popd
|
||||
|
||||
- run:
|
||||
name: Run Detox iOS End-to-End Tests
|
||||
command: yarn run build-ios-e2e && yarn run test-ios-e2e
|
||||
when: always
|
||||
|
||||
- run:
|
||||
name: Run iOS End-to-End Tests
|
||||
command: |
|
||||
# free up port 8081 for the packager before running tests
|
||||
set +eo pipefail
|
||||
lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill
|
||||
set -eo pipefail
|
||||
node ./scripts/run-ci-e2e-tests.js --ios --retries 3;
|
||||
when: always
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.cocoapods/repos
|
||||
key: v1-cocoapods-{{ checksum "template/ios/Podfile" }}
|
||||
|
||||
test_js_e2e:
|
||||
executor: node8
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: node
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
- run: sudo apt-get install rsync
|
||||
|
||||
- run:
|
||||
name: Run JavaScript End-to-End Tests
|
||||
command: node ./scripts/run-ci-e2e-tests.js --js --retries 3
|
||||
|
||||
- store_test_results:
|
||||
path: ~/reports/junit
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Test Android
|
||||
# -------------------------
|
||||
# Run Android tests
|
||||
test_android:
|
||||
executor: reactnativeandroid
|
||||
parameters:
|
||||
run_disabled_tests:
|
||||
type: boolean
|
||||
default: false
|
||||
steps:
|
||||
- restore_cache_checkout:
|
||||
checkout_type: android
|
||||
@@ -504,6 +457,8 @@ jobs:
|
||||
command: source scripts/android-setup.sh && launchAVD
|
||||
background: true
|
||||
|
||||
# Keep configuring Android dependencies while AVD boots up
|
||||
|
||||
# Install Buck
|
||||
- install_buck_tooling
|
||||
|
||||
@@ -536,49 +491,37 @@ jobs:
|
||||
name: Wait for Android Virtual Device
|
||||
command: source scripts/android-setup.sh && waitForAVD
|
||||
|
||||
# Test Suite
|
||||
- run:
|
||||
name: Assemble RNTester App
|
||||
command: ./gradlew packages:rn-tester:android:app:assembleRelease
|
||||
name: Run Unit Tests
|
||||
command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS --xml ~/reports/buck/all-results-raw.xml
|
||||
|
||||
# -------------------------
|
||||
# Run Android tests
|
||||
- run:
|
||||
name: "Run Tests: Android Unit Tests"
|
||||
command: buck test ReactAndroid/src/test/... --config build.threads=$BUILD_THREADS --xml ./reports/buck/all-results-raw.xml
|
||||
- run:
|
||||
name: "Build Tests: Android Instrumentation Tests"
|
||||
# Here, just build the instrumentation tests. There is a known issue with installing the APK to android-21+ emulator.
|
||||
name: Run Instrumentation Tests
|
||||
command: |
|
||||
if [[ ! -e ReactAndroid/src/androidTest/assets/AndroidTestBundle.js ]]; then
|
||||
echo "JavaScript bundle missing, cannot run instrumentation tests. Verify Build JavaScript Bundle step completed successfully."; exit 1;
|
||||
fi
|
||||
source scripts/android-setup.sh && NO_BUCKD=1 retry3 timeout 300 buck build ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS
|
||||
source scripts/android-setup.sh && NO_BUCKD=1 retry3 timeout 300 buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=$BUILD_THREADS
|
||||
|
||||
# Optionally, run disabled tests
|
||||
- when:
|
||||
condition: << parameters.run_disabled_tests >>
|
||||
steps:
|
||||
- run: echo "Failing tests may be moved here temporarily."
|
||||
- run_e2e:
|
||||
platform: android
|
||||
# -------------------------
|
||||
- run:
|
||||
name: Build Android RNTester App
|
||||
command: ./gradlew RNTester:android:app:assembleRelease
|
||||
|
||||
# Collect Results
|
||||
- report_bundle_size:
|
||||
platform: android
|
||||
- run:
|
||||
name: Collect Test Results
|
||||
command: |
|
||||
find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ./reports/build/ \;
|
||||
find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ./reports/outputs/ \;
|
||||
find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ./reports/buck/ \;
|
||||
find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} ~/reports/build/ \;
|
||||
find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} ~/reports/outputs/ \;
|
||||
find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} ~/reports/buck/ \;
|
||||
if [ -f ~/react-native/reports/buck/all-results-raw.xml ]; then
|
||||
cd ~/okbuck
|
||||
./tooling/junit/buck_to_junit.sh ~/react-native/reports/buck/all-results-raw.xml ~/react-native/reports/junit/results.xml
|
||||
./tooling/junit/buck_to_junit.sh ~/react-native/reports/buck/all-results-raw.xml ~/react-native/reports/junit/all-results-junit.xml
|
||||
fi
|
||||
when: always
|
||||
|
||||
- store_test_results:
|
||||
path: ./reports/junit
|
||||
path: ~/reports/junit
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Test Docker
|
||||
@@ -593,101 +536,16 @@ jobs:
|
||||
source ~/.bashrc
|
||||
nvm i node
|
||||
npm i -g yarn
|
||||
echo y | npx envinfo@latest
|
||||
npx envinfo@latest
|
||||
yarn run docker-setup-android
|
||||
yarn run docker-build-android
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Windows
|
||||
# -------------------------
|
||||
test_windows:
|
||||
executor:
|
||||
name: win/default
|
||||
parameters:
|
||||
run_disabled_tests:
|
||||
type: boolean
|
||||
default: false
|
||||
environment:
|
||||
- ANDROID_HOME: "C:\\Android\\android-sdk"
|
||||
- ANDROID_NDK: "C:\\Android\\android-sdk\\ndk\\20.1.5948944"
|
||||
- ANDROID_BUILD_VERSION: 30
|
||||
- ANDROID_TOOLS_VERSION: 30.0.2
|
||||
- GRADLE_OPTS: -Dorg.gradle.daemon=false
|
||||
- NDK_VERSION: 21.4.7075529
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install Node
|
||||
# Note: Version set separately for non-Windows builds, see above.
|
||||
command: |
|
||||
nvm install 14.17.0
|
||||
nvm use 14.17.0
|
||||
|
||||
# Setup Dependencies
|
||||
- run:
|
||||
name: Install Yarn
|
||||
command: choco install yarn
|
||||
|
||||
- run:
|
||||
name: Display Environment info
|
||||
command: npx envinfo@latest
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-win-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: "Yarn: Install Dependencies"
|
||||
command: yarn install --frozen-lockfile --non-interactive
|
||||
- save_cache:
|
||||
key: v1-win-yarn-cache-{{ arch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- C:\Users\circleci\AppData\Local\Yarn
|
||||
|
||||
# Try to install the SDK up to 3 times, since network flakiness can cause install failures
|
||||
# Using a timeout of 9 mins, as circle ci will timeout if there is no output for 10 mins
|
||||
- run:
|
||||
name: Install Android SDK Tools
|
||||
command: choco install android-sdk --timeout 540; if (!$?) { choco install android-sdk --timeout 540 --force --forcedependencies}; if (!$?) { choco install android-sdk --force --forcedependencies}
|
||||
|
||||
- run:
|
||||
name: Setup Android SDKs
|
||||
command: |
|
||||
sdkmanager --licenses
|
||||
sdkmanager "system-images;android-21;google_apis;armeabi-v7a"
|
||||
sdkmanager "platforms;android-%ANDROID_BUILD_VERSION%"
|
||||
sdkmanager "build-tools;%ANDROID_TOOLS_VERSION%"
|
||||
sdkmanager "add-ons;addon-google_apis-google-23"
|
||||
sdkmanager "extras;android;m2repository"
|
||||
sdkmanager "ndk;%NDK_VERSION%"
|
||||
|
||||
# -------------------------
|
||||
# Run Tests
|
||||
- run:
|
||||
name: "Flow: Check Android"
|
||||
command: yarn flow-check-android
|
||||
- run:
|
||||
name: "Flow: Check iOS"
|
||||
command: yarn flow-check-ios
|
||||
- run:
|
||||
name: "Run Tests: JavaScript Tests"
|
||||
command: yarn test
|
||||
|
||||
# Optionally, run disabled tests
|
||||
- when:
|
||||
condition: << parameters.run_disabled_tests >>
|
||||
steps:
|
||||
- run: echo "Failing tests may be moved here temporarily."
|
||||
- run:
|
||||
name: Android Build
|
||||
command: ./gradlew.bat packages:rn-tester:android:app:assembleRelease
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Coverage
|
||||
# -------------------------
|
||||
# Collect JavaScript test coverage
|
||||
js_coverage:
|
||||
executor: nodelts
|
||||
executor: node8
|
||||
environment:
|
||||
- CI_BRANCH: $CIRCLE_BRANCH
|
||||
- CI_PULL_REQUEST: $CIRCLE_PULL_REQUEST
|
||||
@@ -699,31 +557,22 @@ jobs:
|
||||
- setup_artifacts
|
||||
- run_yarn
|
||||
- run:
|
||||
name: Collect test coverage information
|
||||
name: Test coverage
|
||||
command: |
|
||||
scripts/circleci/exec_swallow_error.sh yarn test --coverage --maxWorkers=2
|
||||
if [[ -e ./coverage/lcov.info ]]; then
|
||||
cat ./coverage/lcov.info | scripts/circleci/exec_swallow_error.sh ./node_modules/.bin/coveralls
|
||||
fi
|
||||
yarn test --coverage --maxWorkers=2
|
||||
cat ./coverage/lcov.info | ./node_modules/.bin/coveralls
|
||||
when: always
|
||||
- store_artifacts:
|
||||
path: ~/react-native/coverage/
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Releases
|
||||
# -------------------------
|
||||
# Publishes a new version onto npm
|
||||
# Publishes new version onto npm
|
||||
# Only works on stable branches when a properly tagged commit is pushed
|
||||
publish_npm_package:
|
||||
parameters:
|
||||
publish_npm_args:
|
||||
type: string
|
||||
default: --nonightly
|
||||
executor: reactnativeandroid
|
||||
steps:
|
||||
- run:
|
||||
name: Add github.com to SSH known hosts
|
||||
command: |
|
||||
mkdir -p ~/.ssh
|
||||
echo '|1|If6MU203eXTaaWL678YEfWkVMrw=|kqLeIAyTy8pzpj8x8Ae4Fr8Mtlc= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' >> ~/.ssh/known_hosts
|
||||
- restore_cache_checkout:
|
||||
checkout_type: android
|
||||
- run_yarn
|
||||
@@ -735,25 +584,12 @@ jobs:
|
||||
git config --global user.email "react-native-bot@users.noreply.github.com"
|
||||
git config --global user.name "npm Deployment Script"
|
||||
echo "machine github.com login react-native-bot password $GITHUB_TOKEN" > ~/.netrc
|
||||
- run: node ./scripts/publish-npm.js << parameters.publish_npm_args >>
|
||||
|
||||
# -------------------------
|
||||
# JOBS: Nightly
|
||||
# -------------------------
|
||||
nightly_job:
|
||||
machine: true
|
||||
steps:
|
||||
- run:
|
||||
name: Nightly
|
||||
command: |
|
||||
echo "Nightly build run"
|
||||
- run: node ./scripts/publish-npm.js
|
||||
|
||||
# -------------------------
|
||||
# WORK FLOWS
|
||||
# -------------------------
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
tests:
|
||||
jobs:
|
||||
- setup:
|
||||
@@ -776,67 +612,36 @@ workflows:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- test_js:
|
||||
run_disabled_tests: false
|
||||
requires:
|
||||
- setup_js
|
||||
- test_js_e2e:
|
||||
requires:
|
||||
- setup_js
|
||||
- test_js
|
||||
- test_android:
|
||||
run_disabled_tests: false
|
||||
requires:
|
||||
- setup_android
|
||||
- test_ios:
|
||||
name: test_ios_unit_jsc
|
||||
run_unit_tests: true
|
||||
requires:
|
||||
- setup_ios
|
||||
# DISABLED: USE_FRAMEWORKS=1 not supported by Flipper
|
||||
# - test_ios:
|
||||
# name: test_ios_unit_frameworks_jsc
|
||||
# use_frameworks: true
|
||||
# run_unit_tests: true
|
||||
# requires:
|
||||
# - setup_ios
|
||||
- test_ios:
|
||||
name: test_ios_unit_hermes
|
||||
use_hermes: true
|
||||
run_unit_tests: true
|
||||
name: test_ios_frameworks
|
||||
use_frameworks: true
|
||||
requires:
|
||||
- setup_ios
|
||||
# DISABLED: USE_FRAMEWORKS=1 not supported by Flipper
|
||||
# - test_ios:
|
||||
# name: test_ios_unit_frameworks_hermes
|
||||
# use_hermes: true
|
||||
# use_frameworks: true
|
||||
# run_unit_tests: true
|
||||
# requires:
|
||||
# - setup_ios
|
||||
# DISABLED: Detox tests need to be fixed
|
||||
# - test_ios:
|
||||
# name: test_ios_detox
|
||||
# run_detox_tests: true
|
||||
# requires:
|
||||
# - setup_ios
|
||||
# DISABLED: USE_FRAMEWORKS=1 not supported by Flipper
|
||||
# - test_ios:
|
||||
# name: test_ios_detox_frameworks
|
||||
# use_frameworks: true
|
||||
# run_detox_tests: true
|
||||
# requires:
|
||||
# - setup_ios
|
||||
- test_ios_e2e:
|
||||
requires:
|
||||
- setup_ios
|
||||
- test_js
|
||||
- test_js:
|
||||
name: test_js_prev_lts
|
||||
executor: nodeprevlts
|
||||
name: test_js_lts
|
||||
executor: nodelts
|
||||
requires:
|
||||
- setup_js
|
||||
- test_docker:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- test_windows:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
run_disabled_tests: false
|
||||
|
||||
releases:
|
||||
jobs:
|
||||
- setup:
|
||||
@@ -865,18 +670,11 @@ workflows:
|
||||
|
||||
analysis:
|
||||
jobs:
|
||||
- setup:
|
||||
name: setup_js
|
||||
|
||||
- setup:
|
||||
name: setup_android
|
||||
checkout_type: android
|
||||
executor: reactnativeandroid
|
||||
|
||||
- setup
|
||||
# Run lints on every commit other than those to the gh-pages branch
|
||||
- analyze_code:
|
||||
requires:
|
||||
- setup_android
|
||||
- setup
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
@@ -884,7 +682,7 @@ workflows:
|
||||
# Run code checks on PRs from forks
|
||||
- analyze_pr:
|
||||
requires:
|
||||
- setup_android
|
||||
- setup
|
||||
filters:
|
||||
branches:
|
||||
only: /^pull\/.*$/
|
||||
@@ -892,24 +690,4 @@ workflows:
|
||||
# Gather coverage
|
||||
- js_coverage:
|
||||
requires:
|
||||
- setup_js
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
jobs:
|
||||
- nightly_job
|
||||
|
||||
- setup:
|
||||
name: setup_android
|
||||
checkout_type: android
|
||||
executor: reactnativeandroid
|
||||
|
||||
- publish_npm_package:
|
||||
publish_npm_args: --nightly
|
||||
requires:
|
||||
- setup_android
|
||||
- setup
|
||||
|
||||
@@ -13,12 +13,5 @@ indent_size = 2
|
||||
[*.gradle]
|
||||
indent_size = 4
|
||||
|
||||
[*.kts]
|
||||
indent_size = 4
|
||||
|
||||
[BUCK]
|
||||
indent_size = 4
|
||||
|
||||
# Windows files
|
||||
[*.bat]
|
||||
end_of_line = crlf
|
||||
|
||||
+8
-7
@@ -1,10 +1,11 @@
|
||||
**/main.js
|
||||
# node_modules ignored by default
|
||||
|
||||
**/staticBundle.js
|
||||
bots/node_modules
|
||||
docs/generatedComponentApiDocs.js
|
||||
flow/
|
||||
Libraries/Renderer/*
|
||||
**/main.js
|
||||
Libraries/vendor/**/*
|
||||
node_modules/
|
||||
Libraries/Renderer/*
|
||||
packages/*/node_modules
|
||||
packages/react-native-codegen/lib
|
||||
pr-inactivity-bookmarklet.js
|
||||
question-bookmarklet.js
|
||||
flow/
|
||||
bots/node_modules
|
||||
@@ -5,28 +5,13 @@
|
||||
"./packages/eslint-config-react-native-community/index.js"
|
||||
],
|
||||
|
||||
"plugins": [
|
||||
"@react-native/eslint-plugin-codegen"
|
||||
],
|
||||
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"Libraries/**/*.js",
|
||||
],
|
||||
"rules": {
|
||||
"@react-native-community/no-haste-imports": 2,
|
||||
"@react-native-community/error-subclass-name": 2,
|
||||
"@react-native-community/platform-colors": 2,
|
||||
"@react-native/codegen/react-native-modules": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"flow-typed/**/*.js",
|
||||
],
|
||||
"rules": {
|
||||
quotes: 0
|
||||
rules: {
|
||||
'@react-native-community/no-haste-imports': 2
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -35,7 +20,7 @@
|
||||
"**/__mocks__/**/*.js",
|
||||
"**/__tests__/**/*.js",
|
||||
"jest/**/*.js",
|
||||
"packages/rn-tester/**/*.js",
|
||||
"RNTester/**/*.js",
|
||||
],
|
||||
"globals": {
|
||||
// Expose some Jest globals for test helpers
|
||||
@@ -53,8 +38,8 @@
|
||||
],
|
||||
"env": {
|
||||
"jasmine": true,
|
||||
"jest": true
|
||||
}
|
||||
}
|
||||
]
|
||||
"jest": true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
+17
-12
@@ -11,6 +11,10 @@
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; These should not be required directly
|
||||
; require from fbjs/lib instead: require('fbjs/lib/warning')
|
||||
.*/node_modules/warning/.*
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
@@ -19,20 +23,15 @@
|
||||
|
||||
[include]
|
||||
|
||||
[declarations]
|
||||
.*/node_modules/.*
|
||||
|
||||
[libs]
|
||||
interface.js
|
||||
Libraries/react-native/react-native-interface.js
|
||||
flow/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
exact_by_default=true
|
||||
indexed_access=false
|
||||
|
||||
format.bracket_spacing=false
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
@@ -40,9 +39,9 @@ module.file_ext=.ios.js
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/index.js'
|
||||
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/Libraries/react-native/react-native-implementation'
|
||||
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/\1'
|
||||
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/Libraries/Image/RelativeImageStub'
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/Libraries/Image/RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
@@ -50,16 +49,22 @@ suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FlowFixMeEmpty
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
sketchy-number=warn
|
||||
untyped-type-import=warn
|
||||
nonstrict-import=warn
|
||||
deprecated-type=error
|
||||
deprecated-type=warn
|
||||
unsafe-getters-setters=warn
|
||||
inexact-spread=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
deprecated-utility=error
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
@@ -71,4 +76,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.158.0
|
||||
^0.105.0
|
||||
|
||||
+17
-12
@@ -11,6 +11,10 @@
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; These should not be required directly
|
||||
; require from fbjs/lib instead: require('fbjs/lib/warning')
|
||||
.*/node_modules/warning/.*
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
@@ -19,20 +23,15 @@
|
||||
|
||||
[include]
|
||||
|
||||
[declarations]
|
||||
.*/node_modules/.*
|
||||
|
||||
[libs]
|
||||
interface.js
|
||||
Libraries/react-native/react-native-interface.js
|
||||
flow/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
exact_by_default=true
|
||||
indexed_access=false
|
||||
|
||||
format.bracket_spacing=false
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
@@ -40,9 +39,9 @@ module.file_ext=.android.js
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/index.js'
|
||||
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/Libraries/react-native/react-native-implementation'
|
||||
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/\1'
|
||||
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/Libraries/Image/RelativeImageStub'
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/Libraries/Image/RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
@@ -50,16 +49,22 @@ suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FlowFixMeEmpty
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_android\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_android\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
sketchy-number=warn
|
||||
untyped-type-import=warn
|
||||
nonstrict-import=warn
|
||||
deprecated-type=error
|
||||
deprecated-type=warn
|
||||
unsafe-getters-setters=warn
|
||||
inexact-spread=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
deprecated-utility=error
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
@@ -71,4 +76,4 @@ untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.158.0
|
||||
^0.105.0
|
||||
|
||||
+6
-3
@@ -1,3 +1,6 @@
|
||||
# Windows files should use crlf line endings
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
*.bat text eol=crlf
|
||||
# Force LF line endings for Bash scripts. On Windows the rest of the source
|
||||
# files will typically have CR+LF endings (Git default on Windows), but Bash
|
||||
# scripts need to have LF endings to work (under Cygwin), thus override to force
|
||||
# that.
|
||||
gradlew text eol=lf
|
||||
*.sh text eol=lf
|
||||
|
||||
+1
-4
@@ -29,12 +29,9 @@ Libraries/NativeAnimation/* @janicduplessis
|
||||
Libraries/Image/* @shergin
|
||||
Libraries/Text/* @shergin
|
||||
|
||||
# Modifications to package.json typically require
|
||||
# Modifications to package.json typically require
|
||||
# additional effort from a Facebook employee to land
|
||||
/package.json @hramos @cpojer
|
||||
|
||||
# These should not be modified through a GitHub PR
|
||||
LICENSE* @hramos @cpojer @yungsters
|
||||
|
||||
# The eslint-config-react-native-community package requires manual publishing after merging
|
||||
/packages/eslint-config-react-native-community/* @matt-oakes
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
✋ To keep the backlog clean and actionable, issues will be
|
||||
🚫 closed if they do not follow one of the issue templates:
|
||||
👉 https://github.com/facebook/react-native/issues/new/choose
|
||||
👉 Please follow one of these issue templates:
|
||||
- https://github.com/facebook/react-native/issues/new/choose
|
||||
|
||||
Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates.
|
||||
|
||||
@@ -2,29 +2,39 @@
|
||||
name: "🐛 Bug Report"
|
||||
about: Report a reproducible bug or regression in React Native.
|
||||
title: ''
|
||||
labels: 'Needs: Triage :mag:'
|
||||
labels: 'Bug'
|
||||
|
||||
---
|
||||
|
||||
Please provide all the information requested. Issues that do not follow this format are likely to stall.
|
||||
<!--
|
||||
Please provide a clear and concise description of what the bug is.
|
||||
Include screenshots if needed.
|
||||
Please test using the latest React Native release to make sure your issue has not already been fixed: http://facebook.github.io/react-native/docs/upgrading.html
|
||||
-->
|
||||
|
||||
## Description
|
||||
Please provide a clear and concise description of what the bug is. Include screenshots if needed.
|
||||
Please test using the latest React Native release to make sure your issue has not already been fixed: https://reactnative.dev/docs/upgrading.html
|
||||
|
||||
## React Native version:
|
||||
Run `react-native info` in your terminal and copy the results here.
|
||||
React Native version:
|
||||
<!--
|
||||
Run `react-native info` in your terminal and copy the results here.
|
||||
-->
|
||||
|
||||
## Steps To Reproduce
|
||||
Provide a detailed list of steps that reproduce the issue.
|
||||
|
||||
1.
|
||||
2.
|
||||
|
||||
## Expected Results
|
||||
Describe what you expected to happen.
|
||||
<!--
|
||||
Issues without reproduction steps or code are likely to stall.
|
||||
-->
|
||||
|
||||
Describe what you expected to happen:
|
||||
|
||||
|
||||
Snack, code example, screenshot, or link to a repository:
|
||||
|
||||
<!--
|
||||
Please provide a Snack (https://snack.expo.io/), a link to a repository on GitHub, or
|
||||
provide a minimal code example that reproduces the problem.
|
||||
You may provide a screenshot of the application if you think it is relevant to your bug report.
|
||||
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve. --
|
||||
-->
|
||||
|
||||
## Snack, code example, screenshot, or link to a repository:
|
||||
Please provide a Snack (https://snack.expo.io/), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem.
|
||||
You may provide a screenshot of the application if you think it is relevant to your bug report.
|
||||
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 📃 Documentation Issue
|
||||
url: https://github.com/facebook/react-native-website/issues
|
||||
about: Please report documentation issues in the React Native website repository.
|
||||
- name: ⤴️ Upgrade Issue
|
||||
url: https://github.com/react-native-community/upgrade-support
|
||||
about: Need help upgrading to a newer React Native version? Visit the Upgrade Support repository.
|
||||
- name: 🤔 Questions and Help
|
||||
url: https://reactnative.dev/help
|
||||
about: Looking for help with your app? Please refer to the React Native community's support resources.
|
||||
- name: 🚀 Discussions and Proposals
|
||||
url: https://github.com/react-native-community/discussions-and-proposals
|
||||
about: Discuss the future of React Native in the React Native community's discussions and proposals repository.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: "📃 Documentation Issue"
|
||||
about: Documentation issues are handled in https://github.com/facebook/react-native-website.
|
||||
title: 'Docs:'
|
||||
labels: 'Type: Docs'
|
||||
|
||||
---
|
||||
|
||||
🚨 Please do not open a documentation issue in the core React Native repository. 🚨
|
||||
|
||||
The React Native website is hosted on a separate repository. You may let the
|
||||
team know about any issues with the documentation by opening an issue there:
|
||||
- https://github.com/facebook/react-native-website
|
||||
- https://github.com/facebook/react-native-website/issues
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: "🤔 Questions and Help"
|
||||
about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/react-native.
|
||||
title: 'Question: '
|
||||
labels: 'Type: Question'
|
||||
|
||||
---
|
||||
|
||||
🚨 The issue tracker is not for questions. 🚨
|
||||
|
||||
As it happens, support requests that are created as issues are likely to be closed. We want to make sure you are able to find the help you seek. Please take a look at the following resources.
|
||||
|
||||
## Coding Questions
|
||||
|
||||
### https://stackoverflow.com/questions/tagged/react-native
|
||||
|
||||
If you have a coding question related to React Native, it might be better suited for Stack Overflow. It's a great place to browse through frequent questions about using React Native, as well as ask for help with specific questions.
|
||||
|
||||
|
||||
## Talk to other React Native developers
|
||||
|
||||
### https://www.reactiflux.com/
|
||||
|
||||
Reactiflux is an active community of React and React Native developers. If you are looking for immediate assistance or have a general question about React Native, the #react-native channel is a good place to start.
|
||||
|
||||
If you want to participate in casual discussions about the use of React Native, consider participating in one of the following forums:
|
||||
- Discord Community: https://discord.gg/0ZcbPKXt5bZjGY5n
|
||||
- Spectrum Chat: https://spectrum.chat/react-native
|
||||
- Facebook Group: https://www.facebook.com/groups/react.native.community
|
||||
|
||||
If you'd like to discuss topics related to the future of React Native, or would like to propose a new feature or change before sending a pull request, please check out the discussions and proposals repo:
|
||||
- https://github.com/react-native-community/discussions-and-proposals
|
||||
|
||||
> For a full list of community resources, check out React Native's Community page at https://facebook.github.io/react-native/help.
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
## Changelog
|
||||
|
||||
<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
|
||||
https://github.com/facebook/react-native/wiki/Changelog
|
||||
-->
|
||||
<!-- Help reviewers and the release process by writing your own changelog entry. See https://github.com/facebook/react-native/wiki/Changelog for an example. -->
|
||||
|
||||
[CATEGORY] [TYPE] - Message
|
||||
|
||||
|
||||
+2
-2
@@ -27,9 +27,9 @@ If you'd like to discuss topics related to the future of React Native, please ch
|
||||
|
||||
If you want to participate in casual discussions about the use of React Native, consider participating in one of the following forums:
|
||||
|
||||
- [Reactiflux Discord Server](https://www.reactiflux.com)
|
||||
- [Reactiflux Discord Server](https://www.reactiflux)
|
||||
- [Spectrum Chat](https://spectrum.chat/react-native)
|
||||
- [React Native Community Facebook Group](https://www.facebook.com/groups/react.native.community)
|
||||
|
||||
|
||||
> For a full list of community resources, check out [React Native's Community page](https://reactnative.dev/help).
|
||||
> For a full list of community resources, check out [React Native's Community page](https://facebook.github.io/react-native/help).
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# Configuration for Respond To Issue Based on Label https://github.com/marketplace/actions/respond-to-issue-based-on-label
|
||||
|
||||
"Type: Invalid":
|
||||
close: true
|
||||
"Type: Question":
|
||||
comment: >
|
||||
We are using GitHub issues exclusively to track bugs in React Native. GitHub may not be the ideal place to ask a question, but you can try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native), or on [Reactiflux](https://www.reactiflux.com/).
|
||||
close: true
|
||||
"Type: Docs":
|
||||
comment: >
|
||||
Please report documentation issues in the [`react-native-website`](https://github.com/facebook/react-native-website/issues) repository.
|
||||
close: true
|
||||
"Type: Upgrade Issue":
|
||||
comment: >
|
||||
Do you need help upgrading to a newer React Native version? Visit the [Upgrade Support repository](https://github.com/react-native-community/upgrade-support) or use the [upgrade helper](https://react-native-community.github.io/upgrade-helper/) to see the changes that need to be made to upgrade your app.
|
||||
close: true
|
||||
"Resolution: For Stack Overflow":
|
||||
comment: >
|
||||
We are using GitHub issues exclusively to track bugs in the core React Native library. Please try asking over on [Stack Overflow](http://stackoverflow.com/questions/tagged/react-native) as it is better suited for this type of question.
|
||||
close: true
|
||||
"Needs: Issue Template":
|
||||
comment: >
|
||||
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
|
||||
Missing Required Fields
|
||||
</th></tr><tr><td>:information_source:</td><td>
|
||||
It looks like your issue may be missing some necessary information. GitHub provides an example template whenever a <a href="https://github.com/facebook/react-native/issues/new?template=bug_report.md">new issue is created</a>. Could you go back and make sure to fill out the template? You may edit this issue, or close it and open a new one.
|
||||
</td></tr></tbody></table>
|
||||
labels:
|
||||
- "Needs: Author Feedback"
|
||||
"Needs: Environment Info":
|
||||
comment: >
|
||||
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
|
||||
Missing Environment Information
|
||||
</th></tr><tr><td>:information_source:</td><td>
|
||||
Your issue may be missing information about your development environment. You can obtain the missing information by running <code>react-native info</code> in a console.
|
||||
</td></tr></tbody></table>
|
||||
labels:
|
||||
- "Needs: Author Feedback"
|
||||
"Needs: Verify on Latest Version":
|
||||
comment: >
|
||||
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
|
||||
Using Old Version
|
||||
</th></tr><tr><td>:information_source:</td><td>
|
||||
It looks like you are using an older version of React Native. Please <a href="https://reactnative.dev/docs/upgrading">upgrade</a> to the latest version, and verify if the issue persists. If it does not, please let us know so we can close out this issue. This helps us ensure we are looking at issues that still exist in the current release.
|
||||
</td></tr></tbody></table>
|
||||
labels:
|
||||
- "Needs: Author Feedback"
|
||||
"Needs: Repro":
|
||||
comment: >
|
||||
<table><tbody><tr><th width="50">:warning:</th><th width="100%">
|
||||
Missing Reproducible Example
|
||||
</th></tr><tr><td>:information_source:</td><td>
|
||||
It looks like your issue is missing a reproducible example. Please provide a <a href="https://snack.expo.io">Snack</a> or a repository that demonstrates the issue you are reporting in a <a href="https://stackoverflow.com/help/minimal-reproducible-example">minimal, complete, and reproducible</a> manner.
|
||||
</td></tr></tbody></table>
|
||||
labels:
|
||||
- "Needs: Author Feedback"
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Issue Needs Attention
|
||||
# This workflow is triggered on issue comments.
|
||||
on:
|
||||
issue_comment:
|
||||
types: created
|
||||
|
||||
jobs:
|
||||
applyNeedsAttentionLabel:
|
||||
name: Apply Needs Attention Label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Apply Needs Attention Label
|
||||
uses: hramos/needs-attention@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
response-required-label: "Needs: Author Feedback"
|
||||
needs-attention-label: "Needs: Attention"
|
||||
id: needs-attention
|
||||
- name: Result
|
||||
run: echo '${{ steps.needs-attention.outputs.result }}'
|
||||
@@ -1,16 +0,0 @@
|
||||
name: On Issue Labeled
|
||||
# This workflow is triggered when a label is added to an issue.
|
||||
on:
|
||||
issues:
|
||||
types: labeled
|
||||
|
||||
jobs:
|
||||
respondToIssueBasedOnLabel:
|
||||
name: Respond to Issue Based on Label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Respond to Issue Based on Label
|
||||
uses: hramos/respond-to-issue-based-on-label@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
+10
-38
@@ -23,31 +23,19 @@ project.xcworkspace
|
||||
|
||||
# Gradle
|
||||
/build/
|
||||
/packages/react-native-codegen/android/build/
|
||||
/packages/react-native-gradle-plugin/build/
|
||||
/packages/rn-tester/android/app/.cxx/
|
||||
/packages/rn-tester/android/app/build/
|
||||
/packages/rn-tester/android/app/gradle/
|
||||
/packages/rn-tester/android/app/gradlew
|
||||
/packages/rn-tester/android/app/gradlew.bat
|
||||
/RNTester/android/app/build/
|
||||
/RNTester/android/app/gradle/
|
||||
/RNTester/android/app/gradlew
|
||||
/RNTester/android/app/gradlew.bat
|
||||
/ReactAndroid/build/
|
||||
/ReactAndroid/gradle/
|
||||
/ReactAndroid/gradlew
|
||||
/ReactAndroid/gradlew.bat
|
||||
|
||||
# Buck
|
||||
.buckd
|
||||
buck-out
|
||||
/.lsp.buckd
|
||||
/.lsp-buck-out
|
||||
/ReactAndroid/src/main/jni/prebuilt/lib/
|
||||
/ReactAndroid/src/main/jni/prebuilt/lib/armeabi-v7a/
|
||||
/ReactAndroid/src/main/jni/prebuilt/lib/x86/
|
||||
/ReactAndroid/src/main/gen
|
||||
|
||||
# Android Studio
|
||||
.project
|
||||
.settings
|
||||
.classpath
|
||||
|
||||
# Watchman
|
||||
.watchmanconfig
|
||||
|
||||
@@ -80,33 +68,17 @@ package-lock.json
|
||||
|
||||
# ReactCommon subdir shouldn't have Xcode project
|
||||
/ReactCommon/**/*.xcodeproj
|
||||
/packages/rn-tester/build
|
||||
/packages/rn-tester/android/app/build/*
|
||||
RNTester/build
|
||||
|
||||
# Libs that shouldn't have Xcode project
|
||||
/Libraries/FBLazyVector/**/*.xcodeproj
|
||||
/Libraries/FBReactNativeSpec/**/*.xcodeproj
|
||||
/Libraries/RCTRequired/**/*.xcodeproj
|
||||
/React/CoreModules/**/*.xcodeproj
|
||||
/React/FBReactNativeSpec/**/*.xcodeproj
|
||||
/packages/react-native-codegen/**/*.xcodeproj
|
||||
|
||||
# CocoaPods
|
||||
/template/ios/Pods/
|
||||
/template/ios/Podfile.lock
|
||||
/packages/rn-tester/Gemfile.lock
|
||||
|
||||
# Ignore RNTester specific Pods, but keep the __offline_mirrors__ here.
|
||||
/packages/rn-tester/Pods/*
|
||||
!/packages/rn-tester/Pods/__offline_mirrors__
|
||||
|
||||
# react-native-codegen
|
||||
/packages/react-native-codegen/lib
|
||||
/ReactCommon/react/renderer/components/rncore/
|
||||
/packages/rn-tester/NativeModuleExample/ScreenshotManagerSpec*
|
||||
|
||||
# Visual studio
|
||||
.vscode
|
||||
.vs
|
||||
|
||||
# Android memory profiler files
|
||||
*.hprof
|
||||
RNTester/Pods/Local Podspecs
|
||||
RNTester/Pods/Target Support Files
|
||||
|
||||
+1
-2
@@ -3,6 +3,5 @@
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": false,
|
||||
"jsxBracketSameLine": true,
|
||||
"arrowParens": "avoid"
|
||||
"jsxBracketSameLine": true
|
||||
}
|
||||
|
||||
+3
-75
@@ -1,77 +1,5 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at <opensource-conduct@fb.com>. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to.
|
||||
Please read the [full text](https://code.fb.com/codeofconduct/)
|
||||
so that you can understand what actions will and will not be tolerated.
|
||||
|
||||
+22
-26
@@ -8,9 +8,9 @@ The [Open Source Guides](https://opensource.guide/) website has a collection of
|
||||
* [Building Welcoming Communities](https://opensource.guide/building-community/)
|
||||
|
||||
|
||||
### [Code of Conduct](https://github.com/facebook/react-native/blob/HEAD/CODE_OF_CONDUCT.md)
|
||||
### [Code of Conduct](https://code.fb.com/codeofconduct/)
|
||||
|
||||
As a reminder, all contributors are expected to adhere to the [Code of Conduct](https://github.com/facebook/react-native/blob/HEAD/CODE_OF_CONDUCT.md).
|
||||
As a reminder, all contributors are expected to adhere to the [Code of Conduct](https://code.facebook.com/codeofconduct).
|
||||
|
||||
## Ways to Contribute
|
||||
|
||||
@@ -18,7 +18,7 @@ If you are eager to start contributing code right away, we have a list of [good
|
||||
|
||||
There are other ways you can contribute without writing a single line of code. Here are a few things you can do to help out:
|
||||
|
||||
1. **Replying and handling open issues.** We get a lot of issues every day, and some of them may lack necessary information. You can help out by guiding people through the process of filling out the issue template, asking for clarifying information, or pointing them to existing issues that match their description of the problem. We cover more about this process in the [Issue Triage wiki](https://github.com/facebook/react-native/wiki/Triaging-GitHub-Issues).
|
||||
1. **Replying and handling open issues.** We get a lot of issues every day, and some of them may lack necessary information. You can help out by guiding people through the process of filling out the issue template, asking for clarifying information, or pointing them to existing issues that match their description of the problem. We cover more about this process in the [Issue Triage wiki](https://github.com/facebook/react-native/wiki/Issues#triage).
|
||||
2. **Reviewing pull requests for the docs.** Reviewing [documentation updates](https://github.com/facebook/react-native-website/pulls) can be as simple as checking for spelling and grammar. If you encounter situations that can be explained better in the docs, click **Edit** at the top of most docs pages to get started with your own contribution.
|
||||
3. **Help people write test plans.** Some pull requests sent to the main repository may lack a proper test plan. These help reviewers understand how the change was tested, and can speed up the time it takes for a contribution to be accepted.
|
||||
|
||||
@@ -30,8 +30,8 @@ We use GitHub issues and pull requests to keep track of bug reports and contribu
|
||||
|
||||
You can learn more about the contribution process in the following documents:
|
||||
|
||||
* [Issues](https://github.com/facebook/react-native/wiki/Triaging-GitHub-Issues)
|
||||
* [Pull Requests](https://github.com/facebook/react-native/wiki/Managing-Pull-Requests)
|
||||
* [Issues](https://github.com/facebook/react-native/wiki/Issues)
|
||||
* [Pull Requests](https://github.com/facebook/react-native/wiki/Pull-Requests)
|
||||
|
||||
We also have a thriving community of contributors who would be happy to help you get set up. You can reach out to us through [@ReactNative](http://twitter.com/reactnative) (the React Native team) and [@ReactNativeComm](http://twitter.com/reactnativecomm) (the React Native Community organization).
|
||||
|
||||
@@ -42,7 +42,7 @@ The main repository, <https://github.com/facebook/react-native>, contains the Re
|
||||
There are a few other repositories you might want to familiarize yourself with:
|
||||
|
||||
* **React Native website** which contains the source code for the website, including the documentation, located at <https://github.com/facebook/react-native-website>
|
||||
* **Releases** are coordinated through the <https://github.com/react-native-community/releases> repository. This includes important documents such as the Changelog.
|
||||
* **Releases** are coordinated through the <https://github.com/react-native-community/react-native-releases> repository. This includes important documents such as the Changelog.
|
||||
* **Discussions** about the future of React Native take place in the <https://github.com/react-native-community/discussions-and-proposals> repository.
|
||||
* **High-quality plugins** for React Native can be found throughout the [React Native Community GitHub Organization](http://github.com/react-native-community/).
|
||||
|
||||
@@ -50,7 +50,7 @@ Browsing through these repositories should provide some insight into how the Rea
|
||||
|
||||
## GitHub Issues
|
||||
|
||||
We use GitHub issues to track bugs exclusively. We have documented our issue handling processes in the [Issues wiki](https://github.com/facebook/react-native/wiki/Triaging-GitHub-Issues).
|
||||
We use GitHub issues to track bugs exclusively. We have documented our issue handling processes in the [Issues wiki](https://github.com/facebook/react-native/wiki/Issues).
|
||||
|
||||
### Security Bugs
|
||||
|
||||
@@ -58,46 +58,42 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
||||
|
||||
## Helping with Documentation
|
||||
|
||||
The React Native documentation is hosted as part of the React Native website repository at https://github.com/facebook/react-native-website. The website itself is located at <https://reactnative.dev/> and it is built using [Docusaurus](https://docusaurus.io/). If there's anything you'd like to change in the docs, you can get started by clicking on the "Edit" button located on the upper right of most pages in the website.
|
||||
The React Native documentation is hosted as part of the React Native website repository at https://github.com/facebook/react-native-website. The website itself is located at <https://facebook.github.io/react-native> and it is built using [Docusaurus](https://docusaurus.io/). If there's anything you'd like to change in the docs, you can get started by clicking on the "Edit" button located on the upper right of most pages in the website.
|
||||
|
||||
If you are adding new functionality or introducing a change in behavior, we will ask you to update the documentation to reflect your changes.
|
||||
|
||||
### Contributing to the Blog
|
||||
|
||||
The React Native blog is generated [from the Markdown sources for the blog](https://github.com/facebook/react-native-website/tree/HEAD/website/blog).
|
||||
The React Native blog is generated [from the Markdown sources for the blog](https://github.com/facebook/react-native-website/tree/master/website/blog).
|
||||
|
||||
Please open an issue in the https://github.com/facebook/react-native-website repository or tag us on [@ReactNative on Twitter](http://twitter.com/reactnative) and get the go-ahead from a maintainer before writing an article intended for the React Native blog. In most cases, you might want to share your article on your own blog or writing medium instead. It's worth asking, though, in case we find your article is a good fit for the blog.
|
||||
|
||||
We recommend referring to the [CONTRIBUTING](https://github.com/facebook/react-native-website/blob/HEAD/CONTRIBUTING.md) document for the `react-native-website` repository to learn more about contributing to the website in general.
|
||||
We recommend referring to the [CONTRIBUTING](https://github.com/facebook/react-native-website/blob/master/CONTRIBUTING.md) document for the `react-native-website` repository to learn more about contributing to the website in general.
|
||||
|
||||
## Contributing Code
|
||||
|
||||
Code-level contributions to React Native generally come in the form of [pull requests](https://help.github.com/en/articles/about-pull-requests). These are done by forking the repo and making changes locally. Directly in the repo, there is the [`rn-tester` app](/packages/rn-tester) that you can install on your device (or simulators) and use to test the changes you're making to React Native sources.
|
||||
Code-level contributions to React Native generally come in the form of [pull requests](https://help.github.com/en/articles/about-pull-requests). The process of proposing a change to React Native can be summarized as follows:
|
||||
|
||||
The process of proposing a change to React Native can be summarized as follows:
|
||||
|
||||
1. Fork the React Native repository and create your branch from `main`.
|
||||
2. Make the desired changes to React Native sources. Use the `packages/rn-tester` app to test them out.
|
||||
3. If you've added code that should be tested, add tests.
|
||||
4. If you've changed APIs, update the documentation, which lives in [another repo](https://github.com/facebook/react-native-website/).
|
||||
5. Ensure the test suite passes, either locally or on CI once you opened a pull request.
|
||||
6. Make sure your code lints (for example via `yarn lint --fix`).
|
||||
7. Push the changes to your fork.
|
||||
8. Create a pull request to the React Native repository.
|
||||
9. Review and address comments on your pull request.
|
||||
1. Fork the React Native repository and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the test suite passes, either locally or on CI once you opened a pull request.
|
||||
5. Make sure your code lints (for example via `yarn lint --fix`).
|
||||
6. Push the changes to your fork.
|
||||
7. Create a pull request to the React Native repository.
|
||||
8. Review and address comments on your pull request.
|
||||
1. A bot may comment with suggestions. Generally we ask you to resolve these first before a maintainer will review your code.
|
||||
2. If changes are requested and addressed, please [request review](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) to notify reviewers to take another look.
|
||||
10. If you haven't already, please complete the [Contributor License Agreement](https://github.com/facebook/react-native/wiki/Contributor-License-Agreement) ("CLA"). **[Complete your CLA here.](https://code.facebook.com/cla)**
|
||||
9. If you haven't already, please complete the [Contributor License Agreement](https://github.com/facebook/react-native/wiki/Contributor-License-Agreement) ("CLA"). **[Complete your CLA here.](https://code.facebook.com/cla)**
|
||||
|
||||
If all goes well, your pull request will be merged. If it is not merged, maintainers will do their best to explain the reason why.
|
||||
|
||||
### Step-by-step Guide
|
||||
|
||||
Whenever you are ready to contribute code, check out our [step-by-step guide to sending your first pull request](https://github.com/facebook/react-native/wiki/How-to-Open-a-Pull-Request), or read the [How to Contribute Code](https://github.com/facebook/react-native/wiki/How-to-Contribute-Code) wiki for more details.
|
||||
Whenever you are ready to contribute code, check out our [step-by-step guide to sending your first pull request](https://github.com/facebook/react-native/wiki/Pull-Requests#getting-ready-to-submit-your-first-pull-request), or read the [How to Contribute Code](https://github.com/facebook/react-native/wiki/How-to-Contribute) wiki for more details.
|
||||
|
||||
### Tests
|
||||
|
||||
Tests help us prevent regressions from being introduced to the codebase. The GitHub repository is continuously tested using Circle and Appveyor, the results of which are available through the Checks functionality on [commits](https://github.com/facebook/react-native/commits/HEAD) and pull requests. You can learn more about running and writing tests in the [Tests wiki](http://github.com/facebook/react-native/wiki/Tests).
|
||||
Tests help us prevent regressions from being introduced to the codebase. The GitHub repository is continuously tested using Circle and Appveyor, the results of which are available through the Checks functionality on [commits](https://github.com/facebook/react-native/commits/master) and pull requests. You can learn more about running and writing tests in the [Tests wiki](http://github.com/facebook/react-native/wiki/Tests).
|
||||
|
||||
## Community Contributions
|
||||
|
||||
|
||||
+4
-5
@@ -10,15 +10,14 @@ There are three types of stakeholders:
|
||||
|
||||
## Partners
|
||||
|
||||
Partners are companies that are significantly invested in React Native and have been for years. Informed by their use of React Native, they push for improvements of the core and/or the ecosystem around it. Partners think of React Native as a product: they understand the trade offs that the project makes as well as future plans and goals. Together we shape the vision for React Native to make it the best way to build applications.
|
||||
Partners are companies that are significantly invested in React Native and have been for years. Informed by their use of React Native, they push for improvements of the core and/or the ecosystem around it. Facebook's partners think of React Native as a product: they understand the trade offs that the project makes as well as future plans and goals. Together we shape the vision for React Native to make it the best way to build applications.
|
||||
|
||||
React Native's current set of partners include Callstack, Expo, Facebook, Infinite Red, Microsoft and Software Mansion. Many engineers from these companies are core contributors, and their partner responsibilities also include:
|
||||
Our current set of partners include Callstack, Expo, Infinite Red, Microsoft and Software Mansion. Many engineers from these companies are core contributors, and their partner responsibilities also include:
|
||||
|
||||
* **[Callstack](https://callstack.com/):** Manages releases, maintains the [React Native CLI](https://github.com/react-native-community/react-native-cli) and organizes [React Native EU](https://react-native.eu/)
|
||||
* **[Expo](https://expo.io/):** Builds [expo](https://github.com/expo/expo) on top of React Native to simplify app development
|
||||
* **[Facebook](https://opensource.facebook.com):** Oversees the React Native product and maintains the [React Native core repo](https://reactnative.dev/)
|
||||
* **[Infinite Red](https://infinite.red/):** Maintains the [ignite cli/boilerplate](https://github.com/infinitered/ignite), organizes [Chain React Conf](https://cr.infinite.red/)
|
||||
* **[Microsoft](http://aka.ms/reactnative):** Develops [React Native Windows](https://github.com/Microsoft/react-native-windows) and [React Native macOS](https://github.com/microsoft/react-native-macos) for building apps that target Windows and macOS
|
||||
* **[Infinite Red](https://infinite.red/):** Maintains the [ignite cli/boilerplate](https://github.com/infinitered/ignite), organizes [Chain React Conf](https://infinite.red/ChainReactConf)
|
||||
* **[Microsoft](https://www.microsoft.com/en-gb/):** Develops [React Native Windows](https://github.com/Microsoft/react-native-windows) for the Universal Windows Platform (UWP)
|
||||
* **[Software Mansion](https://swmansion.com/):** Maintain core infrastructure including JSC, Animated, and other popular third-party plugins.
|
||||
|
||||
In terms of open source work, pull requests from partners are commonly prioritized. When you are contributing to React Native, you'll most likely meet somebody who works at one of the partner companies and who is a core contributor:
|
||||
|
||||
@@ -8,14 +8,17 @@
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
import invariant from 'invariant';
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const {View} = ReactNative;
|
||||
const RCTDeviceEventEmitter = require('react-native/Libraries/EventEmitter/RCTDeviceEventEmitter');
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
import NativeAccessibilityManager from 'react-native/Libraries/Components/AccessibilityInfo/NativeAccessibilityManager';
|
||||
import {DeviceEventEmitter, NativeModules, View} from 'react-native';
|
||||
import * as React from 'react';
|
||||
import invariant from 'invariant';
|
||||
|
||||
const {TestModule} = NativeModules;
|
||||
|
||||
class AccessibilityManagerTest extends React.Component<{...}> {
|
||||
class AccessibilityManagerTest extends React.Component<{}> {
|
||||
componentDidMount() {
|
||||
invariant(
|
||||
NativeAccessibilityManager,
|
||||
@@ -36,7 +39,7 @@ class AccessibilityManagerTest extends React.Component<{...}> {
|
||||
accessibilityExtraExtraLarge: 11.0,
|
||||
accessibilityExtraExtraExtraLarge: 12.0,
|
||||
});
|
||||
DeviceEventEmitter.addListener('didUpdateDimensions', update => {
|
||||
RCTDeviceEventEmitter.addListener('didUpdateDimensions', update => {
|
||||
TestModule.markTestPassed(update.window.fontScale === 4.0);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,19 +19,14 @@ const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer')
|
||||
|
||||
const TEST_PAYLOAD = {foo: 'bar'};
|
||||
|
||||
type AppEvent = {
|
||||
data: Object,
|
||||
ts: number,
|
||||
...
|
||||
};
|
||||
type AppEvent = {data: Object, ts: number};
|
||||
type State = {
|
||||
sent: 'none' | AppEvent,
|
||||
received: 'none' | AppEvent,
|
||||
elapsed?: string,
|
||||
...
|
||||
};
|
||||
|
||||
class AppEventsTest extends React.Component<{...}, State> {
|
||||
class AppEventsTest extends React.Component<{}, State> {
|
||||
state: State = {sent: 'none', received: 'none'};
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
@@ -16,7 +16,6 @@ const {AsyncStorage, Text, View, StyleSheet} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
const deepDiffer = require('react-native/Libraries/Utilities/differ/deepDiffer');
|
||||
const nullthrows = require('nullthrows');
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
@@ -44,32 +43,15 @@ function expectTrue(condition: boolean, message: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Type-safe wrapper around JSON.stringify
|
||||
function stringify(
|
||||
value:
|
||||
| void
|
||||
| null
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| {...}
|
||||
| $ReadOnlyArray<mixed>,
|
||||
): string {
|
||||
if (typeof value === 'undefined') {
|
||||
return 'undefined';
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function expectEqual(lhs, rhs, testname: string) {
|
||||
expectTrue(
|
||||
!deepDiffer(lhs, rhs),
|
||||
'Error in test ' +
|
||||
testname +
|
||||
': expected\n' +
|
||||
stringify(rhs) +
|
||||
JSON.stringify(rhs) +
|
||||
'\ngot\n' +
|
||||
stringify(lhs),
|
||||
JSON.stringify(lhs),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +61,7 @@ function expectAsyncNoError(place, err) {
|
||||
}
|
||||
expectTrue(
|
||||
err === null,
|
||||
'Unexpected error in ' + place + ': ' + stringify(err),
|
||||
'Unexpected error in ' + place + ': ' + JSON.stringify(err),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,7 +71,7 @@ function testSetAndGet() {
|
||||
AsyncStorage.getItem(KEY_1, (err2, result) => {
|
||||
expectAsyncNoError('testSetAndGet/getItem', err2);
|
||||
expectEqual(result, VAL_1, 'testSetAndGet setItem');
|
||||
updateMessage('get(key_1) correctly returned ' + String(result));
|
||||
updateMessage('get(key_1) correctly returned ' + result);
|
||||
runTestCase('should get null for missing key', testMissingGet);
|
||||
});
|
||||
});
|
||||
@@ -99,7 +81,7 @@ function testMissingGet() {
|
||||
AsyncStorage.getItem(KEY_2, (err, result) => {
|
||||
expectAsyncNoError('testMissingGet/setItem', err);
|
||||
expectEqual(result, null, 'testMissingGet');
|
||||
updateMessage('missing get(key_2) correctly returned ' + String(result));
|
||||
updateMessage('missing get(key_2) correctly returned ' + result);
|
||||
runTestCase('check set twice results in a single key', testSetTwice);
|
||||
});
|
||||
}
|
||||
@@ -123,9 +105,8 @@ function testRemoveItem() {
|
||||
AsyncStorage.getAllKeys((err, result) => {
|
||||
expectAsyncNoError('testRemoveItem/getAllKeys', err);
|
||||
expectTrue(
|
||||
nullthrows(result).indexOf(KEY_1) >= 0 &&
|
||||
nullthrows(result).indexOf(KEY_2) >= 0,
|
||||
'Missing KEY_1 or KEY_2 in ' + '(' + nullthrows(result).join() + ')',
|
||||
result.indexOf(KEY_1) >= 0 && result.indexOf(KEY_2) >= 0,
|
||||
'Missing KEY_1 or KEY_2 in ' + '(' + result + ')',
|
||||
);
|
||||
updateMessage('testRemoveItem - add two items');
|
||||
AsyncStorage.removeItem(KEY_1, err2 => {
|
||||
@@ -142,8 +123,8 @@ function testRemoveItem() {
|
||||
AsyncStorage.getAllKeys((err4, result3) => {
|
||||
expectAsyncNoError('testRemoveItem/getAllKeys', err4);
|
||||
expectTrue(
|
||||
nullthrows(result3).indexOf(KEY_1) === -1,
|
||||
'Unexpected: KEY_1 present in ' + nullthrows(result3).join(),
|
||||
result3.indexOf(KEY_1) === -1,
|
||||
'Unexpected: KEY_1 present in ' + result3,
|
||||
);
|
||||
updateMessage('proper length returned.');
|
||||
runTestCase('should merge values', testMerge);
|
||||
@@ -156,17 +137,13 @@ function testRemoveItem() {
|
||||
}
|
||||
|
||||
function testMerge() {
|
||||
AsyncStorage.setItem(KEY_MERGE, stringify(VAL_MERGE_1), err1 => {
|
||||
AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), err1 => {
|
||||
expectAsyncNoError('testMerge/setItem', err1);
|
||||
AsyncStorage.mergeItem(KEY_MERGE, stringify(VAL_MERGE_2), err2 => {
|
||||
AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), err2 => {
|
||||
expectAsyncNoError('testMerge/mergeItem', err2);
|
||||
AsyncStorage.getItem(KEY_MERGE, (err3, result) => {
|
||||
expectAsyncNoError('testMerge/setItem', err3);
|
||||
expectEqual(
|
||||
JSON.parse(nullthrows(result)),
|
||||
VAL_MERGE_EXPECT,
|
||||
'testMerge',
|
||||
);
|
||||
expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge');
|
||||
updateMessage('objects deeply merged\nDone!');
|
||||
runTestCase('multi set and get', testOptimizedMultiGet);
|
||||
});
|
||||
@@ -175,10 +152,7 @@ function testMerge() {
|
||||
}
|
||||
|
||||
function testOptimizedMultiGet() {
|
||||
let batch = [
|
||||
[KEY_1, VAL_1],
|
||||
[KEY_2, VAL_2],
|
||||
];
|
||||
let batch = [[KEY_1, VAL_1], [KEY_2, VAL_2]];
|
||||
let keys = batch.map(([key, value]) => key);
|
||||
AsyncStorage.multiSet(batch, err1 => {
|
||||
// yes, twice on purpose
|
||||
@@ -188,7 +162,8 @@ function testOptimizedMultiGet() {
|
||||
expectAsyncNoError(`${i} testOptimizedMultiGet/multiGet`, err2);
|
||||
expectEqual(result, batch, `${i} testOptimizedMultiGet multiGet`);
|
||||
updateMessage(
|
||||
'multiGet([key_1, key_2]) correctly returned ' + stringify(result),
|
||||
'multiGet([key_1, key_2]) correctly returned ' +
|
||||
JSON.stringify(result),
|
||||
);
|
||||
done();
|
||||
});
|
||||
@@ -196,8 +171,8 @@ function testOptimizedMultiGet() {
|
||||
});
|
||||
}
|
||||
|
||||
class AsyncStorageTest extends React.Component<{...}, $FlowFixMeState> {
|
||||
state: any | {|done: boolean, messages: string|} = {
|
||||
class AsyncStorageTest extends React.Component<{}, $FlowFixMeState> {
|
||||
state: any | $TEMPORARY$object<{|done: boolean, messages: string|}> = {
|
||||
messages: 'Initializing...',
|
||||
done: false,
|
||||
};
|
||||
@@ -218,10 +193,9 @@ class AsyncStorageTest extends React.Component<{...}, $FlowFixMeState> {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>
|
||||
{/* $FlowFixMe[incompatible-type] (>=0.54.0 site=react_native_fb,react_
|
||||
* native_oss) This comment suppresses an error found when Flow v0.54
|
||||
* was deployed. To see the error delete this comment and run Flow.
|
||||
*/
|
||||
{/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error found when Flow v0.54 was deployed.
|
||||
* To see the error delete this comment and run Flow. */
|
||||
this.constructor.displayName + ': '}
|
||||
{this.state.done ? 'Done' : 'Testing...'}
|
||||
{'\n\n' + this.state.messages}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
load("@fbsource//tools/build_defs:js_glob.bzl", "js_glob")
|
||||
load("@fbsource//tools/build_defs/oss:metro_defs.bzl", "rn_library")
|
||||
|
||||
# This file was generated by running
|
||||
# js1 build buckfiles
|
||||
|
||||
rn_library(
|
||||
name = "IntegrationTests",
|
||||
srcs = js_glob(
|
||||
[
|
||||
"**/*",
|
||||
],
|
||||
excludes = [
|
||||
"**/__*__/**",
|
||||
"**/*.command",
|
||||
"**/*.md",
|
||||
"websocket_integration_test_server.js",
|
||||
],
|
||||
),
|
||||
labels = ["supermodule:xplat/default/public.react_native.tests"],
|
||||
skip_processors = True,
|
||||
visibility = ["PUBLIC"],
|
||||
deps = [
|
||||
"//xplat/js:node_modules__invariant",
|
||||
"//xplat/js:node_modules__nullthrows",
|
||||
"//xplat/js/RKJSModules/vendor/react:react",
|
||||
"//xplat/js/react-native-github:react-native",
|
||||
"//xplat/js/react-native-github/packages/assets:assets",
|
||||
],
|
||||
)
|
||||
@@ -10,8 +10,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ExtendedError} from 'react-native/Libraries/Core/ExtendedError';
|
||||
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const parseErrorStack = require('react-native/Libraries/Core/Devtools/parseErrorStack');
|
||||
@@ -19,7 +17,7 @@ const {View} = ReactNative;
|
||||
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
class GlobalEvalWithSourceUrlTest extends React.Component<{...}> {
|
||||
class GlobalEvalWithSourceUrlTest extends React.Component<{}> {
|
||||
componentDidMount() {
|
||||
if (typeof global.globalEvalWithSourceUrl !== 'function') {
|
||||
throw new Error(
|
||||
@@ -33,7 +31,7 @@ class GlobalEvalWithSourceUrlTest extends React.Component<{...}> {
|
||||
'Expected globalEvalWithSourceUrl(expression) to return a value',
|
||||
);
|
||||
}
|
||||
let syntaxError: ?ExtendedError;
|
||||
let syntaxError;
|
||||
try {
|
||||
global.globalEvalWithSourceUrl('{');
|
||||
} catch (e) {
|
||||
@@ -44,12 +42,7 @@ class GlobalEvalWithSourceUrlTest extends React.Component<{...}> {
|
||||
'Expected globalEvalWithSourceUrl to throw on a syntax error',
|
||||
);
|
||||
}
|
||||
// Hermes throws an Error instead of a SyntaxError
|
||||
// https://github.com/facebook/hermes/issues/400
|
||||
if (
|
||||
syntaxError.jsEngine !== 'hermes' &&
|
||||
!(syntaxError instanceof SyntaxError)
|
||||
) {
|
||||
if (!(syntaxError instanceof SyntaxError)) {
|
||||
throw new Error(
|
||||
'Expected globalEvalWithSourceUrl to throw SyntaxError on a syntax error',
|
||||
);
|
||||
@@ -66,7 +59,7 @@ class GlobalEvalWithSourceUrlTest extends React.Component<{...}> {
|
||||
'Expected globalEvalWithSourceUrl to throw an Error object',
|
||||
);
|
||||
}
|
||||
const parsedStack = parseErrorStack(error?.stack);
|
||||
const parsedStack = parseErrorStack(error);
|
||||
if (parsedStack[0].file !== url) {
|
||||
throw new Error(
|
||||
`Expected first eval stack frame to be in ${url} but found ${String(
|
||||
|
||||
@@ -27,17 +27,16 @@ const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
const TESTS = ['only-if-cached', 'default', 'reload', 'force-cache'];
|
||||
|
||||
type Props = {...};
|
||||
type Props = {};
|
||||
type State = {
|
||||
'only-if-cached'?: boolean,
|
||||
default?: boolean,
|
||||
reload?: boolean,
|
||||
'force-cache'?: boolean,
|
||||
...
|
||||
};
|
||||
|
||||
class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
state: $FlowFixMe | {...} = {};
|
||||
state: $FlowFixMe | $TEMPORARY$object<{||}> = {};
|
||||
|
||||
shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
|
||||
const results: Array<?boolean> = TESTS.map(x => nextState[x]);
|
||||
@@ -64,7 +63,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
'https://raw.githubusercontent.com/facebook/react-native/HEAD/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
'https://facebook.github.io/react-native/img/favicon.png?cacheBust=notinCache' +
|
||||
Date.now(),
|
||||
cache: 'only-if-cached',
|
||||
}}
|
||||
@@ -75,7 +74,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
'https://raw.githubusercontent.com/facebook/react-native/HEAD/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
'https://facebook.github.io/react-native/img/favicon.png?cacheBust=notinCache' +
|
||||
Date.now(),
|
||||
cache: 'default',
|
||||
}}
|
||||
@@ -86,7 +85,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
'https://raw.githubusercontent.com/facebook/react-native/HEAD/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
'https://facebook.github.io/react-native/img/favicon.png?cacheBust=notinCache' +
|
||||
Date.now(),
|
||||
cache: 'reload',
|
||||
}}
|
||||
@@ -97,7 +96,7 @@ class ImageCachePolicyTest extends React.Component<Props, $FlowFixMeState> {
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
'https://raw.githubusercontent.com/facebook/react-native/HEAD/Libraries/NewAppScreen/components/logo.png?cacheBust=notinCache' +
|
||||
'https://facebook.github.io/react-native/img/favicon.png?cacheBust=notinCache' +
|
||||
Date.now(),
|
||||
cache: 'force-cache',
|
||||
}}
|
||||
|
||||
@@ -15,7 +15,7 @@ const ReactNative = require('react-native');
|
||||
const {Image} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
class ImageSnapshotTest extends React.Component<{...}> {
|
||||
class ImageSnapshotTest extends React.Component<{}> {
|
||||
componentDidMount() {
|
||||
if (!TestModule.verifySnapshot) {
|
||||
throw new Error('TestModule.verifySnapshot not defined.');
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
|
||||
const requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
|
||||
const {Text, View, StyleSheet} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
@@ -56,10 +57,9 @@ class IntegrationTestHarnessTest extends React.Component<Props, State> {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>
|
||||
{/* $FlowFixMe[incompatible-type] (>=0.54.0 site=react_native_fb,react_
|
||||
* native_oss) This comment suppresses an error found when Flow v0.54
|
||||
* was deployed. To see the error delete this comment and run Flow.
|
||||
*/
|
||||
{/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error found when Flow v0.54 was deployed.
|
||||
* To see the error delete this comment and run Flow. */
|
||||
this.constructor.displayName + ': '}
|
||||
{this.state.done ? 'Done' : 'Testing...'}
|
||||
</Text>
|
||||
|
||||
@@ -40,9 +40,9 @@ const TESTS = [
|
||||
];
|
||||
|
||||
TESTS.forEach(
|
||||
/* $FlowFixMe[incompatible-call] (>=0.54.0 site=react_native_fb,react_native_
|
||||
* oss) This comment suppresses an error found when Flow v0.54 was deployed.
|
||||
* To see the error delete this comment and run Flow. */
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_fb,react_native_oss) This comment
|
||||
* suppresses an error found when Flow v0.54 was deployed. To see the error
|
||||
* delete this comment and run Flow. */
|
||||
test => AppRegistry.registerComponent(test.displayName, () => test),
|
||||
);
|
||||
|
||||
@@ -51,7 +51,7 @@ require('./LoggingTestModule');
|
||||
|
||||
type Test = any;
|
||||
|
||||
class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> {
|
||||
class IntegrationTestsApp extends React.Component<{}, $FlowFixMeState> {
|
||||
state = {
|
||||
test: (null: ?Test),
|
||||
};
|
||||
@@ -60,10 +60,9 @@ class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> {
|
||||
if (this.state.test) {
|
||||
return (
|
||||
<ScrollView>
|
||||
{/* $FlowFixMe[type-as-value] (>=0.53.0 site=react_native_fb,react_
|
||||
* native_oss) This comment suppresses an error when upgrading
|
||||
* Flow's support for React. To see the error delete this comment
|
||||
* and run Flow. */}
|
||||
{/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error when upgrading Flow's support for
|
||||
* React. To see the error delete this comment and run Flow. */}
|
||||
<this.state.test />
|
||||
</ScrollView>
|
||||
);
|
||||
@@ -80,10 +79,6 @@ class IntegrationTestsApp extends React.Component<{...}, $FlowFixMeState> {
|
||||
{TESTS.map(test => [
|
||||
<TouchableOpacity
|
||||
onPress={() => this.setState({test})}
|
||||
/* $FlowFixMe[incompatible-type] (>=0.115.0 site=react_native_fb)
|
||||
* This comment suppresses an error found when Flow v0.115 was
|
||||
* deployed. To see the error, delete this comment and run Flow.
|
||||
*/
|
||||
style={styles.row}>
|
||||
<Text style={styles.testName}>{test.displayName}</Text>
|
||||
</TouchableOpacity>,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow strict-local
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -38,7 +38,6 @@ type State = {
|
||||
viewLayout?: Layout,
|
||||
viewStyle?: ViewStyleProp,
|
||||
containerStyle?: ViewStyleProp,
|
||||
...
|
||||
};
|
||||
|
||||
class LayoutEventsTest extends React.Component<Props, State> {
|
||||
@@ -197,5 +196,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
LayoutEventsTest.displayName = 'LayoutEventsTest';
|
||||
module.exports = LayoutEventsTest;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
|
||||
|
||||
const warning = require('fbjs/lib/warning');
|
||||
const invariant = require('invariant');
|
||||
|
||||
const LoggingTestModule = {
|
||||
@@ -23,7 +24,7 @@ const LoggingTestModule = {
|
||||
}, timeout_ms);
|
||||
},
|
||||
warning: function(str) {
|
||||
console.warn(str);
|
||||
warning(false, str);
|
||||
},
|
||||
invariant: function(str) {
|
||||
invariant(false, str);
|
||||
|
||||
@@ -15,7 +15,7 @@ const ReactNative = require('react-native');
|
||||
const {View} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
class PromiseTest extends React.Component<{...}> {
|
||||
class PromiseTest extends React.Component<{}> {
|
||||
shouldResolve: boolean = false;
|
||||
shouldReject: boolean = false;
|
||||
shouldSucceedAsync: boolean = false;
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow strict-local
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const RCTNativeAppEventEmitter = require('react-native/Libraries/EventEmitter/RCTNativeAppEventEmitter');
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
@@ -15,7 +17,7 @@ const ReactNative = require('react-native');
|
||||
const {View} = ReactNative;
|
||||
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
import {type EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
|
||||
import type EmitterSubscription from 'react-native/Libraries/vendor/emitter/EmitterSubscription';
|
||||
|
||||
const reactViewWidth = 101;
|
||||
const reactViewHeight = 102;
|
||||
@@ -31,7 +33,7 @@ type State = {|
|
||||
|
||||
class ReactContentSizeUpdateTest extends React.Component<Props, State> {
|
||||
_timeoutID: ?TimeoutID = null;
|
||||
_subscription: ?EventSubscription = null;
|
||||
_subscription: ?EmitterSubscription = null;
|
||||
|
||||
state: State = {
|
||||
height: reactViewHeight,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow strict-local
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
@@ -13,10 +13,12 @@
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
|
||||
const requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
|
||||
|
||||
const {StyleSheet, View} = ReactNative;
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
|
||||
class SimpleSnapshotTest extends React.Component<{...}> {
|
||||
class SimpleSnapshotTest extends React.Component<{}> {
|
||||
componentDidMount() {
|
||||
if (!TestModule.verifySnapshot) {
|
||||
throw new Error('TestModule.verifySnapshot not defined.');
|
||||
|
||||
@@ -5,16 +5,18 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow strict-local
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const RCTNativeAppEventEmitter = require('react-native/Libraries/EventEmitter/RCTNativeAppEventEmitter');
|
||||
const React = require('react');
|
||||
const ReactNative = require('react-native');
|
||||
const {View} = ReactNative;
|
||||
|
||||
const {TestModule} = ReactNative.NativeModules;
|
||||
import {type EventSubscription} from 'react-native/Libraries/vendor/emitter/EventEmitter';
|
||||
import type EmitterSubscription from 'react-native/Libraries/vendor/emitter/EmitterSubscription';
|
||||
|
||||
const reactViewWidth = 111;
|
||||
const reactViewHeight = 222;
|
||||
@@ -29,7 +31,7 @@ type Props = $ReadOnly<{|
|
||||
|}>;
|
||||
|
||||
class SizeFlexibilityUpdateTest extends React.Component<Props> {
|
||||
_subscription: ?EventSubscription = null;
|
||||
_subscription: ?EmitterSubscription = null;
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this._subscription = RCTNativeAppEventEmitter.addListener(
|
||||
@@ -52,8 +54,7 @@ class SizeFlexibilityUpdateTest extends React.Component<Props> {
|
||||
rootViewDidChangeIntrinsicSize: (intrinsicSize: {
|
||||
height: number,
|
||||
width: number,
|
||||
...
|
||||
}) => void = (intrinsicSize: {width: number, height: number, ...}) => {
|
||||
}) => void = (intrinsicSize: {width: number, height: number}) => {
|
||||
if (finalState) {
|
||||
// If a test reaches its final state, it is not expected to do anything more
|
||||
TestModule.markTestPassed(false);
|
||||
|
||||
@@ -16,7 +16,7 @@ const {View} = ReactNative;
|
||||
|
||||
const {TestModule, RNTesterTestModule} = ReactNative.NativeModules;
|
||||
|
||||
class SyncMethodTest extends React.Component<{...}> {
|
||||
class SyncMethodTest extends React.Component<{}> {
|
||||
componentDidMount() {
|
||||
if (
|
||||
RNTesterTestModule.echoString('test string value') !== 'test string value'
|
||||
|
||||
@@ -97,46 +97,36 @@ class TimersTest extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testSetTimeout0, 1000);
|
||||
}
|
||||
|
||||
testSetTimeout0() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testSetTimeout1, 0);
|
||||
}
|
||||
|
||||
testSetTimeout1() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testSetTimeout50, 1);
|
||||
}
|
||||
|
||||
testSetTimeout50() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testRequestAnimationFrame, 50);
|
||||
}
|
||||
|
||||
testRequestAnimationFrame() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.requestAnimationFrame(this.testSetInterval0);
|
||||
}
|
||||
|
||||
testSetInterval0() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this._nextTest = this.testSetInterval20;
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this._interval = this.setInterval(this._incrementInterval, 0);
|
||||
}
|
||||
|
||||
testSetInterval20() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this._nextTest = this.testSetImmediate;
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this._interval = this.setInterval(this._incrementInterval, 20);
|
||||
}
|
||||
|
||||
testSetImmediate() {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setImmediate(this.testClearTimeout0);
|
||||
}
|
||||
|
||||
@@ -149,7 +139,6 @@ class TimersTest extends React.Component<Props, State> {
|
||||
testClearTimeout30() {
|
||||
const timeout = this.setTimeout(() => this._fail('testClearTimeout30'), 30);
|
||||
this.clearTimeout(timeout);
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testClearMulti, 50);
|
||||
}
|
||||
|
||||
@@ -167,7 +156,6 @@ class TimersTest extends React.Component<Props, State> {
|
||||
fails.forEach(timeout => this.clearTimeout(timeout));
|
||||
this.setTimeout(() => this.clearTimeout(delayClear), 20);
|
||||
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.testOrdering, 50);
|
||||
}
|
||||
|
||||
@@ -203,7 +191,6 @@ class TimersTest extends React.Component<Props, State> {
|
||||
),
|
||||
25,
|
||||
);
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
this.setTimeout(this.done, 50);
|
||||
}
|
||||
|
||||
@@ -262,7 +249,6 @@ class TimersTest extends React.Component<Props, State> {
|
||||
this.clearInterval(this._interval);
|
||||
this._interval = null;
|
||||
}
|
||||
// $FlowFixMe[method-unbinding]
|
||||
this.setState({count: 0}, this._nextTest);
|
||||
return;
|
||||
}
|
||||
@@ -281,5 +267,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
TimersTest.displayName = 'TimersTest';
|
||||
module.exports = TimersTest;
|
||||
|
||||
@@ -28,10 +28,9 @@ type State = {
|
||||
lastMessage: ?string | ?ArrayBuffer,
|
||||
testMessage: string,
|
||||
testExpectedResponse: string,
|
||||
...
|
||||
};
|
||||
|
||||
class WebSocketTest extends React.Component<{...}, State> {
|
||||
class WebSocketTest extends React.Component<{}, State> {
|
||||
state: State = {
|
||||
url: DEFAULT_WS_URL,
|
||||
fetchStatus: null,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
@@ -8,7 +9,6 @@
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// A little helper to make sure we have the right memory allocation ready for use.
|
||||
// We assume that we will only this in one place so no reference counting is necessary.
|
||||
// Needs to be freed when deallocated.
|
||||
|
||||
// This is fragile since this relies on these values not getting reused. Consider
|
||||
// wrapping these in an Obj-C class or some ARC hackery to get refcounting.
|
||||
|
||||
typedef struct {
|
||||
size_t count;
|
||||
CGFloat *array;
|
||||
} ARTCGFloatArray;
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol ARTContainer <NSObject>
|
||||
|
||||
// This is used as a hook for child to mark it's parent as dirty.
|
||||
// This bubbles up to the root which gets marked as dirty.
|
||||
- (void)invalidate;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTContainer.h"
|
||||
#import "ARTNode.h"
|
||||
|
||||
@interface ARTGroup : ARTNode <ARTContainer>
|
||||
|
||||
@property (nonatomic, assign) CGRect clipping;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTGroup.h>
|
||||
|
||||
@implementation ARTGroup
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
|
||||
if (!CGRectIsEmpty(self.clipping)) {
|
||||
CGContextClipToRect(context, self.clipping);
|
||||
}
|
||||
|
||||
for (ARTNode *node in self.subviews) {
|
||||
[node renderTo:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
/**
|
||||
* ART nodes are implemented as empty UIViews but this is just an implementation detail to fit
|
||||
* into the existing view management. They should also be shadow views and painted on a background
|
||||
* thread.
|
||||
*/
|
||||
|
||||
@interface ARTNode : UIView
|
||||
|
||||
@property (nonatomic, assign) CGFloat opacity;
|
||||
|
||||
- (void)invalidate;
|
||||
- (void)renderTo:(CGContextRef)context;
|
||||
|
||||
/**
|
||||
* renderTo will take opacity into account and draw renderLayerTo off-screen if there is opacity
|
||||
* specified, then composite that onto the context. renderLayerTo always draws at opacity=1.
|
||||
* @abstract
|
||||
*/
|
||||
- (void)renderLayerTo:(CGContextRef)context;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTNode.h>
|
||||
|
||||
#import <React/ARTContainer.h>
|
||||
|
||||
@implementation ARTNode
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:atIndex];
|
||||
[self insertSubview:subview atIndex:atIndex];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView *)subview
|
||||
{
|
||||
[super removeReactSubview:subview];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// Do nothing, as subviews are inserted by insertReactSubview:
|
||||
}
|
||||
|
||||
- (void)setOpacity:(CGFloat)opacity
|
||||
{
|
||||
[self invalidate];
|
||||
_opacity = opacity;
|
||||
}
|
||||
|
||||
- (void)setTransform:(CGAffineTransform)transform
|
||||
{
|
||||
[self invalidate];
|
||||
super.transform = transform;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
id<ARTContainer> container = (id<ARTContainer>)self.superview;
|
||||
[container invalidate];
|
||||
}
|
||||
|
||||
- (void)renderTo:(CGContextRef)context
|
||||
{
|
||||
if (self.opacity <= 0) {
|
||||
// Nothing to paint
|
||||
return;
|
||||
}
|
||||
if (self.opacity >= 1) {
|
||||
// Just paint at full opacity
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, self.transform);
|
||||
CGContextSetAlpha(context, 1);
|
||||
[self renderLayerTo:context];
|
||||
CGContextRestoreGState(context);
|
||||
return;
|
||||
}
|
||||
// This needs to be painted on a layer before being composited.
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, self.transform);
|
||||
CGContextSetAlpha(context, self.opacity);
|
||||
CGContextBeginTransparencyLayer(context, NULL);
|
||||
[self renderLayerTo:context];
|
||||
CGContextEndTransparencyLayer(context);
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTBrush.h"
|
||||
#import "ARTCGFloatArray.h"
|
||||
#import "ARTNode.h"
|
||||
|
||||
@interface ARTRenderable : ARTNode
|
||||
|
||||
@property (nonatomic, strong) ARTBrush *fill;
|
||||
@property (nonatomic, assign) CGColorRef stroke;
|
||||
@property (nonatomic, assign) CGFloat strokeWidth;
|
||||
@property (nonatomic, assign) CGLineCap strokeCap;
|
||||
@property (nonatomic, assign) CGLineJoin strokeJoin;
|
||||
@property (nonatomic, assign) ARTCGFloatArray strokeDash;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTRenderable.h>
|
||||
|
||||
@implementation ARTRenderable
|
||||
|
||||
- (void)setFill:(ARTBrush *)fill
|
||||
{
|
||||
[self invalidate];
|
||||
_fill = fill;
|
||||
}
|
||||
|
||||
- (void)setStroke:(CGColorRef)stroke
|
||||
{
|
||||
if (stroke == _stroke) {
|
||||
return;
|
||||
}
|
||||
[self invalidate];
|
||||
CGColorRelease(_stroke);
|
||||
_stroke = CGColorRetain(stroke);
|
||||
}
|
||||
|
||||
- (void)setStrokeWidth:(CGFloat)strokeWidth
|
||||
{
|
||||
[self invalidate];
|
||||
_strokeWidth = strokeWidth;
|
||||
}
|
||||
|
||||
- (void)setStrokeCap:(CGLineCap)strokeCap
|
||||
{
|
||||
[self invalidate];
|
||||
_strokeCap = strokeCap;
|
||||
}
|
||||
|
||||
- (void)setStrokeJoin:(CGLineJoin)strokeJoin
|
||||
{
|
||||
[self invalidate];
|
||||
_strokeJoin = strokeJoin;
|
||||
}
|
||||
|
||||
- (void)setStrokeDash:(ARTCGFloatArray)strokeDash
|
||||
{
|
||||
if (strokeDash.array == _strokeDash.array) {
|
||||
return;
|
||||
}
|
||||
if (_strokeDash.array) {
|
||||
free(_strokeDash.array);
|
||||
}
|
||||
[self invalidate];
|
||||
_strokeDash = strokeDash;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGColorRelease(_stroke);
|
||||
if (_strokeDash.array) {
|
||||
free(_strokeDash.array);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)renderTo:(CGContextRef)context
|
||||
{
|
||||
if (self.opacity <= 0 || self.opacity >= 1 || (self.fill && self.stroke)) {
|
||||
// If we have both fill and stroke, we will need to paint this using normal compositing
|
||||
[super renderTo: context];
|
||||
return;
|
||||
}
|
||||
// This is a terminal with only one painting. Therefore we don't need to paint this
|
||||
// off-screen. We can just composite it straight onto the buffer.
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, self.transform);
|
||||
CGContextSetAlpha(context, self.opacity);
|
||||
[self renderLayerTo:context];
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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';
|
||||
|
||||
// TODO: Move this into an ART mode called "serialized" or something
|
||||
|
||||
const Class = require('art/core/class.js');
|
||||
const Path = require('art/core/path.js');
|
||||
|
||||
const MOVE_TO = 0;
|
||||
const CLOSE = 1;
|
||||
const LINE_TO = 2;
|
||||
const CURVE_TO = 3;
|
||||
const ARC = 4;
|
||||
|
||||
const SerializablePath = Class(Path, {
|
||||
initialize: function(path) {
|
||||
this.reset();
|
||||
if (path instanceof SerializablePath) {
|
||||
this.path = path.path.slice(0);
|
||||
} else if (path) {
|
||||
if (path.applyToPath) {
|
||||
path.applyToPath(this);
|
||||
} else {
|
||||
this.push(path);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onReset: function() {
|
||||
this.path = [];
|
||||
},
|
||||
|
||||
onMove: function(sx, sy, x, y) {
|
||||
this.path.push(MOVE_TO, x, y);
|
||||
},
|
||||
|
||||
onLine: function(sx, sy, x, y) {
|
||||
this.path.push(LINE_TO, x, y);
|
||||
},
|
||||
|
||||
onBezierCurve: function(sx, sy, p1x, p1y, p2x, p2y, x, y) {
|
||||
this.path.push(CURVE_TO, p1x, p1y, p2x, p2y, x, y);
|
||||
},
|
||||
|
||||
_arcToBezier: Path.prototype.onArc,
|
||||
|
||||
onArc: function(sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation) {
|
||||
if (rx !== ry || rotation) {
|
||||
return this._arcToBezier(
|
||||
sx,
|
||||
sy,
|
||||
ex,
|
||||
ey,
|
||||
cx,
|
||||
cy,
|
||||
rx,
|
||||
ry,
|
||||
sa,
|
||||
ea,
|
||||
ccw,
|
||||
rotation,
|
||||
);
|
||||
}
|
||||
this.path.push(ARC, cx, cy, rx, sa, ea, ccw ? 0 : 1);
|
||||
},
|
||||
|
||||
onClose: function() {
|
||||
this.path.push(CLOSE);
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
return this.path;
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = SerializablePath;
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTRenderable.h"
|
||||
|
||||
@interface ARTShape : ARTRenderable
|
||||
|
||||
@property (nonatomic, assign) CGPathRef d;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTShape.h>
|
||||
|
||||
@implementation ARTShape
|
||||
|
||||
- (void)setD:(CGPathRef)d
|
||||
{
|
||||
if (d == _d) {
|
||||
return;
|
||||
}
|
||||
[self invalidate];
|
||||
CGPathRelease(_d);
|
||||
_d = CGPathRetain(d);
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGPathRelease(_d);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
if ((!self.fill && !self.stroke) || !self.d) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGPathDrawingMode mode = kCGPathStroke;
|
||||
if (self.fill) {
|
||||
if ([self.fill applyFillColor:context]) {
|
||||
mode = kCGPathFill;
|
||||
} else {
|
||||
CGContextSaveGState(context);
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextClip(context);
|
||||
[self.fill paint:context];
|
||||
CGContextRestoreGState(context);
|
||||
if (!self.stroke) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.stroke) {
|
||||
CGContextSetStrokeColorWithColor(context, self.stroke);
|
||||
CGContextSetLineWidth(context, self.strokeWidth);
|
||||
CGContextSetLineCap(context, self.strokeCap);
|
||||
CGContextSetLineJoin(context, self.strokeJoin);
|
||||
ARTCGFloatArray dash = self.strokeDash;
|
||||
if (dash.count) {
|
||||
CGContextSetLineDash(context, 0, dash.array, dash.count);
|
||||
}
|
||||
if (mode == kCGPathFill) {
|
||||
mode = kCGPathFillStroke;
|
||||
}
|
||||
}
|
||||
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextDrawPath(context, mode);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "ARTContainer.h"
|
||||
|
||||
@interface ARTSurfaceView : UIView <ARTContainer>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTSurfaceView.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import <React/ARTNode.h>
|
||||
|
||||
@implementation ARTSurfaceView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.opaque = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:atIndex];
|
||||
[self insertSubview:subview atIndex:atIndex];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView *)subview
|
||||
{
|
||||
[super removeReactSubview:subview];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// Do nothing, as subviews are inserted by insertReactSubview:
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
for (ARTNode *node in self.subviews) {
|
||||
[node renderTo:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTRenderable.h"
|
||||
#import "ARTTextFrame.h"
|
||||
|
||||
@interface ARTText : ARTRenderable
|
||||
|
||||
@property (nonatomic, assign) CTTextAlignment alignment;
|
||||
@property (nonatomic, assign) ARTTextFrame textFrame;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTText.h>
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
@implementation ARTText
|
||||
|
||||
- (void)setAlignment:(CTTextAlignment)alignment
|
||||
{
|
||||
[self invalidate];
|
||||
_alignment = alignment;
|
||||
}
|
||||
|
||||
static void ARTFreeTextFrame(ARTTextFrame frame)
|
||||
{
|
||||
if (frame.count) {
|
||||
// We must release each line before freeing up this struct
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
CFRelease(frame.lines[i]);
|
||||
}
|
||||
free(frame.lines);
|
||||
free(frame.widths);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTextFrame:(ARTTextFrame)frame
|
||||
{
|
||||
if (frame.lines != _textFrame.lines) {
|
||||
ARTFreeTextFrame(_textFrame);
|
||||
}
|
||||
[self invalidate];
|
||||
_textFrame = frame;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
ARTFreeTextFrame(_textFrame);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
ARTTextFrame frame = self.textFrame;
|
||||
|
||||
if ((!self.fill && !self.stroke) || !frame.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// to-do: draw along a path
|
||||
|
||||
CGTextDrawingMode mode = kCGTextStroke;
|
||||
if (self.fill) {
|
||||
if ([self.fill applyFillColor:context]) {
|
||||
mode = kCGTextFill;
|
||||
} else {
|
||||
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
CGContextSaveGState(context);
|
||||
// Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
CGContextSetTextDrawingMode(context, kCGTextClip);
|
||||
[self renderLineTo:context atIndex:i];
|
||||
// Inverse the coordinate space back to the original before filling
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
[self.fill paint:context];
|
||||
// Restore the state so that the next line can be clipped separately
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
if (!self.stroke) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.stroke) {
|
||||
CGContextSetStrokeColorWithColor(context, self.stroke);
|
||||
CGContextSetLineWidth(context, self.strokeWidth);
|
||||
CGContextSetLineCap(context, self.strokeCap);
|
||||
CGContextSetLineJoin(context, self.strokeJoin);
|
||||
ARTCGFloatArray dash = self.strokeDash;
|
||||
if (dash.count) {
|
||||
CGContextSetLineDash(context, 0, dash.array, dash.count);
|
||||
}
|
||||
if (mode == kCGTextFill) {
|
||||
mode = kCGTextFillStroke;
|
||||
}
|
||||
}
|
||||
|
||||
CGContextSetTextDrawingMode(context, mode);
|
||||
|
||||
// Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
[self renderLineTo:context atIndex:i];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)renderLineTo:(CGContextRef)context atIndex:(int)index
|
||||
{
|
||||
ARTTextFrame frame = self.textFrame;
|
||||
CGFloat shift;
|
||||
switch (self.alignment) {
|
||||
case kCTTextAlignmentRight:
|
||||
shift = frame.widths[index];
|
||||
break;
|
||||
case kCTTextAlignmentCenter:
|
||||
shift = (frame.widths[index] / 2);
|
||||
break;
|
||||
default:
|
||||
shift = 0;
|
||||
break;
|
||||
}
|
||||
// We should consider snapping this shift to device pixels to improve rendering quality
|
||||
// when a line has subpixel width.
|
||||
CGContextSetTextPosition(context, -shift, -frame.baseLine - frame.lineHeight * index);
|
||||
CTLineRef line = frame.lines[index];
|
||||
CTLineDraw(line, context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
// A little helper to make sure we have a set of lines including width ready for use.
|
||||
// We assume that we will only this in one place so no reference counting is necessary.
|
||||
// Needs to be freed when deallocated.
|
||||
|
||||
// This is fragile since this relies on these values not getting reused. Consider
|
||||
// wrapping these in an Obj-C class or some ARC hackery to get refcounting.
|
||||
|
||||
typedef struct {
|
||||
size_t count;
|
||||
CGFloat baseLine; // Distance from the origin to the base line of the first line
|
||||
CGFloat lineHeight; // Distance between lines
|
||||
CTLineRef *lines;
|
||||
CGFloat *widths; // Width of each line
|
||||
} ARTTextFrame;
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface ARTBrush : NSObject
|
||||
|
||||
/* @abstract */
|
||||
- (instancetype)initWithArray:(NSArray *)data NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* For certain brushes we can fast path a combined fill and stroke.
|
||||
* For those brushes we override applyFillColor which sets the fill
|
||||
* color to be used by those batch paints. Those return YES.
|
||||
* We can't batch gradient painting in CoreGraphics, so those will
|
||||
* return NO and paint gets called instead.
|
||||
* @abstract
|
||||
*/
|
||||
- (BOOL)applyFillColor:(CGContextRef)context;
|
||||
|
||||
/**
|
||||
* paint fills the context with a brush. The context is assumed to
|
||||
* be clipped.
|
||||
* @abstract
|
||||
*/
|
||||
- (void)paint:(CGContextRef)context;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTBrush.h>
|
||||
|
||||
#import <React/RCTDefines.h>
|
||||
|
||||
@implementation ARTBrush
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)data
|
||||
{
|
||||
return [super init];
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
|
||||
- (BOOL)applyFillColor:(CGContextRef)context
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTLinearGradient : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTLinearGradient.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTLinearGradient
|
||||
{
|
||||
CGGradientRef _gradient;
|
||||
CGPoint _startPoint;
|
||||
CGPoint _endPoint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 5) {
|
||||
RCTLogError(@"-[%@ %@] expects 5 elements, received %@",
|
||||
self.class, NSStringFromSelector(_cmd), array);
|
||||
return nil;
|
||||
}
|
||||
_startPoint = [RCTConvert CGPoint:array offset:1];
|
||||
_endPoint = [RCTConvert CGPoint:array offset:3];
|
||||
_gradient = CGGradientRetain([RCTConvert CGGradient:array offset:5]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGGradientRelease(_gradient);
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
CGGradientDrawingOptions extendOptions =
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
|
||||
CGContextDrawLinearGradient(context, _gradient, _startPoint, _endPoint, extendOptions);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTPattern : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTPattern.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTPattern
|
||||
{
|
||||
CGImageRef _image;
|
||||
CGRect _rect;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<id /* imagesource + numbers */> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 6) {
|
||||
RCTLogError(@"-[%@ %@] expects 6 elements, received %@",
|
||||
self.class, NSStringFromSelector(_cmd), array);
|
||||
return nil;
|
||||
}
|
||||
_image = CGImageRetain([RCTConvert CGImage:array[1]]);
|
||||
_rect = [RCTConvert CGRect:array offset:2];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGImageRelease(_image);
|
||||
}
|
||||
|
||||
// Note: This could use applyFillColor with a pattern. This could be more efficient but
|
||||
// to do that, we need to calculate our own user space CTM.
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
CGContextDrawTiledImage(context, _rect, _image);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTRadialGradient : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTRadialGradient.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTRadialGradient
|
||||
{
|
||||
CGGradientRef _gradient;
|
||||
CGPoint _focusPoint;
|
||||
CGPoint _centerPoint;
|
||||
CGFloat _radius;
|
||||
CGFloat _radiusRatio;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 7) {
|
||||
RCTLogError(@"-[%@ %@] expects 7 elements, received %@",
|
||||
self.class, NSStringFromSelector(_cmd), array);
|
||||
return nil;
|
||||
}
|
||||
_radius = [RCTConvert CGFloat:array[3]];
|
||||
_radiusRatio = [RCTConvert CGFloat:array[4]] / _radius;
|
||||
_focusPoint.x = [RCTConvert CGFloat:array[1]];
|
||||
_focusPoint.y = [RCTConvert CGFloat:array[2]] / _radiusRatio;
|
||||
_centerPoint.x = [RCTConvert CGFloat:array[5]];
|
||||
_centerPoint.y = [RCTConvert CGFloat:array[6]] / _radiusRatio;
|
||||
_gradient = CGGradientRetain([RCTConvert CGGradient:array offset:7]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGGradientRelease(_gradient);
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
CGAffineTransform transform = CGAffineTransformMakeScale(1, _radiusRatio);
|
||||
CGContextConcatCTM(context, transform);
|
||||
CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
|
||||
CGContextDrawRadialGradient(context, _gradient, _focusPoint, 0, _centerPoint, _radius, extendOptions);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTSolidColor : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTSolidColor.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTSolidColor
|
||||
{
|
||||
CGColorRef _color;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
_color = CGColorRetain([RCTConvert CGColor:array offset:1]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGColorRelease(_color);
|
||||
}
|
||||
|
||||
- (BOOL)applyFillColor:(CGContextRef)context
|
||||
{
|
||||
CGContextSetFillColorWithColor(context, _color);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
#import "ARTBrush.h"
|
||||
#import "ARTCGFloatArray.h"
|
||||
#import "ARTTextFrame.h"
|
||||
|
||||
@interface RCTConvert (ART)
|
||||
|
||||
+ (CGPathRef)CGPath:(id)json CF_RETURNS_NOT_RETAINED;
|
||||
+ (CTTextAlignment)CTTextAlignment:(id)json;
|
||||
+ (ARTTextFrame)ARTTextFrame:(id)json;
|
||||
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json;
|
||||
+ (ARTBrush *)ARTBrush:(id)json;
|
||||
|
||||
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset;
|
||||
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset;
|
||||
+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset CF_RETURNS_NOT_RETAINED;
|
||||
+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset CF_RETURNS_NOT_RETAINED;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
#import <React/RCTFont.h>
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import <React/ARTLinearGradient.h>
|
||||
#import <React/ARTPattern.h>
|
||||
#import <React/ARTRadialGradient.h>
|
||||
#import <React/ARTSolidColor.h>
|
||||
|
||||
@implementation RCTConvert (ART)
|
||||
|
||||
+ (CGPathRef)CGPath:(id)json
|
||||
{
|
||||
NSArray *arr = [self NSNumberArray:json];
|
||||
|
||||
NSUInteger count = [arr count];
|
||||
|
||||
#define NEXT_VALUE [self double:arr[i++]]
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathMoveToPoint(path, NULL, 0, 0);
|
||||
|
||||
@try {
|
||||
NSUInteger i = 0;
|
||||
while (i < count) {
|
||||
NSUInteger type = [arr[i++] unsignedIntegerValue];
|
||||
switch (type) {
|
||||
case 0:
|
||||
CGPathMoveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
|
||||
break;
|
||||
case 1:
|
||||
CGPathCloseSubpath(path);
|
||||
break;
|
||||
case 2:
|
||||
CGPathAddLineToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
|
||||
break;
|
||||
case 3:
|
||||
CGPathAddCurveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE);
|
||||
break;
|
||||
case 4:
|
||||
CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0);
|
||||
break;
|
||||
default:
|
||||
RCTLogError(@"Invalid CGPath type %llu at element %llu of %@", (unsigned long long)type, (unsigned long long)i, arr);
|
||||
CGPathRelease(path);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Invalid CGPath format: %@", arr);
|
||||
CGPathRelease(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (CGPathRef)CFAutorelease(path);
|
||||
}
|
||||
|
||||
RCT_ENUM_CONVERTER(CTTextAlignment, (@{
|
||||
@"auto": @(kCTTextAlignmentNatural),
|
||||
@"left": @(kCTTextAlignmentLeft),
|
||||
@"center": @(kCTTextAlignmentCenter),
|
||||
@"right": @(kCTTextAlignmentRight),
|
||||
@"justify": @(kCTTextAlignmentJustified),
|
||||
}), kCTTextAlignmentNatural, integerValue)
|
||||
|
||||
// This takes a tuple of text lines and a font to generate a CTLine for each text line.
|
||||
// This prepares everything for rendering a frame of text in ARTText.
|
||||
+ (ARTTextFrame)ARTTextFrame:(id)json
|
||||
{
|
||||
NSDictionary *dict = [self NSDictionary:json];
|
||||
ARTTextFrame frame;
|
||||
frame.count = 0;
|
||||
|
||||
NSArray *lines = [self NSArray:dict[@"lines"]];
|
||||
NSUInteger lineCount = [lines count];
|
||||
if (lineCount == 0) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
CTFontRef font = (__bridge CTFontRef)[self UIFont:dict[@"font"]];
|
||||
if (!font) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
// Create a dictionary for this font
|
||||
CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{
|
||||
(NSString *)kCTFontAttributeName:(__bridge id)font,
|
||||
(NSString *)kCTForegroundColorFromContextAttributeName: @YES
|
||||
};
|
||||
|
||||
// Set up text frame with font metrics
|
||||
CGFloat size = CTFontGetSize(font);
|
||||
frame.count = lineCount;
|
||||
frame.baseLine = size; // estimate base line
|
||||
frame.lineHeight = size * 1.1; // Base on ART canvas line height estimate
|
||||
frame.lines = malloc(sizeof(CTLineRef) * lineCount);
|
||||
frame.widths = malloc(sizeof(CGFloat) * lineCount);
|
||||
|
||||
[lines enumerateObjectsUsingBlock:^(NSString *text, NSUInteger i, BOOL *stop) {
|
||||
|
||||
CFStringRef string = (__bridge CFStringRef)text;
|
||||
CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attrString);
|
||||
CFRelease(attrString);
|
||||
|
||||
frame.lines[i] = line;
|
||||
frame.widths[i] = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
|
||||
}];
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json
|
||||
{
|
||||
NSArray *arr = [self NSNumberArray:json];
|
||||
NSUInteger count = arr.count;
|
||||
|
||||
ARTCGFloatArray array;
|
||||
array.count = count;
|
||||
array.array = NULL;
|
||||
|
||||
if (count) {
|
||||
// Ideally, these arrays should already use the same memory layout.
|
||||
// In that case we shouldn't need this new malloc.
|
||||
array.array = malloc(sizeof(CGFloat) * count);
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
array.array[i] = [arr[i] doubleValue];
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
+ (ARTBrush *)ARTBrush:(id)json
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
NSUInteger type = [self NSUInteger:arr.firstObject];
|
||||
switch (type) {
|
||||
case 0: // solid color
|
||||
// These are probably expensive allocations since it's often the same value.
|
||||
// We should memoize colors but look ups may be just as expensive.
|
||||
return [[ARTSolidColor alloc] initWithArray:arr];
|
||||
case 1: // linear gradient
|
||||
return [[ARTLinearGradient alloc] initWithArray:arr];
|
||||
case 2: // radial gradient
|
||||
return [[ARTRadialGradient alloc] initWithArray:arr];
|
||||
case 3: // pattern
|
||||
return [[ARTPattern alloc] initWithArray:arr];
|
||||
default:
|
||||
RCTLogError(@"Unknown brush type: %llu", (unsigned long long)type);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 2) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(2 + offset), arr);
|
||||
return CGPointZero;
|
||||
}
|
||||
return (CGPoint){
|
||||
[self CGFloat:arr[offset]],
|
||||
[self CGFloat:arr[offset + 1]],
|
||||
};
|
||||
}
|
||||
|
||||
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 4) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr);
|
||||
return CGRectZero;
|
||||
}
|
||||
return (CGRect){
|
||||
{[self CGFloat:arr[offset]], [self CGFloat:arr[offset + 1]]},
|
||||
{[self CGFloat:arr[offset + 2]], [self CGFloat:arr[offset + 3]]},
|
||||
};
|
||||
}
|
||||
|
||||
+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 4) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr);
|
||||
return NULL;
|
||||
}
|
||||
return [self CGColor:[arr subarrayWithRange:(NSRange){offset, 4}]];
|
||||
}
|
||||
|
||||
+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)offset, arr);
|
||||
return NULL;
|
||||
}
|
||||
arr = [arr subarrayWithRange:(NSRange){offset, arr.count - offset}];
|
||||
ARTCGFloatArray colorsAndOffsets = [self ARTCGFloatArray:arr];
|
||||
size_t stops = colorsAndOffsets.count / 5;
|
||||
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
|
||||
CGGradientRef gradient = CGGradientCreateWithColorComponents(
|
||||
rgb,
|
||||
colorsAndOffsets.array,
|
||||
colorsAndOffsets.array + stops * 4,
|
||||
stops
|
||||
);
|
||||
CGColorSpaceRelease(rgb);
|
||||
free(colorsAndOffsets.array);
|
||||
return (CGGradientRef)CFAutorelease(gradient);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,34 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
require "json"
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
|
||||
version = package['version']
|
||||
|
||||
source = { :git => 'https://github.com/facebook/react-native.git' }
|
||||
if version == '1000.0.0'
|
||||
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
|
||||
source[:commit] = `git rev-parse HEAD`.strip
|
||||
else
|
||||
source[:tag] = "v#{version}"
|
||||
end
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "React-ART"
|
||||
s.version = version
|
||||
s.summary = "A library for drawing vector graphics."
|
||||
s.homepage = "http://facebook.github.io/react-native/"
|
||||
s.license = package["license"]
|
||||
s.author = "Facebook, Inc. and its affiliates"
|
||||
s.platforms = { :ios => "9.0", :tvos => "9.2" }
|
||||
s.source = source
|
||||
s.source_files = "**/*.{m}"
|
||||
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
|
||||
s.header_dir = "ART"
|
||||
|
||||
s.dependency "React-Core/ARTHeaders", version
|
||||
end
|
||||
@@ -0,0 +1,612 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 Color = require('art/core/color');
|
||||
const Path = require('./ARTSerializablePath');
|
||||
const Transform = require('art/core/transform');
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const ReactNativeViewAttributes = require('../Components/View/ReactNativeViewAttributes');
|
||||
|
||||
const createReactNativeComponentClass = require('../Renderer/shims/createReactNativeComponentClass');
|
||||
const merge = require('../vendor/core/merge');
|
||||
const invariant = require('invariant');
|
||||
|
||||
// Diff Helpers
|
||||
|
||||
function arrayDiffer(a, b) {
|
||||
if (a == null || b == null) {
|
||||
return true;
|
||||
}
|
||||
if (a.length !== b.length) {
|
||||
return true;
|
||||
}
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function fontAndLinesDiffer(a, b) {
|
||||
if (a === b) {
|
||||
return false;
|
||||
}
|
||||
if (a.font !== b.font) {
|
||||
if (a.font === null) {
|
||||
return true;
|
||||
}
|
||||
if (b.font === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
a.font.fontFamily !== b.font.fontFamily ||
|
||||
a.font.fontSize !== b.font.fontSize ||
|
||||
a.font.fontWeight !== b.font.fontWeight ||
|
||||
a.font.fontStyle !== b.font.fontStyle
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return arrayDiffer(a.lines, b.lines);
|
||||
}
|
||||
|
||||
// Native Attributes
|
||||
|
||||
const SurfaceViewAttributes = merge(ReactNativeViewAttributes.UIView, {
|
||||
// This should contain pixel information such as width, height and
|
||||
// resolution to know what kind of buffer needs to be allocated.
|
||||
// Currently we rely on UIViews and style to figure that out.
|
||||
});
|
||||
|
||||
const NodeAttributes = {
|
||||
transform: {diff: arrayDiffer},
|
||||
opacity: true,
|
||||
};
|
||||
|
||||
const GroupAttributes = merge(NodeAttributes, {
|
||||
clipping: {diff: arrayDiffer},
|
||||
});
|
||||
|
||||
const RenderableAttributes = merge(NodeAttributes, {
|
||||
fill: {diff: arrayDiffer},
|
||||
stroke: {diff: arrayDiffer},
|
||||
strokeWidth: true,
|
||||
strokeCap: true,
|
||||
strokeJoin: true,
|
||||
strokeDash: {diff: arrayDiffer},
|
||||
});
|
||||
|
||||
const ShapeAttributes = merge(RenderableAttributes, {
|
||||
d: {diff: arrayDiffer},
|
||||
});
|
||||
|
||||
const TextAttributes = merge(RenderableAttributes, {
|
||||
alignment: true,
|
||||
frame: {diff: fontAndLinesDiffer},
|
||||
path: {diff: arrayDiffer},
|
||||
});
|
||||
|
||||
// Native Components
|
||||
|
||||
const NativeSurfaceView = createReactNativeComponentClass(
|
||||
'ARTSurfaceView',
|
||||
() => ({
|
||||
validAttributes: SurfaceViewAttributes,
|
||||
uiViewClassName: 'ARTSurfaceView',
|
||||
}),
|
||||
);
|
||||
|
||||
const NativeGroup = createReactNativeComponentClass('ARTGroup', () => ({
|
||||
validAttributes: GroupAttributes,
|
||||
uiViewClassName: 'ARTGroup',
|
||||
}));
|
||||
|
||||
const NativeShape = createReactNativeComponentClass('ARTShape', () => ({
|
||||
validAttributes: ShapeAttributes,
|
||||
uiViewClassName: 'ARTShape',
|
||||
}));
|
||||
|
||||
const NativeText = createReactNativeComponentClass('ARTText', () => ({
|
||||
validAttributes: TextAttributes,
|
||||
uiViewClassName: 'ARTText',
|
||||
}));
|
||||
|
||||
// Utilities
|
||||
|
||||
function childrenAsString(children) {
|
||||
if (!children) {
|
||||
return '';
|
||||
}
|
||||
if (typeof children === 'string') {
|
||||
return children;
|
||||
}
|
||||
if (children.length) {
|
||||
return children.join('\n');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Surface - Root node of all ART
|
||||
|
||||
class Surface extends React.Component {
|
||||
static childContextTypes = {
|
||||
isInSurface: PropTypes.bool,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {isInSurface: true};
|
||||
}
|
||||
|
||||
render() {
|
||||
const height = extractNumber(this.props.height, 0);
|
||||
const width = extractNumber(this.props.width, 0);
|
||||
|
||||
return (
|
||||
<NativeSurfaceView style={[this.props.style, {height, width}]}>
|
||||
{this.props.children}
|
||||
</NativeSurfaceView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Node Props
|
||||
|
||||
// TODO: The desktop version of ART has title and cursor. We should have
|
||||
// accessibility support here too even though hovering doesn't work.
|
||||
|
||||
function extractNumber(value, defaultValue) {
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return +value;
|
||||
}
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
function extractTransform(props) {
|
||||
const scaleX =
|
||||
props.scaleX != null ? props.scaleX : props.scale != null ? props.scale : 1;
|
||||
const scaleY =
|
||||
props.scaleY != null ? props.scaleY : props.scale != null ? props.scale : 1;
|
||||
|
||||
pooledTransform
|
||||
.transformTo(1, 0, 0, 1, 0, 0)
|
||||
.move(props.x || 0, props.y || 0)
|
||||
.rotate(props.rotation || 0, props.originX, props.originY)
|
||||
.scale(scaleX, scaleY, props.originX, props.originY);
|
||||
|
||||
if (props.transform != null) {
|
||||
pooledTransform.transform(props.transform);
|
||||
}
|
||||
|
||||
return [
|
||||
pooledTransform.xx,
|
||||
pooledTransform.yx,
|
||||
pooledTransform.xy,
|
||||
pooledTransform.yy,
|
||||
pooledTransform.x,
|
||||
pooledTransform.y,
|
||||
];
|
||||
}
|
||||
|
||||
function extractOpacity(props) {
|
||||
// TODO: visible === false should also have no hit detection
|
||||
if (props.visible === false) {
|
||||
return 0;
|
||||
}
|
||||
if (props.opacity == null) {
|
||||
return 1;
|
||||
}
|
||||
return +props.opacity;
|
||||
}
|
||||
|
||||
// Groups
|
||||
|
||||
// Note: ART has a notion of width and height on Group but AFAIK it's a noop in
|
||||
// ReactART.
|
||||
|
||||
class Group extends React.Component {
|
||||
static contextTypes = {
|
||||
isInSurface: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
invariant(
|
||||
this.context.isInSurface,
|
||||
'ART: <Group /> must be a child of a <Surface />',
|
||||
);
|
||||
return (
|
||||
<NativeGroup
|
||||
opacity={extractOpacity(props)}
|
||||
transform={extractTransform(props)}>
|
||||
{this.props.children}
|
||||
</NativeGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ClippingRectangle extends React.Component {
|
||||
render() {
|
||||
const props = this.props;
|
||||
const x = extractNumber(props.x, 0);
|
||||
const y = extractNumber(props.y, 0);
|
||||
const w = extractNumber(props.width, 0);
|
||||
const h = extractNumber(props.height, 0);
|
||||
const clipping = [x, y, w, h];
|
||||
// The current clipping API requires x and y to be ignored in the transform
|
||||
const propsExcludingXAndY = merge(props);
|
||||
delete propsExcludingXAndY.x;
|
||||
delete propsExcludingXAndY.y;
|
||||
return (
|
||||
<NativeGroup
|
||||
clipping={clipping}
|
||||
opacity={extractOpacity(props)}
|
||||
transform={extractTransform(propsExcludingXAndY)}>
|
||||
{this.props.children}
|
||||
</NativeGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderables
|
||||
|
||||
const SOLID_COLOR = 0;
|
||||
const LINEAR_GRADIENT = 1;
|
||||
const RADIAL_GRADIENT = 2;
|
||||
const PATTERN = 3;
|
||||
|
||||
function insertColorIntoArray(color, targetArray, atIndex) {
|
||||
const c = new Color(color);
|
||||
targetArray[atIndex + 0] = c.red / 255;
|
||||
targetArray[atIndex + 1] = c.green / 255;
|
||||
targetArray[atIndex + 2] = c.blue / 255;
|
||||
targetArray[atIndex + 3] = c.alpha;
|
||||
}
|
||||
|
||||
function insertColorsIntoArray(stops, targetArray, atIndex) {
|
||||
let i = 0;
|
||||
if ('length' in stops) {
|
||||
while (i < stops.length) {
|
||||
insertColorIntoArray(stops[i], targetArray, atIndex + i * 4);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
for (const offset in stops) {
|
||||
insertColorIntoArray(stops[offset], targetArray, atIndex + i * 4);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return atIndex + i * 4;
|
||||
}
|
||||
|
||||
function insertOffsetsIntoArray(stops, targetArray, atIndex, multi, reverse) {
|
||||
let offsetNumber;
|
||||
let i = 0;
|
||||
if ('length' in stops) {
|
||||
while (i < stops.length) {
|
||||
offsetNumber = (i / (stops.length - 1)) * multi;
|
||||
targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber;
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
for (const offsetString in stops) {
|
||||
offsetNumber = +offsetString * multi;
|
||||
targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return atIndex + i;
|
||||
}
|
||||
|
||||
function insertColorStopsIntoArray(stops, targetArray, atIndex) {
|
||||
const lastIndex = insertColorsIntoArray(stops, targetArray, atIndex);
|
||||
insertOffsetsIntoArray(stops, targetArray, lastIndex, 1, false);
|
||||
}
|
||||
|
||||
function insertDoubleColorStopsIntoArray(stops, targetArray, atIndex) {
|
||||
let lastIndex = insertColorsIntoArray(stops, targetArray, atIndex);
|
||||
lastIndex = insertColorsIntoArray(stops, targetArray, lastIndex);
|
||||
lastIndex = insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, false);
|
||||
insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, true);
|
||||
}
|
||||
|
||||
function applyBoundingBoxToBrushData(brushData, props) {
|
||||
const type = brushData[0];
|
||||
const width = +props.width;
|
||||
const height = +props.height;
|
||||
if (type === LINEAR_GRADIENT) {
|
||||
brushData[1] *= width;
|
||||
brushData[2] *= height;
|
||||
brushData[3] *= width;
|
||||
brushData[4] *= height;
|
||||
} else if (type === RADIAL_GRADIENT) {
|
||||
brushData[1] *= width;
|
||||
brushData[2] *= height;
|
||||
brushData[3] *= width;
|
||||
brushData[4] *= height;
|
||||
brushData[5] *= width;
|
||||
brushData[6] *= height;
|
||||
} else if (type === PATTERN) {
|
||||
// todo
|
||||
}
|
||||
}
|
||||
|
||||
function extractBrush(colorOrBrush, props) {
|
||||
if (colorOrBrush == null) {
|
||||
return null;
|
||||
}
|
||||
if (colorOrBrush._brush) {
|
||||
if (colorOrBrush._bb) {
|
||||
// The legacy API for Gradients allow for the bounding box to be used
|
||||
// as a convenience for specifying gradient positions. This should be
|
||||
// deprecated. It's not properly implemented in canvas mode. ReactART
|
||||
// doesn't handle update to the bounding box correctly. That's why we
|
||||
// mutate this so that if it's reused, we reuse the same resolved box.
|
||||
applyBoundingBoxToBrushData(colorOrBrush._brush, props);
|
||||
colorOrBrush._bb = false;
|
||||
}
|
||||
return colorOrBrush._brush;
|
||||
}
|
||||
const c = new Color(colorOrBrush);
|
||||
return [SOLID_COLOR, c.red / 255, c.green / 255, c.blue / 255, c.alpha];
|
||||
}
|
||||
|
||||
function extractColor(color) {
|
||||
if (color == null) {
|
||||
return null;
|
||||
}
|
||||
const c = new Color(color);
|
||||
return [c.red / 255, c.green / 255, c.blue / 255, c.alpha];
|
||||
}
|
||||
|
||||
function extractStrokeCap(strokeCap) {
|
||||
switch (strokeCap) {
|
||||
case 'butt':
|
||||
return 0;
|
||||
case 'square':
|
||||
return 2;
|
||||
default:
|
||||
return 1; // round
|
||||
}
|
||||
}
|
||||
|
||||
function extractStrokeJoin(strokeJoin) {
|
||||
switch (strokeJoin) {
|
||||
case 'miter':
|
||||
return 0;
|
||||
case 'bevel':
|
||||
return 2;
|
||||
default:
|
||||
return 1; // round
|
||||
}
|
||||
}
|
||||
|
||||
// Shape
|
||||
|
||||
// Note: ART has a notion of width and height on Shape but AFAIK it's a noop in
|
||||
// ReactART.
|
||||
|
||||
export type ShapeProps = {|
|
||||
fill?: mixed,
|
||||
stroke?: mixed,
|
||||
strokeCap?: mixed,
|
||||
strokeDash?: mixed,
|
||||
strokeJoin?: mixed,
|
||||
strokeWidth?: mixed,
|
||||
x?: number,
|
||||
y?: number,
|
||||
opacity?: mixed,
|
||||
|};
|
||||
|
||||
class Shape extends React.Component<ShapeProps> {
|
||||
render() {
|
||||
const props = this.props;
|
||||
const path = props.d || childrenAsString(props.children);
|
||||
const d = (path instanceof Path ? path : new Path(path)).toJSON();
|
||||
return (
|
||||
<NativeShape
|
||||
fill={extractBrush(props.fill, props)}
|
||||
opacity={extractOpacity(props)}
|
||||
stroke={extractColor(props.stroke)}
|
||||
strokeCap={extractStrokeCap(props.strokeCap)}
|
||||
strokeDash={props.strokeDash || null}
|
||||
strokeJoin={extractStrokeJoin(props.strokeJoin)}
|
||||
strokeWidth={extractNumber(props.strokeWidth, 1)}
|
||||
transform={extractTransform(props)}
|
||||
d={d}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
|
||||
const cachedFontObjectsFromString = {};
|
||||
|
||||
const fontFamilyPrefix = /^[\s"']*/;
|
||||
const fontFamilySuffix = /[\s"']*$/;
|
||||
|
||||
function extractSingleFontFamily(fontFamilyString) {
|
||||
// ART on the web allows for multiple font-families to be specified.
|
||||
// For compatibility, we extract the first font-family, hoping
|
||||
// we'll get a match.
|
||||
return fontFamilyString
|
||||
.split(',')[0]
|
||||
.replace(fontFamilyPrefix, '')
|
||||
.replace(fontFamilySuffix, '');
|
||||
}
|
||||
|
||||
function parseFontString(font) {
|
||||
if (cachedFontObjectsFromString.hasOwnProperty(font)) {
|
||||
return cachedFontObjectsFromString[font];
|
||||
}
|
||||
const regexp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm\%]*(?:\s*\/.*?)?\s+)?\s*\"?([^\"]*)/i;
|
||||
const match = regexp.exec(font);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const fontFamily = extractSingleFontFamily(match[3]);
|
||||
const fontSize = +match[2] || 12;
|
||||
const isBold = /bold/.exec(match[1]);
|
||||
const isItalic = /italic/.exec(match[1]);
|
||||
cachedFontObjectsFromString[font] = {
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize,
|
||||
fontWeight: isBold ? 'bold' : 'normal',
|
||||
fontStyle: isItalic ? 'italic' : 'normal',
|
||||
};
|
||||
return cachedFontObjectsFromString[font];
|
||||
}
|
||||
|
||||
function extractFont(font) {
|
||||
if (font == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof font === 'string') {
|
||||
return parseFontString(font);
|
||||
}
|
||||
const fontFamily = extractSingleFontFamily(font.fontFamily);
|
||||
const fontSize = +font.fontSize || 12;
|
||||
const fontWeight =
|
||||
font.fontWeight != null ? font.fontWeight.toString() : '400';
|
||||
return {
|
||||
// Normalize
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize,
|
||||
fontWeight: fontWeight,
|
||||
fontStyle: font.fontStyle,
|
||||
};
|
||||
}
|
||||
|
||||
const newLine = /\n/g;
|
||||
function extractFontAndLines(font, text) {
|
||||
return {font: extractFont(font), lines: text.split(newLine)};
|
||||
}
|
||||
|
||||
function extractAlignment(alignment) {
|
||||
switch (alignment) {
|
||||
case 'right':
|
||||
return 1;
|
||||
case 'center':
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class Text extends React.Component {
|
||||
render() {
|
||||
const props = this.props;
|
||||
const path = props.path;
|
||||
const textPath = path
|
||||
? (path instanceof Path ? path : new Path(path)).toJSON()
|
||||
: null;
|
||||
const textFrame = extractFontAndLines(
|
||||
props.font,
|
||||
childrenAsString(props.children),
|
||||
);
|
||||
return (
|
||||
<NativeText
|
||||
fill={extractBrush(props.fill, props)}
|
||||
opacity={extractOpacity(props)}
|
||||
stroke={extractColor(props.stroke)}
|
||||
strokeCap={extractStrokeCap(props.strokeCap)}
|
||||
strokeDash={props.strokeDash || null}
|
||||
strokeJoin={extractStrokeJoin(props.strokeJoin)}
|
||||
strokeWidth={extractNumber(props.strokeWidth, 1)}
|
||||
transform={extractTransform(props)}
|
||||
alignment={extractAlignment(props.alignment)}
|
||||
frame={textFrame}
|
||||
path={textPath}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Declarative fill type objects - API design not finalized
|
||||
|
||||
function LinearGradient(stops, x1, y1, x2, y2) {
|
||||
const type = LINEAR_GRADIENT;
|
||||
|
||||
if (arguments.length < 5) {
|
||||
const angle = ((x1 == null ? 270 : x1) * Math.PI) / 180;
|
||||
|
||||
let x = Math.cos(angle);
|
||||
let y = -Math.sin(angle);
|
||||
const l = (Math.abs(x) + Math.abs(y)) / 2;
|
||||
|
||||
x *= l;
|
||||
y *= l;
|
||||
|
||||
x1 = 0.5 - x;
|
||||
x2 = 0.5 + x;
|
||||
y1 = 0.5 - y;
|
||||
y2 = 0.5 + y;
|
||||
this._bb = true;
|
||||
} else {
|
||||
this._bb = false;
|
||||
}
|
||||
|
||||
const brushData = [type, +x1, +y1, +x2, +y2];
|
||||
insertColorStopsIntoArray(stops, brushData, 5);
|
||||
this._brush = brushData;
|
||||
}
|
||||
|
||||
function RadialGradient(stops, fx, fy, rx, ry, cx, cy) {
|
||||
if (ry == null) {
|
||||
ry = rx;
|
||||
}
|
||||
if (cx == null) {
|
||||
cx = fx;
|
||||
}
|
||||
if (cy == null) {
|
||||
cy = fy;
|
||||
}
|
||||
if (fx == null) {
|
||||
// As a convenience we allow the whole radial gradient to cover the
|
||||
// bounding box. We should consider dropping this API.
|
||||
fx = fy = rx = ry = cx = cy = 0.5;
|
||||
this._bb = true;
|
||||
} else {
|
||||
this._bb = false;
|
||||
}
|
||||
// The ART API expects the radial gradient to be repeated at the edges.
|
||||
// To simulate this we render the gradient twice as large and add double
|
||||
// color stops. Ideally this API would become more restrictive so that this
|
||||
// extra work isn't needed.
|
||||
const brushData = [RADIAL_GRADIENT, +fx, +fy, +rx * 2, +ry * 2, +cx, +cy];
|
||||
insertDoubleColorStopsIntoArray(stops, brushData, 7);
|
||||
this._brush = brushData;
|
||||
}
|
||||
|
||||
function Pattern(url, width, height, left, top) {
|
||||
this._brush = [PATTERN, url, +left || 0, +top || 0, +width, +height];
|
||||
}
|
||||
|
||||
const ReactART = {
|
||||
LinearGradient: LinearGradient,
|
||||
RadialGradient: RadialGradient,
|
||||
Pattern: Pattern,
|
||||
Transform: Transform,
|
||||
Path: Path,
|
||||
Surface: Surface,
|
||||
Group: Group,
|
||||
ClippingRectangle: ClippingRectangle,
|
||||
Shape: Shape,
|
||||
Text: Text,
|
||||
};
|
||||
|
||||
module.exports = ReactART;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTNodeManager.h"
|
||||
|
||||
@interface ARTGroupManager : ARTNodeManager
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTGroupManager.h>
|
||||
|
||||
#import <React/ARTGroup.h>
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTGroupManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTNode *)node
|
||||
{
|
||||
return [ARTGroup new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(clipping, CGRect)
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@class ARTNode;
|
||||
|
||||
@interface ARTNodeManager : RCTViewManager
|
||||
|
||||
- (ARTNode *)node;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTNodeManager.h>
|
||||
|
||||
#import <React/ARTNode.h>
|
||||
|
||||
@implementation ARTNodeManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTNode *)node
|
||||
{
|
||||
return [ARTNode new];
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [self node];
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(opacity, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(transform, CGAffineTransform)
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTNodeManager.h"
|
||||
#import "ARTRenderable.h"
|
||||
|
||||
@interface ARTRenderableManager : ARTNodeManager
|
||||
|
||||
- (ARTRenderable *)node;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTRenderableManager.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTRenderableManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [ARTRenderable new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeCap, CGLineCap)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeJoin, CGLineJoin)
|
||||
RCT_EXPORT_VIEW_PROPERTY(fill, ARTBrush)
|
||||
RCT_EXPORT_VIEW_PROPERTY(stroke, CGColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeDash, ARTCGFloatArray)
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTRenderableManager.h"
|
||||
|
||||
@interface ARTShapeManager : ARTRenderableManager
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTShapeManager.h>
|
||||
|
||||
#import <React/ARTShape.h>
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTShapeManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [ARTShape new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(d, CGPath)
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@interface ARTSurfaceViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTSurfaceViewManager.h>
|
||||
|
||||
#import <React/ARTSurfaceView.h>
|
||||
|
||||
@implementation ARTSurfaceViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [ARTSurfaceView new];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "ARTRenderableManager.h"
|
||||
|
||||
@interface ARTTextManager : ARTRenderableManager
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/ARTTextManager.h>
|
||||
|
||||
#import <React/ARTText.h>
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTTextManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [ARTText new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
|
||||
RCT_REMAP_VIEW_PROPERTY(frame, textFrame, ARTTextFrame)
|
||||
|
||||
@end
|
||||
@@ -7,18 +7,17 @@
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import RCTActionSheetManager from './NativeActionSheetManager';
|
||||
|
||||
const invariant = require('invariant');
|
||||
const processColor = require('../StyleSheet/processColor');
|
||||
import type {ColorValue} from '../StyleSheet/StyleSheet';
|
||||
import type {ProcessedColorValue} from '../StyleSheet/processColor';
|
||||
|
||||
/**
|
||||
* Display action sheets and share sheets on iOS.
|
||||
*
|
||||
* See https://reactnative.dev/docs/actionsheetios.html
|
||||
* See http://facebook.github.io/react-native/docs/actionsheetios.html
|
||||
*/
|
||||
const ActionSheetIOS = {
|
||||
/**
|
||||
@@ -31,24 +30,21 @@ const ActionSheetIOS = {
|
||||
* - `destructiveButtonIndex` (int or array of ints) - index or indices of destructive buttons in `options`
|
||||
* - `title` (string) - a title to show above the action sheet
|
||||
* - `message` (string) - a message to show below the title
|
||||
* - `disabledButtonIndices` (array of numbers) - a list of button indices which should be disabled
|
||||
*
|
||||
* The 'callback' function takes one parameter, the zero-based index
|
||||
* of the selected item.
|
||||
*
|
||||
* See https://reactnative.dev/docs/actionsheetios.html#showactionsheetwithoptions
|
||||
* See http://facebook.github.io/react-native/docs/actionsheetios.html#showactionsheetwithoptions
|
||||
*/
|
||||
showActionSheetWithOptions(
|
||||
options: {|
|
||||
+title?: ?string,
|
||||
+message?: ?string,
|
||||
+options: Array<string>,
|
||||
+destructiveButtonIndex?: ?number | ?Array<number>,
|
||||
+destructiveButtonIndex?: ?number,
|
||||
+cancelButtonIndex?: ?number,
|
||||
+anchor?: ?number,
|
||||
+tintColor?: ColorValue | ProcessedColorValue,
|
||||
+userInterfaceStyle?: string,
|
||||
+disabledButtonIndices?: Array<number>,
|
||||
+tintColor?: number | string,
|
||||
|},
|
||||
callback: (buttonIndex: number) => void,
|
||||
) {
|
||||
@@ -57,28 +53,12 @@ const ActionSheetIOS = {
|
||||
'Options must be a valid object',
|
||||
);
|
||||
invariant(typeof callback === 'function', 'Must provide a valid callback');
|
||||
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
|
||||
invariant(RCTActionSheetManager, "ActionSheetManager does't exist");
|
||||
|
||||
const {tintColor, destructiveButtonIndex, ...remainingOptions} = options;
|
||||
let destructiveButtonIndices = null;
|
||||
const {tintColor, ...remainingOptions} = options;
|
||||
|
||||
if (Array.isArray(destructiveButtonIndex)) {
|
||||
destructiveButtonIndices = destructiveButtonIndex;
|
||||
} else if (typeof destructiveButtonIndex === 'number') {
|
||||
destructiveButtonIndices = [destructiveButtonIndex];
|
||||
}
|
||||
|
||||
const processedTintColor = processColor(tintColor);
|
||||
invariant(
|
||||
processedTintColor == null || typeof processedTintColor === 'number',
|
||||
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions tintColor',
|
||||
);
|
||||
RCTActionSheetManager.showActionSheetWithOptions(
|
||||
{
|
||||
...remainingOptions,
|
||||
tintColor: processedTintColor,
|
||||
destructiveButtonIndices,
|
||||
},
|
||||
{...remainingOptions, tintColor: processColor(tintColor)},
|
||||
callback,
|
||||
);
|
||||
},
|
||||
@@ -104,7 +84,7 @@ const ActionSheetIOS = {
|
||||
* - a boolean value signifying success or failure
|
||||
* - a string that, in the case of success, indicates the method of sharing
|
||||
*
|
||||
* See https://reactnative.dev/docs/actionsheetios.html#showshareactionsheetwithoptions
|
||||
* See http://facebook.github.io/react-native/docs/actionsheetios.html#showshareactionsheetwithoptions
|
||||
*/
|
||||
showShareActionSheetWithOptions(
|
||||
options: Object,
|
||||
@@ -123,7 +103,7 @@ const ActionSheetIOS = {
|
||||
typeof successCallback === 'function',
|
||||
'Must provide a valid successCallback',
|
||||
);
|
||||
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
|
||||
invariant(RCTActionSheetManager, "ActionSheetManager does't exist");
|
||||
RCTActionSheetManager.showShareActionSheetWithOptions(
|
||||
{...options, tintColor: processColor(options.tintColor)},
|
||||
failureCallback,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
@@ -18,12 +20,11 @@ export interface Spec extends TurboModule {
|
||||
+title?: ?string,
|
||||
+message?: ?string,
|
||||
+options: ?Array<string>,
|
||||
+destructiveButtonIndices?: ?Array<number>,
|
||||
// Supports Array<number> as well.
|
||||
+destructiveButtonIndex?: ?number,
|
||||
+cancelButtonIndex?: ?number,
|
||||
+anchor?: ?number,
|
||||
+tintColor?: ?number,
|
||||
+userInterfaceStyle?: ?string,
|
||||
+disabledButtonIndices?: Array<number>,
|
||||
|},
|
||||
callback: (buttonIndex: number) => void,
|
||||
) => void;
|
||||
@@ -35,7 +36,6 @@ export interface Spec extends TurboModule {
|
||||
+anchor?: ?number,
|
||||
+tintColor?: ?number,
|
||||
+excludedActivityTypes?: ?Array<string>,
|
||||
+userInterfaceStyle?: ?string,
|
||||
|},
|
||||
failureCallback: (error: {|
|
||||
+domain: string,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <React/RCTActionSheetManager.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
@interface RCTActionSheetManager () <UIActionSheetDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTActionSheetManager
|
||||
{
|
||||
// Use NSMapTable, as UIAlertViews do not implement <NSCopying>
|
||||
// which is required for NSDictionary keys
|
||||
NSMapTable *_callbacks;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
- (void)presentViewController:(UIViewController *)alertController
|
||||
onParentViewController:(UIViewController *)parentViewController
|
||||
anchorViewTag:(NSNumber *)anchorViewTag
|
||||
{
|
||||
alertController.modalPresentationStyle = UIModalPresentationPopover;
|
||||
UIView *sourceView = parentViewController.view;
|
||||
|
||||
if (anchorViewTag) {
|
||||
sourceView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
|
||||
} else {
|
||||
alertController.popoverPresentationController.permittedArrowDirections = 0;
|
||||
}
|
||||
alertController.popoverPresentationController.sourceView = sourceView;
|
||||
alertController.popoverPresentationController.sourceRect = sourceView.bounds;
|
||||
[parentViewController presentViewController:alertController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
RCTLogError(@"Unable to show action sheet from app extension");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_callbacks) {
|
||||
_callbacks = [NSMapTable strongToStrongObjectsMapTable];
|
||||
}
|
||||
|
||||
NSString *title = [RCTConvert NSString:options[@"title"]];
|
||||
NSString *message = [RCTConvert NSString:options[@"message"]];
|
||||
NSArray<NSString *> *buttons = [RCTConvert NSStringArray:options[@"options"]];
|
||||
NSInteger cancelButtonIndex = options[@"cancelButtonIndex"] ? [RCTConvert NSInteger:options[@"cancelButtonIndex"]] : -1;
|
||||
NSArray<NSNumber *> *destructiveButtonIndices;
|
||||
if ([options[@"destructiveButtonIndex"] isKindOfClass:[NSArray class]]) {
|
||||
destructiveButtonIndices = [RCTConvert NSArray:options[@"destructiveButtonIndex"]];
|
||||
} else {
|
||||
NSNumber *destructiveButtonIndex = options[@"destructiveButtonIndex"] ? [RCTConvert NSNumber:options[@"destructiveButtonIndex"]] : @-1;
|
||||
destructiveButtonIndices = @[destructiveButtonIndex];
|
||||
}
|
||||
|
||||
UIViewController *controller = RCTPresentedViewController();
|
||||
|
||||
if (controller == nil) {
|
||||
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `anchor` option takes a view to set as the anchor for the share
|
||||
* popup to point to, on iPads running iOS 8. If it is not passed, it
|
||||
* defaults to centering the share popup on screen without any arrows.
|
||||
*/
|
||||
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
|
||||
|
||||
UIAlertController *alertController =
|
||||
[UIAlertController alertControllerWithTitle:title
|
||||
message:message
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
NSInteger index = 0;
|
||||
for (NSString *option in buttons) {
|
||||
UIAlertActionStyle style = UIAlertActionStyleDefault;
|
||||
if ([destructiveButtonIndices containsObject:@(index)]) {
|
||||
style = UIAlertActionStyleDestructive;
|
||||
} else if (index == cancelButtonIndex) {
|
||||
style = UIAlertActionStyleCancel;
|
||||
}
|
||||
|
||||
NSInteger localIndex = index;
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:option
|
||||
style:style
|
||||
handler:^(__unused UIAlertAction *action){
|
||||
callback(@[@(localIndex)]);
|
||||
}]];
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
alertController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
|
||||
[self presentViewController:alertController onParentViewController:controller anchorViewTag:anchorViewTag];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(RCTResponseErrorBlock)failureCallback
|
||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
RCTLogError(@"Unable to show action sheet from app extension");
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray<id> *items = [NSMutableArray array];
|
||||
NSString *message = [RCTConvert NSString:options[@"message"]];
|
||||
if (message) {
|
||||
[items addObject:message];
|
||||
}
|
||||
NSURL *URL = [RCTConvert NSURL:options[@"url"]];
|
||||
if (URL) {
|
||||
if ([URL.scheme.lowercaseString isEqualToString:@"data"]) {
|
||||
NSError *error;
|
||||
NSData *data = [NSData dataWithContentsOfURL:URL
|
||||
options:(NSDataReadingOptions)0
|
||||
error:&error];
|
||||
if (!data) {
|
||||
failureCallback(error);
|
||||
return;
|
||||
}
|
||||
[items addObject:data];
|
||||
} else {
|
||||
[items addObject:URL];
|
||||
}
|
||||
}
|
||||
if (items.count == 0) {
|
||||
RCTLogError(@"No `url` or `message` to share");
|
||||
return;
|
||||
}
|
||||
|
||||
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||
|
||||
NSString *subject = [RCTConvert NSString:options[@"subject"]];
|
||||
if (subject) {
|
||||
[shareController setValue:subject forKey:@"subject"];
|
||||
}
|
||||
|
||||
NSArray *excludedActivityTypes = [RCTConvert NSStringArray:options[@"excludedActivityTypes"]];
|
||||
if (excludedActivityTypes) {
|
||||
shareController.excludedActivityTypes = excludedActivityTypes;
|
||||
}
|
||||
|
||||
UIViewController *controller = RCTPresentedViewController();
|
||||
shareController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
|
||||
if (activityError) {
|
||||
failureCallback(activityError);
|
||||
} else {
|
||||
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
|
||||
}
|
||||
};
|
||||
|
||||
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
|
||||
shareController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
|
||||
|
||||
[self presentViewController:shareController onParentViewController:controller anchorViewTag:anchorViewTag];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,3 +1,4 @@
|
||||
# coding: utf-8
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
@@ -11,7 +12,7 @@ version = package['version']
|
||||
source = { :git => 'https://github.com/facebook/react-native.git' }
|
||||
if version == '1000.0.0'
|
||||
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
|
||||
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
|
||||
source[:commit] = `git rev-parse HEAD`.strip
|
||||
else
|
||||
source[:tag] = "v#{version}"
|
||||
end
|
||||
@@ -20,14 +21,14 @@ Pod::Spec.new do |s|
|
||||
s.name = "React-RCTActionSheet"
|
||||
s.version = version
|
||||
s.summary = "An API for displaying iOS action sheets and share sheets."
|
||||
s.homepage = "https://reactnative.dev/"
|
||||
s.documentation_url = "https://reactnative.dev/docs/actionsheetios"
|
||||
s.homepage = "http://facebook.github.io/react-native/"
|
||||
s.documentation_url = "https://facebook.github.io/react-native/docs/actionsheetios"
|
||||
s.license = package["license"]
|
||||
s.author = "Facebook, Inc. and its affiliates"
|
||||
s.platforms = { :ios => "11.0" }
|
||||
s.platforms = { :ios => "9.0", :tvos => "9.2" }
|
||||
s.source = source
|
||||
s.source_files = "*.{m}"
|
||||
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
|
||||
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
|
||||
s.header_dir = "RCTActionSheet"
|
||||
|
||||
s.dependency "React-Core/RCTActionSheetHeaders", version
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Platform from '../Utilities/Platform';
|
||||
import type {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid';
|
||||
import NativeDialogManagerAndroid, {
|
||||
type DialogOptions,
|
||||
} from '../NativeModules/specs/NativeDialogManagerAndroid';
|
||||
import RCTAlertManager from './RCTAlertManager';
|
||||
|
||||
export type AlertType =
|
||||
@@ -22,19 +26,17 @@ export type Buttons = Array<{
|
||||
text?: string,
|
||||
onPress?: ?Function,
|
||||
style?: AlertButtonStyle,
|
||||
...
|
||||
}>;
|
||||
|
||||
type Options = {
|
||||
cancelable?: ?boolean,
|
||||
onDismiss?: ?() => void,
|
||||
...
|
||||
};
|
||||
|
||||
/**
|
||||
* Launches an alert dialog with the specified title and message.
|
||||
*
|
||||
* See https://reactnative.dev/docs/alert.html
|
||||
* See http://facebook.github.io/react-native/docs/alert.html
|
||||
*/
|
||||
class Alert {
|
||||
static alert(
|
||||
@@ -46,8 +48,6 @@ class Alert {
|
||||
if (Platform.OS === 'ios') {
|
||||
Alert.prompt(title, message, buttons, 'default');
|
||||
} else if (Platform.OS === 'android') {
|
||||
const NativeDialogManagerAndroid = require('../NativeModules/specs/NativeDialogManagerAndroid')
|
||||
.default;
|
||||
if (!NativeDialogManagerAndroid) {
|
||||
return;
|
||||
}
|
||||
@@ -109,6 +109,28 @@ class Alert {
|
||||
keyboardType?: string,
|
||||
): void {
|
||||
if (Platform.OS === 'ios') {
|
||||
if (typeof type === 'function') {
|
||||
console.warn(
|
||||
'You passed a callback function as the "type" argument to Alert.prompt(). React Native is ' +
|
||||
'assuming you want to use the deprecated Alert.prompt(title, defaultValue, buttons, callback) ' +
|
||||
'signature. The current signature is Alert.prompt(title, message, callbackOrButtons, type, defaultValue, ' +
|
||||
'keyboardType) and the old syntax will be removed in a future version.',
|
||||
);
|
||||
|
||||
const callback = type;
|
||||
RCTAlertManager.alertWithArgs(
|
||||
{
|
||||
title: title || '',
|
||||
type: 'plain-text',
|
||||
defaultValue: message || '',
|
||||
},
|
||||
(id, value) => {
|
||||
callback(value);
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let callbacks = [];
|
||||
const buttons = [];
|
||||
let cancelButtonKey;
|
||||
|
||||
@@ -8,13 +8,15 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {TurboModule} from '../TurboModule/RCTExport';
|
||||
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
|
||||
|
||||
export type Args = {|
|
||||
title?: string,
|
||||
message?: string,
|
||||
buttons?: Array<Object>, // TODO(T67565166): have a better type
|
||||
buttons?: Object, // TODO: have a better type
|
||||
type?: string,
|
||||
defaultValue?: string,
|
||||
cancelButtonKey?: string,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user