Skip to content

Commit

Permalink
Fix setup on a null appuserid and adds listener removal methods (#39)
Browse files Browse the repository at this point in the history
* fixes null appuserid

* adds removelisteners methods

* PR comments

* Jest and some sample tests (#41)

* add tests

* test

* makes tests its own job

* removes setup job and changes setup script

* changes remove listener methods to receive an instance

* removes running tests in the ios flow

* fixes tests

* adds tests for other listeners

* PR comments

* updates android image
  • Loading branch information
vegaro authored Jan 23, 2019
1 parent 4b1864f commit 1d2fbb9
Show file tree
Hide file tree
Showing 11 changed files with 6,445 additions and 433 deletions.
118 changes: 62 additions & 56 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,68 +1,74 @@
version: 2
jobs:
setup:
working_directory: ~/react-native-purchases
docker:
- image: circleci/node:8
steps:
- checkout
- run: yarn run setup
android:
working_directory: ~/react-native-purchases
docker:
- image: circleci/android:api-27-node8-alpha
steps:
- checkout
- run:
name: yarn run setup
command: yarn run setup
- run:
name: Android compiles
command: ./gradlew build
working_directory: example/android
ios:
macos:
xcode: "9.0"
working_directory: ~/react-native-purchases
# use a --login shell so our "set Ruby version" command gets picked up for later steps
shell: /bin/bash --login -o pipefail
steps:
- checkout
- run:
name: set Ruby version
command: echo "ruby-2.4" > ~/.ruby-version
- run: yarn run setup
- restore_cache:
key: bundle-v1-{{ checksum "example/ios/Gemfile.lock" }}-{{ arch }}
- run:
command: bundle install
working_directory: example/ios
tests:
working_directory: ~/react-native-purchases
docker:
- image: circleci/node:8
steps:
- checkout
- run: yarn
- run:
name: Tests
command: yarn test
android:
working_directory: ~/react-native-purchases
docker:
- image: circleci/android:api-28-node8-alpha
steps:
- checkout
- run:
name: yarn run setup.example
command: yarn run setup.example
- run:
name: Android compiles
command: ./gradlew build
working_directory: example/android
ios:
macos:
xcode: "9.0"
working_directory: ~/react-native-purchases
# use a --login shell so our "set Ruby version" command gets picked up for later steps
shell: /bin/bash --login -o pipefail
steps:
- checkout
- run:
name: set Ruby version
command: echo "ruby-2.4" > ~/.ruby-version
- run: yarn run setup.example
- restore_cache:
key: bundle-v1-{{ checksum "example/ios/Gemfile.lock" }}-{{ arch }}
- run:
command: bundle install
working_directory: example/ios

- save_cache:
key: bundle-v1-{{ checksum "example/ios/Gemfile.lock" }}-{{ arch }}
paths:
- vendor/bundle
- save_cache:
key: bundle-v1-{{ checksum "example/ios/Gemfile.lock" }}-{{ arch }}
paths:
- vendor/bundle

- run:
command: bundle exec fastlane scan --scheme ReactNativeSample
working_directory: example/ios
- run:
command: bundle exec fastlane scan --scheme ReactNativeSample
working_directory: example/ios

- run:
name: set up test results
working_directory: example/ios
when: always
command: |
mkdir -p test-results/fastlane test-results/xcode
mv fastlane/report.xml test-results/fastlane
mv fastlane/test_output/report.junit test-results/xcode/junit.xml
- store_test_results:
path: example/ios/test-results
- run:
name: set up test results
working_directory: example/ios
when: always
command: |
mkdir -p test-results/fastlane test-results/xcode
mv fastlane/report.xml test-results/fastlane
mv fastlane/test_output/report.junit test-results/xcode/junit.xml
- store_test_results:
path: example/ios/test-results

- store_artifacts:
path: example/ios/test-results
- store_artifacts:
path: example/ios/test-results
workflows:
version: 2
node-android-ios:
jobs:
- android
- ios
tests:
jobs:
- tests
10 changes: 7 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
node_modules/
npm-debug.log
yarn-error.log


# Xcode
#
Expand All @@ -29,7 +29,7 @@ DerivedData
*.ipa
*.xcuserstate
project.xcworkspace


# Android/IntelliJ
#
Expand All @@ -43,4 +43,8 @@ local.properties
buck-out/
\.buckd/
*.keystore


# Jest
.jest/
android/.settings/org.eclipse.buildship.core.prefs
example/android/.settings/org.eclipse.buildship.core.prefs
139 changes: 139 additions & 0 deletions __tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
describe("Purchases", () => {
beforeEach(() => {
jest.resetAllMocks();
jest.mock("NativeEventEmitter");
});

const purchaserInfoStub = {"allExpirationDates":{"onetime_purchase":null,"consumable":null,"annual_freetrial":"2019-01-23T22:34:21Z","onemonth_freetrial":"2019-01-19T01:41:06Z"},"activeSubscriptions":["annual_freetrial"],"expirationsForActiveEntitlements":{"pro":null},"activeEntitlements":["pro"],"allPurchasedProductIdentifiers":["onetime_purchase","consumable","annual_freetrial","onemonth_freetrial"],"latestExpirationDate":"2019-01-23T22:34:21Z"};

it("isUTCDateStringFuture returns true when a date is in the future", () => {
const { isUTCDateStringFuture } = require("../index");
const dateAhead = new Date();
dateAhead.setDate(dateAhead.getDate() + 2);

expect(isUTCDateStringFuture(dateAhead.toUTCString())).toEqual(true);
});

it("addPurchaseListener correctly saves listeners", () => {
const listener = jest.fn();
const Purchases = require("../index").default;

Purchases.addPurchaseListener(listener);

const nativeEmitter = new NativeEventEmitter();

const eventInfo = {
productIdentifier: "test.product.bla",
purchaserInfo: purchaserInfoStub,
error: null,
};

nativeEmitter.emit("Purchases-PurchaseCompleted", eventInfo);

expect(listener).toEqual(expect.any(Function));
expect(listener).toHaveBeenCalledWith(
eventInfo.productIdentifier,
eventInfo.purchaserInfo,
eventInfo.error
);
});

it("removePurchaseListener correctly removes a listener", () => {
const Purchases = require("../index").default;
const listener = jest.fn();
Purchases.addPurchaseListener(listener);
Purchases.removePurchaseListener(listener);

const nativeEmitter = new NativeEventEmitter();

const eventInfo = {
productIdentifier: "test.product.bla",
purchaserInfo: purchaserInfoStub,
error: null,
};

nativeEmitter.emit("Purchases-PurchaseCompleted", eventInfo);

expect(listener).toHaveBeenCalledTimes(0);
});

it("addRestoreTransactionsListener correctly saves listeners", () => {
const listener = jest.fn();
const Purchases = require("../index").default;

Purchases.addRestoreTransactionsListener(listener);

const nativeEmitter = new NativeEventEmitter();

const eventInfo = {
purchaserInfo: purchaserInfoStub,
error: null,
};

nativeEmitter.emit("Purchases-RestoredTransactions", eventInfo);

expect(listener).toEqual(expect.any(Function));
expect(listener).toHaveBeenCalledWith(
eventInfo.purchaserInfo,
eventInfo.error
);
});

it("removeRestoreTransactionsListener correctly removes a listener", () => {
const Purchases = require("../index").default;
const listener = jest.fn();
Purchases.addRestoreTransactionsListener(listener);
Purchases.removeRestoreTransactionsListener(listener);

const nativeEmitter = new NativeEventEmitter();

const eventInfo = {
purchaserInfo: purchaserInfoStub,
error: null,
};

nativeEmitter.emit("Purchases-RestoredTransactions", eventInfo);

expect(listener).toHaveBeenCalledTimes(0);
});

it("addPurchaserInfoUpdateListener correctly saves listeners", () => {
const listener = jest.fn();
const Purchases = require("../index").default;

Purchases.addPurchaserInfoUpdateListener(listener);

const nativeEmitter = new NativeEventEmitter();

const eventInfo = {
purchaserInfo: purchaserInfoStub,
error: null,
};

nativeEmitter.emit("Purchases-PurchaserInfoUpdated", eventInfo);

expect(listener).toEqual(expect.any(Function));
expect(listener).toHaveBeenCalledWith(
eventInfo.purchaserInfo,
eventInfo.error
);
});

it("removePurchaserInfoUpdateListener correctly removes a listener", () => {
const Purchases = require("../index").default;
const listener = jest.fn();
Purchases.addPurchaserInfoUpdateListener(listener);
Purchases.removePurchaserInfoUpdateListener(listener);

const nativeEmitter = new NativeEventEmitter();

const eventInfo = {
purchaserInfo: purchaserInfoStub,
error: null,
};

nativeEmitter.emit("Purchases-PurchaserInfoUpdated", eventInfo);

expect(listener).toHaveBeenCalledTimes(0);
});
});
8 changes: 6 additions & 2 deletions android/src/main/java/com/reactlibrary/RNPurchasesModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,15 @@ public void onCatalystInstanceDestroy() {
}

@ReactMethod
public void setupPurchases(String apiKey, String appUserID, final Promise promise) {
public void setupPurchases(String apiKey, @Nullable String appUserID, final Promise promise) {
if (Purchases.getSharedInstance() != null) {
Purchases.getSharedInstance().close();
}
Purchases purchases = new Purchases.Builder(reactContext, apiKey).appUserID(appUserID).build();
Purchases.Builder builder = new Purchases.Builder(reactContext, apiKey);
if (appUserID != null) {
builder.appUserID(appUserID);
}
Purchases purchases = builder.build();
purchases.setListener(this);
Purchases.setSharedInstance(purchases);
promise.resolve(null);
Expand Down
51 changes: 33 additions & 18 deletions example/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,44 +24,59 @@ export default class App extends Component {
proMonthlyPrice: "Loading",
currentID: ""
};
Purchases.addPurchaseListener((productIdentifier, purchaserInfo, error) => {
if (error && !error.userCancelled) {
this.setState({error: error.message});
return;
}

this.handlePurchaserInfo(purchaserInfo);
});

Purchases.addPurchaserInfoUpdateListener((purchaserInfo, error) => {
if (purchaserInfo) {
this.handlePurchaserInfo(purchaserInfo);
}
});
}

Purchases.addRestoreTransactionsListener((purchaserInfo, error) => {
if (purchaserInfo) {
setListeners = () => {
const listeners = {
purchaseListener: (productIdentifier, purchaserInfo, error) => {
if (error && !error.userCancelled) {
this.setState({error: error.message});
return;
}
this.handlePurchaserInfo(purchaserInfo);
},
purchaserInfoUpdatedListener: (purchaserInfo, error) => {
if (purchaserInfo) {
this.handlePurchaserInfo(purchaserInfo);
}
},
restoreTransactionsListener: (purchaserInfo, error) => {
if (purchaserInfo) {
this.handlePurchaserInfo(purchaserInfo);
}
}
});
}
Purchases.addPurchaseListener(listeners.purchaseListener);
Purchases.addPurchaserInfoUpdateListener(listeners.purchaserInfoUpdatedListener);
Purchases.addRestoreTransactionsListener(listeners.restoreTransactionsListener);

return listeners;
}

async componentDidMount() {
try {
let purchases = await Purchases.setup("LQmxAoIaaQaHpPiWJJayypBDhIpAZCZN", "purchases_sample_id_4");
let entitlements = await Purchases.getEntitlements();
let appUserID = await Purchases.getAppUserID();
const listeners = this.setListeners();
this.setState({
entitlements,
proAnnualPrice: "Buy Annual w/ Trial " + entitlements.pro.annual.price_string,
proMonthlyPrice: "Buy Monthly w/ Trial " + entitlements.pro.monthly.price_string,
currentID: appUserID
currentID: appUserID,
listeners: listeners
});
} catch (e) {
this.setState({error: "Error " + e})
}
};

componentWillUnmount() {
Purchases.removePurchaseListener(this.state.listeners.purchaseListener);
Purchases.removePurchaserInfoUpdateListener(this.state.listeners.purchaserInfoUpdatedListener);
Purchases.removeRestoreTransactionsListener(this.state.listeners.restoreTransactionsListener);
}

updateID = async() => {
const currentID = await Purchases.getAppUserID();
this.setState({currentID})
Expand Down
Loading

0 comments on commit 1d2fbb9

Please sign in to comment.