diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
new file mode 100644
index 0000000..697cfe8
--- /dev/null
+++ b/.github/workflows/android.yml
@@ -0,0 +1,61 @@
+name: "Android: build & test"
+
+on: [push, pull_request]
+
+jobs:
+ build_and_test:
+ name: Build & test
+ runs-on: macos-latest
+ timeout-minutes: 30
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+ with:
+ fetch-depth: 1
+
+ - name: Node
+ uses: actions/setup-node@v1
+
+ - name: Use specific Java version for sdkmanager to work
+ uses: joschi/setup-jdk@v1
+ with:
+ java-version: 'openjdk8'
+ architecture: 'x64'
+
+ - name: Download Android Emulator Image
+ run: |
+ echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install "system-images;android-29;google_apis;x86"
+ echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd --force --name emu --device "Nexus 5X" -k 'system-images;android-29;google_apis;x86'
+ $ANDROID_HOME/emulator/emulator -list-avds
+
+ - name: Install npm dependencies
+ working-directory: ./examples/detox
+ run: |
+ yarn install --frozen-lockfile
+
+ - name: Build
+ working-directory: ./examples/detox
+ run: |
+ yarn build:android-ci
+
+ - name: Start android emulator
+ working-directory: ./examples/detox
+ continue-on-error: true
+ run: |
+ echo "Starting emulator"
+ nohup $ANDROID_HOME/emulator/emulator -avd emu -no-audio -no-snapshot -no-window &
+ $ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done; input keyevent 82'
+ $ANDROID_HOME/platform-tools/adb devices
+ echo "Emulator started"
+
+ - name: Run tests
+ working-directory: ./examples/detox
+ run: yarn start & yarn test:android-ci
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v1
+ if: failure()
+ with:
+ name: Failing tests
+ path: ./examples/detox/artifacts
diff --git a/.watchmanconfig b/.watchmanconfig
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/.watchmanconfig
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/README.md b/README.md
index 7aa4ac4..1c51332 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,5 @@
-# React Native Date Picker [](https://www.npmjs.com/package/react-native-date-picker) [](https://www.npmjs.com/package/react-native-date-picker)
+# React Native Date Picker [](https://www.npmjs.com/package/react-native-date-picker) [](https://github.com/henninghall/react-native-date-picker/actions) [](https://www.npmjs.com/package/react-native-date-picker)
-
This is a React Native Date Picker with following main features:
@@ -13,10 +8,6 @@ This is a React Native Date Picker with following main features:
🌍 Multiple languages
🎨 Customizable
-
-
iOS |
diff --git a/android/build.gradle b/android/build.gradle
index abe1003..26e7f33 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -25,7 +25,7 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.facebook.react:react-native:+'
- implementation 'com.henninghall.android:NumberPickerView:1.1.1'
- implementation 'org.apache.commons:commons-lang3:3.6'
+ implementation 'com.henninghall.android:NumberPickerView:1.1.2'
+ implementation 'org.apache.commons:commons-lang3:3.7'
implementation group: 'net.time4j', name: 'time4j-android', version: '4.2-2018i'
}
diff --git a/android/src/main/java/com/henninghall/date_picker/DatePickerManager.java b/android/src/main/java/com/henninghall/date_picker/DatePickerManager.java
index ece4bcd..f15bd7f 100644
--- a/android/src/main/java/com/henninghall/date_picker/DatePickerManager.java
+++ b/android/src/main/java/com/henninghall/date_picker/DatePickerManager.java
@@ -1,9 +1,6 @@
package com.henninghall.date_picker;
-import android.content.res.Resources;
-import android.util.Log;
-import android.util.TypedValue;
-
+import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
@@ -19,6 +16,8 @@ import java.util.TimeZone;
public class DatePickerManager extends SimpleViewManager {
public static final String REACT_CLASS = "DatePickerManager";
+ private static final int SCROLL = 1;
+
public static ThemedReactContext context;
private String date;
@@ -92,6 +91,21 @@ public class DatePickerManager extends SimpleViewManager {
if(index == 0) view.style.setHeight(style);
}
+ @Override
+ public Map getCommandsMap() {
+ return MapBuilder.of(
+ "scroll", SCROLL
+ );
+ }
+
+ public void receiveCommand(final PickerView view, int command, final ReadableArray args) {
+ if (command == SCROLL) {
+ int wheelIndex = args.getInt(0);
+ int scrollTimes = args.getInt(1);
+ view.scroll(wheelIndex, scrollTimes);
+ }
+ }
+
@Override
protected void onAfterUpdateTransaction(PickerView view) {
super.onAfterUpdateTransaction(view);
diff --git a/android/src/main/java/com/henninghall/date_picker/PickerView.java b/android/src/main/java/com/henninghall/date_picker/PickerView.java
index 32b9e20..7321e3d 100644
--- a/android/src/main/java/com/henninghall/date_picker/PickerView.java
+++ b/android/src/main/java/com/henninghall/date_picker/PickerView.java
@@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
-import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
@@ -130,7 +129,7 @@ public class PickerView extends RelativeLayout {
}
// Rounding cal to closest minute interval
- public Calendar getInitialDate() {
+ public Calendar getInitialDate() {
Calendar cal = Calendar.getInstance();
if(minuteInterval <= 1) return cal;
int exactMinute = Integer.valueOf(minutesWheel.format.format(cal.getTime()));
@@ -232,4 +231,15 @@ public class PickerView extends RelativeLayout {
return new SimpleDateFormat(getFormatPattern(), locale);
}
+ public void scroll(int wheelIndex, int scrollTimes) {
+ NumberPickerView picker = wheelOrder.getVisibleWheel(wheelIndex).picker;
+ int currentIndex = picker.getValue();
+ int maxValue = picker.getMaxValue();
+ boolean isWrapping = picker.getWrapSelectorWheel();
+ int nextValue = currentIndex + scrollTimes;
+ if(nextValue <= maxValue || isWrapping) {
+ picker.smoothScrollToValue(nextValue % (maxValue + 1));
+ }
+
+ }
}
diff --git a/android/src/main/java/com/henninghall/date_picker/Style.java b/android/src/main/java/com/henninghall/date_picker/Style.java
index 7d3a02f..89a9000 100644
--- a/android/src/main/java/com/henninghall/date_picker/Style.java
+++ b/android/src/main/java/com/henninghall/date_picker/Style.java
@@ -2,8 +2,6 @@ package com.henninghall.date_picker;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
-import android.view.View;
-import android.view.ViewGroup;
import android.widget.ImageView;
import com.henninghall.date_picker.wheelFunctions.SetShowCount;
import com.henninghall.date_picker.wheelFunctions.TextColor;
diff --git a/android/src/main/java/com/henninghall/date_picker/WheelOrder.java b/android/src/main/java/com/henninghall/date_picker/WheelOrder.java
index 29de3e1..fdf24a2 100644
--- a/android/src/main/java/com/henninghall/date_picker/WheelOrder.java
+++ b/android/src/main/java/com/henninghall/date_picker/WheelOrder.java
@@ -27,7 +27,7 @@ public class WheelOrder
}};
}
- private void updateValueWheels(final Locale locale) {
+ private void updateAllWheels(final Locale locale) {
try {
this.orderedWheels = getOrderedWheels(locale);
pickerView.wheelsWrapper.removeAllViews();
@@ -45,7 +45,7 @@ public class WheelOrder
void update(final Locale locale) {
- updateValueWheels(locale);
+ updateAllWheels(locale);
pickerView.emptyWheelUpdater.update();
}
diff --git a/android/src/main/res/layout/datepicker_view.xml b/android/src/main/res/layout/datepicker_view.xml
index 0d088d8..558068b 100644
--- a/android/src/main/res/layout/datepicker_view.xml
+++ b/android/src/main/res/layout/datepicker_view.xml
@@ -29,6 +29,7 @@
/>
{
await detox.init(config)
})
-beforeEach(async function() {
+beforeEach(async function () {
+ await reset()
await adapter.beforeEach(this)
})
-afterEach(async function() {
+afterEach(async function () {
await adapter.afterEach(this)
})
diff --git a/examples/detox/e2e/mocha.opts b/examples/detox/e2e/mocha.opts
index d75a815..32c9522 100644
--- a/examples/detox/e2e/mocha.opts
+++ b/examples/detox/e2e/mocha.opts
@@ -1,4 +1,4 @@
--recursive
--timeout 300000
---bail
--file e2e/init.js
+--slow 30000
diff --git a/examples/detox/e2e/tests/24h-mode/hourWheel.spec.js b/examples/detox/e2e/tests/24h-mode/hourWheel.spec.js
new file mode 100644
index 0000000..e1a86c0
--- /dev/null
+++ b/examples/detox/e2e/tests/24h-mode/hourWheel.spec.js
@@ -0,0 +1,28 @@
+const { scrollWheel, expectDate } = require("../../utils")
+
+
+describe('Hour wheel', () => {
+
+ before(async () => {
+ await device.reloadReactNative()
+ await element(by.text('Advanced')).tap()
+ })
+
+ it('should have 24 hours', async () => {
+ await scroll3HoursAndExpect("2000-01-01 03:00:00")
+ await scroll3HoursAndExpect("2000-01-01 06:00:00")
+ await scroll3HoursAndExpect("2000-01-01 09:00:00")
+ await scroll3HoursAndExpect("2000-01-01 12:00:00")
+ await scroll3HoursAndExpect("2000-01-01 15:00:00")
+ await scroll3HoursAndExpect("2000-01-01 18:00:00")
+ await scroll3HoursAndExpect("2000-01-01 21:00:00")
+ await scroll3HoursAndExpect("2000-01-01 00:00:00")
+ })
+
+ const scroll3HoursAndExpect = async (date) => {
+ await scrollWheel(1, 3)
+ await expectDate(date)
+ }
+
+})
+
diff --git a/examples/detox/e2e/tests/24h-mode/mode.spec.js b/examples/detox/e2e/tests/24h-mode/mode.spec.js
new file mode 100644
index 0000000..d22241d
--- /dev/null
+++ b/examples/detox/e2e/tests/24h-mode/mode.spec.js
@@ -0,0 +1,49 @@
+const { setMode } = require("../../utils")
+
+describe('Modes - 24h', () => {
+
+ before(async () => {
+ await device.reloadReactNative()
+ await element(by.text('Advanced')).tap()
+ })
+
+ it('datetime', async () => {
+ await setMode("datetime")
+
+ await expect(element(by.id('day'))).toBeVisible()
+ await expect(element(by.id('minutes'))).toBeVisible()
+ await expect(element(by.id('hour'))).toBeVisible()
+
+ await expect(element(by.id('ampm'))).toNotExist()
+ await expect(element(by.id('month'))).toNotExist()
+ await expect(element(by.id('date'))).toNotExist()
+ await expect(element(by.id('year'))).toNotExist()
+ })
+
+ it('date', async () => {
+ await setMode("date")
+
+ await expect(element(by.id('month'))).toBeVisible()
+ await expect(element(by.id('date'))).toBeVisible()
+ await expect(element(by.id('year'))).toBeVisible()
+
+ await expect(element(by.id('day'))).toNotExist()
+ await expect(element(by.id('minutes'))).toNotExist()
+ await expect(element(by.id('hour'))).toNotExist()
+ await expect(element(by.id('ampm'))).toNotExist()
+ })
+
+ it('time', async () => {
+ await setMode("time")
+
+ await expect(element(by.id('minutes'))).toBeVisible()
+ await expect(element(by.id('hour'))).toBeVisible()
+
+ await expect(element(by.id('ampm'))).toNotExist()
+ await expect(element(by.id('day'))).toNotExist()
+ await expect(element(by.id('month'))).toNotExist()
+ await expect(element(by.id('date'))).toNotExist()
+ await expect(element(by.id('year'))).toNotExist()
+ })
+
+})
diff --git a/examples/detox/e2e/tests/maximumDate.spec.js b/examples/detox/e2e/tests/maximumDate.spec.js
new file mode 100644
index 0000000..b307d0d
--- /dev/null
+++ b/examples/detox/e2e/tests/maximumDate.spec.js
@@ -0,0 +1,131 @@
+const { scrollWheel, expectDate, setMaximumDate, setMode } = require("../utils")
+
+const initialDate = new Date(2000, 0, 1, 0, 0);
+const secondOfJanuary = new Date(2000, 0, 2, 0, 0);
+const secondOfJanuary2001 = new Date(2001, 0, 2, 0, 0);
+
+describe('Maximum date', () => {
+
+ before(async () => {
+ await device.reloadReactNative()
+ await element(by.text('Advanced')).tap()
+ })
+
+ describe('cannot pass max date - datetime mode', () => {
+
+ before(async () => {
+ await setMode("datetime")
+ await setMaximumDate(initialDate)
+ })
+
+ it('day wheel', async () => {
+ await scrollWheel(0, 2)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ it('hour wheel', async () => {
+ await scrollWheel(1, 1)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ it('minute wheel', async () => {
+ await scrollWheel(2, 1)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ })
+
+ describe('cannot pass max date - date mode', () => {
+
+ before(async () => {
+ await setMode("date")
+ await setMaximumDate(initialDate)
+ })
+
+ it('month wheel', async () => {
+ await scrollWheel(0, 1)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ it('date wheel', async () => {
+ await scrollWheel(1, 1)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ it('year wheel', async () => {
+ await scrollWheel(2, 1)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ })
+
+
+ describe('overshooting max date', () => {
+
+ before(async () => {
+ await setMaximumDate(secondOfJanuary)
+ })
+
+ it('day wheel should not be possible to overshoot since it is not wrapping (no invalid dates exists)', async () => {
+ await setMode("datetime")
+ await scrollWheel(0, 1)
+ await expectDate("2000-01-02 00:00:00")
+ await scrollWheel(0, 1)
+ await expectDate("2000-01-02 00:00:00")
+ })
+
+ describe('date mode', () => {
+
+ before(async () => {
+ await setMode("date")
+ await setMaximumDate(secondOfJanuary)
+ })
+
+ it('overshooting month wheel should set all other wheels to maximum possible date', async () => {
+ await scrollWheel(0, 1)
+ await expectDate("2000-01-02 00:00:00")
+ })
+
+ it('overshooting date wheel should reverse to highest possible date', async () => {
+ await scrollWheel(1, 5)
+ await expectDate("2000-01-02 00:00:00")
+ })
+
+ it('overshooting year wheel should set all other wheels to maximum possible date', async () => {
+ await setMaximumDate(secondOfJanuary2001)
+ await scrollWheel(0, 1) // set month to feb
+ await scrollWheel(2, 1)
+ await expectDate("2001-01-02 00:00:00")
+ })
+
+ })
+
+ describe('time mode', () => {
+
+ before(async () => {
+ await setMode("time")
+ await setMaximumDate(initialDate)
+ })
+
+ it('overshooting hour wheel should reverse to highest possible time', async () => {
+ await scrollWheel(0, 5)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ it('overshooting minute wheel should reverse to highest possible time', async () => {
+ await scrollWheel(1, 5)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ it('overshooting am/pm wheel should reverse to highest possible time', async () => {
+ await scrollWheel(2, 1)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ })
+
+
+
+ })
+
+})
\ No newline at end of file
diff --git a/examples/detox/e2e/tests/minimumDate.spec.js b/examples/detox/e2e/tests/minimumDate.spec.js
new file mode 100644
index 0000000..d70f4b5
--- /dev/null
+++ b/examples/detox/e2e/tests/minimumDate.spec.js
@@ -0,0 +1,134 @@
+const { setDate, scrollWheel, expectDate, setMinimumDate, setMode } = require("../utils")
+
+const oneMinuteBeforeJanuary2 = new Date(2000, 0, 1, 23, 59, 0);
+
+describe('Minimum date', () => {
+
+ before(async () => {
+ await device.reloadReactNative()
+ await element(by.text('Advanced')).tap()
+ })
+
+ beforeEach(async () => {
+ await setDate(oneMinuteBeforeJanuary2.toISOString())
+ })
+
+ describe('cannot pass min date - mode: ', () => {
+
+ describe('datetime', () => {
+
+ before(async () => {
+ await setMode("datetime")
+ await setMinimumDate(oneMinuteBeforeJanuary2)
+ })
+
+ it('day wheel', async () => {
+ await scrollWheel(0, -2)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ it('hour wheel', async () => {
+ await scrollWheel(1, -1)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ it('minute wheel', async () => {
+ await scrollWheel(2, -1)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ })
+
+ describe('date', () => {
+
+ before(async () => {
+ await setMode("date")
+ await setMinimumDate(oneMinuteBeforeJanuary2)
+ })
+
+ it('month wheel', async () => {
+ await scrollWheel(0, -1)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ it('date wheel', async () => {
+ await scrollWheel(1, -1)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ it('year wheel', async () => {
+ await scrollWheel(2, -1)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ })
+ })
+
+
+ describe('overshooting min date - mode:', () => {
+
+ before(async () => {
+ await setMinimumDate(oneMinuteBeforeJanuary2)
+ })
+
+ describe('datetime', () => {
+
+ before(async () => {
+ await setMode("datetime")
+ })
+
+ it('day wheel should not be possible to overshoot since it is not wrapping (no invalid dates exists)', async () => {
+ await scrollWheel(0, -1)
+ await expectDate("2000-01-01 23:59:00")
+ await scrollWheel(0, -1)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ })
+
+ describe('date', () => {
+
+ before(async () => {
+ await setMode("date")
+ })
+
+ it('overshooting month wheel should set all other wheels to minimum possible date', async () => {
+ await scrollWheel(0, -1)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ it('overshooting date wheel should reverse to minimum possible date', async () => {
+ await scrollWheel(1, -5)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ })
+
+ describe('time mode', () => {
+
+ before(async () => {
+ await setMode("time")
+ await setMinimumDate(oneMinuteBeforeJanuary2)
+ })
+
+ it('overshooting hour wheel should reverse to minimum possible time', async () => {
+ await scrollWheel(0, -5)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ it('overshooting minute wheel should reverse to minimum possible time', async () => {
+ await scrollWheel(1, -5)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ it('overshooting am/pm wheel should reverse to minimum possible time', async () => {
+ await scrollWheel(2, -1)
+ await expectDate("2000-01-01 23:59:00")
+ })
+
+ })
+
+
+ })
+
+})
\ No newline at end of file
diff --git a/examples/detox/e2e/tests/minuteInterval.spec.js b/examples/detox/e2e/tests/minuteInterval.spec.js
new file mode 100644
index 0000000..fbbdb98
--- /dev/null
+++ b/examples/detox/e2e/tests/minuteInterval.spec.js
@@ -0,0 +1,34 @@
+const { scrollWheel, expectDate, setMinuteInterval } = require("../utils")
+
+const scrollMinuteWheel = () => scrollWheel(2, 1)
+
+describe('Minute interval', () => {
+
+ before(async () => {
+ await device.reloadReactNative()
+ await element(by.text('Advanced')).tap()
+ })
+
+ it('1 minute (default)', async () => {
+ await setMinuteInterval(1)
+ await scrollMinuteWheel()
+ await expectDate("2000-01-01 00:01:00")
+ })
+
+ it('5 minutes', async () => {
+ await setMinuteInterval(5)
+ await scrollMinuteWheel()
+ await expectDate("2000-01-01 00:05:00")
+ })
+
+ it('15 minutes', async () => {
+ await setMinuteInterval(15)
+ await scrollMinuteWheel()
+ await expectDate("2000-01-01 00:15:00")
+ await scrollMinuteWheel()
+ await expectDate("2000-01-01 00:30:00")
+ await scrollMinuteWheel()
+ await expectDate("2000-01-01 00:45:00")
+ })
+
+})
\ No newline at end of file
diff --git a/examples/detox/e2e/tests/mode.spec.js b/examples/detox/e2e/tests/mode.spec.js
new file mode 100644
index 0000000..9a95a21
--- /dev/null
+++ b/examples/detox/e2e/tests/mode.spec.js
@@ -0,0 +1,49 @@
+const { setMode } = require("../utils")
+
+describe('Modes', () => {
+
+ before(async () => {
+ await device.reloadReactNative()
+ await element(by.text('Advanced')).tap()
+ })
+
+ it('datetime', async () => {
+ await setMode("datetime")
+
+ await expect(element(by.id('day'))).toBeVisible()
+ await expect(element(by.id('minutes'))).toBeVisible()
+ await expect(element(by.id('hour'))).toBeVisible()
+ await expect(element(by.id('ampm'))).toBeVisible()
+
+ await expect(element(by.id('month'))).toNotExist()
+ await expect(element(by.id('date'))).toNotExist()
+ await expect(element(by.id('year'))).toNotExist()
+ })
+
+ it('date', async () => {
+ await setMode("date")
+
+ await expect(element(by.id('month'))).toBeVisible()
+ await expect(element(by.id('date'))).toBeVisible()
+ await expect(element(by.id('year'))).toBeVisible()
+
+ await expect(element(by.id('day'))).toNotExist()
+ await expect(element(by.id('minutes'))).toNotExist()
+ await expect(element(by.id('hour'))).toNotExist()
+ await expect(element(by.id('ampm'))).toNotExist()
+ })
+
+ it('time', async () => {
+ await setMode("time")
+
+ await expect(element(by.id('minutes'))).toBeVisible()
+ await expect(element(by.id('hour'))).toBeVisible()
+ await expect(element(by.id('ampm'))).toBeVisible()
+
+ await expect(element(by.id('day'))).toNotExist()
+ await expect(element(by.id('month'))).toNotExist()
+ await expect(element(by.id('date'))).toNotExist()
+ await expect(element(by.id('year'))).toNotExist()
+ })
+
+})
diff --git a/examples/detox/e2e/tests/scrollAround.spec.js b/examples/detox/e2e/tests/scrollAround.spec.js
new file mode 100644
index 0000000..629318e
--- /dev/null
+++ b/examples/detox/e2e/tests/scrollAround.spec.js
@@ -0,0 +1,45 @@
+const { scrollWheel, expectDate } = require("../utils")
+
+
+describe('Scroll around', () => {
+
+ before(async () => {
+ await device.reloadReactNative()
+ await element(by.text('Advanced')).tap()
+ })
+
+ it.skip('Hour wheel should scroll all way around and switch AM/PM when passing 12', async () => {
+ await scroll3HoursAndExpect("2000-01-01 03:00:00")
+ await scroll3HoursAndExpect("2000-01-01 06:00:00")
+ await scroll3HoursAndExpect("2000-01-01 09:00:00")
+ await scrollWheel(1, 2)
+ await expectDate("2000-01-01 11:00:00")
+ await scrollWheel(1, 2)
+ await expectDate("2000-01-01 13:00:00")
+ await scroll3HoursAndExpect("2000-01-01 16:00:00")
+ await scroll3HoursAndExpect("2000-01-01 19:00:00")
+ await scroll3HoursAndExpect("2000-01-01 22:00:00")
+ await scroll3HoursAndExpect("2000-01-01 01:00:00")
+ })
+
+ it('Minute wheel should be possible to scroll all way around', async () => {
+ await scrollWheel(2, 55)
+ await expectDate("2000-01-01 00:55:00")
+ await scrollWheel(2, 10)
+ await expectDate("2000-01-01 00:05:00")
+ })
+
+ it('Day wheel should change year when passing new year', async () => {
+ await scrollWheel(0, -1)
+ await expectDate("1999-12-31 00:00:00")
+ await scrollWheel(0, 1)
+ await expectDate("2000-01-01 00:00:00")
+ })
+
+ const scroll3HoursAndExpect = async (date) => {
+ await scrollWheel(1, 3)
+ await expectDate(date)
+ }
+
+})
+
diff --git a/examples/detox/e2e/tests/wheelOrder.spec.js b/examples/detox/e2e/tests/wheelOrder.spec.js
new file mode 100644
index 0000000..9a629d9
--- /dev/null
+++ b/examples/detox/e2e/tests/wheelOrder.spec.js
@@ -0,0 +1,95 @@
+const { scrollWheelWithIndexAndExpectDate, setMode, setLocale, setMaximumDate } = require("../utils")
+
+
+describe('Wheel order', () => {
+
+ before(async () => {
+ await device.reloadReactNative()
+ await element(by.text('Advanced')).tap()
+ await setMaximumDate("undefined")
+ })
+
+ describe('datetime', () => {
+
+ before(async () => {
+ await setMode("datetime")
+ })
+
+ it('US', async () => {
+ await setLocale("en-US")
+ await scrollWheelWithIndexAndExpectDate(0, "2000-01-02 00:00:00")
+ await scrollWheelWithIndexAndExpectDate(1, "2000-01-01 01:00:00")
+ await scrollWheelWithIndexAndExpectDate(2, "2000-01-01 00:01:00")
+ await scrollWheelWithIndexAndExpectDate(3, "2000-01-01 12:00:00")
+ })
+
+ it('Korean', async () => {
+ await setLocale("ko-KR")
+ await scrollWheelWithIndexAndExpectDate(0, "2000-01-02 00:00:00")
+ await scrollWheelWithIndexAndExpectDate(1, "2000-01-01 12:00:00")
+ await scrollWheelWithIndexAndExpectDate(2, "2000-01-01 01:00:00")
+ await scrollWheelWithIndexAndExpectDate(3, "2000-01-01 00:01:00")
+ })
+
+ })
+
+ describe('date', () => {
+
+ before(async () => {
+ await setMode("date")
+ })
+
+ it('US', async () => {
+ await setLocale("en-US")
+ await scrollWheelWithIndexAndExpectDate(0, "2000-02-01 00:00:00")
+ await scrollWheelWithIndexAndExpectDate(1, "2000-01-02 00:00:00")
+ await scrollWheelWithIndexAndExpectDate(2, "2001-01-01 00:00:00")
+ })
+
+ it('UK', async () => {
+ await setLocale("en-GB")
+ await scrollWheelWithIndexAndExpectDate(0, "2000-01-02 00:00:00")
+ await scrollWheelWithIndexAndExpectDate(1, "2000-02-01 00:00:00")
+ await scrollWheelWithIndexAndExpectDate(2, "2001-01-01 00:00:00")
+ })
+
+ it('Korean', async () => {
+ await setLocale("ko-KR")
+ await scrollWheelWithIndexAndExpectDate(0, "2001-01-01 00:00:00")
+ await scrollWheelWithIndexAndExpectDate(1, "2000-02-01 00:00:00")
+ await scrollWheelWithIndexAndExpectDate(2, "2000-01-02 00:00:00")
+ })
+
+ })
+
+ describe('time', () => {
+
+ before(async () => {
+ await setMode("time")
+ })
+
+ it('US', async () => {
+ await setLocale("en-US")
+ await scrollWheelWithIndexAndExpectDate(0, "2000-01-01 01:00:00")
+ await scrollWheelWithIndexAndExpectDate(1, "2000-01-01 00:01:00")
+ await scrollWheelWithIndexAndExpectDate(2, "2000-01-01 12:00:00")
+ })
+
+ it('UK', async () => {
+ await setLocale("en-GB")
+ await scrollWheelWithIndexAndExpectDate(0, "2000-01-01 01:00:00")
+ await scrollWheelWithIndexAndExpectDate(1, "2000-01-01 00:01:00")
+ await scrollWheelWithIndexAndExpectDate(2, "2000-01-01 12:00:00")
+ })
+
+ it('Korean', async () => {
+ await setLocale("ko-KR")
+ await scrollWheelWithIndexAndExpectDate(0, "2000-01-01 12:00:00")
+ await scrollWheelWithIndexAndExpectDate(1, "2000-01-01 01:00:00")
+ await scrollWheelWithIndexAndExpectDate(2, "2000-01-01 00:01:00")
+ })
+
+ })
+
+
+})
diff --git a/examples/detox/e2e/utils.js b/examples/detox/e2e/utils.js
new file mode 100644
index 0000000..a04120c
--- /dev/null
+++ b/examples/detox/e2e/utils.js
@@ -0,0 +1,39 @@
+
+const expectDate = async (date) => {
+ await expect(element(by.id('dateOutput'))).toHaveText(date)
+}
+
+const reset = async () => {
+ await element(by.id('props/scroll')).tap()
+ await element(by.id('reset')).tap()
+}
+
+const scrollWheel = async (index, times) => {
+ await element(by.id('props/scroll')).tap()
+ await element(by.id('wheelIndex')).replaceText(`${index}`)
+ await element(by.id('scrollTimes')).replaceText(`${times}`)
+ await element(by.id('doScroll')).tap()
+}
+
+const changeProp = name => async value => {
+ await element(by.id('propName')).replaceText(name)
+ await element(by.id('propValue')).replaceText(`${value}`)
+ await element(by.id('changeProp')).tap()
+}
+
+const scrollWheelWithIndexAndExpectDate = async (index, expectedDate) => {
+ await scrollWheel(index, 1)
+ await expectDate(expectedDate)
+ await reset()
+}
+
+exports.setDate = changeProp("date")
+exports.setLocale = changeProp("locale")
+exports.setMinimumDate = changeProp("minimumDate")
+exports.setMaximumDate = changeProp("maximumDate")
+exports.setMinuteInterval = changeProp("minuteInterval")
+exports.setMode = changeProp("mode")
+exports.scrollWheel = scrollWheel
+exports.expectDate = expectDate
+exports.scrollWheelWithIndexAndExpectDate = scrollWheelWithIndexAndExpectDate
+exports.reset = reset
diff --git a/examples/detox/index.android.js b/examples/detox/index.android.js
index f8b4649..5ac2ba1 100644
--- a/examples/detox/index.android.js
+++ b/examples/detox/index.android.js
@@ -1 +1 @@
-require('./app')
+require('./src/App')
diff --git a/examples/detox/index.js b/examples/detox/index.js
index f8b4649..5ac2ba1 100644
--- a/examples/detox/index.js
+++ b/examples/detox/index.js
@@ -1 +1 @@
-require('./app')
+require('./src/App')
diff --git a/examples/detox/ios/example.xcodeproj/project.pbxproj b/examples/detox/ios/example.xcodeproj/project.pbxproj
index 310a1e2..e37ab5d 100644
--- a/examples/detox/ios/example.xcodeproj/project.pbxproj
+++ b/examples/detox/ios/example.xcodeproj/project.pbxproj
@@ -5,6 +5,7 @@
};
objectVersion = 46;
objects = {
+
/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@@ -20,8 +21,8 @@
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; };
3953CBBA229AA464005DD98C /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3953CBB9229AA464005DD98C /* JavaScriptCore.framework */; };
- 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
3A75CD301B1543908E566531 /* libRNDatePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A630BBD6C5F543E09245F95D /* libRNDatePicker.a */; };
+ 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -308,8 +309,8 @@
46EE185A2047463E00FAAB0E /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = ""; };
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; };
- E9E57B22E2C74DE5846A9803 /* RNDatePicker.xcodeproj */ = {isa = PBXFileReference; name = "RNDatePicker.xcodeproj"; path = "../node_modules/react-native-date-picker/ios/RNDatePicker.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
- A630BBD6C5F543E09245F95D /* libRNDatePicker.a */ = {isa = PBXFileReference; name = "libRNDatePicker.a"; path = "libRNDatePicker.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
+ A630BBD6C5F543E09245F95D /* libRNDatePicker.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNDatePicker.a; sourceTree = ""; };
+ E9E57B22E2C74DE5846A9803 /* RNDatePicker.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNDatePicker.xcodeproj; path = "../node_modules/react-native-date-picker/ios/RNDatePicker.xcodeproj"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -460,6 +461,14 @@
name = Products;
sourceTree = "";
};
+ 5B8247D323EAED56002A0C87 /* Recovered References */ = {
+ isa = PBXGroup;
+ children = (
+ A630BBD6C5F543E09245F95D /* libRNDatePicker.a */,
+ );
+ name = "Recovered References";
+ sourceTree = "";
+ };
78C398B11ACF4ADC00677621 /* Products */ = {
isa = PBXGroup;
children = (
@@ -505,6 +514,7 @@
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* exampleTests */,
83CBBA001A601CBA00E9B192 /* Products */,
+ 5B8247D323EAED56002A0C87 /* Recovered References */,
);
indentWidth = 2;
sourceTree = "";
@@ -944,23 +954,23 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEAD_CODE_STRIPPING = NO;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../node_modules/react-native-date-picker/ios/RNDatePicker",
+ );
INFOPLIST_FILE = example/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/example\"",
+ );
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.wix.demo.react.native;
PRODUCT_NAME = example;
- LIBRARY_SEARCH_PATHS = (
- "$(inherited)",
- "\"$(SRCROOT)/example\"",
- );
- HEADER_SEARCH_PATHS = (
- "$(inherited)",
- "$(SRCROOT)/../node_modules/react-native-date-picker/ios/RNDatePicker",
- );
};
name = Debug;
};
@@ -968,23 +978,23 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../node_modules/react-native-date-picker/ios/RNDatePicker",
+ );
INFOPLIST_FILE = example/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/example\"",
+ );
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.wix.demo.react.native;
PRODUCT_NAME = example;
- LIBRARY_SEARCH_PATHS = (
- "$(inherited)",
- "\"$(SRCROOT)/example\"",
- );
- HEADER_SEARCH_PATHS = (
- "$(inherited)",
- "$(SRCROOT)/../node_modules/react-native-date-picker/ios/RNDatePicker",
- );
};
name = Release;
};
diff --git a/examples/detox/metro.config.js b/examples/detox/metro.config.js
new file mode 100644
index 0000000..1c7ded2
--- /dev/null
+++ b/examples/detox/metro.config.js
@@ -0,0 +1,29 @@
+/**
+ * Metro configuration for React Native
+ * https://github.com/facebook/react-native
+ *
+ * @format
+ */
+
+const path = require('path')
+const blacklist = require('metro-config/src/defaults/blacklist')
+
+const reactNativeLib = path.resolve(__dirname, '../..')
+
+module.exports = {
+ watchFolders: [path.resolve(__dirname, 'node_modules'), reactNativeLib],
+ resolver: {
+ blacklistRE: blacklist([
+ new RegExp(`${reactNativeLib}/node_modules/react-native/.*`),
+ ]),
+ },
+ transformer: {
+ getTransformOptions: async () => ({
+ transform: {
+ experimentalImportSupport: false,
+ inlineRequires: false,
+ },
+ }),
+ },
+ maxWorkers: 2,
+}
diff --git a/examples/detox/package.json b/examples/detox/package.json
index 753fea9..ce67ea7 100644
--- a/examples/detox/package.json
+++ b/examples/detox/package.json
@@ -4,21 +4,23 @@
"private": true,
"scripts": {
"start": "react-native start",
- "build:ios": "detox build --configuration ios.sim.debug",
- "build:android-debug": "detox build --configuration android.emu.debug",
- "build:android-release": "detox build --configuration android.emu.release",
- "test:ios": "detox test --configuration ios.sim.debug",
- "test:android-debug": "detox test --configuration android.emu.debug -l verbose",
- "test:android-release": "detox test --configuration android.emu.release -l verbose",
- "test:android-explicit-require": "detox test e2eExplicitRequire -c android.emu.release -l verbose --runner-config e2eExplicitRequire/mocha.opts",
- "e2e:ios": "npm run build:ios && npm run test:ios",
- "e2e:android-debug": "npm run build:android-debug && npm run test:android-debug",
- "e2e:android-release": "npm run build:android-release && npm run test:android-release"
+ "postinstall": "yarn make-example-runnable",
+ "make-example-runnable": "(cd ../../ && npm i react react-native --no-save)",
+ "build:ios": "detox build --configuration ios.debug",
+ "build:android-debug": "detox build --configuration android.debug",
+ "build:android-ci": "detox build --configuration android.ci",
+ "test:ios": "detox test --configuration ios.debug",
+ "set-time-format": "adb shell settings put system time_12_24",
+ "test:android-24h-mode": "yarn set-time-format 24 && detox test --configuration android.debug e2e/tests/24h-mode/*.spec.js",
+ "test:android-24h-mode-ci": "yarn set-time-format 24 && detox test --configuration android.ci e2e/tests/24h-mode/*.spec.js --record-logs failing --record-videos failing --take-screenshots failing",
+ "test:android-12h-mode": "yarn set-time-format 12 && detox test --configuration android.debug e2e/tests/*.spec.js",
+ "test:android-12h-mode-ci": "yarn set-time-format 12 && detox test --configuration android.ci e2e/tests/*.spec.js --record-logs failing --record-videos failing --take-screenshots failing",
+ "test:android": "yarn test:android-12h-mode && yarn test:android-24h-mode",
+ "test:android-ci": "yarn test:android-12h-mode-ci && yarn test:android-24h-mode-ci"
},
"dependencies": {
"react": "16.8.3",
- "react-native": "0.59.9",
- "react-native-date-picker": "^2.6.1"
+ "react-native": "0.59.9"
},
"devDependencies": {
"detox": "^14.0.1",
@@ -28,40 +30,30 @@
"test-runner": "mocha",
"runner-config": "e2e/mocha.opts",
"configurations": {
- "ios.sim.release": {
+ "ios.release": {
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/example.app",
"build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -project ios/example.xcodeproj -UseNewBuildSystem=NO -scheme example -configuration Release -sdk iphonesimulator -derivedDataPath ios/build -quiet",
"type": "ios.simulator",
"name": "iPhone X"
},
- "ios.sim.debug": {
+ "ios.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/example.app",
"build": "xcodebuild -project ios/example.xcodeproj -UseNewBuildSystem=NO -scheme example -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone X"
},
- "ios.none": {
- "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/example.app",
- "build": "xcodebuild -project ios/example.xcodeproj -UseNewBuildSystem=NO -scheme example -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
- "type": "ios.none",
- "name": "iPhone X",
- "session": {
- "server": "ws://localhost:8099",
- "sessionId": "com.wix.demo.react.native"
- }
- },
- "android.emu.debug": {
+ "android.ci": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd android ; ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug ; cd -",
"type": "android.emulator",
- "name": "Nexus_4_API_28"
+ "name": "emu"
},
- "android.emu.release": {
- "binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
- "build": "cd android ; ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release ; cd -",
+ "android.debug": {
+ "binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
+ "build": "cd android ; ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug ; cd -",
"type": "android.emulator",
- "name": "Nexus_4_API_28"
+ "name": "Pixel_2_API_29"
}
}
}
-}
+}
\ No newline at end of file
diff --git a/examples/detox/src/App.js b/examples/detox/src/App.js
new file mode 100644
index 0000000..1ca2119
--- /dev/null
+++ b/examples/detox/src/App.js
@@ -0,0 +1,84 @@
+import React, { Component } from 'react'
+import { ScrollView, AppRegistry, StyleSheet, Text, TouchableOpacity } from 'react-native'
+import examples from './examples'
+
+class App extends Component {
+ state = {
+ picker: undefined,
+ backgroundColor: '#ffffff',
+ }
+
+ render() {
+ return (
+
+ {!this.state.picker && 'Examples'}
+ {!this.state.picker && this.renderButtons()}
+ {!!this.state.picker && this.renderBackButton()}
+ {!!this.state.picker && this.renderPicker()}
+
+ )
+ }
+
+ setBackgroundColor = backgroundColor => this.setState({ backgroundColor })
+
+ renderPicker = () => {
+ const Picker = examples[this.state.picker].component
+ return (
+
+ )
+ }
+
+ renderButtons = () =>
+ Object.keys(examples)
+ .filter(key => key !== this.state.picker)
+ .map(this.renderButton)
+
+ renderButton = key => (
+ this.setState({ picker: key })}
+ style={{ margin: 10 }}
+ >
+ {examples[key].buttonTitle}
+
+ )
+
+ renderBackButton = key => (
+ this.setState({ picker: undefined })}
+ style={{ margin: 10, position: 'absolute', top: 0, left: 10 }}
+ >
+ Back
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ paddingTop: 15,
+ borderWidth: 1,
+ },
+ content: {
+ alignItems: 'center',
+ },
+ text: {
+ color: 'dodgerblue',
+ fontSize: 16,
+ },
+ header: {
+ color: 'black',
+ fontSize: 22,
+ margin: 20,
+ },
+})
+
+AppRegistry.registerComponent('example', () => App)
\ No newline at end of file
diff --git a/examples/detox/src/CustomPropValue.js b/examples/detox/src/CustomPropValue.js
new file mode 100644
index 0000000..0309286
--- /dev/null
+++ b/examples/detox/src/CustomPropValue.js
@@ -0,0 +1,49 @@
+import React, { useState } from 'react'
+import { Button, TextInput, Text, View, TouchableOpacity } from 'react-native'
+
+export default function CustomPropValue(props) {
+ const [propName, setPropName] = useState("")
+ const [propValue, setPropValue] = useState("")
+
+ const getPropValue = () => {
+ if (propValue === "undefined") return undefined
+ if (propName === "minuteInterval") return parseInt(propValue)
+ if (["date", "maximumDate", "minimumDate"].includes(propName)) return new Date(propValue)
+ return propValue
+ }
+
+ return (
+
+
+ Prop name
+
+ Prop value
+
+
+ props.changeProp({ propName, propValue: getPropValue() })}
+ >Change
+
+ )
+}
+
+const input = {
+ height: 30,
+ borderColor: 'gray',
+ borderWidth: 0.5,
+ margin: 2,
+ padding: 0,
+ alignItems: "center",
+ textAlign: "center"
+}
\ No newline at end of file
diff --git a/examples/detox/src/PropButton.js b/examples/detox/src/PropButton.js
new file mode 100644
index 0000000..2822ed8
--- /dev/null
+++ b/examples/detox/src/PropButton.js
@@ -0,0 +1,6 @@
+import React, { Component } from 'react'
+import { Button } from 'react-native'
+
+export const PropButton = ({ title, value, onChange }) => (
+