diff --git a/.bazelrc b/.bazelrc index 6ae7618e0..2fdcfafd7 100644 --- a/.bazelrc +++ b/.bazelrc @@ -18,6 +18,7 @@ build --android_databinding_use_v3_4_args build --android_databinding_use_androidx build --experimental_google_legacy_api query --experimental_google_legacy_api +sync --experimental_google_legacy_api # Android demo app flags build --noincremental_dexing --fat_apk_cpu=armeabi-v7a,arm64-v8a,x86,x86_64 diff --git a/.circleci/config.yml b/.circleci/config.yml index 82ba741c9..376cb220d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -16,7 +16,7 @@ orbs: executors: base: docker: - - image: docker.io/playerui/bazel-docker + - image: docker.io/playerui/bazel-docker:7 working_directory: ~/player resource_class: xlarge environment: @@ -53,7 +53,9 @@ commands: - run: mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - - run: echo -e $GPG_KEY | gpg --import --batch + - run: | + echo -e $GPG_KEY | gpg --import --batch + echo -e "pinentry-mode loopback\npassphrase $DEPLOY_MAVEN_GPG_PASSPHRASE" > ~/.gnupg/gpg.conf - run: | source ~/.bashrc bundle install @@ -209,7 +211,20 @@ jobs: command: | circle-android wait-for-boot - - run: bazel test --define=APPLITOOLS_API_KEY=${APPLITOOLS_API_KEY} --define=APPLITOOLS_BATCH_ID=${CIRCLE_SHA1} --define=APPLITOOLS_PR_NUMBER=${CIRCLE_PULL_REQUEST##*/} --config=ci -- //android/demo:android_instrumentation_test + - run: bazel test --config=ci -- //android/demo:android_instrumentation_test + + - run: + when: always + command: | + RESULTS_DIR=_test_results + find -L ./bazel-testlogs -name test.xml | while read line + do + mkdir -p $RESULTS_DIR/$(dirname $line) + cp $line $RESULTS_DIR/$(dirname $line) + done + + - store_test_results: + path: _test_results - store_artifacts: path: screenshots @@ -229,7 +244,7 @@ jobs: # union the bundle targets until //... doesnt explode analyzing jvm/android targets - run: | BUNDLE_TARGETS=$(bazel query "kind(js_test, //plugins/...) union kind(js_test, //react/...) union kind(js_test, //core/...)" --output label 2>/dev/null | tr '\n' ' ') - bazel coverage --combined_report=lcov --config=ci -- $BUNDLE_TARGETS + bazel coverage --combined_report=lcov --config=ci -- $BUNDLE_TARGETS -//plugins/reference-assets/mocks:all_flows_test_binary - run: when: always @@ -264,20 +279,6 @@ jobs: steps: - auto_shipit - applitools_init: - executor: base - steps: - - run: - name: Initialize Applitools - command: curl -L -d '' -X POST "$APPLITOOLS_SERVER_URL/api/externals/github/push?apiKey=$APPLITOOLS_API_KEY&CommitSha=$CIRCLE_SHA1&BranchName=$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/$CIRCLE_BRANCH" - - applitools_cleanup: - executor: base - steps: - - run: - name: Cleanup Applitools - command: "curl -X POST \"$APPLITOOLS_SERVER_URL/api/externals/github/servers/github.com/commit/$CIRCLE_SHA1/complete?apiKey=$APPLITOOLS_API_KEY\" -H Content-Type:application/json --data '{\"serverId\": \"github.com\", \"commitSha\": \"$CIRCLE_SHA1\"}' -v -L" - workflows: build_and_test_pr: jobs: @@ -299,16 +300,6 @@ workflows: requires: - setup - - applitools_init: - filters: - branches: - ignore: - - /pull\/.*/ - context: - - applitools - requires: - - bazelrc - - build: name: build-trunk filters: @@ -333,8 +324,6 @@ workflows: branches: ignore: - /pull\/.*/ - context: - - applitools requires: - bazelrc @@ -364,10 +353,7 @@ workflows: branches: ignore: - /pull\/.*/ - context: - - applitools requires: - - applitools_init - build-trunk - android_test: @@ -399,17 +385,6 @@ workflows: - build-fork - build-ios-fork - - applitools_cleanup: - filters: - branches: - ignore: - - /pull\/.*/ - context: - - applitools - requires: - - android-test-trunk - - build-ios-trunk - build_and_test_main: when: equal: [ "", << pipeline.parameters.GHA_Action >> ] @@ -426,14 +401,10 @@ workflows: - setup - build_ios: - context: - - applitools requires: - setup - android_test: - context: - - applitools requires: - build @@ -461,14 +432,10 @@ workflows: - setup - build_ios: - context: - - applitools requires: - setup - android_test: - context: - - applitools requires: - build diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 494a4e213..000000000 --- a/.editorconfig +++ /dev/null @@ -1,2 +0,0 @@ -[*.{kt,kts}] -disabled_rules=no-wildcard-imports \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index fb7ed35a5..c2792efdb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -31,4 +31,17 @@ Indicate the type of change your pull request is: ```typescript const plugin = new Plugin(...) ``` +--> + + +### Does your PR have any documentation updates? +- [ ] Updated docs +- [ ] No Update needed +- [ ] Unable to update docs + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 07eddaaee..1e5ae77e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,383 @@ +# 0.7.2 (Wed Apr 10 2024) + +### Release Notes + +#### Update Player Tools Version ([#334](https://github.com/player-ui/player/pull/334)) + +- Update Player Tools to latest + + +### Does your PR have any documentation updates? +- [ ] Updated docs +- [x] No Update needed +- [ ] Unable to update docs + +#### Version Selector Fixes ([#330](https://github.com/player-ui/player/pull/330)) + +Docs - Fix version selector not working and preserve route when changing versions + +#### [Docs] Update the DSL Benefits in Schema Section ([#326](https://github.com/player-ui/player/pull/326)) + +Docs - Update DSL Schema benefits section + +### Does your PR have any documentation updates? +- [x] Updated docs +- [ ] No Update needed +- [ ] Unable to update docs + +#### Expose More Information About Expression Parsing Errors ([#328](https://github.com/player-ui/player/pull/328)) + +Types - Expose types/utilities around expression parsing errors + +### Does your PR have any documentation updates? +- [ ] Updated docs +- [x] No Update needed +- [ ] Unable to update docs + +#### Fix `commaNumber` Formatting when Using a Precision of 0 ([#319](https://github.com/player-ui/player/pull/319)) + +Common Types Plugin - Fix `commaNumber` Formatting when Using a Precision of 0 + +#### Expression Parser Strictness ([#315](https://github.com/player-ui/player/pull/315)) + +Expose Expression Parser's strictness option via the `resolveOptions` hook + +--- + +#### 🐛 Bug Fix + +- Release main [#335](https://github.com/player-ui/player/pull/335) ([@intuit-svc](https://github.com/intuit-svc)) +- Update Player Tools Version [#334](https://github.com/player-ui/player/pull/334) ([@KetanReddy](https://github.com/KetanReddy)) +- Version Selector Fixes [#330](https://github.com/player-ui/player/pull/330) ([@KetanReddy](https://github.com/KetanReddy)) +- Move managed player mock flows to shared reference asset mocks [#217](https://github.com/player-ui/player/pull/217) (nancy_wu1@intuit.com [@nancywu1](https://github.com/nancywu1)) +- Android: Preserve old rendering path for non-suspendable assets [#314](https://github.com/player-ui/player/pull/314) ([@sugarmanz](https://github.com/sugarmanz) [@brocollie08](https://github.com/brocollie08)) +- Expose More Information About Expression Parsing Errors [#328](https://github.com/player-ui/player/pull/328) ([@KetanReddy](https://github.com/KetanReddy)) +- update iOS contributing guide [#323](https://github.com/player-ui/player/pull/323) ([@hborawski](https://github.com/hborawski)) +- update rules_player to latest 0.12.0 [#322](https://github.com/player-ui/player/pull/322) ([@hborawski](https://github.com/hborawski) [@brocollie08](https://github.com/brocollie08)) +- Fix `commaNumber` Formatting when Using a Precision of 0 [#319](https://github.com/player-ui/player/pull/319) ([@KetanReddy](https://github.com/KetanReddy)) +- Expression Parser Strictness [#315](https://github.com/player-ui/player/pull/315) ([@KetanReddy](https://github.com/KetanReddy)) +- Common Types Plugin restoring old dataRefs [#302](https://github.com/player-ui/player/pull/302) (alejandro_fimbres@intuit.com [@lexfm](https://github.com/lexfm) [@KetanReddy](https://github.com/KetanReddy)) + +#### 📝 Documentation + +- [Docs] Update the DSL Benefits in Schema Section [#326](https://github.com/player-ui/player/pull/326) ([@KetanReddy](https://github.com/KetanReddy)) +- refactor nav docs slightly to better call out onEnd expressions [#321](https://github.com/player-ui/player/pull/321) ([@hborawski](https://github.com/hborawski)) +- PR Checklist update [#309](https://github.com/player-ui/player/pull/309) (marlon_ercillo@intuit.com [@mercillo](https://github.com/mercillo)) +- add syntax examples for default expressions [#317](https://github.com/player-ui/player/pull/317) ([@hborawski](https://github.com/hborawski)) + +#### Authors: 11 + +- [@brocollie08](https://github.com/brocollie08) +- [@intuit-svc](https://github.com/intuit-svc) +- [@nancywu1](https://github.com/nancywu1) +- afimbres (alejandro_fimbres@intuit.com) +- Alex Fimbres ([@lexfm](https://github.com/lexfm)) +- Harris Borawski ([@hborawski](https://github.com/hborawski)) +- Jeremiah Zucker ([@sugarmanz](https://github.com/sugarmanz)) +- Ketan Reddy ([@KetanReddy](https://github.com/KetanReddy)) +- Marlon "Marky" Ercillo ([@mercillo](https://github.com/mercillo)) +- mercillo (marlon_ercillo@intuit.com) +- zwu01 (nancy_wu1@intuit.com) + +--- + +# 0.7.1 (Tue Mar 05 2024) + +### Release Notes + +#### [Android] `AsyncHydrationTrackerPlugin` ([#296](https://github.com/player-ui/player/pull/296)) + +Introduction of `AsyncHydrationTrackerPlugin` to provide a mechanism for reacting when `SuspendableAsset` hydration is completely finished. + +```kotlin +androidPlayer.asyncHydrationTrackerPlugin!!.hooks.onHydrationComplete.tap(this::class.java.name) { + // process effects after hydration is complete +} +``` + +#### [Sync] Performance and Bugfix ([#306](https://github.com/player-ui/player/pull/306)) + +- Skip view updates for silent data changes +- Replace `reduce` calls for performance reasons +- Fix data change events not cascading properly when setting data + +--- + +#### 🐛 Bug Fix + +- Release main [#313](https://github.com/player-ui/player/pull/313) ([@intuit-svc](https://github.com/intuit-svc)) +- bump @player-tools packages to 0.5.1 [#312](https://github.com/player-ui/player/pull/312) ([@hborawski](https://github.com/hborawski)) +- iOS: prefix resource bundles to prevent naming collisions [#310](https://github.com/player-ui/player/pull/310) ([@hborawski](https://github.com/hborawski)) +- [Android] `AsyncHydrationTrackerPlugin` [#296](https://github.com/player-ui/player/pull/296) ([@sugarmanz](https://github.com/sugarmanz)) +- [Docs] Platform consolidation [#287](https://github.com/player-ui/player/pull/287) ([@sugarmanz](https://github.com/sugarmanz) nancy_wu1@intuit.com) +- [JVM] Handle invalid JSON as Player error [#303](https://github.com/player-ui/player/pull/303) ([@sugarmanz](https://github.com/sugarmanz)) +- [Sync] Performance and Bugfix [#306](https://github.com/player-ui/player/pull/306) (ketan_reddy@intuit.com) + +#### 📝 Documentation + +- Fix documentation error on custom asset [#311](https://github.com/player-ui/player/pull/311) ([@ktamilvanan](https://github.com/ktamilvanan)) +- [Docs] Update: DSLSchema [#304](https://github.com/player-ui/player/pull/304) (alejandro_fimbres@intuit.com [@lexfm](https://github.com/lexfm)) + +#### Authors: 8 + +- [@intuit-svc](https://github.com/intuit-svc) +- afimbres (alejandro_fimbres@intuit.com) +- Alex Fimbres ([@lexfm](https://github.com/lexfm)) +- Harris Borawski ([@hborawski](https://github.com/hborawski)) +- Jeremiah Zucker ([@sugarmanz](https://github.com/sugarmanz)) +- Ketan Reddy ([@KetanReddy](https://github.com/KetanReddy)) +- KT ([@ktamilvanan](https://github.com/ktamilvanan)) +- nancywu1 (nancy_wu1@intuit.com) + +--- + +# 0.7.0 (Thu Feb 15 2024) + +### Release Notes + +#### `com.intuit.playerui` publishing scope ([#253](https://github.com/player-ui/player/pull/253)) + +Embracing the `player-ui` namespace, the base group ID, and correlating package scopes, have changed: +`com.intuit.player` -> `com.intuit.playerui` + +1. Dropping `.jvm` from non-android artifacts + - This was originally done to leave room for intermediate JS resource artifacts. This is no longer necessary due to improvements in our build process, and enables us to remove this redundancy. + +| Artifact | Internal | External | +| -------- | -------- | -------- | +| Headless Player | `com.intuit.player.jvm:core` | `com.intuit.playerui:core` | +| Android Player | `com.intuit.player.android:player` | `com.intuit.playerui:android` | +| Plugins | `com.intuit.player.plugins`
`com.intuit.player.jvm.plugins`
`com.intuit.player.android.plugins` | `com.intuit.playerui.plugins` | + +#### Refactor existing DSL docs. ([#288](https://github.com/player-ui/player/pull/288)) + +Update DSL docs + +#### Remove Applitools ([#277](https://github.com/player-ui/player/pull/277)) + +Enhance `AsyncViewStub.awaitView()` to ensure any child `AsyncViewStub`s are resolved as well. This really only affects initial hydration, preventing weird isolated rendering jank by ensuring everything is ready to be shown on screen before actually rendering the top-level asset. + + + +--- + +#### 🚀 Enhancement + +- `com.intuit.playerui` publishing scope [#253](https://github.com/player-ui/player/pull/253) ([@sugarmanz](https://github.com/sugarmanz)) +- feat: add github.dev links to docs [#278](https://github.com/player-ui/player/pull/278) (rafael_campos@intuit.com [@rafbcampos](https://github.com/rafbcampos)) + +#### 🐛 Bug Fix + +- Release main [#301](https://github.com/player-ui/player/pull/301) ([@intuit-svc](https://github.com/intuit-svc)) +- iOS: add asyncnodeplugin resource path to bazel.build zip [#300](https://github.com/player-ui/player/pull/300) (nancy_wu1@intuit.com [@nancywu1](https://github.com/nancywu1)) +- fix: missing docs/site on docs links [#299](https://github.com/player-ui/player/pull/299) (rafael_campos@intuit.com [@rafbcampos](https://github.com/rafbcampos)) +- fix: help to improve docs links with relative path [#298](https://github.com/player-ui/player/pull/298) (rafael_campos@intuit.com [@rafbcampos](https://github.com/rafbcampos)) +- Refactor existing DSL docs. [#288](https://github.com/player-ui/player/pull/288) ([@KetanReddy](https://github.com/KetanReddy)) +- AsyncNodePlugin- use named export, port iOS plugin [#295](https://github.com/player-ui/player/pull/295) (nancy_wu1@intuit.com [@nancywu1](https://github.com/nancywu1)) +- Fix broken link in CONTRIBUTING.md [#291](https://github.com/player-ui/player/pull/291) ([@KetanReddy](https://github.com/KetanReddy)) +- Remove Applitools [#277](https://github.com/player-ui/player/pull/277) ([@sugarmanz](https://github.com/sugarmanz) [@hborawski](https://github.com/hborawski)) + +#### 📝 Documentation + +- [Docs] plugin and cli updates, links fix [#294](https://github.com/player-ui/player/pull/294) (alejandro_fimbres@intuit.com [@lexfm](https://github.com/lexfm)) +- AssetProviderPlugin - Docs update [#283](https://github.com/player-ui/player/pull/283) (marlon_ercillo@intuit.com [@mercillo](https://github.com/mercillo)) +- Plugins android clean up [#290](https://github.com/player-ui/player/pull/290) (sentony03@gmail.com nancy_wu1@intuit.com [@brocollie08](https://github.com/brocollie08)) +- docDays/updated FAQS [#282](https://github.com/player-ui/player/pull/282) (marlon_ercillo@intuit.com [@mercillo](https://github.com/mercillo)) +- iOS: Update plugin documentation [#284](https://github.com/player-ui/player/pull/284) ([@hborawski](https://github.com/hborawski) nancy_wu1@intuit.com) +- Update Team Page with New Members [#280](https://github.com/player-ui/player/pull/280) ([@KetanReddy](https://github.com/KetanReddy)) +- iOS: Update Writing Plugins guide [#279](https://github.com/player-ui/player/pull/279) ([@hborawski](https://github.com/hborawski)) +- [JVM] pom with minimal oss requirements [#275](https://github.com/player-ui/player/pull/275) ([@sugarmanz](https://github.com/sugarmanz)) +- Fix SwiftUIPendingTransactionPlugin Docs Page [#276](https://github.com/player-ui/player/pull/276) ([@KetanReddy](https://github.com/KetanReddy)) + +#### Authors: 14 + +- [@brocollie08](https://github.com/brocollie08) +- [@intuit-svc](https://github.com/intuit-svc) +- [@nancywu1](https://github.com/nancywu1) +- afimbres (alejandro_fimbres@intuit.com) +- Alex Fimbres ([@lexfm](https://github.com/lexfm)) +- brocollie08 (sentony03@gmail.com) +- Harris Borawski ([@hborawski](https://github.com/hborawski)) +- Jeremiah Zucker ([@sugarmanz](https://github.com/sugarmanz)) +- Ketan Reddy ([@KetanReddy](https://github.com/KetanReddy)) +- Marlon "Marky" Ercillo ([@mercillo](https://github.com/mercillo)) +- mercillo (marlon_ercillo@intuit.com) +- nancywu1 (nancy_wu1@intuit.com) +- Rafael Campos ([@rafbcampos](https://github.com/rafbcampos)) +- rcampos2 (rafael_campos@intuit.com) + +--- + +# 0.6.0 (Thu Jan 25 2024) + +#### 🚀 Enhancement + +- Latest sync including AsyncNodePlugin [#263](https://github.com/player-ui/player/pull/263) (sentony03@gmail.com [@nancywu1](https://github.com/nancywu1) [@brocollie08](https://github.com/brocollie08)) + +#### 🐛 Bug Fix + +- Release main [#274](https://github.com/player-ui/player/pull/274) ([@intuit-svc](https://github.com/intuit-svc)) +- Playa 8756 - iOS add callTryCatchWrapper function on JSValue [#270](https://github.com/player-ui/player/pull/270) (nancy_wu1@intuit.com [@nancywu1](https://github.com/nancywu1)) +- Release main [#273](https://github.com/player-ui/player/pull/273) ([@intuit-svc](https://github.com/intuit-svc)) +- Fix `com.intuit.player:j2v8` transitive deps [#256](https://github.com/player-ui/player/pull/256) ([@sugarmanz](https://github.com/sugarmanz)) +- update iOS StageRevertDataPluginTests [#264](https://github.com/player-ui/player/pull/264) (nancy_wu1@intuit.com [@nancywu1](https://github.com/nancywu1)) + +#### 📝 Documentation + +- DSL documentation changes [#266](https://github.com/player-ui/player/pull/266) (rafael_campos@intuit.com [@rafbcampos](https://github.com/rafbcampos)) + +#### Authors: 9 + +- [@brocollie08](https://github.com/brocollie08) +- [@intuit-svc](https://github.com/intuit-svc) +- [@nancywu1](https://github.com/nancywu1) +- brocollie08 (sentony03@gmail.com) +- Jeremiah Zucker ([@sugarmanz](https://github.com/sugarmanz)) +- nancywu1 (nancy_wu1@intuit.com) +- Rafael Campos ([@rafbcampos](https://github.com/rafbcampos)) +- rcampos2 (rafael_campos@intuit.com) +- zwu01 (nancy_wu1@intuit.com) + +--- + +# 0.5.1 (Thu Dec 07 2023) + +#### 🐛 Bug Fix + +- Release main [#259](https://github.com/player-ui/player/pull/259) ([@intuit-svc](https://github.com/intuit-svc)) +- iOS - allow navigationFlowViewState attributes to take Any instead of string [#258](https://github.com/player-ui/player/pull/258) ([@zwu011](https://github.com/zwu011) [@nancywu1](https://github.com/nancywu1)) + +#### Authors: 3 + +- [@intuit-svc](https://github.com/intuit-svc) +- [@nancywu1](https://github.com/nancywu1) +- [@zwu011](https://github.com/zwu011) + +--- + +# 0.5.0 (Tue Dec 05 2023) + +### Release Notes + +#### Sync Android and JVM packages to latest ([#222](https://github.com/player-ui/player/pull/222)) + + + +--- + +#### 🚀 Enhancement + +- Sync Android and JVM packages to latest [#222](https://github.com/player-ui/player/pull/222) ([@Kiwiegg](https://github.com/Kiwiegg) [@sugarmanz](https://github.com/sugarmanz)) + +#### 🐛 Bug Fix + +- Release main [#254](https://github.com/player-ui/player/pull/254) ([@intuit-svc](https://github.com/intuit-svc)) + +#### Authors: 3 + +- [@intuit-svc](https://github.com/intuit-svc) +- Jeremiah Zucker ([@sugarmanz](https://github.com/sugarmanz)) +- Larry ([@Kiwiegg](https://github.com/Kiwiegg)) + +--- + +# 0.4.5 (Mon Nov 27 2023) + +#### 🐛 Bug Fix + +- Release main [#251](https://github.com/player-ui/player/pull/251) ([@intuit-svc](https://github.com/intuit-svc)) +- 177/ add plugin examples and managed player to demo app [#215](https://github.com/player-ui/player/pull/215) ([@zwu011](https://github.com/zwu011) [@nancywu1](https://github.com/nancywu1)) +- iOS: add SwiftUIPendingTransactionPlugin to reference asset dependencies [#250](https://github.com/player-ui/player/pull/250) ([@hborawski](https://github.com/hborawski)) +- Mocks for plugins [#206](https://github.com/player-ui/player/pull/206) (marlon_ercillo@intuit.com [@mercillo](https://github.com/mercillo) [@zwu011](https://github.com/zwu011) [@hborawski](https://github.com/hborawski) [@brocollie08](https://github.com/brocollie08) [@adierkens](https://github.com/adierkens) [@intuit-svc](https://github.com/intuit-svc)) +- store cancellables in ManagedPlayerViewModelTests [#210](https://github.com/player-ui/player/pull/210) ([@hborawski](https://github.com/hborawski)) +- apply swift 6 warning fix to flaky iOS tests [#207](https://github.com/player-ui/player/pull/207) ([@hborawski](https://github.com/hborawski)) +- iOS: make AssetBeacon equatable and add public init for metadata [#248](https://github.com/player-ui/player/pull/248) ([@hborawski](https://github.com/hborawski)) +- Singular workflow for CI [#214](https://github.com/player-ui/player/pull/214) (sentony03@gmail.com [@brocollie08](https://github.com/brocollie08)) +- update Gemfile.lock [#208](https://github.com/player-ui/player/pull/208) ([@hborawski](https://github.com/hborawski)) + +#### 🏠 Internal + +- Update ruby version in build [#246](https://github.com/player-ui/player/pull/246) ([@adierkens](https://github.com/adierkens)) + +#### Authors: 9 + +- [@brocollie08](https://github.com/brocollie08) +- [@intuit-svc](https://github.com/intuit-svc) +- [@nancywu1](https://github.com/nancywu1) +- [@zwu011](https://github.com/zwu011) +- Adam Dierkens ([@adierkens](https://github.com/adierkens)) +- brocollie08 (sentony03@gmail.com) +- Harris Borawski ([@hborawski](https://github.com/hborawski)) +- Marlon "Marky" Ercillo ([@mercillo](https://github.com/mercillo)) +- mercillo (marlon_ercillo@intuit.com) + +--- + +# 0.4.4 (Mon Nov 20 2023) + +#### 🐛 Bug Fix + +- Release main [#249](https://github.com/player-ui/player/pull/249) ([@intuit-svc](https://github.com/intuit-svc)) +- iOS: make AssetBeacon equatable and add public init for metadata [#248](https://github.com/player-ui/player/pull/248) ([@hborawski](https://github.com/hborawski)) + +#### 🏠 Internal + +- Update ruby version in build [#246](https://github.com/player-ui/player/pull/246) ([@adierkens](https://github.com/adierkens)) + +#### Authors: 3 + +- [@intuit-svc](https://github.com/intuit-svc) +- Adam Dierkens ([@adierkens](https://github.com/adierkens)) +- Harris Borawski ([@hborawski](https://github.com/hborawski)) + +--- + +# 0.4.3 (Fri Nov 17 2023) + +### Release Notes + +#### Add Automation ID to Error Element in Storybook ([#245](https://github.com/player-ui/player/pull/245)) + +Storybook error element has a `data-automation-id` property which allows it to be programmably found in tests + +--- + +#### 🐛 Bug Fix + +- Release main [#247](https://github.com/player-ui/player/pull/247) ([@intuit-svc](https://github.com/intuit-svc)) +- Add Automation ID to Error Element in Storybook [#245](https://github.com/player-ui/player/pull/245) ([@KetanReddy](https://github.com/KetanReddy)) +- iOS: port async WrappedFunction update [#244](https://github.com/player-ui/player/pull/244) ([@hborawski](https://github.com/hborawski)) +- iOS: rewrite publisher assertions to be async [#234](https://github.com/player-ui/player/pull/234) ([@hborawski](https://github.com/hborawski)) +- Fix PR Titles Created During Release [#242](https://github.com/player-ui/player/pull/242) ([@KetanReddy](https://github.com/KetanReddy)) + +#### 🏠 Internal + +- Update auto version. Filter release trigger from changelogs [#243](https://github.com/player-ui/player/pull/243) ([@adierkens](https://github.com/adierkens)) + +#### 📝 Documentation + +- Update broken link in contributing docs [#238](https://github.com/player-ui/player/pull/238) ([@adierkens](https://github.com/adierkens)) + +#### Authors: 4 + +- [@intuit-svc](https://github.com/intuit-svc) +- Adam Dierkens ([@adierkens](https://github.com/adierkens)) +- Harris Borawski ([@hborawski](https://github.com/hborawski)) +- Ketan Reddy ([@KetanReddy](https://github.com/KetanReddy)) + +--- + # 0.4.2 (Thu Nov 16 2023) ### Release Notes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6c97f47f..5b5d51746 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,7 @@ For small bug-fixes, documentation updates, or other trivial changes, feel free If the changes are larger (API design, architecture, etc), [opening an issue](https://github.com/player-ui/player/issues/new/choose) can be helpful to reduce implementation churn as we hash out the design. ## Requirements +* [bazelisk](https://github.com/bazelbuild/bazelisk) * [npm >= 8.19.2](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) * [yarn >= 1.22.19](https://yarnpkg.com/) diff --git a/VERSION b/VERSION index f7abe273d..d5cc44d1d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.2 \ No newline at end of file +0.7.2 \ No newline at end of file diff --git a/android/build.bzl b/android/build.bzl deleted file mode 100644 index fff59f206..000000000 --- a/android/build.bzl +++ /dev/null @@ -1,28 +0,0 @@ -def _values_to_const(k, v): - return "public val %s = %s;" % (k, v) - -def _applitools_config_impl(ctx, **kwargs): - o = ctx.actions.declare_file("ApplitoolsConfig.kt") - head = [ - "package %s;" % ctx.attr.package, - "public object ApplitoolsConfig {", - " public val API_KEY = " + ("\"" + ctx.var["APPLITOOLS_API_KEY"] + "\"" if "APPLITOOLS_API_KEY" in ctx.var else "\"UNSET\""), - " public val BATCH_ID = " + ("\"" + ctx.var["APPLITOOLS_BATCH_ID"] + "\"" if "APPLITOOLS_BATCH_ID" in ctx.var else "\"local\""), - " public val PR_NUMBER = " + ("\"" + ctx.var["APPLITOOLS_PR_NUMBER"] + "\"" if "APPLITOOLS_PR_NUMBER" in ctx.var else "\"UNSET\""), - ] - last = ["}"] - values = ctx.attr.values - xs = [_values_to_const(x, values[x]) for x in values] - ctx.actions.write(o, "\n".join( - head + xs + last, - )) - return [DefaultInfo(files = depset([o])), OutputGroupInfo(all_files = depset([o]))] - -applitools_config = rule( - implementation = _applitools_config_impl, - output_to_genfiles = True, - attrs = { - "values": attr.string_dict(doc = "BuildConfig values, KEY -> VALUE"), - "package": attr.string(mandatory = True, doc = "package for generated class"), - }, -) diff --git a/android/demo/BUILD b/android/demo/BUILD index de2c4a4e8..cbb099ebd 100644 --- a/android/demo/BUILD +++ b/android/demo/BUILD @@ -2,7 +2,6 @@ load("@build_bazel_rules_android//android:rules.bzl", "android_binary") load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_import") load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") load(":deps.bzl", "main_deps", "test_deps") -load("//android:build.bzl", "applitools_config") load("@rules_player//kotlin:lint.bzl", "lint") kt_android_library( @@ -10,7 +9,7 @@ kt_android_library( srcs = glob(["src/main/java/**"]), assets = glob(["src/main/assets/mocks/**"]), assets_dir = "src/main/assets", - custom_package = "com.intuit.player.android.reference.demo", + custom_package = "com.intuit.playerui.android.reference.demo", manifest = ":src/main/AndroidManifest.xml", resource_files = glob(["src/main/res/**"]), deps = main_deps, @@ -18,14 +17,13 @@ kt_android_library( android_binary( name = "demo", - custom_package = "com.intuit.player.android.reference.demo", + custom_package = "com.intuit.playerui.android.reference.demo", dex_shards = 10, enable_data_binding = True, manifest = ":src/main/AndroidManifest.xml", multidex = "native", deps = [ ":demo_lib", - "@android_j2v8//aar", "@maven//:androidx_databinding_databinding_common", "@maven//:androidx_databinding_databinding_runtime", "@maven//:org_jetbrains_kotlin_kotlin_reflect", @@ -35,6 +33,7 @@ android_binary( kt_jvm_import( name = "kotlinx_coroutines_core_jvm_fixed", jars = ["@kotlinx_coroutines_core_fixed//:v1/https/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.2/kotlinx-coroutines-core-jvm-1.5.2.jar"], + srcjar = "@kotlinx_coroutines_core_fixed//:v1/https/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.2/kotlinx-coroutines-core-jvm-1.5.2-sources.jar", visibility = ["//visibility:public"], deps = [ ":sun_dependencies_neverlink", @@ -52,20 +51,15 @@ java_library( neverlink = True, ) -applitools_config( - name = "applitools_config", - package = "com.intuit.player.android.reference.demo", -) - kt_android_library( name = "demo_ui_test", - srcs = glob(["src/androidTest/**/*.kt"]) + [":applitools_config"], + srcs = glob(["src/androidTest/**/*.kt"]), deps = test_deps + [":demo_lib"], ) android_binary( name = "demo_test_app", - custom_package = "com.intuit.player.android.reference.demo", + custom_package = "com.intuit.playerui.android.reference.demo", instruments = ":demo", manifest = ":src/androidTest/AndroidManifest.xml", deps = ["demo_ui_test"], @@ -86,6 +80,8 @@ sh_test( data = [ ":demo", ":demo_test_app", + "@android_test_orchestrator_apk//file", + "@android_test_services_apk//file", ], ) diff --git a/android/demo/README.md b/android/demo/README.md index a7f36a57a..ef67846c5 100644 --- a/android/demo/README.md +++ b/android/demo/README.md @@ -11,7 +11,7 @@ Assuming you have read the [requirements on the root contributing guide](https:/ 1. Once you have Android Studio installed, you will need to go to tools->SDK Manager->SDK Platforms. 1. Make sure you have **only** the following SDK installed: Android API 32. *If you are using Android Giraffe, you may need to click on show package details and it will be under Android12L. (Android SDK Platrform 32)* -2. The next step will be to make sure you have the right SDK Build tools and NDK. Click on the SDK Tools tab and make sure you have the following clicked: +2. The next step will be to make sure you have the right (and _only_ the right) SDK Build tools and NDK. Click on the SDK Tools tab and make sure you have only the following clicked: 1. 30.0.3 2. 21.4.7075529 3. You will now need to create an android device for emulation. Click on Device Manager and do create new device. @@ -25,7 +25,7 @@ bazel run //android:demo ``` ``` -bazel mobile-install //android/demo +bazel run //android/demo:install ``` If those command do not run, you can find the apk in `bazelbin/android/demo/install.runfiles/player/android/demo/demo.apk` and drag this apk onto the emulated device. This will install it. ( you may need to swipe on the device to see the application) @@ -56,12 +56,3 @@ Make sure you have done a `bundle install` **Possible Solution:** Check your SDK and NDK versions in SDK Manager in Android Studio. As well as your `ANDROID_HOME` and `ANDROID_NDK_HOME` in your bash or zsh profiles to make sure they are properly set. You can also do `ls $ANDROID_HOME/platforms` and make sure that there are no versions higher than 30. - - - -### 4. Mvn Error Message: -``` - //jvm/j2v8:j2v8-android depends on @android_j2v8//aar:aar in repository @android_j2v8 which failed to fetch. no such package '@android_j2v8//aar': android_j2v8 requires mvn as a dependency. Please check your PATH. -``` -Check to make sure `mvn` is installed. -`brew install maven` \ No newline at end of file diff --git a/android/demo/androidsettings.xml b/android/demo/androidsettings.xml deleted file mode 100644 index cc05a1fbd..000000000 --- a/android/demo/androidsettings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - central - bintray - https://jcenter.bintray.com/ - - - bintray - - - - bintray - - diff --git a/android/demo/deps.bzl b/android/demo/deps.bzl index e9bc946fa..8b6344aef 100644 --- a/android/demo/deps.bzl +++ b/android/demo/deps.bzl @@ -5,16 +5,18 @@ maven_main = [ "androidx.navigation:navigation-runtime:%s" % versions.androidx.navigation, "androidx.navigation:navigation-ui-ktx:%s" % versions.androidx.navigation, "androidx.navigation:navigation-fragment-ktx:%s" % versions.androidx.navigation, - + "androidx.lifecycle:lifecycle-viewmodel-ktx:%s" % versions.androidx.lifecycle, + "androidx.lifecycle:lifecycle-runtime-ktx:%s" % versions.androidx.lifecycle, "com.afollestad.material-dialogs:core:%s" % versions.material_dialogs, "com.google.android.material:material:%s" % versions.material, #"com.squareup.leakcanary:leakcanary-android:2.2", + "com.facebook.stetho:stetho:%s" % versions.facebook.stetho, + "com.github.AlexTrotsenko:j2v8-debugger:%s" % versions.j2v8.debugger, ] maven_test = [ "androidx.test.espresso:espresso-intents:%s" % versions.androidx.test.espresso, "androidx.test.ext:junit-ktx:%s" % versions.androidx.test.junit, - "com.applitools:eyes-android-espresso:%s" % versions.testing.applitools, ] maven = maven_main + maven_test @@ -24,10 +26,9 @@ main_deps = parse_coordinates(maven_main) + [ "//plugins/reference-assets/android:assets", "//plugins/common-types/jvm:common-types", "//plugins/pending-transaction/jvm:pending-transaction", - "//plugins/reference-assets/mocks:jar", + "//plugins/mocks:jar", ] test_deps = parse_coordinates(maven_test) + [ "//jvm/utils", - "@androidx_eyes_components//aar", ] diff --git a/android/demo/proguard-rules.pro b/android/demo/proguard-rules.pro index 80a755b06..7a4d55ae8 100644 --- a/android/demo/proguard-rules.pro +++ b/android/demo/proguard-rules.pro @@ -22,10 +22,10 @@ -keepattributes *Annotation*, InnerClasses -dontnote kotlinx.serialization.SerializationKt --keep,includedescriptorclasses class com.intuit.player.android.demo.**$$serializer { *; } --keepclassmembers class com.intuit.player.android.demo.** { +-keep,includedescriptorclasses class com.intuit.playerui.android.demo.**$$serializer { *; } +-keepclassmembers class com.intuit.playerui.android.demo.** { *** Companion; } --keepclasseswithmembers class com.intuit.player.android.demo.** { +-keepclasseswithmembers class com.intuit.playerui.android.demo.** { kotlinx.serialization.KSerializer serializer(...); } diff --git a/android/demo/scripts/androidinstall.sh b/android/demo/scripts/androidinstall.sh index c43649069..c5b245896 100755 --- a/android/demo/scripts/androidinstall.sh +++ b/android/demo/scripts/androidinstall.sh @@ -3,4 +3,4 @@ set -u -e -o pipefail adb install -r -d android/demo/demo.apk -adb shell monkey -p com.intuit.player.android.reference.demo 1 \ No newline at end of file +adb shell monkey -p com.intuit.playerui.android.reference.demo 1 \ No newline at end of file diff --git a/android/demo/scripts/androidtest.sh b/android/demo/scripts/androidtest.sh index 34ac50564..495c6183c 100755 --- a/android/demo/scripts/androidtest.sh +++ b/android/demo/scripts/androidtest.sh @@ -2,7 +2,84 @@ set -u -e -o pipefail +# Install app to test adb install android/demo/demo.apk + +# Install test app adb install android/demo/demo_test_app.apk -adb shell am instrument -w com.intuit.player.android.reference.demo.test/androidx.test.runner.AndroidJUnitRunner +DEVICE_API_LEVEL=$(adb shell getprop ro.build.version.sdk) + +FORCE_QUERYABLE_OPTION="" +if [[ $DEVICE_API_LEVEL -ge 30 ]]; then + FORCE_QUERYABLE_OPTION="--force-queryable" +fi + +# TODO: Hooks up properly to runfiles +cp external/android_test_orchestrator_apk/file/downloaded orchestrator.apk +cp external/android_test_services_apk/file/downloaded test_services.apk + +# Install the test orchestrator. +adb install $FORCE_QUERYABLE_OPTION -r orchestrator.apk + +# Install test services. +adb install $FORCE_QUERYABLE_OPTION -r test_services.apk + +adb shell settings put global window_animation_scale 0 +adb shell settings put global transition_animation_scale 0 +adb shell settings put global animator_duration_scale 0 + +# Inspiration: https://gist.github.com/swenson/f797ffea7e243d889406#file-runtests-sh + +# adb shell throws away the return value, so we have to hack do some magic +# see https://code.google.com/p/android/issues/detail?id=3254 + +adb logcat -c && +python2 - < + package="com.intuit.playerui.android.reference.demo.test"> - diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/collection/CollectionUITest.kt b/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/collection/CollectionUITest.kt deleted file mode 100644 index 86ef2a553..000000000 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/collection/CollectionUITest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.intuit.player.android.reference.demo.test.assets.collection - -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withText -import com.intuit.player.android.reference.demo.test.base.AssetUITest -import com.intuit.player.android.reference.demo.test.base.shouldBePlayerState -import com.intuit.player.jvm.core.player.state.InProgressState -import org.junit.Test - -class CollectionUITest : AssetUITest("collection") { - - @Test - fun basic() { - launchMock() - - onView(withText("Item 1")) - .check(matches(isDisplayed())) - - onView(withText("Item 2")) - .check(matches(isDisplayed())) - - currentState.shouldBePlayerState() - } -} diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/input/InputUITest.kt b/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/input/InputUITest.kt deleted file mode 100644 index 224a74ea8..000000000 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/input/InputUITest.kt +++ /dev/null @@ -1,127 +0,0 @@ -package com.intuit.player.android.reference.demo.test.assets.input - -import android.view.View -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.clearText -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.pressImeActionButton -import androidx.test.espresso.action.ViewActions.typeText -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.hasErrorText -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withChild -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.matcher.ViewMatchers.withParent -import androidx.test.espresso.matcher.ViewMatchers.withText -import com.intuit.player.android.reference.demo.R -import com.intuit.player.android.reference.demo.test.base.AssetUITest -import com.intuit.player.android.reference.demo.test.base.shouldBePlayerState -import com.intuit.player.jvm.core.player.state.InProgressState -import com.intuit.player.jvm.core.player.state.dataModel -import org.hamcrest.Matcher -import org.hamcrest.Matchers.allOf -import org.junit.Assert.assertEquals -import org.junit.Test - -class InputUITest : AssetUITest("input") { - - fun verifyIsDisplayed(matcher: Matcher) = onView(matcher) - .check(matches(isDisplayed())) - - @Test - fun basic() { - launchMock() - - onView(withText("This is an input")) - .check(matches(isDisplayed())) - - onView(withId(R.id.input_field)) - .check(matches(isDisplayed())) - .perform(click()) - .perform(typeText("text")) - .perform(pressImeActionButton()) - - currentState.shouldBePlayerState { - assertEquals("text", dataModel.get("foo.bar")) - } - } - - @Test - fun validationAgeFormatter() { - launchMock("validation") - - val ageContainer = allOf( - withId(R.id.input_container), - withChild(withChild(withText("Age"))) - ).also(::verifyIsDisplayed) - - val ageInput = allOf( - withId(R.id.input_field), - withParent(ageContainer) - ).also(::verifyIsDisplayed) - - onView(ageInput) - .perform(typeText("text")) - .perform(pressImeActionButton()) - - currentState.shouldBePlayerState { - assertEquals("", dataModel.get("person.age")) - } - - eyes.checkPlayer("invalid-age") - - onView(ageInput) - .perform(typeText("30")) - .perform(pressImeActionButton()) - - currentState.shouldBePlayerState { - assertEquals(30, dataModel.get("person.age")) - } - } - - @Test - fun validationName() { - launchMock("validation") - - val nameContainer = allOf( - withId(R.id.input_container), - withChild(withChild(withText("Name"))) - ).also(::verifyIsDisplayed) - - val nameInput = allOf( - withId(R.id.input_field), - withParent(nameContainer) - ).also(::verifyIsDisplayed) - - onView(nameInput) - .perform(typeText("more than 10 characters")) - .perform(pressImeActionButton()) - .check(matches(hasErrorText("Up to 10 characters allowed"))) - - currentState.shouldBePlayerState { - assertEquals("", dataModel.get("person.name")) - } - - eyes.checkPlayer("upper-limit-validation") - - onView(nameInput) - .perform(click()) - .perform(clearText()) - .perform(pressImeActionButton()) - .check(matches(hasErrorText("At least 1 characters needed"))) - - currentState.shouldBePlayerState { - assertEquals("", dataModel.get("person.name")) - } - - eyes.checkPlayer("lower-limit-validation") - - onView(nameInput) - .perform(typeText("Jeremiah")) - .perform(pressImeActionButton()) - - currentState.shouldBePlayerState { - assertEquals("Jeremiah", dataModel.get("person.name")) - } - } -} diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/ApplitoolsTest.kt b/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/ApplitoolsTest.kt deleted file mode 100644 index c0d44e796..000000000 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/ApplitoolsTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.intuit.player.android.reference.demo.test.base - -import android.app.Application -import androidx.test.core.app.ApplicationProvider -import androidx.test.espresso.matcher.ViewMatchers -import com.applitools.eyes.android.common.BatchInfo -import com.applitools.eyes.android.common.Feature -import com.applitools.eyes.android.common.config.Configuration -import com.applitools.eyes.android.components.androidx.AndroidXComponentsProvider -import com.applitools.eyes.android.espresso.Eyes -import com.applitools.eyes.android.espresso.fluent.Target -import com.intuit.player.android.reference.demo.ApplitoolsConfig.API_KEY -import com.intuit.player.android.reference.demo.ApplitoolsConfig.BATCH_ID -import com.intuit.player.android.reference.demo.ApplitoolsConfig.PR_NUMBER -import com.intuit.player.android.reference.demo.R - -abstract class ApplitoolsTest { - - val context: Application by lazy { - ApplicationProvider.getApplicationContext() - } - - // Initialize the eyes SDK and set your private API key. - val eyes by lazy { - Eyes().apply { - componentsProvider = AndroidXComponentsProvider() - configuration = Configuration().apply { - appName = "Android Reference Assets" - addProperty("platform", "android") - batch = batchInfo - apiKey = API_KEY - setFeatures(Feature.PIXEL_COPY_SCREENSHOT) - setServerUrl("https://intuiteyesapi.applitools.com") - } - } - } - - fun Eyes.open(appName: String, testName: String, block: Eyes.() -> Unit) { - try { - open(appName, testName) - block() - close() - } finally { - abortIfNotClosed() - } - } - - fun Eyes.checkPlayer(name: String) = check(name, Target.region(ViewMatchers.withId(R.id.player_canvas))) - - companion object { - val batchInfo = BatchInfo("reference-assets@$PR_NUMBER").apply { - // Only manually set the batch ID if it's not a hardcoded fallback - if (BATCH_ID != "local") id = BATCH_ID - } - } -} diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/MainActivityTest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/MainActivityTest.kt similarity index 64% rename from android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/MainActivityTest.kt rename to android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/MainActivityTest.kt index 47a1e86eb..abce364e8 100644 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/MainActivityTest.kt +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/MainActivityTest.kt @@ -1,4 +1,4 @@ -package com.intuit.player.android.reference.demo.test +package com.intuit.playerui.android.reference.demo.test import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click @@ -6,8 +6,9 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.rules.activityScenarioRule -import com.intuit.player.android.reference.demo.test.base.PerformanceTest -import com.intuit.player.android.reference.demo.ui.main.MainActivity +import com.intuit.playerui.android.reference.demo.test.base.PerformanceTest +import com.intuit.playerui.android.reference.demo.test.base.waitForViewInRoot +import com.intuit.playerui.android.reference.demo.ui.main.MainActivity import org.hamcrest.Matchers.allOf import org.junit.Rule import org.junit.Test @@ -18,13 +19,13 @@ class MainActivityTest : PerformanceTest { @Test fun verifyDefault() { - onView(withText("Android Reference Assets")) + waitForViewInRoot(withText("Android Reference Assets")) .check(matches(isDisplayed())) onView( allOf( - withText("Random Mock") - ) + withText("Random Mock"), + ), ).perform(click()) } } diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/SplashActivityTest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/SplashActivityTest.kt similarity index 72% rename from android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/SplashActivityTest.kt rename to android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/SplashActivityTest.kt index a005e9932..24c32b0e0 100644 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/SplashActivityTest.kt +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/SplashActivityTest.kt @@ -1,16 +1,15 @@ -package com.intuit.player.android.reference.demo.test +package com.intuit.playerui.android.reference.demo.test import android.content.Intent import android.net.Uri import androidx.test.core.app.ApplicationProvider -import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.rules.activityScenarioRule -import com.intuit.player.android.reference.demo.test.base.ApplitoolsTest -import com.intuit.player.android.reference.demo.ui.splash.SplashActivity -import com.intuit.player.jvm.utils.makeFlow +import com.intuit.playerui.android.reference.demo.test.base.waitForViewInRoot +import com.intuit.playerui.android.reference.demo.ui.splash.SplashActivity +import com.intuit.playerui.utils.makeFlow import org.junit.Rule import org.junit.Test @@ -19,7 +18,7 @@ import org.junit.Test * If there are new tests added that should be run on device farm, * update testFilter inclusion list in buildConfig.yml */ -class SplashActivityTest : ApplitoolsTest() { +class SplashActivityTest { private val json by lazy { makeFlow( @@ -29,7 +28,7 @@ class SplashActivityTest : ApplitoolsTest() { "type": "text", "value": "Deep link test!" } - """.trimIndent() + """.trimIndent(), ) } @@ -45,6 +44,6 @@ class SplashActivityTest : ApplitoolsTest() { @Test fun deepLinkTest() { - onView(withText("Deep link test!")).check(matches(isDisplayed())) + waitForViewInRoot(withText("Deep link test!")).check(matches(isDisplayed())) } } diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/action/ActionUITest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/action/ActionUITest.kt similarity index 52% rename from android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/action/ActionUITest.kt rename to android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/action/ActionUITest.kt index 7781e38bf..693412ef0 100644 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/action/ActionUITest.kt +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/action/ActionUITest.kt @@ -1,31 +1,30 @@ -package com.intuit.player.android.reference.demo.test.assets.action +package com.intuit.playerui.android.reference.demo.test.assets.action -import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText -import com.intuit.player.android.reference.demo.test.base.AssetUITest -import com.intuit.player.android.reference.demo.test.base.shouldBePlayerState -import com.intuit.player.jvm.core.player.state.CompletedState -import com.intuit.player.jvm.core.player.state.ErrorState -import com.intuit.player.jvm.core.player.state.InProgressState -import com.intuit.player.jvm.core.player.state.dataModel +import com.intuit.playerui.android.reference.demo.test.base.AssetUITest +import com.intuit.playerui.android.reference.demo.test.base.shouldBePlayerState +import com.intuit.playerui.android.reference.demo.test.base.waitForViewInRoot +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.player.state.dataModel import org.junit.Assert.assertEquals import org.junit.Test -class ActionUITest : AssetUITest("action") { +class ActionUITest : AssetUITest("reference-assets") { @Test fun basic() { - launchMock() + launchMock("action-basic") repeat(10) { - onView(withText("Count: $it")) - .check(matches(isDisplayed())) + waitForViewInRoot(withText("Count: $it")) .perform(click()) - eyes.checkPlayer("click $it") + waitForViewInRoot(withText("Count: ${it + 1}")) } currentState.shouldBePlayerState { @@ -35,9 +34,9 @@ class ActionUITest : AssetUITest("action") { @Test fun transitionToEndSuccess() { - launchMock("transition-to-end") + launchMock("action-transition-to-end") - onView(withText("End the flow (success)")) + waitForViewInRoot(withText("End the flow (success)")) .check(matches(isDisplayed())) .perform(click()) @@ -48,9 +47,9 @@ class ActionUITest : AssetUITest("action") { @Test fun transitionToEndError() { - launchMock("transition-to-end") + launchMock("action-transition-to-end") - onView(withText("End the flow (error)")) + waitForViewInRoot(withText("End the flow (error)")) .check(matches(isDisplayed())) .perform(click()) diff --git a/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/collection/CollectionUITest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/collection/CollectionUITest.kt new file mode 100644 index 000000000..52fb274f4 --- /dev/null +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/collection/CollectionUITest.kt @@ -0,0 +1,30 @@ +package com.intuit.playerui.android.reference.demo.test.assets.collection + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import com.intuit.playerui.android.reference.demo.test.base.AssetUITest +import com.intuit.playerui.android.reference.demo.test.base.shouldBePlayerState +import com.intuit.playerui.android.reference.demo.test.base.waitForViewInRoot +import com.intuit.playerui.core.player.state.InProgressState +import org.junit.Test + +class CollectionUITest : AssetUITest("reference-assets") { + + @Test + fun basic() { + launchMock("collection-basic") + + waitForViewInRoot(withText("Collections are used to group assets.")) + .check(matches(isDisplayed())) + + onView(withText("This is the first item in the collection")) + .check(matches(isDisplayed())) + + onView(withText("This is the second item in the collection")) + .check(matches(isDisplayed())) + + currentState.shouldBePlayerState() + } +} diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/info/InfoUITest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/info/InfoUITest.kt similarity index 67% rename from android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/info/InfoUITest.kt rename to android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/info/InfoUITest.kt index ed448ca7f..175e5123b 100644 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/info/InfoUITest.kt +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/info/InfoUITest.kt @@ -1,24 +1,24 @@ -package com.intuit.player.android.reference.demo.test.assets.info +package com.intuit.playerui.android.reference.demo.test.assets.info import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText -import com.intuit.player.android.reference.demo.test.base.AssetUITest -import com.intuit.player.android.reference.demo.test.base.shouldBePlayerState -import com.intuit.player.jvm.core.player.state.InProgressState +import com.intuit.playerui.android.reference.demo.test.base.AssetUITest +import com.intuit.playerui.android.reference.demo.test.base.shouldBePlayerState +import com.intuit.playerui.android.reference.demo.test.base.waitForViewInRoot +import com.intuit.playerui.core.player.state.InProgressState import org.junit.Test -class InfoUITest : AssetUITest("info") { +class InfoUITest : AssetUITest("reference-assets") { enum class Action { Next, Dismiss } fun verifyView(view: Int) { - eyes.checkPlayer("View $view") - onView(withText("View $view")) + waitForViewInRoot(withText("View $view")) .check(matches(isDisplayed())) } @@ -34,7 +34,7 @@ class InfoUITest : AssetUITest("info") { @Test fun basic() { - launchMock("modal-flow") + launchMock("info-modal-flow") verifyAndProceed(1, Action.Next) verifyAndProceed(2, Action.Dismiss) diff --git a/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/input/InputUITest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/input/InputUITest.kt new file mode 100644 index 000000000..ac436dc6b --- /dev/null +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/input/InputUITest.kt @@ -0,0 +1,81 @@ +package com.intuit.playerui.android.reference.demo.test.assets.input + +import android.view.View +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.pressImeActionButton +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withChild +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import com.intuit.playerui.android.reference.demo.R +import com.intuit.playerui.android.reference.demo.test.base.AssetUITest +import com.intuit.playerui.android.reference.demo.test.base.shouldBePlayerState +import com.intuit.playerui.android.reference.demo.test.base.waitForViewInRoot +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.player.state.dataModel +import org.hamcrest.Matcher +import org.hamcrest.Matchers.allOf +import org.junit.Assert.assertEquals +import org.junit.Test + +class InputUITest : AssetUITest("reference-assets") { + + fun verifyIsDisplayed(matcher: Matcher) = waitForViewInRoot(matcher) + .check(matches(isDisplayed())) + + @Test + fun basic() { + launchMock("input-basic") + + waitForViewInRoot(withText("This is an input")) + .check(matches(isDisplayed())) + + onView(withId(R.id.input_field)) + .check(matches(isDisplayed())) + .perform(click()) + .perform(typeText("text")) + .perform(pressImeActionButton()) + + currentState.shouldBePlayerState { + assertEquals("text", dataModel.get("foo.bar")) + } + } + + @Test + fun validation() { + launchMock("input-validation") + + verifyIsDisplayed( + allOf( + withId(R.id.input_label_container), + withChild(withText("Input with validation and formatting")), + ), + ) + + verifyIsDisplayed( + allOf( + withId(R.id.input_note_container), + withChild(withText("It expects a positive integer")), + ), + ) + + onView(withId(R.id.input_field)) + .perform(typeText("t")) + .perform(pressImeActionButton()) + + currentState.shouldBePlayerState { + assertEquals(null, dataModel.get("foo.bar")) + } + + onView(withId(R.id.input_field)) + .perform(typeText("30")) + .perform(pressImeActionButton()) + + currentState.shouldBePlayerState { + assertEquals(30, dataModel.get("foo.bar")) + } + } +} diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/text/TextUITest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/text/TextUITest.kt similarity index 68% rename from android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/text/TextUITest.kt rename to android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/text/TextUITest.kt index 078edfcbf..ca4e1664a 100644 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/assets/text/TextUITest.kt +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/assets/text/TextUITest.kt @@ -1,9 +1,8 @@ -package com.intuit.player.android.reference.demo.test.assets.text +package com.intuit.playerui.android.reference.demo.test.assets.text import android.app.Activity.RESULT_CANCELED import android.app.Instrumentation.ActivityResult import android.content.Intent.ACTION_VIEW -import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.intent.Intents.intended @@ -12,32 +11,33 @@ import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction import androidx.test.espresso.intent.matcher.IntentMatchers.hasData import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText -import com.intuit.player.android.reference.demo.test.base.AssetUITest +import com.intuit.playerui.android.reference.demo.test.base.AssetUITest +import com.intuit.playerui.android.reference.demo.test.base.waitForViewInRoot import org.hamcrest.Matchers.allOf import org.junit.Test -class TextUITest : AssetUITest("text") { +class TextUITest : AssetUITest("reference-assets") { @Test fun basic() { - launchMock() + launchMock("text-basic") - onView(withText("Some text content")) + waitForViewInRoot(withText("This is some text.")) .check(matches(isDisplayed())) } @Test fun link() { - launchMock("with-link") + launchMock("text-with-link") val openLink = allOf( hasAction(ACTION_VIEW), - hasData("http://www.intuit.com") + hasData("http://www.intuit.com"), ) intending(openLink).respondWith(ActivityResult(RESULT_CANCELED, null)) - onView(withText("A Link")) + waitForViewInRoot(withText("A Link")) .check(matches(isDisplayed())) .perform(click()) diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/assertions.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/Assertions.kt similarity index 82% rename from android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/assertions.kt rename to android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/Assertions.kt index 17fc1a31f..79f0c7902 100644 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/assertions.kt +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/Assertions.kt @@ -1,15 +1,15 @@ -package com.intuit.player.android.reference.demo.test.base +package com.intuit.playerui.android.reference.demo.test.base import android.view.View -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.jvm.core.player.state.PlayerFlowState +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.core.player.state.PlayerFlowState import org.junit.Assert.assertTrue import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @OptIn(ExperimentalContracts::class) inline fun Any?.shouldBeAsset( - block: T.() -> Unit = {} + block: T.() -> Unit = {}, ): T { shouldBeInstanceOf(this) block() diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/AssetUITest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/AssetUITest.kt similarity index 65% rename from android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/AssetUITest.kt rename to android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/AssetUITest.kt index 1b02951f6..f73a5575f 100644 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/AssetUITest.kt +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/AssetUITest.kt @@ -1,18 +1,18 @@ -package com.intuit.player.android.reference.demo.test.base +package com.intuit.playerui.android.reference.demo.test.base import androidx.test.espresso.intent.Intents import androidx.test.ext.junit.rules.activityScenarioRule -import com.intuit.player.android.reference.demo.lifecycle.DemoPlayerViewModel -import com.intuit.player.android.reference.demo.ui.main.MainActivity -import com.intuit.player.android.reference.demo.ui.main.MainViewModel -import com.intuit.player.jvm.core.player.state.PlayerFlowState -import com.intuit.player.jvm.utils.mocks.Mock +import com.intuit.playerui.android.reference.demo.lifecycle.DemoPlayerViewModel +import com.intuit.playerui.android.reference.demo.ui.main.MainActivity +import com.intuit.playerui.android.reference.demo.ui.main.MainViewModel +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.utils.mocks.Mock import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.rules.TestName -abstract class AssetUITest(val group: String? = null) : ApplitoolsTest() { +abstract class AssetUITest(val group: String? = null) { @get:Rule val name = TestName() @@ -40,10 +40,7 @@ abstract class AssetUITest(val group: String? = null) : ApplitoolsTest() { @After fun after() { - Intents.assertNoUnverifiedIntents() Intents.release() - eyes.checkWindow("done") - eyes.close() } fun launchMock() { @@ -53,7 +50,7 @@ abstract class AssetUITest(val group: String? = null) : ApplitoolsTest() { fun launchMock(name: String) { launchMock( mocks.find { it.name == name || it.name == "$group-$name" } - ?: throw IllegalArgumentException("$name not found in mocks: ${mocks.map { "${it.group}/${it.name}" }}") + ?: throw IllegalArgumentException("$name not found in mocks: ${mocks.map { "${it.group}/${it.name}" }}"), ) } @@ -64,8 +61,5 @@ abstract class AssetUITest(val group: String? = null) : ApplitoolsTest() { playerViewModel = it.currentPlayer?.playerViewModel as? DemoPlayerViewModel ?: throw IllegalStateException("player not found") } - - eyes.open("Android Reference Assets Demo", "${mock.group}/${name.methodName}") - eyes.checkPlayer("init") } } diff --git a/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/Matchers.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/Matchers.kt new file mode 100644 index 000000000..4ae5ba6e9 --- /dev/null +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/Matchers.kt @@ -0,0 +1,58 @@ +package com.intuit.playerui.android.reference.demo.test.base + +import android.view.View +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.PerformException +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.ViewInteraction +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.util.HumanReadables +import androidx.test.espresso.util.TreeIterables +import org.hamcrest.Matcher +import org.hamcrest.Matchers +import org.hamcrest.StringDescription +import java.util.concurrent.TimeoutException + +fun waitForViewInRoot(viewMatcher: Matcher, timeout: Long = 10000, waitForDisplayed: Boolean = true): ViewInteraction { + onView(isRoot()).perform(waitForView(viewMatcher, timeout, waitForDisplayed)) + + return onView(viewMatcher) +} + +fun waitForView(viewMatcher: Matcher, timeout: Long = 10000, waitForDisplayed: Boolean = true): ViewAction { + return object : ViewAction { + override fun getConstraints(): Matcher { + return Matchers.any(View::class.java) + } + + override fun getDescription(): String { + val matcherDescription = StringDescription() + viewMatcher.describeTo(matcherDescription) + return "wait for a specific view <$matcherDescription> to be ${if (waitForDisplayed) "displayed" else "not displayed during $timeout millis."}" + } + + override fun perform(uiController: UiController, view: View) { + uiController.loopMainThreadUntilIdle() + val startTime = System.currentTimeMillis() + val endTime = startTime + timeout + val visibleMatcher = ViewMatchers.isDisplayed() + + do { + val viewVisible = TreeIterables.breadthFirstViewTraversal(view) + .any { viewMatcher.matches(it) && visibleMatcher.matches(it) } + + if (viewVisible == waitForDisplayed) return + uiController.loopMainThreadForAtLeast(50) + } while (System.currentTimeMillis() < endTime) + + // Timeout happens. + throw PerformException.Builder() + .withActionDescription(this.description) + .withViewDescription(HumanReadables.describe(view)) + .withCause(TimeoutException()) + .build() + } + } +} diff --git a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/PerformanceTest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/PerformanceTest.kt similarity index 94% rename from android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/PerformanceTest.kt rename to android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/PerformanceTest.kt index 94b2479fa..86489d555 100644 --- a/android/demo/src/androidTest/java/com/intuit/player/android/reference/demo/test/base/PerformanceTest.kt +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/base/PerformanceTest.kt @@ -1,4 +1,4 @@ -package com.intuit.player.android.reference.demo.test.base +package com.intuit.playerui.android.reference.demo.test.base import android.app.Activity import android.os.Bundle @@ -36,20 +36,20 @@ interface PerformanceTest { val averageTime = calculateAverage(histogram, totalFrames) assertTrue( "Over a total of $totalFrames frames, the slowest, median and average time it took for frames to render was ${maxTime}ms, ${medianTime}ms, and ${averageTime}ms respectively", - (maxTime < 250 && medianTime < 32 && averageTime < 24) || true + (maxTime < 250 && medianTime < 32 && averageTime < 24) || true, ) activityRule.scenario.close() } enum class FrameStats constructor( - val pattern: Pattern + val pattern: Pattern, ) { TOTAL_FRAMES(Pattern.compile("\\s*$totalFrames: (\\d+)")), FIFTY_PERCENTILE(Pattern.compile("\\s*$fiftiethPercentile: (\\d+)ms")), NINETY_PERCENTILE(Pattern.compile("\\s*$ninetiethPercentile: (\\d+)ms")), NINETY_FIVE_PERCENTILE(Pattern.compile("\\s*$ninetyFifthPercentile: (\\d+)ms")), NINETY_NINE_PERCENTILE(Pattern.compile("\\s*$ninetyNinethPercentile: (\\d+)ms")), - HISTOGRAM(Pattern.compile("\\s*HISTOGRAM: (.*)")) + HISTOGRAM(Pattern.compile("\\s*HISTOGRAM: (.*)")), } fun startPerformanceTest() { @@ -105,7 +105,7 @@ interface PerformanceTest { } companion object { - const val pkg = "com.intuit.player.android.reference.demo" + const val pkg = "com.intuit.playerui.android.reference.demo" const val totalFrames = "Total frames rendered" const val fiftiethPercentile = "50th percentile" const val ninetiethPercentile = "90th percentile" diff --git a/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/fragment/PlayerFragmentScrollingTest.kt b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/fragment/PlayerFragmentScrollingTest.kt new file mode 100644 index 000000000..7cc89c09b --- /dev/null +++ b/android/demo/src/androidTest/java/com/intuit/playerui/android/reference/demo/test/fragment/PlayerFragmentScrollingTest.kt @@ -0,0 +1,24 @@ +package com.intuit.playerui.android.reference.demo.test.fragment + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText +import com.intuit.playerui.android.reference.demo.test.base.AssetUITest +import com.intuit.playerui.android.reference.demo.test.base.waitForViewInRoot +import org.junit.Test + +class PlayerFragmentScrollingTest : AssetUITest("misc") { + + @Test + fun shouldScrollToTopOnTransition() { + launchMock("long-multi-view") + waitForViewInRoot(withText("It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way—in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.\n\nThere were a king with a large jaw and a queen with a plain face, on the throne of England; there were a king with a large jaw and a queen with a fair face, on the throne of France. In both countries it was clearer than crystal to the lords of the State preserves of loaves and fishes, that things in general were settled for ever.\n\nIt was the year of Our Lord one thousand seven hundred and seventy-five. Spiritual revelations were conceded to England at that favoured period, as at this. Mrs. Southcott had recently attained her five-and-twentieth blessed birthday, of whom a prophetic private in the Life Guards had heralded the sublime appearance by announcing that arrangements were made for the swallowing up of London and Westminster. Even the Cock-lane ghost had been laid only a round dozen of years, after rapping out its messages, as the spirits of this very year last past (supernaturally deficient in originality) rapped out theirs. Mere messages in the earthly order of events had lately come to the English Crown and People, from a congress of British subjects in America: which, strange to relate, have proved more important to the human race than any communications yet received through any of the chickens of the Cock-lane brood.\n\nFrance, less favoured on the whole as to matters spiritual than her sister of the shield and trident, rolled with exceeding smoothness down hill, making paper money and spending it. Under the guidance of her Christian pastors, she entertained herself, besides, with such humane achievements as sentencing a youth to have his hands cut off, his tongue torn out with pincers, and his body burned alive, because he had not kneeled down in the rain to do honour to a dirty procession of monks which passed within his view, at a distance of some fifty or sixty yards. It is likely enough that, rooted in the woods of France and Norway, there were growing trees, when that sufferer was put to death, already marked by the Woodman, Fate, to come down and be sawn into boards, to make a certain movable framework with a sack and a knife in it, terrible in history. It is likely enough that in the rough outhouses of some tillers of the heavy lands adjacent to Paris, there were sheltered from the weather that very day, rude carts, bespattered with rustic mire, snuffed about by pigs, and roosted in by poultry, which the Farmer, Death, had already set apart to be his tumbrils of the Revolution. But that Woodman and that Farmer, though they work unceasingly, work silently, and no one heard them as they went about with muffled tread: the rather, forasmuch as to entertain any suspicion that they were awake, was to be atheistical and traitorous.\n\nIn England, there was scarcely an amount of order and protection to justify much national boasting. Daring burglaries by armed men, and highway robberies, took place in the capital itself every night; families were publicly cautioned not to go out of town without removing their furniture to upholsterers’ warehouses for security; the highwayman in the dark was a City tradesman in the light, and, being recognised and challenged by his fellow-tradesman whom he stopped in his character of “the Captain,” gallantly shot him through the head and rode away; the mail was waylaid by seven robbers, and the guard shot three dead, and then got shot dead himself by the other four, “in consequence of the failure of his ammunition:” after which the mail was robbed in peace; that magnificent potentate, the Lord Mayor of London, was made to stand and deliver on Turnham Green, by one highwayman, who despoiled the illustrious creature in sight of all his retinue; prisoners in London gaols fought battles with their turnkeys, and the majesty of the law fired blunderbusses in among them, loaded with rounds of shot and ball; thieves snipped off diamond crosses from the necks of noble lords at Court drawing-rooms; musketeers went into St. Giles’s, to search for contraband goods, and the mob fired on the musketeers, and the musketeers fired on the mob, and nobody thought any of these occurrences much out of the common way. In the midst of them, the hangman, ever busy and ever worse than useless, was in constant requisition; now, stringing up long rows of miscellaneous criminals; now, hanging a housebreaker on Saturday who had been taken on Tuesday; now, burning people in the hand at Newgate by the dozen, and now burning pamphlets at the door of Westminster Hall; to-day, taking the life of an atrocious murderer, and to-morrow of a wretched pilferer who had robbed a farmer’s boy of sixpence.\n\nAll these things, and a thousand like them, came to pass in and close upon the dear old year one thousand seven hundred and seventy-five. Environed by them, while the Woodman and the Farmer worked unheeded, those two of the large jaws, and those other two of the plain and the fair faces, trod with stir enough, and carried their divine rights with a high hand. Thus did the year one thousand seven hundred and seventy-five conduct their Greatnesses, and myriads of small creatures—the creatures of this chronicle among the rest—along the roads that lay before them.")) + .check(matches(isDisplayed())) + onView(withText("Go to view 2")).perform(scrollTo(), click()) + waitForViewInRoot(withText("Can you see me?")) + .check(matches(isDisplayed())) + } +} diff --git a/android/demo/src/main/AndroidManifest.xml b/android/demo/src/main/AndroidManifest.xml index ef468a905..f8681eea4 100644 --- a/android/demo/src/main/AndroidManifest.xml +++ b/android/demo/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + package="com.intuit.playerui.android.reference.demo"> (null) - - public val playerFlowState: StateFlow = _playerFlowState.asStateFlow() - - override fun apply(androidPlayer: AndroidPlayer) { - super.apply(androidPlayer) - - androidPlayer.hooks.state.tap { state -> - _playerFlowState.tryEmit(state) - } - } -} diff --git a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/base/BasePlayerFragment.kt b/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/base/BasePlayerFragment.kt deleted file mode 100644 index 3e938f52f..000000000 --- a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/base/BasePlayerFragment.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.intuit.player.android.reference.demo.ui.base - -import android.graphics.drawable.GradientDrawable -import android.view.View -import androidx.core.view.get -import androidx.fragment.app.viewModels -import androidx.navigation.fragment.findNavController -import com.afollestad.materialdialogs.MaterialDialog -import com.intuit.player.android.lifecycle.ManagedPlayerState -import com.intuit.player.android.lifecycle.PlayerViewModel -import com.intuit.player.android.reference.demo.lifecycle.DemoPlayerViewModel -import com.intuit.player.android.ui.PlayerFragment -import com.intuit.player.jvm.core.managed.AsyncFlowIterator -import kotlinx.coroutines.* - -/** Simple [PlayerFragment] example that builds a [DemoPlayerViewModel] w/ a single flow iterator */ -abstract class BasePlayerFragment : PlayerFragment() { - - abstract val flow: String - - override val playerViewModel by viewModels { - PlayerViewModel.Factory(AsyncFlowIterator(flow), ::DemoPlayerViewModel) - } - - private val currentPlayerCanvas get() = binding.playerCanvas.getChildAt(0) - - private suspend fun toggleScreenShare(active: Boolean) = withContext(Dispatchers.Main) { - binding.playerCanvas.background = if (active) GradientDrawable().apply { - setStroke(30, resources.getColor(android.R.color.holo_green_light)) - } else null - } - - override fun buildFallbackView(exception: Exception): View? = currentPlayerCanvas - - override fun buildDoneView(): View? = currentPlayerCanvas - - override fun onDone(doneState: ManagedPlayerState.Done) { - showDialog { - title(text = "Flows completed successfully!") - message(text = doneState.completedState?.endState.toString()) - } - } - - override fun onError(errorState: ManagedPlayerState.Error) { - showDialog { - title(text = "Error in Flow!") - message(text = errorState.exception.message) - } - } - - protected fun showDialog(builder: MaterialDialog.() -> Unit) { - MaterialDialog(requireContext()).show { - positiveButton(text = "Reset") { reset() } - negativeButton(text = "Back") { - findNavController().popBackStack() - } - cancelable(false) - builder() - } - } -} diff --git a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/player/DemoPlayerFragment.kt b/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/player/DemoPlayerFragment.kt deleted file mode 100644 index c0dc944d5..000000000 --- a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/player/DemoPlayerFragment.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.intuit.player.android.reference.demo.ui.player - -import com.intuit.player.android.reference.demo.ui.base.BasePlayerFragment - -class DemoPlayerFragment : BasePlayerFragment() { - - override val flow: String get() = arguments?.getString("flow")!! -} diff --git a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/start/StartFragment.kt b/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/start/StartFragment.kt deleted file mode 100644 index 28d3dcdb6..000000000 --- a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/start/StartFragment.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.intuit.player.android.reference.demo.ui.start - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.fragment.app.activityViewModels -import com.intuit.player.android.lifecycle.ManagedPlayerState -import com.intuit.player.android.reference.demo.ui.base.BasePlayerFragment -import com.intuit.player.android.reference.demo.ui.main.MainViewModel -import com.intuit.player.jvm.utils.mocks.getFlow - -class StartFragment : BasePlayerFragment() { - private val mainViewModel: MainViewModel by activityViewModels() - - override val flow: String by lazy { - mainViewModel.defaultMock.getFlow(requireActivity().assets) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = super.onCreateView(inflater, container, savedInstanceState).apply { - reset() - } - - override fun onDone(doneState: ManagedPlayerState.Done) { - when (doneState.completedState?.endState?.outcome) { - "dismiss" -> binding.playerCanvas.removeAllViews() - "randomize" -> mainViewModel.randomize() - } - } -} diff --git a/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/lifecycle/DemoPlayerViewModel.kt b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/lifecycle/DemoPlayerViewModel.kt new file mode 100644 index 000000000..f0e25922f --- /dev/null +++ b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/lifecycle/DemoPlayerViewModel.kt @@ -0,0 +1,47 @@ +package com.intuit.playerui.android.reference.demo.lifecycle + +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.AndroidPlayer.Config +import com.intuit.playerui.android.asset.SuspendableAsset.AsyncHydrationTrackerPlugin +import com.intuit.playerui.android.asset.asyncHydrationTrackerPlugin +import com.intuit.playerui.android.lifecycle.PlayerViewModel +import com.intuit.playerui.android.reference.assets.ReferenceAssetsPlugin +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.managed.AsyncFlowIterator +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.plugins.transactions.PendingTransactionPlugin +import com.intuit.playerui.plugins.types.CommonTypesPlugin +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class DemoPlayerViewModel(iterator: AsyncFlowIterator) : PlayerViewModel(iterator) { + + override val plugins = listOf( + CommonTypesPlugin(), + ReferenceAssetsPlugin(), + PendingTransactionPlugin(), + AsyncHydrationTrackerPlugin(), + ) + + override val config: Config = Config( + debuggable = true, + ) + + private val _playerFlowState = MutableStateFlow(null) + + public val playerFlowState: StateFlow = _playerFlowState.asStateFlow() + + @OptIn(ExperimentalPlayerApi::class) + override fun apply(androidPlayer: AndroidPlayer) { + super.apply(androidPlayer) + + androidPlayer.hooks.state.tap { state -> + _playerFlowState.tryEmit(state) + } + + androidPlayer.asyncHydrationTrackerPlugin!!.hooks.onHydrationComplete.tap(this::class.java.name) { + androidPlayer.logger.info("Done hydrating!!!!") + } + } +} diff --git a/android/demo/src/main/java/com/intuit/player/android/reference/demo/model/AssetMock.kt b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/model/AssetMock.kt similarity index 69% rename from android/demo/src/main/java/com/intuit/player/android/reference/demo/model/AssetMock.kt rename to android/demo/src/main/java/com/intuit/playerui/android/reference/demo/model/AssetMock.kt index 7efa13733..502b726d1 100644 --- a/android/demo/src/main/java/com/intuit/player/android/reference/demo/model/AssetMock.kt +++ b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/model/AssetMock.kt @@ -1,12 +1,12 @@ -package com.intuit.player.android.reference.demo.model +package com.intuit.playerui.android.reference.demo.model import android.content.res.AssetManager -import com.intuit.player.jvm.utils.mocks.Mock +import com.intuit.playerui.utils.mocks.Mock open class AssetMock( override val group: String, override val name: String, - override val path: String + override val path: String, ) : Mock { override fun read(source: AssetManager) = source .open(normalizedPath) diff --git a/android/demo/src/main/java/com/intuit/player/android/reference/demo/model/StringMock.kt b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/model/StringMock.kt similarity index 57% rename from android/demo/src/main/java/com/intuit/player/android/reference/demo/model/StringMock.kt rename to android/demo/src/main/java/com/intuit/playerui/android/reference/demo/model/StringMock.kt index 12dfb2dd5..52ff3d246 100644 --- a/android/demo/src/main/java/com/intuit/player/android/reference/demo/model/StringMock.kt +++ b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/model/StringMock.kt @@ -1,12 +1,12 @@ -package com.intuit.player.android.reference.demo.model +package com.intuit.playerui.android.reference.demo.model -import com.intuit.player.jvm.utils.mocks.Mock +import com.intuit.playerui.utils.mocks.Mock class StringMock( private val json: String, override val group: String = "", override val name: String = "Player", - override val path: String = "" + override val path: String = "", ) : Mock { override fun read(source: Any) = json } diff --git a/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/base/BasePlayerFragment.kt b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/base/BasePlayerFragment.kt new file mode 100644 index 000000000..013c987c7 --- /dev/null +++ b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/base/BasePlayerFragment.kt @@ -0,0 +1,70 @@ +package com.intuit.playerui.android.reference.demo.ui.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.afollestad.materialdialogs.MaterialDialog +import com.alexii.j2v8debugger.StethoHelper +import com.intuit.playerui.android.lifecycle.ManagedPlayerState +import com.intuit.playerui.android.lifecycle.PlayerViewModel +import com.intuit.playerui.android.reference.demo.lifecycle.DemoPlayerViewModel +import com.intuit.playerui.android.ui.PlayerFragment +import com.intuit.playerui.core.bridge.serialization.json.prettify +import com.intuit.playerui.core.bridge.toJson +import com.intuit.playerui.core.managed.AsyncFlowIterator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** Simple [PlayerFragment] example that builds a [DemoPlayerViewModel] w/ a single flow iterator */ +abstract class BasePlayerFragment : PlayerFragment() { + + abstract val flow: String + + override val playerViewModel by viewModels { + PlayerViewModel.Factory(AsyncFlowIterator(flow), ::DemoPlayerViewModel) + } + + private val currentPlayerCanvas get() = binding.playerCanvas.getChildAt(0) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + StethoHelper.initializeDebugger(requireContext(), playerViewModel.player) + return super.onCreateView(inflater, container, savedInstanceState) + } + + override fun buildFallbackView(exception: Exception): View? = currentPlayerCanvas + + override fun buildDoneView(): View? = currentPlayerCanvas + + override fun onDone(doneState: ManagedPlayerState.Done) { + val message = doneState.completedState?.endState?.node?.toJson()?.prettify() + showDialog { + title(text = "Flows completed successfully!") + message(text = message) + } + } + + override fun onError(errorState: ManagedPlayerState.Error) { + val message = errorState.exception.message + showDialog { + title(text = "Error in Flow!") + message(text = message) + } + } + + protected fun showDialog(builder: MaterialDialog.() -> Unit) { + lifecycleScope.launch(Dispatchers.Main) { + MaterialDialog(requireContext()).show { + positiveButton(text = "Reset") { reset() } + negativeButton(text = "Back") { + findNavController().popBackStack() + } + cancelable(false) + builder() + } + } + } +} diff --git a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/main/MainActivity.kt b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/main/MainActivity.kt similarity index 70% rename from android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/main/MainActivity.kt rename to android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/main/MainActivity.kt index e290995ce..abb30ce86 100644 --- a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/main/MainActivity.kt +++ b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/main/MainActivity.kt @@ -1,4 +1,4 @@ -package com.intuit.player.android.reference.demo.ui.main +package com.intuit.playerui.android.reference.demo.ui.main import android.os.Bundle import android.view.Menu @@ -7,19 +7,28 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.observe +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.* +import androidx.navigation.fragment.NavHostFragment.findNavController +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.onNavDestinationSelected +import androidx.navigation.ui.setupActionBarWithNavController +import androidx.navigation.ui.setupWithNavController import com.google.android.material.navigation.NavigationView -import com.intuit.player.android.reference.demo.R -import com.intuit.player.android.reference.demo.model.AssetMock -import com.intuit.player.android.reference.demo.model.StringMock -import com.intuit.player.android.ui.PlayerFragment -import com.intuit.player.jvm.utils.mocks.ClassLoaderMock -import com.intuit.player.jvm.utils.mocks.Mock -import com.intuit.player.jvm.utils.mocks.getFlow +import com.intuit.playerui.android.reference.demo.R +import com.intuit.playerui.android.reference.demo.model.AssetMock +import com.intuit.playerui.android.reference.demo.model.StringMock +import com.intuit.playerui.android.ui.PlayerFragment +import com.intuit.playerui.utils.mocks.ClassLoaderMock +import com.intuit.playerui.utils.mocks.Mock +import com.intuit.playerui.utils.mocks.getFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { @@ -36,6 +45,16 @@ class MainActivity : AppCompatActivity() { return navController.navigateUp(navConfig) || super.onSupportNavigateUp() } + init { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + viewModel.currentMock.collect { + startFlow(it) + } + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -48,9 +67,6 @@ class MainActivity : AppCompatActivity() { setupActionBarWithNavController(navController, navConfig) storybookNav.setupWithNavController(navController) storybookNav.menu.let(viewModel::groupMocks) - viewModel.currentMock.observe(this) { - startFlow(it) - } } override fun onCreateOptionsMenu(menu: Menu?): Boolean { @@ -84,7 +100,7 @@ class MainActivity : AppCompatActivity() { is StringMock -> mock.getFlow("") else -> throw IllegalArgumentException("mock of type ${mock::class}[$mock] not supported") }, - mock.name + mock.name, ) private fun launchFlow(flow: String, name: String?) { @@ -93,7 +109,7 @@ class MainActivity : AppCompatActivity() { R.id.action_launch_player, name?.let { bundleOf("name" to name, "flow" to flow) - } ?: bundleOf("flow" to flow) + } ?: bundleOf("flow" to flow), ) } } diff --git a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/main/MainViewModel.kt b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/main/MainViewModel.kt similarity index 70% rename from android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/main/MainViewModel.kt rename to android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/main/MainViewModel.kt index 7ea347116..1e016d6e9 100644 --- a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/main/MainViewModel.kt +++ b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/main/MainViewModel.kt @@ -1,14 +1,17 @@ -package com.intuit.player.android.reference.demo.ui.main +package com.intuit.playerui.android.reference.demo.ui.main import android.app.Application import android.view.Menu import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.intuit.player.android.reference.demo.model.AssetMock -import com.intuit.player.android.reference.demo.model.StringMock -import com.intuit.player.jvm.utils.mocks.ClassLoaderMocksReader -import com.intuit.player.jvm.utils.mocks.Mock +import androidx.lifecycle.viewModelScope +import com.intuit.playerui.android.reference.demo.model.AssetMock +import com.intuit.playerui.android.reference.demo.model.StringMock +import com.intuit.playerui.utils.mocks.ClassLoaderMocksReader +import com.intuit.playerui.utils.mocks.Mock +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch import java.io.FileNotFoundException class MainViewModel(private val context: Application) : AndroidViewModel(context) { @@ -21,14 +24,16 @@ class MainViewModel(private val context: Application) : AndroidViewModel(context AssetMock("", "default", "mocks/default.json") } - private val _currentMock = MutableLiveData>() + private val _currentMock = MutableSharedFlow>() /** [currentMock] LiveData that represents the mock that is currently displayed */ - val currentMock: LiveData> get() = _currentMock + val currentMock: SharedFlow> = _currentMock.asSharedFlow() fun launch(json: String) = launch(StringMock(json)) - fun launch(mock: Mock<*>) = _currentMock.postValue(mock) + fun launch(mock: Mock<*>) = viewModelScope.launch { + _currentMock.emit(mock) + } private fun readMocksFromClasspath() = ClassLoaderMocksReader(MainViewModel::class.java.classLoader!!).mocks diff --git a/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/player/DemoPlayerFragment.kt b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/player/DemoPlayerFragment.kt new file mode 100644 index 000000000..03297f481 --- /dev/null +++ b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/player/DemoPlayerFragment.kt @@ -0,0 +1,8 @@ +package com.intuit.playerui.android.reference.demo.ui.player + +import com.intuit.playerui.android.reference.demo.ui.base.BasePlayerFragment + +class DemoPlayerFragment : BasePlayerFragment() { + + override val flow: String get() = arguments?.getString("flow")!! +} diff --git a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/splash/SplashActivity.kt b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/splash/SplashActivity.kt similarity index 79% rename from android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/splash/SplashActivity.kt rename to android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/splash/SplashActivity.kt index d84be5f77..b08ad823b 100644 --- a/android/demo/src/main/java/com/intuit/player/android/reference/demo/ui/splash/SplashActivity.kt +++ b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/splash/SplashActivity.kt @@ -1,9 +1,9 @@ -package com.intuit.player.android.reference.demo.ui.splash +package com.intuit.playerui.android.reference.demo.ui.splash import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import com.intuit.player.android.reference.demo.ui.main.MainActivity +import com.intuit.playerui.android.reference.demo.ui.main.MainActivity class SplashActivity : AppCompatActivity() { diff --git a/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/start/StartFragment.kt b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/start/StartFragment.kt new file mode 100644 index 000000000..cf3be76c8 --- /dev/null +++ b/android/demo/src/main/java/com/intuit/playerui/android/reference/demo/ui/start/StartFragment.kt @@ -0,0 +1,48 @@ +package com.intuit.playerui.android.reference.demo.ui.start + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import com.intuit.playerui.android.lifecycle.ManagedPlayerState +import com.intuit.playerui.android.reference.demo.ui.base.BasePlayerFragment +import com.intuit.playerui.android.reference.demo.ui.main.MainViewModel +import com.intuit.playerui.utils.mocks.getFlow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class StartFragment : BasePlayerFragment() { + private val mainViewModel: MainViewModel by activityViewModels() + + override val flow: String by lazy { + mainViewModel.defaultMock.getFlow(requireActivity().assets) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ) = super.onCreateView(inflater, container, savedInstanceState).apply { + when (val state = playerViewModel.state.value) { + is ManagedPlayerState.Running -> lifecycleScope.launch(Dispatchers.Default) { + handleAssetUpdate(state.asset, state.animateViewTransition) + } + + is ManagedPlayerState.Done, + is ManagedPlayerState.Error, + -> reset() + + ManagedPlayerState.NotStarted, + ManagedPlayerState.Pending, + -> Unit + } + } + + override fun onDone(doneState: ManagedPlayerState.Done) { + when (doneState.completedState?.endState?.outcome) { + "dismiss" -> binding.playerCanvas.removeAllViews() + "randomize" -> mainViewModel.randomize() + } + } +} diff --git a/android/demo/src/main/res/navigation/nav_graph.xml b/android/demo/src/main/res/navigation/nav_graph.xml index 6497c11fe..8dbfbb0b7 100644 --- a/android/demo/src/main/res/navigation/nav_graph.xml +++ b/android/demo/src/main/res/navigation/nav_graph.xml @@ -7,7 +7,7 @@ @@ -18,7 +18,7 @@ diff --git a/android/player/BUILD b/android/player/BUILD index dab9b7a25..700d4c5d6 100644 --- a/android/player/BUILD +++ b/android/player/BUILD @@ -1,23 +1,29 @@ -load(":deps.bzl", "main_deps", "main_resources", "test_deps") +load(":deps.bzl", "main_deps", "main_exports", "main_resources", "test_deps") +load("@build_constants//:constants.bzl", "VERSION") load("//jvm:build.bzl", "distribution") -load("@grab_bazel_common//tools/databinding:databinding.bzl", "kt_db_android_library") +load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") load("@junit//junit5-jupiter-starter-bazel:junit5.bzl", "kt_jvm_junit5_test") load("@rules_player//kotlin:lint.bzl", "lint") +load("@grab_bazel_common//tools/databinding:databinding.bzl", "kt_db_android_library") kt_db_android_library( name = "player", srcs = glob(["src/main/java/**/*.kt"]), - custom_package = "com.intuit.player.android", + custom_package = "com.intuit.playerui.android", manifest = ":src/main/AndroidManifest.xml", resource_files = glob(["src/main/res/**"]), resources = main_resources, - deps = main_deps, + tags = ["maven_coordinates=com.intuit.playerui:android:aar:%s" % VERSION], visibility = ["//visibility:public"], - tags = ["maven_coordinates=com.intuit.player.android:player:{pom_version}"] + exports = main_exports, + deps = main_deps, ) distribution( name = "player", + lib_name = "player-databinding", + maven_coordinates = "com.intuit.playerui:android:%s" % VERSION, ) kt_jvm_junit5_test( @@ -25,7 +31,7 @@ kt_jvm_junit5_test( srcs = glob(["src/test/java/**"]), associates = [":player-kotlin"], kotlinc_opts = "//jvm:test_options", - test_package = "com.intuit.player.android", + test_package = "com.intuit.playerui.android", deps = [":player"] + test_deps, ) diff --git a/android/player/api/player.api b/android/player/api/player.api index ec64dc481..506e3547e 100644 --- a/android/player/api/player.api +++ b/android/player/api/player.api @@ -129,25 +129,25 @@ public final class com/intuit/player/android/asset/RenderableAsset$Serializer : public abstract interface class com/intuit/player/android/asset/RenderableAsset$ViewportAsset { } -public final class com/intuit/player/android/databinding/DefaultFallbackBinding : androidx/viewbinding/ViewBinding { +public final class com/intuit/player/android/databinding/FallbackViewBinding : androidx/viewbinding/ViewBinding { public final field error Landroid/widget/TextView; public final field reset Landroid/widget/Button; public final field retry Landroid/widget/Button; public final field title Landroid/widget/TextView; - public static fun bind (Landroid/view/View;)Lcom/intuit/player/android/databinding/DefaultFallbackBinding; + public static fun bind (Landroid/view/View;)Lcom/intuit/player/android/databinding/FallbackViewBinding; public synthetic fun getRoot ()Landroid/view/View; public fun getRoot ()Landroidx/constraintlayout/widget/ConstraintLayout; - public static fun inflate (Landroid/view/LayoutInflater;)Lcom/intuit/player/android/databinding/DefaultFallbackBinding; - public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Z)Lcom/intuit/player/android/databinding/DefaultFallbackBinding; + public static fun inflate (Landroid/view/LayoutInflater;)Lcom/intuit/player/android/databinding/FallbackViewBinding; + public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Z)Lcom/intuit/player/android/databinding/FallbackViewBinding; } -public final class com/intuit/player/android/databinding/FragmentPlayerBinding : androidx/viewbinding/ViewBinding { +public final class com/intuit/player/android/databinding/PlayerFragmentBinding : androidx/viewbinding/ViewBinding { public final field playerCanvas Landroid/widget/FrameLayout; - public static fun bind (Landroid/view/View;)Lcom/intuit/player/android/databinding/FragmentPlayerBinding; + public static fun bind (Landroid/view/View;)Lcom/intuit/player/android/databinding/PlayerFragmentBinding; public synthetic fun getRoot ()Landroid/view/View; public fun getRoot ()Landroid/widget/ScrollView; - public static fun inflate (Landroid/view/LayoutInflater;)Lcom/intuit/player/android/databinding/FragmentPlayerBinding; - public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Z)Lcom/intuit/player/android/databinding/FragmentPlayerBinding; + public static fun inflate (Landroid/view/LayoutInflater;)Lcom/intuit/player/android/databinding/PlayerFragmentBinding; + public static fun inflate (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Z)Lcom/intuit/player/android/databinding/PlayerFragmentBinding; } public final class com/intuit/player/android/extensions/IntoKt { @@ -233,10 +233,10 @@ public class com/intuit/player/android/lifecycle/PlayerViewModel : androidx/life public final fun fail (Ljava/lang/Throwable;)V public final fun getBeacons ()Lkotlinx/coroutines/flow/SharedFlow; protected final fun getManager ()Lcom/intuit/player/jvm/core/managed/AsyncIterationManager; - protected final fun getPlayer ()Lcom/intuit/player/android/AndroidPlayer; + public final fun getPlayer ()Lcom/intuit/player/android/AndroidPlayer; protected fun getPlugins ()Ljava/util/List; public final fun getState ()Lkotlinx/coroutines/flow/StateFlow; - protected fun onCleared ()V + public fun onCleared ()V public final fun recycle ()V public final fun release ()V public final fun retry ()V @@ -260,7 +260,7 @@ public abstract class com/intuit/player/android/ui/PlayerFragment : androidx/fra public fun buildFallbackView (Ljava/lang/Exception;)Landroid/view/View; public fun buildLoadingView ()Landroid/view/View; public fun buildTransitionAnimation ()Landroidx/transition/Transition; - protected final fun getBinding ()Lcom/intuit/player/android/databinding/FragmentPlayerBinding; + protected final fun getBinding ()Lcom/intuit/player/android/databinding/PlayerFragmentBinding; public abstract fun getPlayerViewModel ()Lcom/intuit/player/android/lifecycle/PlayerViewModel; protected fun handleAssetUpdate (Lcom/intuit/player/android/asset/RenderableAsset;Z)V public fun onCreateView (Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Landroid/os/Bundle;)Landroid/view/View; diff --git a/android/player/deps.bzl b/android/player/deps.bzl index 0d340505a..b6aa15d2a 100644 --- a/android/player/deps.bzl +++ b/android/player/deps.bzl @@ -16,7 +16,7 @@ maven = [ ] main_exports = [ - "//jvm/j2v8:j2v8-android" + "//jvm/j2v8:j2v8-android", ] main_deps = main_exports + parse_coordinates(maven) + [ @@ -36,4 +36,5 @@ test_deps = [ "@grab_bazel_common//tools/test:mockable-android-jar", "@maven//:io_mockk_mockk", "//jvm/testutils", + "@maven//:org_jetbrains_kotlinx_kotlinx_coroutines_test", ] diff --git a/android/player/src/androidTest/java/com/intuit/player/android/AssetContextAndroidTest.kt b/android/player/src/androidTest/java/com/intuit/playerui/android/AssetContextAndroidTest.kt similarity index 92% rename from android/player/src/androidTest/java/com/intuit/player/android/AssetContextAndroidTest.kt rename to android/player/src/androidTest/java/com/intuit/playerui/android/AssetContextAndroidTest.kt index eb7995a22..b3b8575a1 100644 --- a/android/player/src/androidTest/java/com/intuit/player/android/AssetContextAndroidTest.kt +++ b/android/player/src/androidTest/java/com/intuit/playerui/android/AssetContextAndroidTest.kt @@ -1,12 +1,12 @@ -package com.intuit.player.android +package com.intuit.playerui.android import android.content.Context import android.view.View import android.widget.TextView import androidx.test.platform.app.InstrumentationRegistry -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.testutils.Node +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.testutils.Node import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -22,6 +22,7 @@ internal class AssetContextAndroidTest { private fun Map.toAsset(): Asset = Node(this).let(::Asset) private val assetMap = mapOf("id" to "first", "type" to "rando") + @Suppress("DEPRECATION_ERROR") private fun renderableAsset(assetContext: AssetContext) = object : RenderableAsset(assetContext) { override fun initView() = TextView(context) diff --git a/android/player/src/main/AndroidManifest.xml b/android/player/src/main/AndroidManifest.xml index 11aae60fe..bb4fa48cd 100644 --- a/android/player/src/main/AndroidManifest.xml +++ b/android/player/src/main/AndroidManifest.xml @@ -1,3 +1,3 @@ - + diff --git a/android/player/src/main/java/com/intuit/player/android/asset/StaleViewException.kt b/android/player/src/main/java/com/intuit/player/android/asset/StaleViewException.kt deleted file mode 100644 index c556343fc..000000000 --- a/android/player/src/main/java/com/intuit/player/android/asset/StaleViewException.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.intuit.player.android.asset - -import com.intuit.player.android.AssetContext -import com.intuit.player.jvm.core.player.PlayerException - -internal class StaleViewException constructor(val assetContext: AssetContext) : PlayerException("asset[${assetContext.id}] is stale") diff --git a/android/player/src/main/java/com/intuit/player/android/ui/PlayerFragment.kt b/android/player/src/main/java/com/intuit/player/android/ui/PlayerFragment.kt deleted file mode 100644 index 288bf30eb..000000000 --- a/android/player/src/main/java/com/intuit/player/android/ui/PlayerFragment.kt +++ /dev/null @@ -1,176 +0,0 @@ -package com.intuit.player.android.ui - -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ProgressBar -import androidx.core.view.doOnLayout -import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import androidx.transition.Transition -import com.intuit.player.android.AndroidPlayer -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.android.databinding.DefaultFallbackBinding -import com.intuit.player.android.databinding.FragmentPlayerBinding -import com.intuit.player.android.extensions.into -import com.intuit.player.android.extensions.transitionInto -import com.intuit.player.android.lifecycle.ManagedPlayerState -import com.intuit.player.android.lifecycle.PlayerViewModel -import com.intuit.player.android.lifecycle.fail -import com.intuit.player.jvm.core.managed.AsyncFlowIterator -import kotlinx.coroutines.flow.collect - -/** - * [Fragment] wrapper integration with the [AndroidPlayer]. Delegates - * to an implementation of the [PlayerViewModel] for player management, - * including hooking the player up to the fragment lifecycle. - * - * Subclasses will need to provide a [PlayerViewModel] implementation, - * which requires an [AsyncFlowIterator]. For pre-defined flow use case, - * the subclass can use the [AsyncFlowIterator] pseudo constructor - * for convenience. - * - * // TODO: Check this - * By default, the [PlayerViewModel] will be started as soon as the - * [Context] is attached to the [Fragment] in [onAttach]. When - * [onDestroyView] is invoked, [PlayerViewModel.recycle] is called to - * clear any Android lifecycle specific cached data. - * - * Additionally, this automatically observes all [ManagedPlayerState] - * changes and calls into the corresponding [ManagedPlayerState.Listener] - * handlers. [buildLoadingView] and [buildFallbackView] are used to - * build views for when the [PlayerViewModel] is pending the next view - * or encountered an error. These methods do have default implementations - * to enable consumers to plug-n-play, but can be overridden to customize - * the UI in these states. - */ -public abstract class PlayerFragment : Fragment(), ManagedPlayerState.Listener { - - private var _binding: FragmentPlayerBinding? = null - - /** - * [FragmentPlayerBinding] instance - * This property is only valid between onCreateView and onDestroyView. - * Will throw a NPE if called out of turn. - */ - protected val binding: FragmentPlayerBinding get() = _binding!! - - /** - * [ViewModel][androidx.lifecycle.ViewModel] responsible for managing an [AndroidPlayer] - * with respect to a specific [FlowManager][com.intuit.player.jvm.core.managed.FlowManager]. - */ - public abstract val playerViewModel: PlayerViewModel - - init { - lifecycleScope.launchWhenStarted { - playerViewModel.state.collect { - when (it) { - ManagedPlayerState.NotStarted -> { - buildLoadingView() into binding.playerCanvas - onNotStarted() - } - - ManagedPlayerState.Pending -> { - buildLoadingView() into binding.playerCanvas - onPending() - } - - is ManagedPlayerState.Running -> { - try { - handleAssetUpdate(it.asset, it.animateViewTransition) - onRunning(it) - } catch (exception: Exception) { - exception.printStackTrace() - playerViewModel.fail("Error rendering asset", exception) - } - } - - is ManagedPlayerState.Error -> { - buildFallbackView(it.exception) into binding.playerCanvas - onError(it) - } - - is ManagedPlayerState.Done -> { - buildDoneView() into binding.playerCanvas - onDone(it) - } - } - } - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View = FragmentPlayerBinding.inflate(inflater, container, false).run { - _binding = this - root - } - - override fun onDestroyView() { - // recycle views - _binding = null - playerViewModel.recycle() - super.onDestroyView() - } - - /** Reset the [PlayerViewModel.manager] from the beginning */ - public fun reset() { - playerViewModel.start() - } - - /** - * Handle [asset] updates from the [PlayerViewModel]. By default, - * this will invoke [RenderableAsset.render] with no additional - * styles and inject that into the view tree. - */ - protected open fun handleAssetUpdate(asset: RenderableAsset?, animateTransition: Boolean) { - val startTime = System.currentTimeMillis() - if (asset is RenderableAsset.ViewportAsset) binding.scrollContainer.isFillViewport = true - val view = asset?.render(requireContext())?.also { - it.doOnLayout { playerViewModel.logRenderTime(asset, System.currentTimeMillis() - startTime) } - } - if (animateTransition) { - buildTransitionAnimation()?.let { - view.transitionInto(binding.playerCanvas, it) - return - } - } - view into binding.playerCanvas - } - - public open fun buildTransitionAnimation(): Transition? = null - - /** - * Builder method to provide a [View] to be shown when the [PlayerViewModel] is loading, which can be either - * [NotStarted][ManagedPlayerState.NotStarted] or[Pending][ManagedPlayerState.Pending] the next flow. - * Defaults to simple [ProgressBar]. - */ - public open fun buildLoadingView(): View? = ProgressBar(context) - - /** - * Builder method to provide a fallback [View] to be shown when the - * [PlayerViewModel] encounters an [exception]. Defaults to an instance of [DefaultFallbackBinding]. - */ - public open fun buildFallbackView(exception: Exception): View? = - DefaultFallbackBinding.inflate(layoutInflater).apply { - this.error.text = exception.localizedMessage - - retry.setOnClickListener { - playerViewModel.retry() - } - - reset.setOnClickListener { - reset() - } - }.root - - /** - * Builder method to provide a [View] to be shown when the [PlayerViewModel] - * finishes. Defaults to null. - */ - public open fun buildDoneView(): View? = null -} diff --git a/android/player/src/main/java/com/intuit/player/android/AndroidPlayer.kt b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt similarity index 68% rename from android/player/src/main/java/com/intuit/player/android/AndroidPlayer.kt rename to android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt index 918ae56cd..42cd4e894 100644 --- a/android/player/src/main/java/com/intuit/player/android/AndroidPlayer.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt @@ -1,34 +1,44 @@ -package com.intuit.player.android +package com.intuit.playerui.android import android.content.Context import android.view.View +import com.alexii.j2v8debugger.ScriptSourceProvider +import com.intuit.hooks.BailResult import com.intuit.hooks.HookContext +import com.intuit.hooks.SyncBailHook +import com.intuit.hooks.SyncHook import com.intuit.hooks.SyncWaterfallHook -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.android.extensions.Styles -import com.intuit.player.android.extensions.overlayStyles -import com.intuit.player.android.extensions.removeSelf -import com.intuit.player.android.logger.AndroidLogger -import com.intuit.player.android.registry.RegistryPlugin -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Completable -import com.intuit.player.jvm.core.bridge.format -import com.intuit.player.jvm.core.bridge.serialization.format.registerContextualSerializer -import com.intuit.player.jvm.core.logger.TapableLogger -import com.intuit.player.jvm.core.player.* -import com.intuit.player.jvm.core.player.state.CompletedState -import com.intuit.player.jvm.core.player.state.PlayerFlowState -import com.intuit.player.jvm.core.plugins.LoggerPlugin -import com.intuit.player.jvm.core.plugins.Plugin -import com.intuit.player.jvm.core.plugins.findPlugin -import com.intuit.player.plugins.beacon.BeaconPlugin -import com.intuit.player.plugins.coroutines.FlowScopePlugin -import com.intuit.player.plugins.pubsub.PubSubPlugin +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.android.debug.UnsupportedScriptProvider +import com.intuit.playerui.android.extensions.Styles +import com.intuit.playerui.android.extensions.overlayStyles +import com.intuit.playerui.android.extensions.removeSelf +import com.intuit.playerui.android.logger.AndroidLogger +import com.intuit.playerui.android.registry.RegistryPlugin +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Completable +import com.intuit.playerui.core.bridge.format +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeConfig +import com.intuit.playerui.core.bridge.serialization.format.registerContextualSerializer +import com.intuit.playerui.core.logger.TapableLogger +import com.intuit.playerui.core.player.HeadlessPlayer +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.core.plugins.LoggerPlugin +import com.intuit.playerui.core.plugins.Plugin +import com.intuit.playerui.core.plugins.findPlugin +import com.intuit.playerui.plugins.beacon.BeaconPlugin +import com.intuit.playerui.plugins.coroutines.FlowScopePlugin +import com.intuit.playerui.plugins.pubsub.PubSubPlugin +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.* -import kotlinx.serialization.modules.plus import kotlin.reflect.KClass +public typealias AndroidPlayerConfig = AndroidPlayer.Config + /** * [android.view.View] [Player] that is backed by another [Player]. * If no backing [Player] is supplied, it will build a self-contained @@ -37,12 +47,13 @@ import kotlin.reflect.KClass public class AndroidPlayer private constructor( private val player: HeadlessPlayer, override val plugins: List = player.plugins, -) : Player() { +) : Player(), ScriptSourceProvider by player.runtime as? ScriptSourceProvider ?: UnsupportedScriptProvider(player.runtime) { /** Convenience constructor to provide vararg style [plugins] parameter */ public constructor( vararg plugins: Plugin, - ) : this(plugins.toList()) + config: Config = Config(), + ) : this(plugins.toList(), config) /** * Constructor that takes a [context] and collection of [Plugin]s. @@ -52,7 +63,8 @@ public class AndroidPlayer private constructor( */ public constructor( plugins: List, - ) : this(HeadlessPlayer(*plugins.injectDefaultPlugins().toTypedArray())) + config: Config = Config(), + ) : this(HeadlessPlayer(plugins.injectDefaultPlugins(), config = config)) /** * Allow the [AndroidPlayer] to be built on top of a pre-existing @@ -84,16 +96,43 @@ public class AndroidPlayer private constructor( public fun call(context: Context): Context = super.call( context, { f, acc, hookCtx -> f(hookCtx, acc) }, - { f, hookCtx -> f(hookCtx, context) } + { f, hookCtx -> f(hookCtx, context) }, ) } + + internal class RecycleHook : SyncHook<(HookContext) -> Unit>() { + public fun call(): Unit = super.call { f, context -> + f(context) + } + } + + internal class ReleaseHook : SyncHook<(HookContext) -> Unit>() { + public fun call(): Unit = super.call { f, context -> + f(context) + } + } + + public class UpdateHook : SyncBailHook<(RenderableAsset?) -> BailResult, Unit>() { + public fun call(asset: RenderableAsset?, default: (HookContext) -> Unit): Unit? = super.call( + { f, _ -> + f(asset) + }, + default, + ) + } + public val context: ContextHook = ContextHook() + public val update: UpdateHook = UpdateHook() + internal val recycle: RecycleHook = RecycleHook() + internal val release: ReleaseHook = ReleaseHook() } override val hooks: Hooks = Hooks(player.hooks) override val state: PlayerFlowState by player::state + override val scope: CoroutineScope by player::scope + override fun start(flow: String): Completable = player.start(flow) private val assetSerializer = RenderableAsset.Serializer(this) @@ -115,8 +154,9 @@ public class AndroidPlayer private constructor( /** Helper provided to reduce overhead for asset registrations with metaData */ public fun registerAsset(klass: KClass, props: Map, factory: (AssetContext) -> RenderableAsset) { assetRegistry.register(props, factory) - if (player.format.serializersModule.getContextual(klass) == null) + if (player.format.serializersModule.getContextual(klass) == null) { player.format.registerContextualSerializer(klass, assetSerializer.conform(klass)) + } } /** Apply [AndroidPlayerPlugin]s last */ @@ -139,7 +179,16 @@ public class AndroidPlayer private constructor( transition.value = true clearCaches() view?.hooks?.onUpdate?.tap { asset -> - assetHandler(expandAsset(asset), transition.value) + try { + expandAsset(asset).let { expandedAsset -> + hooks.update.call(expandedAsset) { + assetHandler(expandedAsset, transition.value) + } + } + } catch (exception: Exception) { + logger.error("Error while expanding ${asset?.id}", exception) + inProgressState?.fail(PlayerException("Error while expanding ${asset?.id}", exception)) + } } } } @@ -207,13 +256,18 @@ public class AndroidPlayer private constructor( * prevent a leak. */ public fun recycle() { + // TODO: Remove this check by enhancing TapableLogger to out-last Player lifecycle to use default + if (!player.runtime.isReleased()) logger.debug("AndroidPlayer: recycling player") clearCaches() - // TODO: Allow [AndroidPlayerPlugins] the chance to "recycle" too + hooks.recycle.call() } override fun release() { + // TODO: Remove this check by enhancing TapableLogger to out-last Player lifecycle to use default + if (!player.runtime.isReleased()) player.logger.debug("AndroidPlayer: releasing player") clearCaches() player.release() + hooks.release.call() } /** @@ -252,8 +306,16 @@ public class AndroidPlayer private constructor( /** Helper to add default plugins if there isn't already an instance of that plugin */ private fun List.injectDefaultPlugins() = buildDefaultPlugins() .fold(this) { plugins, (defaultPluginClass, defaultPlugin) -> - if (plugins.filterIsInstance(defaultPluginClass).isEmpty()) plugins + defaultPlugin - else plugins + if (plugins.filterIsInstance(defaultPluginClass).isEmpty()) { + plugins + defaultPlugin + } else { + plugins + } } } + + public data class Config( + override var debuggable: Boolean = false, + override var coroutineExceptionHandler: CoroutineExceptionHandler? = null, + ) : PlayerRuntimeConfig() } diff --git a/android/player/src/main/java/com/intuit/player/android/AndroidPlayerPlugin.kt b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayerPlugin.kt similarity index 55% rename from android/player/src/main/java/com/intuit/player/android/AndroidPlayerPlugin.kt rename to android/player/src/main/java/com/intuit/playerui/android/AndroidPlayerPlugin.kt index ec0076ed5..e3f9c5b53 100644 --- a/android/player/src/main/java/com/intuit/player/android/AndroidPlayerPlugin.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayerPlugin.kt @@ -1,6 +1,6 @@ -package com.intuit.player.android +package com.intuit.playerui.android -import com.intuit.player.jvm.core.plugins.Plugin +import com.intuit.playerui.core.plugins.Plugin public interface AndroidPlayerPlugin : Plugin { public fun apply(androidPlayer: AndroidPlayer) diff --git a/android/player/src/main/java/com/intuit/player/android/AssetContext.kt b/android/player/src/main/java/com/intuit/playerui/android/AssetContext.kt similarity index 65% rename from android/player/src/main/java/com/intuit/player/android/AssetContext.kt rename to android/player/src/main/java/com/intuit/playerui/android/AssetContext.kt index adef23107..1d00a0c4e 100644 --- a/android/player/src/main/java/com/intuit/player/android/AssetContext.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/AssetContext.kt @@ -1,13 +1,13 @@ -package com.intuit.player.android +package com.intuit.playerui.android import android.content.Context import androidx.annotation.StyleRes -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.android.extensions.Style -import com.intuit.player.android.extensions.Styles -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.core.player.state.inProgressState +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.android.extensions.Style +import com.intuit.playerui.android.extensions.Styles +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.state.inProgressState /** Structure containing all the information needed to render an [Asset]. */ public data class AssetContext( @@ -35,7 +35,7 @@ public data class AssetContext( factory: (AssetContext) -> RenderableAsset, ) : this(context, asset, player, factory, asset.id) - @Deprecated("Replacing with `with`-style builder calls", ReplaceWith("withTag(tag)", "com.intuit.player.android.AssetContext.withTag")) + @Deprecated("Replacing with `with`-style builder calls", ReplaceWith("withTag(tag)", "com.intuit.playerui.android.AssetContext.withTag")) public fun postFixId(tag: String): AssetContext = withTag(tag) val type: String by lazy { @@ -51,12 +51,16 @@ public fun AssetContext.withContext(context: Context): AssetContext = copy(conte public fun AssetContext.withStyles(@StyleRes vararg styles: Style?): AssetContext = withStyles(styles.filterNotNull()) /** Create a new, styled [AssetContext] */ -public fun AssetContext.withStyles(@StyleRes styles: Styles): AssetContext = if (styles.isEmpty()) this else copy( - context = context?.let { player.getCachedStyledContext(it, styles) } ?: run { - val error = PlayerException("Android context not found! Ensure the asset is rendered with a valid Android context.") - player.inProgressState?.fail(error) - throw error - } -) +public fun AssetContext.withStyles(@StyleRes styles: Styles): AssetContext = if (styles.isEmpty()) { + this +} else { + copy( + context = context?.let { player.getCachedStyledContext(it, styles) } ?: run { + val error = PlayerException("Android context not found! Ensure the asset is rendered with a valid Android context.") + player.inProgressState?.fail(error) + throw error + }, + ) +} public fun AssetContext.build(): RenderableAsset = factory(this) diff --git a/android/player/src/main/java/com/intuit/player/android/Constants.kt b/android/player/src/main/java/com/intuit/playerui/android/Constants.kt similarity index 85% rename from android/player/src/main/java/com/intuit/player/android/Constants.kt rename to android/player/src/main/java/com/intuit/playerui/android/Constants.kt index 803348414..b33b2ea5e 100644 --- a/android/player/src/main/java/com/intuit/player/android/Constants.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/Constants.kt @@ -1,4 +1,4 @@ -package com.intuit.player.android +package com.intuit.playerui.android internal const val TYPE = "type" internal const val METADATA = "metaData" diff --git a/android/player/src/main/java/com/intuit/player/android/asset/DecodableAsset.kt b/android/player/src/main/java/com/intuit/playerui/android/asset/DecodableAsset.kt similarity index 54% rename from android/player/src/main/java/com/intuit/player/android/asset/DecodableAsset.kt rename to android/player/src/main/java/com/intuit/playerui/android/asset/DecodableAsset.kt index 0793f3841..de4b0b992 100644 --- a/android/player/src/main/java/com/intuit/player/android/asset/DecodableAsset.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/asset/DecodableAsset.kt @@ -1,16 +1,23 @@ -package com.intuit.player.android.asset +package com.intuit.playerui.android.asset -import com.intuit.player.android.AssetContext -import com.intuit.player.jvm.core.player.PlayerException +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.core.player.PlayerException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException /** Extension of [RenderableAsset] that provides data decoding through the [serializer] */ @Suppress("DEPRECATION_ERROR") public abstract class DecodableAsset(assetContext: AssetContext, private val serializer: KSerializer) : RenderableAsset(assetContext) { + /** Suspendable way to deserialize an instance of [Data] */ + public suspend fun getData(): Data = withContext(Dispatchers.Default) { + data + } + @Deprecated("Direct access to this property encourages blocking runtime access, use suspendable getData instead to ensure threads aren't blocked on data decoding.", ReplaceWith("getData()")) /** Instance of [Data] is passed to [hydrate] */ - public val data: Data by lazy { + public open val data: Data by lazy { try { asset.deserialize(serializer) } catch (exception: SerializationException) { diff --git a/android/player/src/main/java/com/intuit/player/android/asset/RenderableAsset.kt b/android/player/src/main/java/com/intuit/playerui/android/asset/RenderableAsset.kt similarity index 79% rename from android/player/src/main/java/com/intuit/player/android/asset/RenderableAsset.kt rename to android/player/src/main/java/com/intuit/playerui/android/asset/RenderableAsset.kt index cc79dfb29..2a0d217b3 100644 --- a/android/player/src/main/java/com/intuit/player/android/asset/RenderableAsset.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/asset/RenderableAsset.kt @@ -1,24 +1,29 @@ -package com.intuit.player.android.asset +package com.intuit.playerui.android.asset import android.content.Context import android.view.View import androidx.annotation.StyleRes -import com.intuit.player.android.* -import com.intuit.player.android.DEPRECATED_WITH_DECODABLEASSET -import com.intuit.player.android.extensions.Style -import com.intuit.player.android.extensions.Styles -import com.intuit.player.android.extensions.removeSelf -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.asset.AssetWrapper -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.encoding.requireNodeDecoder -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.core.player.state.fail -import com.intuit.player.jvm.core.player.state.inProgressState -import com.intuit.player.plugins.beacon.beacon -import com.intuit.player.plugins.coroutines.subScope +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.DEPRECATED_WITH_DECODABLEASSET +import com.intuit.playerui.android.build +import com.intuit.playerui.android.extensions.Style +import com.intuit.playerui.android.extensions.Styles +import com.intuit.playerui.android.extensions.removeSelf +import com.intuit.playerui.android.withContext +import com.intuit.playerui.android.withStyles +import com.intuit.playerui.android.withTag +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.asset.AssetWrapper +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.encoding.requireNodeDecoder +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.state.fail +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.plugins.beacon.beacon +import com.intuit.playerui.plugins.coroutines.subScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.serialization.ContextualSerializer @@ -31,6 +36,8 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlin.reflect.KClass +internal typealias CachedAssetView = Pair + /** * [RenderableAsset] is the base class for each asset in an asset tree. * It is the second stage in the transform process. It's most important @@ -60,7 +67,7 @@ public constructor(public val assetContext: AssetContext) : NodeWrapper { * Helper to get the current cached [AssetContext] and [View]. * Will return empty pair if not found. */ - private val cachedAssetView get() = + internal val cachedAssetView: CachedAssetView get() = player.getCachedAssetView(assetContext) ?: cachedAssetViewNotFound /** Main API */ @@ -88,6 +95,12 @@ public constructor(public val assetContext: AssetContext) : NodeWrapper { get() = player.getCachedHydrationScope(assetContext) set(value) = player.cacheHydrationScope(assetContext, value) + internal fun renewHydrationScope(message: String): CoroutineScope { + _hydrationScope?.cancel(message) + _hydrationScope = player.subScope() + return hydrationScope + } + /** * Construct a [View] that represents the asset. * @@ -100,11 +113,14 @@ public constructor(public val assetContext: AssetContext) : NodeWrapper { requireContext() when { // View not found. Create and hydrate. - cachedView == null -> initView().also(::rehydrate) - // View found, but contexts are out of sync. Remove cached view and create and hydrate. + cachedView == null -> { + renewHydrationScope("recreating view") + initView().also { it.hydrate() } + } // View found, but contexts are out of sync. Remove cached view and create and hydrate. cachedAssetContext?.context != context || cachedAssetContext?.asset?.type != asset.type -> { + renewHydrationScope("recreating view") cachedView.removeSelf() - initView().also(::rehydrate) + initView().also { it.hydrate() } } // View found, but assets are out of sync. Rehydrate. It is possible for the hydrate // implementation to throw [StaleViewException] to signify that the view is out of sync. @@ -120,7 +136,7 @@ public constructor(public val assetContext: AssetContext) : NodeWrapper { // View found, everything is in sync. Do nothing. else -> cachedView } - }.also { player.cacheAssetView(assetContext, it) } + }.also { if (it !is SuspendableAsset.AsyncViewStub) player.cacheAssetView(assetContext, it) } /** Invalidate view, causing a complete re-render of the current asset */ public fun invalidateView() { @@ -128,6 +144,12 @@ public constructor(public val assetContext: AssetContext) : NodeWrapper { throw StaleViewException(assetContext) } + /** Private helper for managing scope for hydration */ + private fun rehydrate(view: View) { + renewHydrationScope("rehydrating ${asset.id}") + view.hydrate() + } + /** Instruct a [RenderableAsset] to [rehydrate] */ public fun rehydrate(): Unit = cachedAssetView.let { (_, view) -> try { @@ -137,16 +159,13 @@ public constructor(public val assetContext: AssetContext) : NodeWrapper { } } - /** Private helper for managing scope for hydration */ - private fun rehydrate(view: View) { - _hydrationScope?.cancel("rehydrating ${asset.id}") - _hydrationScope = player.subScope() - view.hydrate() - } - /** * Render the asset using the resulting [Context] of the [AndroidPlayer.Hooks.ContextHook] * called with the provided [context]. + * + * This should only be called from the Activity/Fragment to provide a [context] for [RenderableAsset]s to render with. + * Rendering of nested children assets should instead invoke the contextual [RenderableAsset.render] methods + * to automatically pull [context] from their parents. */ public fun render(context: Context): View = assetContext .withContext(player.hooks.context.call(context)) @@ -235,7 +254,7 @@ public constructor(public val assetContext: AssetContext) : NodeWrapper { action: String, element: String, asset: Asset = this.asset, - data: Any? = null + data: Any? = null, ): Unit = player.beacon(action, element, asset, data) public fun requireContext(): Context = context ?: run { @@ -256,7 +275,7 @@ public constructor(public val assetContext: AssetContext) : NodeWrapper { public class Serializer(private val player: AndroidPlayer) : KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("com.intuit.player.android.asset.RenderableAsset") + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("com.intuit.playerui.android.asset.RenderableAsset") /** Deserialize using the expansion process */ override fun deserialize(decoder: Decoder): RenderableAsset? = decoder.requireNodeDecoder() @@ -270,13 +289,17 @@ public constructor(public val assetContext: AssetContext) : NodeWrapper { throw SerializationException("DecodableAsset.Serializer.serialize is not supported") /** Conform this [Serializer] to cast the expanded asset to [T] */ - public inline fun conform(): KSerializer = object : KSerializer by this as KSerializer { - override fun deserialize(decoder: Decoder): T = this@Serializer.deserialize(decoder) as T - } - - public fun conform(klass: KClass): KSerializer = object : KSerializer by this as KSerializer { - override fun deserialize(decoder: Decoder): T = klass.javaObjectType.cast(this@Serializer.deserialize(decoder))!! - } + public inline fun conform(): KSerializer = object : KSerializer by this as KSerializer { + override fun deserialize(decoder: Decoder) = this@Serializer.deserialize(decoder) as? T + } as KSerializer + + public fun conform(klass: KClass): KSerializer = object : KSerializer by this as KSerializer { + override fun deserialize(decoder: Decoder) = try { + klass.javaObjectType.cast(this@Serializer.deserialize(decoder)) + } catch (e: ClassCastException) { + null + } + } as KSerializer } // Seemingly needed to prevent stack overflow: https://github.com/Kotlin/kotlinx.serialization/issues/1776 diff --git a/android/player/src/main/java/com/intuit/playerui/android/asset/StaleViewException.kt b/android/player/src/main/java/com/intuit/playerui/android/asset/StaleViewException.kt new file mode 100644 index 000000000..b51581663 --- /dev/null +++ b/android/player/src/main/java/com/intuit/playerui/android/asset/StaleViewException.kt @@ -0,0 +1,6 @@ +package com.intuit.playerui.android.asset + +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.core.player.PlayerException + +internal class StaleViewException constructor(val assetContext: AssetContext) : PlayerException("asset[${assetContext.id}] is stale") diff --git a/android/player/src/main/java/com/intuit/playerui/android/asset/SuspendableAsset.kt b/android/player/src/main/java/com/intuit/playerui/android/asset/SuspendableAsset.kt new file mode 100644 index 000000000..6476fb167 --- /dev/null +++ b/android/player/src/main/java/com/intuit/playerui/android/asset/SuspendableAsset.kt @@ -0,0 +1,209 @@ +package com.intuit.playerui.android.asset + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import android.view.View +import android.view.View.OnAttachStateChangeListener +import android.view.ViewGroup +import androidx.core.view.children +import com.intuit.hooks.HookContext +import com.intuit.hooks.SyncHook +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.AndroidPlayerPlugin +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.R +import com.intuit.playerui.android.asset.SuspendableAsset.AsyncHydrationTrackerPlugin +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.core.plugins.findPlugin +import com.intuit.playerui.core.utils.InternalPlayerApi +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.KSerializer +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.coroutineContext + +/** Extension of [DecodableAsset] that provides suspendable [initView] and [hydrate] APIs that will provide an instance of [Data] to use during [View] updates */ +public abstract class SuspendableAsset(assetContext: AssetContext, serializer: KSerializer) : DecodableAsset(assetContext, serializer) { + + // To be launched in Dispatchers.Default + public abstract suspend fun initView(data: Data): View + + final override fun initView(): View { + // ensure we pre-track hydration to ensure all assets are accounted for during async hydration + player.asyncHydrationTrackerPlugin?.trackHydration(this@SuspendableAsset) + return AsyncViewStub( + hydrationScope, + hydrationScope.async { doInitView() }, + requireContext(), + ) { doHydrate(); player.cacheAssetView(assetContext, this) } + } + + private suspend fun doInitView() = withContext(Dispatchers.Default) { + initView(getData()).apply { setTag(R.bool.view_hydrated, false) } + } + + // To be launched in Dispatchers.Main + public abstract suspend fun View.hydrate(data: Data) + + final override fun View.hydrate() { + if (this is AsyncViewStub) return + + player.asyncHydrationTrackerPlugin?.trackHydration(this@SuspendableAsset) + setTag(R.bool.view_hydrated, false) + hydrationScope.launch(Dispatchers.Main) { doHydrate() } + } + + private suspend fun View.doHydrate() = withContext(Dispatchers.Main) { + try { + hydrate(getData()) + setTag(R.bool.view_hydrated, true) + } catch (exception: StaleViewException) { + // b/c we're launched in a scope that isn't cared about anymore, we can't appropriately handle this, so just fast fail + player.inProgressState?.fail(PlayerException("SuspendableAssets can't appropriately handle invalidateViews currently, this should be handled in a future major", exception)) + } finally { + player.asyncHydrationTrackerPlugin?.hydrationDone(this@SuspendableAsset) + } + } + + @ExperimentalPlayerApi + public class AsyncHydrationTrackerPlugin : AndroidPlayerPlugin { + private var trackedHydrations = mutableSetOf() + + public val hooks: Hooks = Hooks() + + public fun trackHydration(asset: SuspendableAsset<*>) { + synchronized(trackedHydrations) { + if (trackedHydrations.isEmpty()) hooks.onHydrationStarted.call() + trackedHydrations.add(asset.assetContext.id) + } + } + + public fun hydrationDone(asset: SuspendableAsset<*>) { + val doneHydrating = synchronized(trackedHydrations) { + trackedHydrations.remove(asset.assetContext.id) + trackedHydrations.isEmpty() + } + + if (doneHydrating) { + hooks.onHydrationComplete.call() + } + } + + override fun apply(androidPlayer: AndroidPlayer) { + androidPlayer.onUpdate { _, _ -> + synchronized(trackedHydrations) { + trackedHydrations.clear() + } + } + } + + public class Hooks { + public class OnHydrationStartedHook : SyncHook<(HookContext) -> Unit>() { + public fun call(): Unit = super.call { f, context -> + f(context) + } + } + + public class OnHydrationCompleteHook : SyncHook<(HookContext) -> Unit>() { + public fun call(): Unit = super.call { f, context -> + f(context) + } + } + + public val onHydrationStarted: OnHydrationStartedHook = OnHydrationStartedHook() + public val onHydrationComplete: OnHydrationCompleteHook = OnHydrationCompleteHook() + } + } + + /** ViewStub derivative that will replace itself in the view tree once the [view] has resolved */ + @SuppressLint("ViewConstructor") + @InternalPlayerApi + public class AsyncViewStub @JvmOverloads constructor( + private val scope: CoroutineScope, + private val view: Deferred, + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, + private val onView: suspend View.() -> Unit = {}, + ) : View(context, attrs, defStyle), OnAttachStateChangeListener { + + private val hydratedView: Deferred = scope.async { + view.await().also { onView(it) } + } + + init { + addOnAttachStateChangeListener(this) + } + + /** Suspend until there is a hydrated view, or returns null if the provided [scope] is cancelled */ + public suspend fun awaitView(): View? = try { + suspend fun ViewGroup.awaitAsyncChildren() { + children.forEach { + when (it) { + is AsyncViewStub -> it.awaitView() + is ViewGroup -> it.awaitAsyncChildren() + } + } + } + + hydratedView.await().also { parent -> + (parent as? ViewGroup)?.awaitAsyncChildren() + } + } catch (e: CancellationException) { + // if it was the calling scope that is cancelled, this will re-raise + coroutineContext.ensureActive() + null + } + + /** Callback handler for when there is a hydrated view */ + public fun onView(handler: (View) -> Unit) { + scope.launch { + awaitView()?.let(handler) + } + } + + override fun onViewAttachedToWindow(v: View) { + scope.launch(Dispatchers.Main) { + awaitView()?.let(::replaceSelfWithView) + } + } + + override fun onViewDetachedFromWindow(v: View) {} + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + setMeasuredDimension(0, 0) + } + + @SuppressLint("MissingSuperCall") + override fun onAttachedToWindow() {} + + @SuppressLint("MissingSuperCall") + override fun draw(canvas: Canvas?) {} + + override fun dispatchDraw(canvas: Canvas?) {} + + private fun replaceSelfWithView(view: View) { + val parent = parent as? ViewGroup ?: return + val index = parent.indexOfChild(this) + parent.removeViewInLayout(this) + val layoutParams = layoutParams + if (layoutParams != null) { + parent.addView(view, index, layoutParams) + } else { + parent.addView(view, index) + } + removeOnAttachStateChangeListener(this) + } + } +} + +public val AndroidPlayer.asyncHydrationTrackerPlugin: AsyncHydrationTrackerPlugin? get() = findPlugin() diff --git a/android/player/src/main/java/com/intuit/playerui/android/debug/UnsupportedScriptProvider.kt b/android/player/src/main/java/com/intuit/playerui/android/debug/UnsupportedScriptProvider.kt new file mode 100644 index 000000000..8eb58b69a --- /dev/null +++ b/android/player/src/main/java/com/intuit/playerui/android/debug/UnsupportedScriptProvider.kt @@ -0,0 +1,14 @@ +package com.intuit.playerui.android.debug + +import com.alexii.j2v8debugger.ScriptSourceProvider +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.player.PlayerException + +internal class UnsupportedScriptProvider(private val runtime: Runtime<*>) : ScriptSourceProvider { + override val allScriptIds: Collection + get() = throw PlayerException("Unsupported exception, $runtime runtime does not support JS debugging") + + override fun getSource(scriptId: String): String { + throw PlayerException("Unsupported exception, $runtime runtime does not support JS debugging") + } +} diff --git a/android/player/src/main/java/com/intuit/player/android/extensions/into.kt b/android/player/src/main/java/com/intuit/playerui/android/extensions/Into.kt similarity index 87% rename from android/player/src/main/java/com/intuit/player/android/extensions/into.kt rename to android/player/src/main/java/com/intuit/playerui/android/extensions/Into.kt index 67eeac788..3d1c868f3 100644 --- a/android/player/src/main/java/com/intuit/player/android/extensions/into.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/extensions/Into.kt @@ -1,4 +1,4 @@ -package com.intuit.player.android.extensions +package com.intuit.playerui.android.extensions import android.view.View import android.view.ViewGroup @@ -36,7 +36,10 @@ public infix fun View?.into(root: FrameLayout) { public fun View?.transitionInto(root: FrameLayout, transition: Transition?) { root.removeAllViews() if (this == null) { - root.visibility = View.GONE + if (root.visibility != View.GONE) { + root.visibility = View.GONE + root.removeAllViews() + } } else { root.visibility = View.VISIBLE if (!root.children.contains(this)) { @@ -78,8 +81,10 @@ public infix fun View?.into(root: ViewGroup) { public infix fun List.into(root: ViewGroup) { val filtered = filterNotNull() if (filtered.isEmpty()) { - root.visibility = View.GONE - root.removeAllViews() + if (root.visibility != View.GONE) { + root.visibility = View.GONE + root.removeAllViews() + } } else { root.visibility = View.VISIBLE @@ -98,6 +103,6 @@ public infix fun List.into(root: ViewGroup) { } // Remove any leftover views - while (root.childCount > size) { root.removeViewAt(root.childCount - 1) } + while (root.childCount > filtered.size) { root.removeViewAt(root.childCount - 1) } } } diff --git a/android/player/src/main/java/com/intuit/player/android/extensions/overlayStyles.kt b/android/player/src/main/java/com/intuit/playerui/android/extensions/OverlayStyles.kt similarity index 90% rename from android/player/src/main/java/com/intuit/player/android/extensions/overlayStyles.kt rename to android/player/src/main/java/com/intuit/playerui/android/extensions/OverlayStyles.kt index 522785aaf..d65fc17b7 100644 --- a/android/player/src/main/java/com/intuit/player/android/extensions/overlayStyles.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/extensions/OverlayStyles.kt @@ -1,4 +1,4 @@ -package com.intuit.player.android.extensions +package com.intuit.playerui.android.extensions import android.content.Context import androidx.annotation.StyleRes @@ -22,7 +22,7 @@ private fun Style?.toStyles() = this?.let { public fun Context.overlayStyles( @StyleRes vararg additionalStyles: Style, - @StyleRes baseStyle: Style? + @StyleRes baseStyle: Style?, ): Context = overlayStyles(baseStyle.toStyles(), additionalStyles.toList()) public fun Context.overlayStyles(@StyleRes vararg additionalStyles: Style): Context = @@ -37,6 +37,6 @@ public fun Context.overlayStyles(@StyleRes vararg additionalStyles: Style): Cont */ public fun Context.overlayStyles( @StyleRes baseStyles: Styles = emptyList(), - @StyleRes additionalStyles: Styles = emptyList() + @StyleRes additionalStyles: Styles = emptyList(), ): Context = (baseStyles + additionalStyles) .fold(this, ::ContextThemeWrapper) diff --git a/android/player/src/main/java/com/intuit/player/android/extensions/removeSelf.kt b/android/player/src/main/java/com/intuit/playerui/android/extensions/RemoveSelf.kt similarity index 81% rename from android/player/src/main/java/com/intuit/player/android/extensions/removeSelf.kt rename to android/player/src/main/java/com/intuit/playerui/android/extensions/RemoveSelf.kt index 12a0491a0..16694758c 100644 --- a/android/player/src/main/java/com/intuit/player/android/extensions/removeSelf.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/extensions/RemoveSelf.kt @@ -1,4 +1,4 @@ -package com.intuit.player.android.extensions +package com.intuit.playerui.android.extensions import android.view.View import android.view.ViewManager diff --git a/android/player/src/main/java/com/intuit/player/android/lifecycle/ManagedPlayerState.kt b/android/player/src/main/java/com/intuit/playerui/android/lifecycle/ManagedPlayerState.kt similarity index 84% rename from android/player/src/main/java/com/intuit/player/android/lifecycle/ManagedPlayerState.kt rename to android/player/src/main/java/com/intuit/playerui/android/lifecycle/ManagedPlayerState.kt index f3b47be69..832c1aed7 100644 --- a/android/player/src/main/java/com/intuit/player/android/lifecycle/ManagedPlayerState.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/lifecycle/ManagedPlayerState.kt @@ -1,21 +1,25 @@ -package com.intuit.player.android.lifecycle +package com.intuit.playerui.android.lifecycle -import com.intuit.player.android.AndroidPlayer -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.jvm.core.managed.AsyncIterationManager -import com.intuit.player.jvm.core.player.state.CompletedState -import com.intuit.player.jvm.core.player.state.PlayerFlowState +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.core.managed.AsyncIterationManager +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.PlayerFlowState /** State of a managed player which serves as an aggregation of the players [state][PlayerFlowState] and the managers [state][AsyncIterationManager.State] */ public sealed class ManagedPlayerState { /** Initial state of the [PlayerViewModel] */ public object NotStarted : ManagedPlayerState() + /** State that represents any error encountered when instantiating a player, retrieving a flow, or running a flow */ public data class Error(public val exception: Exception) : ManagedPlayerState() + /** [Pending] represents the time spent retrieving a flow, either before any flow or after the [AndroidPlayer] reaches the [CompletedState] */ public object Pending : ManagedPlayerState() + /** State containing the current [asset] representation of the current in-progress flow */ public data class Running(public val asset: RenderableAsset?, public val animateViewTransition: Boolean) : ManagedPlayerState() + /** The [PlayerViewModel] reaches the [Done] state once the [PlayerViewModel.manager] has no more flows to produce */ public data class Done(public val completedState: CompletedState?) : ManagedPlayerState() diff --git a/android/player/src/main/java/com/intuit/player/android/lifecycle/PlayerViewModel.kt b/android/player/src/main/java/com/intuit/playerui/android/lifecycle/PlayerViewModel.kt similarity index 62% rename from android/player/src/main/java/com/intuit/player/android/lifecycle/PlayerViewModel.kt rename to android/player/src/main/java/com/intuit/playerui/android/lifecycle/PlayerViewModel.kt index 5d0b553a6..f9d0c3369 100644 --- a/android/player/src/main/java/com/intuit/player/android/lifecycle/PlayerViewModel.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/lifecycle/PlayerViewModel.kt @@ -1,28 +1,46 @@ -package com.intuit.player.android.lifecycle +package com.intuit.playerui.android.lifecycle import android.app.Application import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import com.intuit.player.android.AndroidPlayer -import com.intuit.player.android.AndroidPlayerPlugin -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.experimental.ExperimentalPlayerApi -import com.intuit.player.jvm.core.managed.AsyncFlowIterator -import com.intuit.player.jvm.core.managed.AsyncIterationManager -import com.intuit.player.jvm.core.managed.FlowManager -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.core.player.state.* -import com.intuit.player.jvm.core.plugins.Plugin -import com.intuit.player.jvm.core.plugins.RuntimePlugin -import com.intuit.player.plugins.beacon.onBeacon -import kotlinx.coroutines.flow.* +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.AndroidPlayerPlugin +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.managed.AsyncFlowIterator +import com.intuit.playerui.core.managed.AsyncIterationManager +import com.intuit.playerui.core.managed.FlowManager +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.player.state.NotStartedState +import com.intuit.playerui.core.player.state.ReleasedState +import com.intuit.playerui.core.player.state.completedState +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.core.plugins.Plugin +import com.intuit.playerui.core.plugins.RuntimePlugin +import com.intuit.playerui.plugins.beacon.onBeacon +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking /** * Android lifecycle-aware player manager that integrates and manages - * state between a [FlowManager] and an [AndroidPlayer]. This sta + * state between a [FlowManager] and an [AndroidPlayer]. * * As-is, this bare-bones implementation does not include any additional * plugins, meaning that any flows will not actually expand into @@ -45,21 +63,29 @@ public open class PlayerViewModel(flows: AsyncFlowIterator) : ViewModel(), Andro */ protected open val plugins: List = emptyList() - // TODO: accessing non-final fields in constructor -- must not use player before init is done - // This could be fixed by requiring [plugins] be in the constructor, but this is kinda - // backwards since subclasses are required to configure plugin behavior manually. Maybe - // apps just supply a meta plugin to configure things? Although, the reason for configuring - // plugins here is to potentially hook into the fragment. External actions is a great - // example, where the app needs to actually load a different native experience outside the - // player scope. Not sure how to solve this. Maybe a player factory instead that requires - // the [PlayerViewModel] to be finished initialization? - protected val player: AndroidPlayer by lazy { - AndroidPlayer(plugins + this) + protected open val config: AndroidPlayer.Config = AndroidPlayer.Config() + + @ExperimentalPlayerApi + public val deferredPlayer: Deferred = viewModelScope.async(Dispatchers.Default) { + // this is unfortunate, but is essentially for ensuring view model has completely initialized + while (plugins == null) { delay(5) } + AndroidPlayer(plugins + this@PlayerViewModel, config) + } + + @OptIn(ExperimentalCoroutinesApi::class, ExperimentalPlayerApi::class) + public val player: AndroidPlayer by lazy { + if (deferredPlayer.isCompleted) { + deferredPlayer.getCompleted() + } else { + runBlocking { + deferredPlayer.await() + } + } } protected val manager: FlowManager = FlowManager(flows) - private lateinit var runtime: Runtime<*> + private var runtime: Runtime<*>? = null private var _state = MutableStateFlow(ManagedPlayerState.NotStarted) private val _beacons = MutableSharedFlow() @@ -69,7 +95,7 @@ public open class PlayerViewModel(flows: AsyncFlowIterator) : ViewModel(), Andro init { // next() TODO: If we fix the non-final field error, we can prefetch here - viewModelScope.launch { + viewModelScope.launch(Dispatchers.Default) { manager.state.collect { when (it) { AsyncIterationManager.State.NotStarted -> _state.emit(ManagedPlayerState.NotStarted) @@ -86,11 +112,11 @@ public open class PlayerViewModel(flows: AsyncFlowIterator) : ViewModel(), Andro when { it.isSuccess -> player.logger.info( "Flow completed successfully!", - it.getOrNull()?.endState + it.getOrNull()?.endState, ) it.isFailure -> player.logger.error( "Error in Flow!", - it.exceptionOrNull()?.stackTraceToString() + it.exceptionOrNull(), ) } } @@ -114,6 +140,7 @@ public open class PlayerViewModel(flows: AsyncFlowIterator) : ViewModel(), Andro // which will either start a new flow or transition to done is CompletedState -> manager.next(state) is ErrorState -> _state.tryEmit(ManagedPlayerState.Error(state.error)) + is InProgressState, ReleasedState, null -> Unit } } } @@ -122,20 +149,21 @@ public open class PlayerViewModel(flows: AsyncFlowIterator) : ViewModel(), Andro this.runtime = runtime } - override fun onCleared() { - runtime.scope.launch { - if (manager.state.value != AsyncIterationManager.State.Done) manager.iterator.terminate() - release() + public override fun onCleared() { + if (manager.state.value != AsyncIterationManager.State.Done) { + runBlocking { + manager.iterator.terminate() + } } + + release() } public fun recycle() { - player.logger.debug("PlayerViewModel: recycling player") player.recycle() } public fun release() { - player.logger.debug("PlayerViewModel: releasing player") player.release() } @@ -153,12 +181,19 @@ public open class PlayerViewModel(flows: AsyncFlowIterator) : ViewModel(), Andro when (state.value) { ManagedPlayerState.NotStarted -> manager.next() is ManagedPlayerState.Error, - is ManagedPlayerState.Running -> when (val currentFlow = manager.state.value) { + is ManagedPlayerState.Running, + -> when (manager.state.value) { AsyncIterationManager.State.NotStarted -> manager.next() - is AsyncIterationManager.State.Item<*> -> start(currentFlow.value as String) - // try to re-retrieve the next flow from the previous state - is AsyncIterationManager.State.Error -> manager.next(player.completedState) + is AsyncIterationManager.State.Item<*>, + is AsyncIterationManager.State.Error, + -> manager.next(player.completedState) + AsyncIterationManager.State.Done, + AsyncIterationManager.State.Pending, + -> Unit } + is ManagedPlayerState.Done, + ManagedPlayerState.Pending, + -> Unit } } @@ -174,7 +209,7 @@ public open class PlayerViewModel(flows: AsyncFlowIterator) : ViewModel(), Andro /** Generic [ViewModelProvider.AndroidViewModelFactory] to conveniently construct some [T] with an [Application] and [AsyncFlowIterator] */ public class Factory( private val iterator: AsyncFlowIterator, - private val factory: (AsyncFlowIterator) -> T = { i -> PlayerViewModel(i) as T } + private val factory: (AsyncFlowIterator) -> T = { i -> PlayerViewModel(i) as T }, ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { diff --git a/android/player/src/main/java/com/intuit/player/android/logger/AndroidLogger.kt b/android/player/src/main/java/com/intuit/playerui/android/logger/AndroidLogger.kt similarity index 87% rename from android/player/src/main/java/com/intuit/player/android/logger/AndroidLogger.kt rename to android/player/src/main/java/com/intuit/playerui/android/logger/AndroidLogger.kt index 5c1ed2ff0..2944f950f 100644 --- a/android/player/src/main/java/com/intuit/player/android/logger/AndroidLogger.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/logger/AndroidLogger.kt @@ -1,7 +1,7 @@ -package com.intuit.player.android.logger +package com.intuit.playerui.android.logger import android.util.Log -import com.intuit.player.jvm.core.plugins.LoggerPlugin +import com.intuit.playerui.core.plugins.LoggerPlugin /** Default Android logger */ internal class AndroidLogger(val name: String = TAG) : LoggerPlugin { diff --git a/android/player/src/main/java/com/intuit/player/android/registry/RegistryPlugin.kt b/android/player/src/main/java/com/intuit/playerui/android/registry/RegistryPlugin.kt similarity index 68% rename from android/player/src/main/java/com/intuit/player/android/registry/RegistryPlugin.kt rename to android/player/src/main/java/com/intuit/playerui/android/registry/RegistryPlugin.kt index 20e188142..e7d3dc646 100644 --- a/android/player/src/main/java/com/intuit/player/android/registry/RegistryPlugin.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/registry/RegistryPlugin.kt @@ -1,8 +1,9 @@ -package com.intuit.player.android.registry +package com.intuit.playerui.android.registry -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.plugins.JSPluginWrapper +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.plugins.JSPluginWrapper internal class RegistryPlugin : JSPluginWrapper { @@ -15,13 +16,13 @@ internal class RegistryPlugin : JSPluginWrapper { runtime.execute(readSource("plugins/partial-match-fingerprint/core/dist/partial-match-fingerprint-plugin.prod.js")) runtime.execute(readSource("core/partial-match-registry/dist/partial-match-registry.prod.js")) instance = runtime.execute( - """(new PartialMatchFingerprintPlugin.PartialMatchFingerprintPlugin(new Registry.Registry()))""" + """(new PartialMatchFingerprintPlugin.PartialMatchFingerprintPlugin(new Registry.Registry()))""", ) as Node } fun register(match: Map, value: T) { registry.add(value) - instance.getFunction("register")!!.invoke(match, registry.size - 1) + instance.getInvokable("register")!!.invoke(match, registry.size - 1) } /** @@ -31,7 +32,7 @@ internal class RegistryPlugin : JSPluginWrapper { */ operator fun get(id: String): T? = getAssetIndex(id)?.let(registry::getOrNull) - private fun getAssetIndex(id: String): Int? = instance.getFunction("get")!!.invoke(id) as? Int + private fun getAssetIndex(id: String): Int? = instance.getInvokable("get")!!.invoke(id) as? Int private fun readSource(source: String) = RegistryPlugin::class.java.classLoader!! .getResource(source) diff --git a/android/player/src/main/java/com/intuit/playerui/android/ui/PlayerFragment.kt b/android/player/src/main/java/com/intuit/playerui/android/ui/PlayerFragment.kt new file mode 100644 index 000000000..2c016f566 --- /dev/null +++ b/android/player/src/main/java/com/intuit/playerui/android/ui/PlayerFragment.kt @@ -0,0 +1,224 @@ +package com.intuit.playerui.android.ui + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ProgressBar +import androidx.core.view.doOnLayout +import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle.State +import androidx.lifecycle.LifecycleCoroutineScope +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.whenStarted +import androidx.transition.Transition +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.android.asset.SuspendableAsset +import com.intuit.playerui.android.databinding.FallbackViewBinding +import com.intuit.playerui.android.databinding.PlayerFragmentBinding +import com.intuit.playerui.android.extensions.into +import com.intuit.playerui.android.extensions.transitionInto +import com.intuit.playerui.android.lifecycle.ManagedPlayerState +import com.intuit.playerui.android.lifecycle.ManagedPlayerState.Done +import com.intuit.playerui.android.lifecycle.ManagedPlayerState.Error +import com.intuit.playerui.android.lifecycle.ManagedPlayerState.NotStarted +import com.intuit.playerui.android.lifecycle.ManagedPlayerState.Pending +import com.intuit.playerui.android.lifecycle.ManagedPlayerState.Running +import com.intuit.playerui.android.lifecycle.PlayerViewModel +import com.intuit.playerui.android.lifecycle.fail +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.managed.AsyncFlowIterator +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.plus +import kotlinx.coroutines.withContext + +/** + * [Fragment] wrapper integration with the [AndroidPlayer]. Delegates + * to an implementation of the [PlayerViewModel] for player management, + * including hooking the player up to the fragment lifecycle. + * + * Subclasses will need to provide a [PlayerViewModel] implementation, + * which requires an [AsyncFlowIterator]. For pre-defined flow use case, + * the subclass can use the [AsyncFlowIterator] pseudo constructor + * for convenience. + * + * // TODO: Check this + * By default, the [PlayerViewModel] will be started as soon as the + * [Context] is attached to the [Fragment] in [onAttach]. When + * [onDestroyView] is invoked, [PlayerViewModel.recycle] is called to + * clear any Android lifecycle specific cached data. + * + * Additionally, this automatically observes all [ManagedPlayerState] + * changes and calls into the corresponding [ManagedPlayerState.Listener] + * handlers. [buildLoadingView] and [buildFallbackView] are used to + * build views for when the [PlayerViewModel] is pending the next view + * or encountered an error. These methods do have default implementations + * to enable consumers to plug-n-play, but can be overridden to customize + * the UI in these states. + */ +public abstract class PlayerFragment : Fragment(), ManagedPlayerState.Listener { + + /** [LifecycleCoroutineScope.launchWhenStarted] extension for waiting on [AndroidPlayer] instance */ + @ExperimentalPlayerApi + protected fun LifecycleCoroutineScope.launchWhenReady(block: suspend CoroutineScope.(player: AndroidPlayer) -> Unit) { + launchWhenStarted { + block(playerViewModel.deferredPlayer.await()) + } + } + + private var _binding: PlayerFragmentBinding? = null + + /** + * [PlayerFragmentBinding] instance + * This property is only valid between onCreateView and onDestroyView. + * Will throw a NPE if called out of turn. + */ + protected val binding: PlayerFragmentBinding get() = _binding!! + + /** + * [ViewModel][androidx.lifecycle.ViewModel] responsible for managing an [AndroidPlayer] + * with respect to a specific [FlowManager][com.intuit.playerui.core.managed.FlowManager]. + */ + public abstract val playerViewModel: PlayerViewModel + + init { + lifecycleScope.launch { + repeatOnLifecycle(State.STARTED) { + // forward state events to callbacks + playerViewModel.state.onEach { + when (it) { + NotStarted -> onNotStarted() + Pending -> onPending() + is Running -> onRunning(it) + is Error -> onError(it) + is Done -> onDone(it) + } + }.launchIn(this + Dispatchers.Default) + + // update UI for latest state + playerViewModel.state.collectLatest { + when (it) { + NotStarted, Pending -> buildLoadingView() into binding.playerCanvas + is Running -> try { + handleAssetUpdate(it.asset, it.animateViewTransition) + } catch (exception: Exception) { + if (exception is CancellationException) throw exception + exception.printStackTrace() + playerViewModel.fail("Error rendering asset", exception) + } + + is Error -> buildFallbackView(it.exception) into binding.playerCanvas + is Done -> buildDoneView() into binding.playerCanvas + } + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View = PlayerFragmentBinding.inflate(inflater, container, false).run { + _binding = this + root + } + + override fun onDestroyView() { + // recycle views + _binding = null + playerViewModel.recycle() + super.onDestroyView() + } + + /** Reset the [PlayerViewModel.manager] from the beginning */ + public fun reset() { + playerViewModel.start() + } + + /** Default suspendable implementation of [handleAssetUpdate] */ + @ExperimentalPlayerApi + protected open suspend fun renderIntoPlayerCanvas(asset: RenderableAsset?, animateTransition: Boolean) { + val startTime = System.currentTimeMillis() + val view = asset?.render(requireContext())?.let { + // unwrap if we know we have an async view stub, and just wait on the actual view + if (it is SuspendableAsset.AsyncViewStub) it.awaitView() else it + } + + view?.doOnLayout { + playerViewModel.logRenderTime(asset, System.currentTimeMillis() - startTime) + } + + // swap to main + withContext(Dispatchers.Main) { + if (asset is RenderableAsset.ViewportAsset) binding.scrollContainer.isFillViewport = true + + animateTransition + .takeIf { it } + ?.let { binding.scrollContainer.scrollTo(0, 0) } + ?.let { buildTransitionAnimation() } + ?.let { view.transitionInto(binding.playerCanvas, it) } + ?: (view into binding.playerCanvas) + } + } + + /** + * Handle [asset] updates from the [PlayerViewModel]. By default, + * this will invoke [RenderableAsset.render] with no additional + * styles and inject that into the view tree. + */ + protected open fun handleAssetUpdate(asset: RenderableAsset?, animateTransition: Boolean) { + lifecycleScope.launch(if (asset is SuspendableAsset<*>) Dispatchers.Default else Dispatchers.Main) { + whenStarted { // TODO: This'll go away when we can call a suspend version of this + try { + renderIntoPlayerCanvas(asset, animateTransition) + } catch (exception: Exception) { + if (exception is CancellationException) throw exception + exception.printStackTrace() + playerViewModel.fail("Error rendering asset", exception) + } + } + } + } + + public open fun buildTransitionAnimation(): Transition? = null + + /** + * Builder method to provide a [View] to be shown when the [PlayerViewModel] is loading, which can be either + * [NotStarted][ManagedPlayerState.NotStarted] or[Pending][ManagedPlayerState.Pending] the next flow. + * Defaults to simple [ProgressBar]. + */ + public open fun buildLoadingView(): View? = ProgressBar(context) + + /** + * Builder method to provide a fallback [View] to be shown when the + * [PlayerViewModel] encounters an [exception]. Defaults to an instance of [FallbackViewBinding]. + */ + public open fun buildFallbackView(exception: Exception): View? = + FallbackViewBinding.inflate(layoutInflater).apply { + this.error.text = exception.localizedMessage + + retry.setOnClickListener { + playerViewModel.retry() + } + + reset.setOnClickListener { + reset() + } + }.root + + /** + * Builder method to provide a [View] to be shown when the [PlayerViewModel] + * finishes. Defaults to null. + */ + public open fun buildDoneView(): View? = null +} diff --git a/android/player/src/main/res/layout/default_fallback.xml b/android/player/src/main/res/layout/fallback_view.xml similarity index 100% rename from android/player/src/main/res/layout/default_fallback.xml rename to android/player/src/main/res/layout/fallback_view.xml diff --git a/android/player/src/main/res/layout/fragment_player.xml b/android/player/src/main/res/layout/player_fragment.xml similarity index 100% rename from android/player/src/main/res/layout/fragment_player.xml rename to android/player/src/main/res/layout/player_fragment.xml diff --git a/android/player/src/main/res/values/tags.xml b/android/player/src/main/res/values/tags.xml new file mode 100644 index 000000000..2deaa9c28 --- /dev/null +++ b/android/player/src/main/res/values/tags.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/player/src/test/java/android/util/Log.kt b/android/player/src/test/java/android/util/Log.kt index b4f6a03dd..6c779bc31 100644 --- a/android/player/src/test/java/android/util/Log.kt +++ b/android/player/src/test/java/android/util/Log.kt @@ -2,37 +2,56 @@ package android.util -internal var e: String? = null +internal var e: MutableList = mutableListOf() internal fun e(tag: String, msg: String): Int { - e = "ERROR: $tag: $msg" - println(e) + val message = "ERROR: $tag: $msg" + e.add(message) + println(message) return 0 } -internal var w: String? = null +internal var w: MutableList = mutableListOf() internal fun w(tag: String, msg: String): Int { - w = "WARN: $tag: $msg" - println(w) + val message = "WARN: $tag: $msg" + w.add(message) + println(message) return 0 } -internal var i: String? = null +internal var i: MutableList = mutableListOf() internal fun i(tag: String, msg: String): Int { - i = "INFO: $tag: $msg" - println(i) + val message = "INFO: $tag: $msg" + i.add(message) + println(message) return 0 } -internal var d: String? = null +internal var d: MutableList = mutableListOf() internal fun d(tag: String, msg: String): Int { - d = "DEBUG: $tag: $msg" - println(d) + val message = "DEBUG: $tag: $msg" + d.add(message) + println(message) return 0 } -internal var t: String? = null -internal fun t(tag: String, msg: String): Int { - d = "DEBUG: $tag: $msg" - println(d) +internal var v: MutableList = mutableListOf() +internal fun v(tag: String, msg: String): Int { + val message = "TRACE: $tag: $msg" + v.add(message) + println(message) return 0 } + +internal fun clearLogs() = listOf(e, w, i, d, v).forEach { it.clear() } + +internal enum class Level { + Error, Warn, Info, Debug, Verbose; + + fun getLogs(): List = when (this) { + Error -> e + Warn -> w + Info -> i + Debug -> d + Verbose -> v + } +} diff --git a/android/player/src/test/java/com/intuit/player/android/renderer/NestedAssetTest.kt b/android/player/src/test/java/com/intuit/player/android/renderer/NestedAssetTest.kt deleted file mode 100644 index 7d501e279..000000000 --- a/android/player/src/test/java/com/intuit/player/android/renderer/NestedAssetTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.intuit.player.android.renderer - -import android.widget.LinearLayout -import com.intuit.player.android.AndroidPlayer -import com.intuit.player.android.AssetContext -import com.intuit.player.android.utils.NestedAsset -import com.intuit.player.android.utils.SimpleAsset -import com.intuit.player.android.utils.awaitFirstView -import com.intuit.player.jvm.utils.test.runBlockingTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test - -internal class NestedAssetTest : BaseRenderableAssetTest() { - - override val asset get() = NestedAsset.sampleAsset - - override val player get() = AndroidPlayer(beaconPlugin).apply { - registerAsset("simple", ::SimpleAsset) - registerAsset("nested", ::NestedAsset) - } - - override val assetContext: AssetContext by lazy { - AssetContext(mockContext, asset, player, ::NestedAsset) - } - - @Test - fun `tested nested asset constructs`() { - val nested = NestedAsset(assetContext).render(mockContext) - assertTrue(nested is LinearLayout) - } - - @Test - fun `test nested asset context`() = runBlockingTest { - val asset = player.awaitFirstView(NestedAsset.sampleFlow)!! as NestedAsset - asset.render(mockContext) - assertEquals(mockContext, NestedAsset.dummy?.context) - NestedAsset.dummy2?.forEach { - assertEquals(mockContext, it?.context) - } ?: Unit - } -} diff --git a/android/player/src/test/java/com/intuit/player/android/renderer/SimpleAssetTest.kt b/android/player/src/test/java/com/intuit/player/android/renderer/SimpleAssetTest.kt deleted file mode 100644 index a36a4ba9f..000000000 --- a/android/player/src/test/java/com/intuit/player/android/renderer/SimpleAssetTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.intuit.player.android.renderer - -import android.widget.TextView -import com.intuit.player.android.AssetContext -import com.intuit.player.android.utils.SimpleAsset -import com.intuit.player.android.utils.SimpleAsset.Companion.sampleFlow -import com.intuit.player.android.utils.stringify -import com.intuit.player.android.withContext -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test - -internal class SimpleAssetTest : BaseRenderableAssetTest() { - - override val asset get() = SimpleAsset.sampleAsset - - override val assetContext: AssetContext by lazy { - AssetContext(mockContext, asset, player, ::SimpleAsset) - } - - @Test - fun `test rendering with no styles`() { - val simple = SimpleAsset(assetContext).render(mockContext) - assertTrue(simple is TextView) - } - - @Test - fun `test rendering with tag`() { - player.start(sampleFlow.stringify()) - - val asset = SimpleAsset(assetContext.withContext(mockContext)) - - with(asset) { - val firstView = asset.render() - assertTrue(firstView is TextView) - - val secondView = asset.render("tag") - assertTrue(secondView is TextView) - - assertEquals(secondView, asset.render("tag")) - assertNotEquals(firstView, secondView) - } - } - - @Test - fun `test beacon helper`() { - val simple = SimpleAsset(assetContext) - simple.beacon("viewed", "button") - assertEquals(BeaconArgs("viewed", "button", asset), lastBeaconed) - } - - @Test - fun `test beacon helper with custom ID`() { - val simple = SimpleAsset(assetContext) - simple.beacon("viewed", "button", data = "custom-id") - assertEquals(BeaconArgs("viewed", "button", asset, "custom-id"), lastBeaconed) - } -} diff --git a/android/player/src/test/java/com/intuit/player/android/utils/OtherSimpleAsset.kt b/android/player/src/test/java/com/intuit/player/android/utils/OtherSimpleAsset.kt deleted file mode 100644 index 9ce122875..000000000 --- a/android/player/src/test/java/com/intuit/player/android/utils/OtherSimpleAsset.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.intuit.player.android.utils - -import android.view.View -import android.widget.ImageView -import com.intuit.player.android.AssetContext -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.runtime.serialize -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.j2v8.bridge.runtime.J2V8 -import com.intuit.player.jvm.utils.makeFlow -import kotlinx.serialization.json.Json - -@Suppress("DEPRECATION_ERROR") -internal class OtherSimpleAsset(assetContext: AssetContext) : RenderableAsset(assetContext) { - - override fun initView() = ImageView(context) - - override fun View.hydrate() = Unit - - companion object { - val sampleMap = mapOf( - "id" to "some-id", - "type" to "simple", - "metaData" to mapOf("role" to "other") - ) - val sampleAsset: Asset = J2V8.create().serialize(sampleMap) as Asset - val sampleJson = Json.encodeToJsonElement(GenericSerializer(), sampleMap) - val sampleFlow = makeFlow(sampleJson) - } -} diff --git a/android/player/src/test/java/com/intuit/player/android/utils/SimpleAsset.kt b/android/player/src/test/java/com/intuit/player/android/utils/SimpleAsset.kt deleted file mode 100644 index 1dab8b870..000000000 --- a/android/player/src/test/java/com/intuit/player/android/utils/SimpleAsset.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.intuit.player.android.utils - -import android.view.View -import android.widget.TextView -import com.intuit.player.android.AssetContext -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.runtime.serialize -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.j2v8.bridge.runtime.J2V8 -import com.intuit.player.jvm.utils.makeFlow -import kotlinx.serialization.json.Json - -@Suppress("DEPRECATION_ERROR") -internal class SimpleAsset(assetContext: AssetContext) : RenderableAsset(assetContext) { - - override fun initView() = TextView(context) - - override fun View.hydrate() = Unit - - companion object { - val sampleMap = mapOf( - "id" to "simple-asset", - "data" to "{{someBinding}}", - "type" to "simple", - "metaData" to mapOf("a" to "b") - ) - val runtime = J2V8.create() - val sampleAsset = runtime.serialize(sampleMap) as Asset - val sampleJson = Json.encodeToJsonElement(GenericSerializer(), sampleMap) - val sampleFlow = makeFlow(sampleJson) - } -} diff --git a/android/player/src/test/java/com/intuit/player/android/utils/TestAssetsPlugin.kt b/android/player/src/test/java/com/intuit/player/android/utils/TestAssetsPlugin.kt deleted file mode 100644 index 4b24cd180..000000000 --- a/android/player/src/test/java/com/intuit/player/android/utils/TestAssetsPlugin.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.intuit.player.android.utils - -import com.intuit.player.android.AndroidPlayer -import com.intuit.player.android.AndroidPlayerPlugin -import com.intuit.player.android.METADATA -import com.intuit.player.android.TYPE - -internal object TestAssetsPlugin : AndroidPlayerPlugin { - override fun apply(androidPlayer: AndroidPlayer) { - androidPlayer.registerAsset("simple", ::SimpleAsset) - androidPlayer.registerAsset( - mapOf( - TYPE to "simple", - METADATA to mapOf("role" to "other") - ), - ::OtherSimpleAsset - ) - } -} diff --git a/android/player/src/test/java/com/intuit/player/android/AndroidPlayerTest.kt b/android/player/src/test/java/com/intuit/playerui/android/AndroidPlayerTest.kt similarity index 67% rename from android/player/src/test/java/com/intuit/player/android/AndroidPlayerTest.kt rename to android/player/src/test/java/com/intuit/playerui/android/AndroidPlayerTest.kt index 0e9e684a6..4220968b2 100644 --- a/android/player/src/test/java/com/intuit/player/android/AndroidPlayerTest.kt +++ b/android/player/src/test/java/com/intuit/playerui/android/AndroidPlayerTest.kt @@ -1,19 +1,26 @@ -package com.intuit.player.android +package com.intuit.playerui.android import android.content.Context -import com.intuit.player.android.utils.SimpleAsset -import com.intuit.player.android.utils.awaitFirstView -import com.intuit.player.jvm.core.player.HeadlessPlayer -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.utils.start -import com.intuit.player.jvm.utils.test.runBlockingTest -import com.intuit.player.plugins.beacon.BeaconPlugin -import com.intuit.player.plugins.beacon.beaconPlugin -import com.intuit.player.plugins.pubsub.PubSubPlugin -import com.intuit.player.plugins.pubsub.pubSubPlugin +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.android.utils.SimpleAsset +import com.intuit.playerui.android.utils.TestAssetsPlugin +import com.intuit.playerui.android.utils.awaitFirstView +import com.intuit.playerui.core.bridge.PlayerRuntimeException +import com.intuit.playerui.core.player.HeadlessPlayer +import com.intuit.playerui.plugins.beacon.BeaconPlugin +import com.intuit.playerui.plugins.beacon.beaconPlugin +import com.intuit.playerui.plugins.pubsub.PubSubPlugin +import com.intuit.playerui.plugins.pubsub.pubSubPlugin +import com.intuit.playerui.utils.start +import com.intuit.playerui.utils.test.runBlockingTest import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension -import org.junit.jupiter.api.Assertions.* +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith @@ -97,7 +104,7 @@ internal class AndroidPlayerTest { fun `release puts player in unusable state`() { val player = AndroidPlayer() player.release() - assertThrows { + assertThrows { player.start(SimpleAsset.sampleFlow) } } @@ -114,4 +121,18 @@ internal class AndroidPlayerTest { assertNull(player.getCachedAssetView(asset.assetContext)) assertNotNull(player.start(SimpleAsset.sampleFlow)) } + + @Test + fun `cannot encode a renderable asset`() = runBlockingTest { + val player = AndroidPlayer(TestAssetsPlugin) + val serializer = RenderableAsset.Serializer(player).conform() + player.registerAsset("simple", ::SimpleAsset) + val asset = player.awaitFirstView(SimpleAsset.sampleFlow)!! + assertEquals( + "DecodableAsset.Serializer.serialize is not supported", + assertThrows { + Json.encodeToString(serializer, asset) + }.message, + ) + } } diff --git a/android/player/src/test/java/com/intuit/player/android/AssetContextTest.kt b/android/player/src/test/java/com/intuit/playerui/android/AssetContextTest.kt similarity index 68% rename from android/player/src/test/java/com/intuit/player/android/AssetContextTest.kt rename to android/player/src/test/java/com/intuit/playerui/android/AssetContextTest.kt index 7bf42a436..517a062b7 100644 --- a/android/player/src/test/java/com/intuit/player/android/AssetContextTest.kt +++ b/android/player/src/test/java/com/intuit/playerui/android/AssetContextTest.kt @@ -1,15 +1,21 @@ -package com.intuit.player.android +package com.intuit.playerui.android import android.content.Context -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.jvm.core.asset.Asset +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.android.utils.SimpleAsset +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.utils.start import io.mockk.every import io.mockk.mockk import io.mockk.spyk import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows internal class AssetContextTest { private val context = spyk() @@ -59,4 +65,15 @@ internal class AssetContextTest { val newContext = assetContext.withTag("tag") assertEquals("some-id-tag", newContext.id) } + + @Test + fun `can't overlay styles without a context`() { + val player = AndroidPlayer().apply { + start(SimpleAsset.sampleFlow) + } + assertThrows { + AssetContext(null, asset, player, factory).withStyles(R.style.TextAppearance_AppCompat) + } + assertTrue(player.state is ErrorState) + } } diff --git a/android/player/src/test/java/com/intuit/playerui/android/extensions/CoroutineTestDispatcherExtension.kt b/android/player/src/test/java/com/intuit/playerui/android/extensions/CoroutineTestDispatcherExtension.kt new file mode 100644 index 000000000..1bb6d1831 --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/extensions/CoroutineTestDispatcherExtension.kt @@ -0,0 +1,20 @@ +package com.intuit.playerui.android.extensions + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext + +@OptIn(ExperimentalCoroutinesApi::class) +public class CoroutineTestDispatcherExtension : AfterEachCallback, BeforeEachCallback { + + private val dispatcher = UnconfinedTestDispatcher() + + override fun beforeEach(context: ExtensionContext?): Unit = Dispatchers.setMain(dispatcher) + + override fun afterEach(context: ExtensionContext?): Unit = Dispatchers.resetMain() +} diff --git a/android/player/src/test/java/com/intuit/playerui/android/extensions/IntoKtTest.kt b/android/player/src/test/java/com/intuit/playerui/android/extensions/IntoKtTest.kt new file mode 100644 index 000000000..c915cd630 --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/extensions/IntoKtTest.kt @@ -0,0 +1,113 @@ +package com.intuit.playerui.android.extensions + +import android.view.View +import android.view.View.GONE +import android.view.ViewGroup +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(MockKExtension::class) +internal class IntoKtTest { + + @MockK + lateinit var view1: View + + @MockK + lateinit var view2: View + + @MockK + lateinit var view3: View + + @MockK + lateinit var view4: View + + @MockK(relaxed = true) + lateinit var rootView: ViewGroup + + @BeforeEach + fun setup() { + every { view1.parent } answers { rootView } + every { view2.parent } answers { rootView } + every { view3.parent } answers { rootView } + every { view4.parent } answers { rootView } + } + + @Test + fun `the root view is hidden if the filtered collection is empty`() { + listOf(null, null, null) into rootView + verify { + rootView.visibility = GONE + rootView.removeAllViews() + } + } + + @Test + fun `views are inserted into the root view in the correct order`() { + listOf(view1, view2, view3) into rootView + verify { + rootView.addView(view1, 0) + rootView.addView(view2, 1) + rootView.addView(view3, 2) + } + } + + @Test + fun `views that are already in the root view and also in the new view are not inserted again`() { + mockChildIndices() + + listOf(view1, view2, view3) into rootView + verify(inverse = true) { + rootView.addView(view1, any()) + } + } + + @Test + fun `views that are not present in updated collection are removed`() { + var childCount = 3 + mockChildIndices() + mockChildCount { childCount } + every { rootView.removeViewAt(any()) } answers { childCount-- } + + listOf(view1, view2) into rootView + verify { rootView.removeViewAt(2) } + } + + @Test + fun `views that are not present in updated collection are removed even with null entries`() { + var childCount = 3 + mockChildIndices() + mockChildCount { childCount } + every { rootView.removeViewAt(any()) } answers { childCount-- } + + listOf(view1, view2, null) into rootView + verify { rootView.removeViewAt(2) } + } + + @Test + fun `views that are different have the old reference removed and the new one added`() { + mockChildIndices() + var childCount = 3 + every { rootView.removeViewAt(any()) } answers { childCount-- } + + listOf(view1, view2, view4) into rootView + verify { + rootView.removeView(view3) + rootView.addView(view4, 2) + } + } + + private fun mockChildIndices() { + every { rootView.getChildAt(0) } returns view1 + every { rootView.getChildAt(1) } returns view2 + every { rootView.getChildAt(2) } returns view3 + } + + private fun mockChildCount(childCount: () -> Int) { + every { rootView.childCount } answers { childCount() } + } +} diff --git a/android/player/src/test/java/com/intuit/playerui/android/lifecycle/PlayerViewModelTest.kt b/android/player/src/test/java/com/intuit/playerui/android/lifecycle/PlayerViewModelTest.kt new file mode 100644 index 000000000..d092d7d4e --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/lifecycle/PlayerViewModelTest.kt @@ -0,0 +1,262 @@ +package com.intuit.playerui.android.lifecycle + +import android.util.Level +import android.util.clearLogs +import com.intuit.playerui.android.utils.SimpleAsset +import com.intuit.playerui.core.bridge.PlayerRuntimeException +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.managed.AsyncFlowIterator +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.core.player.state.ReleasedState +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.withTimeout +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(MockKExtension::class) +internal class PlayerViewModelTest { + + private val validFlow = "{\"id\": \"id\",\"navigation\": {\"BEGIN\": \"FLOW_1\",\"FLOW_1\": {\"startState\": \"END_Done\",\"END_Done\": {\"state_type\": \"END\",\"outcome\": \"done\"}}}}" + + private val invalidFlow = "{\"id\": \"id\",\"navigation\": {\"BEGIN\": \"FLOW\",\"FLOW_1\": {\"startState\": \"END_Done\",\"END_Done\": {\"state_type\": \"END\",\"outcome\": \"done\"}}}}" + + @MockK lateinit var flowIterator: AsyncFlowIterator + + @MockK lateinit var runtime: Runtime<*> + + lateinit var viewModel: PlayerViewModel + + // TODO: This likely doesn't need to happen if we can inject a test dispatcher effectively + private suspend fun suspendUntilCondition(timeout: Long = 5000, getValue: () -> T, condition: (T) -> Boolean, messageSupplier: (T) -> String): T = try { + withTimeout(timeout) { + var result: T = getValue() + while (!condition(result)) { + delay(50) + result = getValue() + } + + result + } + } catch (exception: TimeoutCancellationException) { + throw AssertionError(messageSupplier(getValue())) + } + + private suspend fun Level.assertLogged(value: String, times: Int = 1, timeout: Long = 5000) { + suspendUntilCondition( + timeout, + this::getLogs, + { it.filter { it == value }.size == times }, + { "$this log not captured: $value\nin ${getLogs()}" }, + ) + } + + private suspend inline fun assertPlayerState(timeout: Long = 5000): T = suspendUntilCondition( + timeout, + viewModel.player::state, + { it is T }, + { "Expected Player state to eventually be ${T::class}, but is ${viewModel.player.state}" }, + ) as T + + private suspend inline fun assertManagedPlayerState(timeout: Long = 5000): T = suspendUntilCondition( + timeout, + viewModel.state::value, + { it is T }, + { "Expected Managed Player state to eventually be ${T::class}, but is ${viewModel.state.value}" }, + ) as T + + @BeforeEach + fun setup() { + viewModel = PlayerViewModel(flowIterator) + every { runtime.scope } returns TestCoroutineScope() + // TODO: Change to StandardTestDispatcher + every { runtime.scope } returns CoroutineScope(Dispatchers.Default) + coEvery { flowIterator.terminate() } returns Unit + } + + @AfterEach + fun tearDown() = clearLogs() + + @Test + fun `test factory`() { + assertNotNull( + PlayerViewModel.Factory(flowIterator) { + PlayerViewModel(it) + }.create(PlayerViewModel::class.java), + ) + } + + @Test + fun `AndroidPlayer isn't null`() { + assertNotNull(viewModel.player) + } + + @Test + fun `apply android player onUpdate`() = runBlocking { + coEvery { flowIterator.next(any()) } returns SimpleAsset.sampleFlow.toString() + viewModel.start() + coVerify(exactly = 1) { flowIterator.next(any()) } + assertPlayerState() + Level.Warn.assertLogged("WARN: AndroidPlayer: Warning in flow: simple-asset of type simple is not registered") + } + + @Test + fun `apply android player state hook tap`() = runBlocking { + coEvery { flowIterator.next(any()) } returns validFlow andThen invalidFlow + viewModel.start() + coVerify(exactly = 1) { flowIterator.next(null) } + coVerify(exactly = 1) { flowIterator.next(any()) } + assertEquals("Error: No flow defined for: FLOW", assertPlayerState().error.message) + } + + @Test + fun `start calls next on manager`() { + coEvery { flowIterator.next(any()) } returns SimpleAsset.sampleFlow.toString() + viewModel.start() + coVerify(exactly = 1) { flowIterator.next(null) } + } + + @Test + fun `start eventually finishes if iterator does not return a new flow`() = runBlocking { + coEvery { flowIterator.next(any()) } returns null + viewModel.start() + val doneState = assertManagedPlayerState() + assertEquals(ManagedPlayerState.Done(null), doneState) + } + + @Test + fun `start happy path`() = runBlocking { + coEvery { flowIterator.next(any()) } returns validFlow andThen null + viewModel.start() + assertPlayerState() + Level.Info.assertLogged("INFO: AndroidPlayer: Flow completed successfully!, {state_type=END, outcome=done}") + } + + @Test + fun `start error path`() = runBlocking { + coEvery { flowIterator.next(any()) } returns invalidFlow + viewModel.start() + assertPlayerState() + Level.Error.assertLogged("ERROR: AndroidPlayer: Error in Flow!, {}") + Level.Error.assertLogged("ERROR: AndroidPlayer: Something went wrong: No flow defined for: FLOW") + } + + @Test + fun `start will emit error state if iterator errors out`() = runBlocking { + val exception = Exception("oh no") + coEvery { flowIterator.next(any()) } throws exception + viewModel.start() + val errorState = assertManagedPlayerState() + assertEquals("oh no", errorState.exception.message) + } + + @Test + fun `recycle calls into AndroidPlayer recycle`() { + var recycled = false + viewModel.player.hooks.recycle.tap("releaseTest") { + recycled = true + } + viewModel.recycle() + assertTrue(recycled) + } + + @Test + fun `onCleared releases player`() = runBlocking { + coEvery { flowIterator.terminate() } returns Unit + viewModel.apply(runtime) + viewModel.onCleared() + assertPlayerState() + assertEquals( + "[J2V8] Runtime object has been released!", + assertThrows { + viewModel.player.start(SimpleAsset.sampleFlow.toString()) + }.message, + ) + } + + @Test + fun `release clears player cache and releases runtime`() { + var released = false + viewModel.player.hooks.release.tap("releaseTest") { + released = true + } + coEvery { flowIterator.next(null) } returns SimpleAsset.sampleFlow.toString() + viewModel.release() + assertThrows { + viewModel.player.start(SimpleAsset.sampleFlow.toString()) + } + assertTrue(released) + } + + @Test + fun `test fail`() = runBlocking { + val exception = Exception("oh no") + coEvery { flowIterator.next(any()) } returns SimpleAsset.sampleFlow.toString() + viewModel.start() + + assertPlayerState() + + viewModel.fail("extension fail", exception) + assertEquals("extension fail", assertPlayerState().error.message) + } + + @Test + fun `retry should start player if not started`() { + coEvery { flowIterator.next(any()) } returns SimpleAsset.sampleFlow.toString() + viewModel.retry() + coVerify(exactly = 1) { flowIterator.next(null) } + } + + @Test + fun `retry should call manager next if it's running`() = runBlocking { + coEvery { flowIterator.next(any()) } returns SimpleAsset.sampleFlow.toString() + viewModel.start() + + assertManagedPlayerState() + + viewModel.retry() + coVerify(exactly = 2) { flowIterator.next(null) } + } + + @Test + fun `retry should call manager next if it's in error state`() = runBlocking { + val exception = Exception("oh no") + coEvery { flowIterator.next(any()) } throws exception + viewModel.start() + + assertManagedPlayerState() + + viewModel.retry() + coVerify(exactly = 2) { flowIterator.next(null) } + } + + @Test + fun `view model can be cleared successfully if player is never used`() = runBlocking { + val exception = Exception("oh no") + coEvery { flowIterator.next(any()) } throws exception + viewModel.start() + + assertManagedPlayerState() + + // ensures safe handling during cleanup when player is never instantiated + viewModel.onCleared() + } +} diff --git a/android/player/src/test/java/com/intuit/player/android/logger/AndroidLoggerTest.kt b/android/player/src/test/java/com/intuit/playerui/android/logger/AndroidLoggerTest.kt similarity index 77% rename from android/player/src/test/java/com/intuit/player/android/logger/AndroidLoggerTest.kt rename to android/player/src/test/java/com/intuit/playerui/android/logger/AndroidLoggerTest.kt index 31023a9fc..f3c61dfba 100644 --- a/android/player/src/test/java/com/intuit/player/android/logger/AndroidLoggerTest.kt +++ b/android/player/src/test/java/com/intuit/playerui/android/logger/AndroidLoggerTest.kt @@ -1,15 +1,17 @@ -package com.intuit.player.android.logger +package com.intuit.playerui.android.logger +import android.util.clearLogs import android.util.d import android.util.e import android.util.i -import android.util.t +import android.util.v import android.util.w +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test internal class AndroidLoggerTest { - companion object { const val defaultTag = "AndroidLogger" const val trace = "this is very verbose" @@ -19,10 +21,15 @@ internal class AndroidLoggerTest { const val error = "this is an error" } + @AfterEach + fun tearDown() = clearLogs() + @Test fun `test log methods`() { val logger = AndroidLogger() assertEquals(defaultTag, logger.name) + logger.trace(trace) + assertTrace() logger.debug(debug) assertDebug() logger.info(info) @@ -38,6 +45,8 @@ internal class AndroidLoggerTest { val tag = "Logger" val logger = AndroidLogger(tag) assertEquals(tag, logger.name) + logger.trace(trace) + assertTrace(tag) logger.debug(debug) assertDebug(tag) logger.info(info) @@ -49,20 +58,17 @@ internal class AndroidLoggerTest { } private fun assertTrace(tag: String = defaultTag, msg: String = trace) = - t.assertLogged("TRACE", tag, msg) + v.assertLogged("TRACE", tag, msg) private fun assertDebug(tag: String = defaultTag, msg: String = debug) = d.assertLogged("DEBUG", tag, msg) - private fun assertInfo(tag: String = defaultTag, msg: String = info) = i.assertLogged("INFO", tag, msg) - private fun assertWarn(tag: String = defaultTag, msg: String = warn) = w.assertLogged("WARN", tag, msg) - private fun assertError(tag: String = defaultTag, msg: String = error) = e.assertLogged("ERROR", tag, msg) - private fun String?.assertLogged(level: String, tag: String, msg: String) = - assertEquals("$level: $tag: $msg", this) + private fun List.assertLogged(level: String, tag: String, msg: String) = + assertTrue(this.contains("$level: $tag: $msg")) } diff --git a/android/player/src/test/java/com/intuit/player/android/registry/RegistryPluginTest.kt b/android/player/src/test/java/com/intuit/playerui/android/registry/RegistryPluginTest.kt similarity index 61% rename from android/player/src/test/java/com/intuit/player/android/registry/RegistryPluginTest.kt rename to android/player/src/test/java/com/intuit/playerui/android/registry/RegistryPluginTest.kt index 7831c67ed..920099b5c 100644 --- a/android/player/src/test/java/com/intuit/player/android/registry/RegistryPluginTest.kt +++ b/android/player/src/test/java/com/intuit/playerui/android/registry/RegistryPluginTest.kt @@ -1,10 +1,10 @@ -package com.intuit.player.android.registry +package com.intuit.playerui.android.registry -import com.intuit.player.android.renderer.BaseRenderableAssetTest -import com.intuit.player.android.utils.OtherSimpleAsset -import com.intuit.player.android.utils.SimpleAsset -import com.intuit.player.android.utils.awaitFirstView -import com.intuit.player.jvm.utils.test.runBlockingTest +import com.intuit.playerui.android.renderer.BaseRenderableAssetTest +import com.intuit.playerui.android.utils.OtherSimpleAsset +import com.intuit.playerui.android.utils.SimpleAsset +import com.intuit.playerui.android.utils.awaitFirstView +import com.intuit.playerui.utils.test.runBlockingTest import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test diff --git a/android/player/src/test/java/com/intuit/player/android/renderer/BaseRenderableAssetTest.kt b/android/player/src/test/java/com/intuit/playerui/android/renderer/BaseRenderableAssetTest.kt similarity index 56% rename from android/player/src/test/java/com/intuit/player/android/renderer/BaseRenderableAssetTest.kt rename to android/player/src/test/java/com/intuit/playerui/android/renderer/BaseRenderableAssetTest.kt index f7694c8af..a52fd0397 100644 --- a/android/player/src/test/java/com/intuit/player/android/renderer/BaseRenderableAssetTest.kt +++ b/android/player/src/test/java/com/intuit/playerui/android/renderer/BaseRenderableAssetTest.kt @@ -1,29 +1,35 @@ -package com.intuit.player.android.renderer +package com.intuit.playerui.android.renderer import android.content.Context import android.widget.TextView -import com.intuit.player.android.AndroidPlayer -import com.intuit.player.android.AssetContext -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.android.utils.TestAssetsPlugin -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.plugins.Plugin -import com.intuit.player.plugins.beacon.BeaconPlugin +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.android.extensions.CoroutineTestDispatcherExtension +import com.intuit.playerui.android.utils.TestAssetsPlugin +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.plugins.Plugin +import com.intuit.playerui.plugins.beacon.BeaconPlugin +import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension +import io.mockk.mockk import io.mockk.spyk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(MockKExtension::class) +@ExtendWith(CoroutineTestDispatcherExtension::class) internal abstract class BaseRenderableAssetTest { data class BeaconArgs( val action: String, val element: String, val asset: Asset, - val data: Any? = null + val data: Any? = null, ) var beaconPlugin: BeaconPlugin = spyk(BeaconPlugin()) @@ -39,7 +45,7 @@ internal abstract class BaseRenderableAssetTest { lastBeaconed = BeaconArgs(action as String, element as String, asset as Asset, data) } - every { mockRenderableAsset.render(any()) } returns TextView(mockContext) + coEvery { mockRenderableAsset.render(any()) } returns TextView(mockContext) } @MockK @@ -53,7 +59,11 @@ internal abstract class BaseRenderableAssetTest { } open val player by lazy { - AndroidPlayer(plugins) + AndroidPlayer(plugins).apply { + val inProgressState: InProgressState = mockk() + every { inProgressState.flow } returns Flow(id = "fake-flow-test") + hooks.state.call(HashMap(), arrayOf(inProgressState)) + } } open val assetContext by lazy { diff --git a/android/player/src/test/java/com/intuit/playerui/android/renderer/BrokenAssetTest.kt b/android/player/src/test/java/com/intuit/playerui/android/renderer/BrokenAssetTest.kt new file mode 100644 index 000000000..fc2f315bf --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/renderer/BrokenAssetTest.kt @@ -0,0 +1,77 @@ +package com.intuit.playerui.android.renderer + +import android.content.Context +import android.widget.FrameLayout +import android.widget.LinearLayout +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.asset.StaleViewException +import com.intuit.playerui.android.utils.BrokenAsset +import com.intuit.playerui.android.utils.BrokenAsset.Companion.asset +import com.intuit.playerui.android.utils.TestAssetsPlugin +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.utils.start +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(MockKExtension::class) +internal class BrokenAssetTest { + + private val runtime = BrokenAsset.runtime + + @MockK + lateinit var mockContext: Context + + val player: AndroidPlayer by lazy { + AndroidPlayer(TestAssetsPlugin) + } + + val baseContext by lazy { + AssetContext(null, runtime.asset(), player, ::BrokenAsset) + } + + @BeforeEach fun setup() { + player.start(BrokenAsset.sampleFlow) + } + + @Test + fun `invalidate view should fail on first render`() { + assertThrows { + BrokenAsset(baseContext.copy(asset = runtime.asset(shouldFail = true))).render(mockContext) + } + } + + @Test + fun `invalidate view should handle gracefully in a rehydrate (if asset renders properly the second time)`() { + assertTrue(BrokenAsset(baseContext.copy(asset = runtime.asset(layout = BrokenAsset.Layout.Frame))).render(mockContext) is FrameLayout) + assertTrue(BrokenAsset(baseContext.copy(asset = runtime.asset(layout = BrokenAsset.Layout.Linear))).render(mockContext) is LinearLayout) + } + + @Test + fun `manual rehydration should fail the player on invalidate view`() { + BrokenAsset(baseContext.copy(asset = runtime.asset(layout = BrokenAsset.Layout.Frame))).apply { + assertTrue(render(mockContext) is FrameLayout) + data.layout = BrokenAsset.Layout.Linear + rehydrate() + } + assertTrue(player.state is ErrorState) + } + + @Test + fun `cannot render without a context`() { + assertEquals( + "Android context not found! Ensure the asset is rendered with a valid Android context.", + assertThrows { + BrokenAsset(baseContext).requireContext() + }.message, + ) + assertTrue(player.state is ErrorState) + } +} diff --git a/android/player/src/test/java/com/intuit/player/android/renderer/HydrationScopeTest.kt b/android/player/src/test/java/com/intuit/playerui/android/renderer/HydrationScopeTest.kt similarity index 55% rename from android/player/src/test/java/com/intuit/player/android/renderer/HydrationScopeTest.kt rename to android/player/src/test/java/com/intuit/playerui/android/renderer/HydrationScopeTest.kt index 49f80bea3..774c97599 100644 --- a/android/player/src/test/java/com/intuit/player/android/renderer/HydrationScopeTest.kt +++ b/android/player/src/test/java/com/intuit/playerui/android/renderer/HydrationScopeTest.kt @@ -1,25 +1,31 @@ -package com.intuit.player.android.renderer +package com.intuit.playerui.android.renderer import android.view.View import android.widget.TextView -import com.intuit.player.android.AndroidPlayer -import com.intuit.player.android.AssetContext -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.runtime.serialize -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializer -import com.intuit.player.jvm.core.flow.forceTransition -import com.intuit.player.jvm.core.player.state.inProgressState -import com.intuit.player.jvm.j2v8.bridge.runtime.J2V8 -import com.intuit.player.jvm.utils.makeFlow -import com.intuit.player.jvm.utils.start -import com.intuit.player.jvm.utils.test.runBlockingTest -import com.intuit.player.plugins.coroutines.flowScope +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.asset.SuspendableAsset +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializer +import com.intuit.playerui.core.flow.forceTransition +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.j2v8.bridge.runtime.J2V8 +import com.intuit.playerui.plugins.coroutines.flowScope +import com.intuit.playerui.utils.makeFlow +import com.intuit.playerui.utils.start +import com.intuit.playerui.utils.test.runBlockingTest +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json.Default.encodeToString -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -27,11 +33,10 @@ internal class HydrationScopeTest : BaseRenderableAssetTest() { private var completed: Boolean = false - @Suppress("DEPRECATION_ERROR") - inner class TestAsset(assetContext: AssetContext) : RenderableAsset(assetContext) { - override fun initView(): View = TextView(context) + inner class TestAsset(assetContext: AssetContext) : SuspendableAsset(assetContext, NodeSerializer()) { + override suspend fun initView(data: Node): View = TextView(context) - override fun View.hydrate() { + override suspend fun View.hydrate(data: Node) { hydrationScope.launch { delay(500) completed = true @@ -45,7 +50,7 @@ internal class HydrationScopeTest : BaseRenderableAssetTest() { mapOf( "id" to "some-id", "type" to "test", - ) + ), ) as Asset override val player by lazy { @@ -64,14 +69,23 @@ internal class HydrationScopeTest : BaseRenderableAssetTest() { } @Test - fun `test hydration scope can launch coroutines`() { + fun `test awaiting async view stub doesn't cancel parent scope`() = runBlocking { val test = TestAsset(assetContext) - test.render(mockContext) + val asyncView = test.render(mockContext) as SuspendableAsset.AsyncViewStub + test.currentHydrationScope.cancel("hello") + assertNull(asyncView.awaitView()) + } + + @Test + fun `test hydration scope can launch coroutines`() = runBlocking { + val test = TestAsset(assetContext) + val asyncView = test.render(mockContext) as SuspendableAsset.AsyncViewStub + asyncView.awaitView() waitForCompleted() } @Test - fun `test existing hydration scope is cancelled on re-render`() { + fun `test existing hydration scope is cancelled on re-render`() = runBlocking { val test = TestAsset(assetContext) test.render(mockContext) val currentHydrationScope = test.currentHydrationScope @@ -82,7 +96,7 @@ internal class HydrationScopeTest : BaseRenderableAssetTest() { } @Test - fun `test hydration scope is refreshed on re-render`() { + fun `test hydration scope is refreshed on re-render`() = runBlocking { val test = TestAsset(assetContext) test.render(mockContext) val firstHydrationScope = test.currentHydrationScope @@ -96,7 +110,7 @@ internal class HydrationScopeTest : BaseRenderableAssetTest() { } @Test - fun `test hydration scope is cancelled on flow end`() { + fun `test hydration scope is cancelled on flow end`() = runBlocking { val test = TestAsset(assetContext) test.render(mockContext) player.inProgressState?.forceTransition("") @@ -105,6 +119,16 @@ internal class HydrationScopeTest : BaseRenderableAssetTest() { assertFalse(completed) } + @Test + fun `test hydration scope is cancelled on player release`() = runBlocking { + val test = TestAsset(assetContext) + test.render(mockContext) + player.release() + assertFalse(player.flowScope!!.isActive) + assertFalse(test.currentHydrationScope.isActive) + assertFalse(completed) + } + private fun waitForCompleted(count: Int = 5, delay: Long = 500) { waitForCondition(count, delay) { completed } } diff --git a/android/player/src/test/java/com/intuit/playerui/android/renderer/NestedAssetTest.kt b/android/player/src/test/java/com/intuit/playerui/android/renderer/NestedAssetTest.kt new file mode 100644 index 000000000..0be8c08b7 --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/renderer/NestedAssetTest.kt @@ -0,0 +1,86 @@ +package com.intuit.playerui.android.renderer + +import android.widget.LinearLayout +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.asset.SuspendableAsset +import com.intuit.playerui.android.asset.SuspendableAsset.AsyncHydrationTrackerPlugin +import com.intuit.playerui.android.asset.SuspendableAsset.AsyncViewStub +import com.intuit.playerui.android.asset.asyncHydrationTrackerPlugin +import com.intuit.playerui.android.utils.NestedAsset +import com.intuit.playerui.android.utils.SimpleAsset +import com.intuit.playerui.android.utils.awaitFirstView +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.utils.test.runBlockingTest +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +internal class NestedAssetTest : BaseRenderableAssetTest() { + + override val asset get() = NestedAsset.sampleAsset + + override val player get() = AndroidPlayer(beaconPlugin, AsyncHydrationTrackerPlugin()).apply { + registerAsset("simple", ::SimpleAsset) + registerAsset("nested", ::NestedAsset) + val inProgressState = mockk() + every { inProgressState.flow } returns Flow() + hooks.state.call(HashMap(), arrayOf(inProgressState)) + } + + override val assetContext: AssetContext by lazy { + AssetContext(mockContext, asset, player, ::NestedAsset) + } + + @Test + fun `tested nested asset constructs`() = runBlocking { + val nested = NestedAsset(assetContext).render(mockContext).let { + if (it is SuspendableAsset.AsyncViewStub) it.awaitView() else it + } + assertTrue(nested is LinearLayout) + } + + @Test + fun `test nested asset context`() = runBlockingTest { + val asset = player.awaitFirstView(NestedAsset.sampleFlow)!! as NestedAsset + asset.render(mockContext).let { + if (it is SuspendableAsset.AsyncViewStub) it.awaitView() else it + } + assertEquals(mockContext, NestedAsset.dummy?.context) + NestedAsset.dummy2?.forEach { + assertEquals(mockContext, it?.context) + } ?: Unit + } + + @OptIn(ExperimentalPlayerApi::class) + @Test + fun `test hydration tracker`() = runBlockingTest { + val player = player + val plugin = player.asyncHydrationTrackerPlugin!! + var onHydrationStarted = false + plugin.hooks.onHydrationStarted.tap("test") { + onHydrationStarted = true + } + var onHydrationCompleted = false + plugin.hooks.onHydrationComplete.tap("test") { + onHydrationCompleted = true + } + + val asset = player.awaitFirstView(NestedAsset.sampleFlow) as NestedAsset + + assertFalse(onHydrationStarted) + assertFalse(onHydrationCompleted) + val view = asset.render(mockContext) as AsyncViewStub + assertTrue(onHydrationStarted) + assertFalse(onHydrationCompleted) + view.awaitView() + assertTrue(onHydrationStarted) + assertTrue(onHydrationCompleted) + } +} diff --git a/android/player/src/test/java/com/intuit/playerui/android/renderer/SimpleAssetTest.kt b/android/player/src/test/java/com/intuit/playerui/android/renderer/SimpleAssetTest.kt new file mode 100644 index 000000000..ebee18ced --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/renderer/SimpleAssetTest.kt @@ -0,0 +1,92 @@ +package com.intuit.playerui.android.renderer + +import android.widget.TextView +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.R +import com.intuit.playerui.android.utils.SimpleAsset +import com.intuit.playerui.android.utils.SimpleAsset.Companion.sampleFlow +import com.intuit.playerui.android.utils.stringify +import com.intuit.playerui.android.withContext +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +internal class SimpleAssetTest : BaseRenderableAssetTest() { + + override val asset get() = SimpleAsset.sampleAsset + + override val assetContext: AssetContext by lazy { + AssetContext(mockContext, asset, player, ::SimpleAsset) + } + + @Test + fun `test rendering with no styles`() { + val simple = SimpleAsset(assetContext).render(mockContext) + assertTrue(simple is TextView) + } + + @Test + fun `test rendering with some styles`() { + val simple = SimpleAsset(assetContext.withContext(mockContext)).run { + render(R.style.TextAppearance_AppCompat) + } + assertTrue(simple is TextView) + } + + @Test + fun `test rendering with some styles using another render method`() { + val simple = SimpleAsset(assetContext.withContext(mockContext)).run { + render(listOf(R.style.TextAppearance_AppCompat)) + } + assertTrue(simple is TextView) + } + + @Test + fun `test rendering with some styles and a tag`() { + val simple = SimpleAsset(assetContext.withContext(mockContext)).run { + render(R.style.TextAppearance_AppCompat, tag = "tag") + } + assertTrue(simple is TextView) + } + + @Test + fun `test rendering with some styles and a tag using another render method`() { + val simple = SimpleAsset(assetContext.withContext(mockContext)).run { + render(listOf(R.style.TextAppearance_AppCompat), "tag") + } + assertTrue(simple is TextView) + } + + @Test + fun `test rendering with tag`() { + player.start(sampleFlow.stringify()) + + val asset = SimpleAsset(assetContext.withContext(mockContext)) + + with(asset) { + val firstView = asset.render() + assertTrue(firstView is TextView) + + val secondView = asset.render("tag") + assertTrue(secondView is TextView) + + assertEquals(secondView, asset.render("tag")) + assertNotEquals(firstView, secondView) + } + } + + @Test + fun `test beacon helper`() { + val simple = SimpleAsset(assetContext) + simple.beacon("viewed", "button") + assertEquals(BeaconArgs("viewed", "button", asset), lastBeaconed) + } + + @Test + fun `test beacon helper with custom ID`() { + val simple = SimpleAsset(assetContext) + simple.beacon("viewed", "button", data = "custom-id") + assertEquals(BeaconArgs("viewed", "button", asset, "custom-id"), lastBeaconed) + } +} diff --git a/android/player/src/test/java/com/intuit/playerui/android/utils/BrokenAsset.kt b/android/player/src/test/java/com/intuit/playerui/android/utils/BrokenAsset.kt new file mode 100644 index 000000000..a12215ed5 --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/utils/BrokenAsset.kt @@ -0,0 +1,60 @@ +package com.intuit.playerui.android.utils + +import android.view.View +import android.widget.FrameLayout +import android.widget.LinearLayout +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.asset.DecodableAsset +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.j2v8.bridge.runtime.J2V8 +import com.intuit.playerui.utils.makeFlow +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json + +internal class BrokenAsset(assetContext: AssetContext) : DecodableAsset(assetContext, Data.serializer()) { + + @Serializable + data class Data( + var layout: Layout, + val shouldFail: Boolean, + ) + + @Serializable + enum class Layout { + Frame, Linear + } + + override fun initView() = when (data.layout) { + Layout.Frame -> FrameLayout(requireContext()) + Layout.Linear -> LinearLayout(requireContext()) + } + + override fun View.hydrate() { + if (data.shouldFail || ( + data.layout == Layout.Frame && this is LinearLayout + ) || ( + data.layout == Layout.Linear && this is FrameLayout + ) + ) { + invalidateView() + } + } + + companion object { + val sampleMap = mapOf( + "id" to "broken-asset", + "type" to "broken", + "layout" to "Frame", + "shouldFail" to false, + ) + fun Runtime<*>.asset(layout: Layout = Layout.Frame, shouldFail: Boolean = false): Asset = + serialize(sampleMap + mapOf("layout" to layout.toString(), "shouldFail" to shouldFail)) as Asset + val runtime = J2V8.create() + val sampleAsset = runtime.serialize(sampleMap) as Asset + val sampleJson = Json.encodeToJsonElement(GenericSerializer(), sampleMap) + val sampleFlow = makeFlow(sampleJson) + } +} diff --git a/android/player/src/test/java/com/intuit/player/android/utils/json.kt b/android/player/src/test/java/com/intuit/playerui/android/utils/Json.kt similarity index 80% rename from android/player/src/test/java/com/intuit/player/android/utils/json.kt rename to android/player/src/test/java/com/intuit/playerui/android/utils/Json.kt index ac8f06e5b..e46f8a675 100644 --- a/android/player/src/test/java/com/intuit/player/android/utils/json.kt +++ b/android/player/src/test/java/com/intuit/playerui/android/utils/Json.kt @@ -1,4 +1,4 @@ -package com.intuit.player.android.utils +package com.intuit.playerui.android.utils import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement diff --git a/android/player/src/test/java/com/intuit/player/android/utils/NestedAsset.kt b/android/player/src/test/java/com/intuit/playerui/android/utils/NestedAsset.kt similarity index 59% rename from android/player/src/test/java/com/intuit/player/android/utils/NestedAsset.kt rename to android/player/src/test/java/com/intuit/playerui/android/utils/NestedAsset.kt index 9b49995cf..3467839fd 100644 --- a/android/player/src/test/java/com/intuit/player/android/utils/NestedAsset.kt +++ b/android/player/src/test/java/com/intuit/playerui/android/utils/NestedAsset.kt @@ -1,26 +1,29 @@ -package com.intuit.player.android.utils +package com.intuit.playerui.android.utils import android.view.View import android.widget.LinearLayout -import com.intuit.player.android.AssetContext -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.android.extensions.into -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.runtime.serialize -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.j2v8.bridge.runtime.J2V8 -import com.intuit.player.jvm.utils.makeFlow +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.android.asset.SuspendableAsset +import com.intuit.playerui.android.extensions.into +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializer +import com.intuit.playerui.j2v8.bridge.runtime.J2V8 +import com.intuit.playerui.utils.makeFlow import kotlinx.serialization.json.Json @Suppress("DEPRECATION_ERROR") -internal class NestedAsset(assetContext: AssetContext) : RenderableAsset(assetContext) { +internal class NestedAsset(assetContext: AssetContext) : SuspendableAsset(assetContext, NodeSerializer()) { val nested = expand("nested") val nestedList = expandList("nestedAssets") - override fun initView() = LinearLayout(context) + override suspend fun initView(data: Node) = LinearLayout(context) - override fun View.hydrate() { + override suspend fun View.hydrate(data: Node) { require(this is LinearLayout) nested?.render() into this dummy = nested @@ -39,18 +42,18 @@ internal class NestedAsset(assetContext: AssetContext) : RenderableAsset(assetCo "asset" to mapOf( "id" to "some-nested-id", "type" to "simple", - "metaData" to mapOf("a" to "b") - ) + "metaData" to mapOf("a" to "b"), + ), ), "nestedAssets" to listOf( mapOf( "asset" to mapOf( "id" to "some-nested-id", "type" to "simple", - "metaData" to mapOf("a" to "b") - ) - ) - ) + "metaData" to mapOf("a" to "b"), + ), + ), + ), ) val sampleAsset: Asset = J2V8.create().serialize(sampleMap) as Asset val sampleJson = Json.encodeToJsonElement(GenericSerializer(), sampleMap) diff --git a/android/player/src/test/java/com/intuit/playerui/android/utils/OtherSimpleAsset.kt b/android/player/src/test/java/com/intuit/playerui/android/utils/OtherSimpleAsset.kt new file mode 100644 index 000000000..2618a7338 --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/utils/OtherSimpleAsset.kt @@ -0,0 +1,33 @@ +package com.intuit.playerui.android.utils + +import android.view.View +import android.widget.ImageView +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.asset.SuspendableAsset +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializer +import com.intuit.playerui.j2v8.bridge.runtime.J2V8 +import com.intuit.playerui.utils.makeFlow +import kotlinx.serialization.json.Json + +@Suppress("DEPRECATION_ERROR") +internal class OtherSimpleAsset(assetContext: AssetContext) : SuspendableAsset(assetContext, NodeSerializer()) { + + override suspend fun initView(data: Node) = ImageView(context) + + override suspend fun View.hydrate(data: Node) = Unit + + companion object { + val sampleMap = mapOf( + "id" to "some-id", + "type" to "simple", + "metaData" to mapOf("role" to "other"), + ) + val sampleAsset: Asset = J2V8.create().serialize(sampleMap) as Asset + val sampleJson = Json.encodeToJsonElement(GenericSerializer(), sampleMap) + val sampleFlow = makeFlow(sampleJson) + } +} diff --git a/android/player/src/test/java/com/intuit/playerui/android/utils/SimpleAsset.kt b/android/player/src/test/java/com/intuit/playerui/android/utils/SimpleAsset.kt new file mode 100644 index 000000000..1311b47c2 --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/utils/SimpleAsset.kt @@ -0,0 +1,34 @@ +package com.intuit.playerui.android.utils + +import android.view.View +import android.widget.TextView +import com.intuit.playerui.android.AssetContext +import com.intuit.playerui.android.asset.DecodableAsset +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.j2v8.bridge.runtime.J2V8 +import com.intuit.playerui.utils.makeFlow +import kotlinx.serialization.json.Json + +@Suppress("DEPRECATION_ERROR") +internal class SimpleAsset(assetContext: AssetContext) : DecodableAsset(assetContext, Node.serializer()) { + + override fun initView() = TextView(context) + + override fun View.hydrate() = Unit + + companion object { + val sampleMap = mapOf( + "id" to "simple-asset", + "data" to "{{someBinding}}", + "type" to "simple", + "metaData" to mapOf("a" to "b"), + ) + val runtime = J2V8.create() + val sampleAsset = runtime.serialize(sampleMap) as Asset + val sampleJson = Json.encodeToJsonElement(GenericSerializer(), sampleMap) + val sampleFlow = makeFlow(sampleJson) + } +} diff --git a/android/player/src/test/java/com/intuit/playerui/android/utils/TestAssetsPlugin.kt b/android/player/src/test/java/com/intuit/playerui/android/utils/TestAssetsPlugin.kt new file mode 100644 index 000000000..db707e7cb --- /dev/null +++ b/android/player/src/test/java/com/intuit/playerui/android/utils/TestAssetsPlugin.kt @@ -0,0 +1,20 @@ +package com.intuit.playerui.android.utils + +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.AndroidPlayerPlugin +import com.intuit.playerui.android.METADATA +import com.intuit.playerui.android.TYPE + +internal object TestAssetsPlugin : AndroidPlayerPlugin { + override fun apply(androidPlayer: AndroidPlayer) { + androidPlayer.registerAsset("simple", ::SimpleAsset) + androidPlayer.registerAsset( + mapOf( + TYPE to "simple", + METADATA to mapOf("role" to "other"), + ), + ::OtherSimpleAsset, + ) + androidPlayer.registerAsset("broken", ::BrokenAsset) + } +} diff --git a/android/player/src/test/java/com/intuit/player/android/utils/updates.kt b/android/player/src/test/java/com/intuit/playerui/android/utils/Updates.kt similarity index 64% rename from android/player/src/test/java/com/intuit/player/android/utils/updates.kt rename to android/player/src/test/java/com/intuit/playerui/android/utils/Updates.kt index 0c8c9538c..0d9ed0802 100644 --- a/android/player/src/test/java/com/intuit/player/android/utils/updates.kt +++ b/android/player/src/test/java/com/intuit/playerui/android/utils/Updates.kt @@ -1,11 +1,11 @@ -package com.intuit.player.android.utils +package com.intuit.playerui.android.utils -import com.intuit.player.android.AndroidPlayer -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.core.player.state.ErrorState -import com.intuit.player.jvm.core.player.state.PlayerFlowState -import com.intuit.player.jvm.utils.start +import com.intuit.playerui.android.AndroidPlayer +import com.intuit.playerui.android.asset.RenderableAsset +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.utils.start import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow @@ -29,18 +29,21 @@ internal fun AndroidPlayer.updates(flow: JsonElement, take: Int = 1) = updates(f internal fun AndroidPlayer.updates(flow: String, take: Int = 1): Flow = callbackFlow { var count = 0 onUpdate { asset, _ -> - offer(Update.Asset(asset, count)) + trySend(Update.Asset(asset, count)).isSuccess if (++count >= take) close() } hooks.state.tap { state -> - offer(Update.State(state ?: ErrorState.from("state was null"))) + trySend(Update.State(state ?: ErrorState.from("state was null"))).isSuccess } start(flow).onComplete { close( - it.exceptionOrNull() ?: if (count != take) - PlayerException("did not meet expected asset updates ($count != $take)") else null + it.exceptionOrNull() ?: if (count != take) { + PlayerException("did not meet expected asset updates ($count != $take)") + } else { + null + }, ) } diff --git a/build_constants.bzl b/build_constants.bzl new file mode 100644 index 000000000..4ef3adfd4 --- /dev/null +++ b/build_constants.bzl @@ -0,0 +1,36 @@ +def _build_constants_impl(repository_ctx): + _BUILD_FILE = """ +# DO NOT EDIT: automatically generated for _build_constants rule +filegroup( + name = 'files', + srcs = glob(['**']), + visibility = ['//visibility:public'] +) +""" + repository_ctx.file("BUILD", _BUILD_FILE, False) + + version = repository_ctx.read(repository_ctx.attr.version_file) + + _CONSTANTS_FILE = """ +# DO NOT EDIT: automatically generated for _build_constants rule +VERSION = \"{version}\" +""" + + repository_ctx.file( + "constants.bzl", + _CONSTANTS_FILE.format(version = version), + False, + ) + +_build_constants = repository_rule( + implementation = _build_constants_impl, + attrs = { + "version_file": attr.label( + default = Label("//:VERSION"), + allow_single_file = True, + ), + }, +) + +def build_constants(): + _build_constants(name = "build_constants") diff --git a/core/player/src/__tests__/view.test.ts b/core/player/src/__tests__/view.test.ts index af9732e00..a75bcc873 100644 --- a/core/player/src/__tests__/view.test.ts +++ b/core/player/src/__tests__/view.test.ts @@ -625,3 +625,91 @@ describe("state node expression tests", () => { expect(state().controllers.data.get("count")).toBe(11); }); }); + +describe("view update scheduling", () => { + test("schedules view updates", async () => { + const player = new Player(); + player.start(minimal as any); + + const view = (player.getState() as InProgressState).controllers.view + .currentView?.lastUpdate; + + expect(view).toStrictEqual({ + id: "view-1", + type: "view", + label: { + asset: { + id: "action-label", + type: "text", + value: "Clicked 0 times", + }, + }, + }); + + (player.getState() as InProgressState).controllers.data.set([["count", 1]]); + + await vitest.waitFor(() => { + expect( + (player.getState() as InProgressState).controllers.view.currentView + ?.lastUpdate, + ).toStrictEqual({ + id: "view-1", + type: "view", + label: { + asset: { + id: "action-label", + type: "text", + value: "Clicked 1 times", + }, + }, + }); + }); + + (player.getState() as InProgressState).controllers.data.set( + [["count", 2]], + { silent: true }, + ); + + // Add a delay here to flush any queued updates + await new Promise((resolve) => { + setTimeout(resolve, 10); + }); + + expect( + (player.getState() as InProgressState).controllers.view.currentView + ?.lastUpdate, + ).toStrictEqual({ + id: "view-1", + type: "view", + label: { + asset: { + id: "action-label", + type: "text", + value: "Clicked 1 times", + }, + }, + }); + + // non-silent update an unrelated field, should trigger an update to the original + (player.getState() as InProgressState).controllers.data.set([ + ["not-count", 1], + ]); + + await vitest.waitFor(() => { + expect( + (player.getState() as InProgressState).controllers.view.currentView + ?.lastUpdate, + ).toStrictEqual({ + id: "view-1", + type: "view", + label: { + asset: { + id: "action-label", + type: "text", + value: "Clicked 2 times", + }, + }, + }); + }); + }); +}); diff --git a/core/player/src/controllers/flow/__tests__/flow.test.ts b/core/player/src/controllers/flow/__tests__/flow.test.ts index 6bafbde96..2542c6163 100644 --- a/core/player/src/controllers/flow/__tests__/flow.test.ts +++ b/core/player/src/controllers/flow/__tests__/flow.test.ts @@ -147,7 +147,7 @@ test("Fails to transition when not started", () => { expect(() => flow.transition("foo")).toThrowError(); }); -test("Fails to transition during another transition", () => { +test("Fails to transition during another transition", async () => { const flow = new FlowInstance("flow", { startState: "View1", View1: { @@ -167,15 +167,32 @@ test("Fails to transition during another transition", () => { }, }); + let deferredVar: string; + + const transition = () => { + try { + flow.transition("Next"); + return "foo"; + } catch (error: unknown) { + return "bar"; + } + }; + flow.hooks.resolveTransitionNode.intercept({ call: (nextState) => { if (nextState?.onStart) { - expect(() => flow.transition("Next")).toThrowError(); + deferredVar = transition(); } }, }); flow.start(); + + await vitest.waitFor(() => { + expect(deferredVar).toBeDefined(); + }); + + expect(deferredVar!).toBe("bar"); }); describe("promise api", () => { diff --git a/core/player/src/controllers/validation/controller.ts b/core/player/src/controllers/validation/controller.ts index 25eb71bdd..eb625f231 100644 --- a/core/player/src/controllers/validation/controller.ts +++ b/core/player/src/controllers/validation/controller.ts @@ -184,13 +184,10 @@ class ValidatedBinding { public getAll(): Array { return this.applicableValidations.reduce((all, statefulObj) => { if (statefulObj.state === "active" && statefulObj.response) { - return [ - ...all, - { - ...statefulObj.response, - blocking: this.checkIfBlocking(statefulObj), - }, - ]; + all.push({ + ...statefulObj.response, + blocking: this.checkIfBlocking(statefulObj), + }); } return all; @@ -604,18 +601,18 @@ export class ValidationController implements BindingTracker { // Get all of the validations from each provider const possibleValidations = this.getValidationProviders().reduce< Array - >( - (vals, provider) => [ - ...vals, + >((vals, provider) => { + vals.push( ...(provider.provider .getValidationsForBinding?.(binding) ?.map((valObj) => ({ ...valObj, [VALIDATION_PROVIDER_NAME_SYMBOL]: provider.source, })) ?? []), - ], - [], - ); + ); + + return vals; + }, []); if (possibleValidations.length === 0) { return; diff --git a/core/player/src/controllers/view/controller.ts b/core/player/src/controllers/view/controller.ts index ebd07b633..373f0894c 100644 --- a/core/player/src/controllers/view/controller.ts +++ b/core/player/src/controllers/view/controller.ts @@ -41,6 +41,8 @@ export class ViewController { private pendingUpdate?: { /** pending data binding changes */ changedBindings?: Set; + /** Whether we have a microtask queued to handle this pending update */ + scheduled?: boolean; }; public currentView?: ViewInstance; @@ -53,10 +55,11 @@ export class ViewController { ) { this.viewOptions = options; this.viewMap = initialViews.reduce>( - (viewMap, view) => ({ - ...viewMap, - [view.id]: view, - }), + (viewMap, view) => { + // eslint-disable-next-line no-param-reassign + viewMap[view.id] = view; + return viewMap; + }, {}, ); @@ -76,19 +79,25 @@ export class ViewController { ); /** Trigger a view update */ - const update = (updates: Set) => { + const update = (updates: Set, silent = false) => { if (this.currentView) { if (this.optimizeUpdates) { - this.queueUpdate(updates); + this.queueUpdate(updates, silent); } else { this.currentView.update(); } } }; - options.model.hooks.onUpdate.tap("viewController", (updates) => { - update(new Set(updates.map((t) => t.binding))); - }); + options.model.hooks.onUpdate.tap( + "viewController", + (updates, updateOptions) => { + update( + new Set(updates.map((t) => t.binding)), + updateOptions?.silent ?? false, + ); + }, + ); options.model.hooks.onDelete.tap("viewController", (binding) => { const parentBinding = binding.parent(); @@ -103,14 +112,21 @@ export class ViewController { }); } - private queueUpdate(bindings: Set) { + private queueUpdate(bindings: Set, silent = false) { if (this.pendingUpdate?.changedBindings) { + // If there's already a pending update, just add to it don't worry about silent updates here yet this.pendingUpdate.changedBindings = new Set([ ...this.pendingUpdate.changedBindings, ...bindings, ]); } else { - this.pendingUpdate = { changedBindings: bindings }; + this.pendingUpdate = { changedBindings: bindings, scheduled: false }; + } + + // If there's no pending update, schedule one only if this one isn't silent + // otherwise if this is silent, we'll just wait for the next non-silent update and make sure our bindings are included + if (!this.pendingUpdate.scheduled && !silent) { + this.pendingUpdate.scheduled = true; queueMicrotask(() => { const updates = this.pendingUpdate?.changedBindings; this.pendingUpdate = undefined; diff --git a/core/player/src/expressions/evaluator.ts b/core/player/src/expressions/evaluator.ts index c54ea901c..0688cce90 100644 --- a/core/player/src/expressions/evaluator.ts +++ b/core/player/src/expressions/evaluator.ts @@ -78,6 +78,9 @@ export interface HookOptions extends ExpressionContext { * The caller is responsible for handling the error. */ throwErrors?: boolean; + + /** Whether expressions should be parsed strictly or not */ + strict?: boolean; } export type ExpressionEvaluatorOptions = Omit< @@ -226,7 +229,8 @@ export class ExpressionEvaluator { try { storedAST = - this.expressionsCache.get(matchedExp) ?? parseExpression(matchedExp); + this.expressionsCache.get(matchedExp) ?? + parseExpression(matchedExp, { strict: options.strict }); this.expressionsCache.set(matchedExp, storedAST); } catch (e: any) { if (options.throwErrors || !this.hooks.onError.call(e)) { diff --git a/core/player/src/expressions/parser.ts b/core/player/src/expressions/parser.ts index de428c05a..9e038bc5e 100644 --- a/core/player/src/expressions/parser.ts +++ b/core/player/src/expressions/parser.ts @@ -2,7 +2,12 @@ /** * An expression to AST parser based on JSEP: http://jsep.from.so/ */ -import type { ExpressionNode, ExpressionNodeType, NodeLocation } from "./types"; +import type { + ErrorWithLocation, + ExpressionNode, + ExpressionNodeType, + NodeLocation, +} from "./types"; import { ExpNodeOpaqueIdentifier } from "./types"; const PERIOD_CODE = 46; // '.' @@ -62,16 +67,8 @@ const binaryOps: Record = { "%": 14, }; -interface ErrorWithLocation extends Error { - /** The place in the string where the error occurs */ - index: number; - - /** a helpful description */ - description: string; -} - /** Wrap the message and index in an error and throw it */ -function throwError(message: string, index: number) { +function throwError(message: string, index: number): ErrorWithLocation { const err = new Error(`${message} at character ${index}`); (err as ErrorWithLocation).index = index; @@ -797,7 +794,7 @@ export function parseExpression( args.push(node); } - if (charIndex !== termination) { + if (strictMode && charIndex !== termination) { throwError(`Expected ${String.fromCharCode(termination)}`, index); } @@ -929,7 +926,7 @@ export function parseExpression( nodes.push(node); // If we weren't able to find a binary expression and are out of room, then // the expression passed in probably has too much - } else if (index < length) { + } else if (strictMode && index < length) { throwError(`Unexpected "${exprI(index)}"`, index); } } diff --git a/core/player/src/expressions/types.ts b/core/player/src/expressions/types.ts index bbe23a63f..b9f7a6f0b 100644 --- a/core/player/src/expressions/types.ts +++ b/core/player/src/expressions/types.ts @@ -231,3 +231,11 @@ export type ExpressionNode = | ObjectNode; export type ExpressionNodeType = ExpressionNode["type"]; + +export interface ErrorWithLocation extends Error { + /** The place in the string where the error occurs */ + index: number; + + /** a helpful description */ + description: string; +} diff --git a/core/player/src/expressions/utils.ts b/core/player/src/expressions/utils.ts index 5fa190c3f..1ba36cdcd 100644 --- a/core/player/src/expressions/utils.ts +++ b/core/player/src/expressions/utils.ts @@ -1,5 +1,6 @@ import { isExpressionNode } from "./types"; import type { + ErrorWithLocation, ExpressionHandler, ExpressionNode, ExpressionObjectType, @@ -148,3 +149,13 @@ export function isObjectExpression( "value" in expr ); } + +/** + * Type guard for ErrorWithLocation + */ +export function isErrorWithLocation(error: Error): error is ErrorWithLocation { + return ( + (error as ErrorWithLocation).index !== undefined && + (error as ErrorWithLocation).description !== undefined + ); +} diff --git a/core/player/src/view/parser/index.ts b/core/player/src/view/parser/index.ts index 234f9cdf2..dc6e3292f 100644 --- a/core/player/src/view/parser/index.ts +++ b/core/player/src/view/parser/index.ts @@ -149,7 +149,6 @@ export class Parser { path: string[] = [], ): NestedObj => { if (typeof objToParse !== "object" || objToParse === null) { - // value = objToParse; return { value: objToParse, children: [] }; } diff --git a/docs/site/components/Navigation.tsx b/docs/site/components/Navigation.tsx index 024ec2fd9..672cfe49d 100644 --- a/docs/site/components/Navigation.tsx +++ b/docs/site/components/Navigation.tsx @@ -216,7 +216,21 @@ export const VersionSelector = () => { }} value={router.basePath || "latest"} onChange={(e) => { - router.push(`${DOCS_BASE_URL}/${e.target.value}`); + const currentRoute = router.pathname + .split('/') + .filter((s) => { + // filter out empty string and url elements that include the version + if ( + s && + !['latest', 'next', ...released.map((r) => r.path)].includes(s) + ) { + return true; + } + + return false; + }) + .join('/'); + router.push(`${DOCS_BASE_URL}${e.target.value}/${currentRoute}`); }} > diff --git a/docs/site/components/PlayerTeam.tsx b/docs/site/components/PlayerTeam.tsx index 409efdbcb..9d06e4d4b 100644 --- a/docs/site/components/PlayerTeam.tsx +++ b/docs/site/components/PlayerTeam.tsx @@ -21,9 +21,11 @@ const ProfileCard = (props: any) => { export const PlayerTeam = () => { return ( - {teamdata.map((element) => { - return ; - })} + {teamdata + .sort(() => 0.5 - Math.random()) + .map((element) => { + return ; + })} ); }; diff --git a/docs/site/components/mdx-components.tsx b/docs/site/components/mdx-components.tsx index 2a96851b4..a9d36f15b 100644 --- a/docs/site/components/mdx-components.tsx +++ b/docs/site/components/mdx-components.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import path from 'path'; +import path, { parse } from 'path'; import Link from 'next/link'; import { Heading, @@ -23,12 +23,19 @@ import { Tr, Td, Link as CLink, + Alert as ChakraAlert, + AlertStatus, + AlertTitle, + AlertDescription, + AlertIcon, + Box, } from '@chakra-ui/react'; import { MDXProviderComponents } from '@mdx-js/react'; import { withRouter, useRouter } from 'next/router'; import { CodeHighlight } from './code-highlight'; import { withBasePrefix } from './Image'; import { PlayerTeam } from './PlayerTeam'; +import {AppContext} from "./Context"; /** * Generic Tab Component that extends Chakra's Tab @@ -53,6 +60,11 @@ const CodeTabsNameMap = new Map([ ['android', 'Android'], ]); +const ContentTabsNameMap = new Map([ + ['json', 'JSON'], + ['tsx', 'TSX'], +]); + const CodeTabsMap = new Map([['gradle', GradleTab]]); /** @@ -62,7 +74,7 @@ const Tabs = (props: any) => { return ( props.callback?.(index)} > @@ -127,6 +139,17 @@ const PlatformTabs = (props: React.PropsWithChildren) => { ); }; +/** + * Tab section for Content Authoring. This should include tsx and/or example JSON files. + */ +const ContentTabs = (props: React.PropsWithChildren) => { + const children = React.Children.toArray(props.children).filter((c: any) => { + return ContentTabsNameMap.has(c.props.mdxType.toLowerCase()); + }); + + return {children}; +}; + const langMap: Record = { js: 'javascript', ts: 'typescript', @@ -210,6 +233,26 @@ export const InlineCode = (props: JSX.IntrinsicElements['code']) => { ); }; +type ChakraAlertProps = React.PropsWithChildren<{ + status?: AlertStatus; + title?: string; + description?: string; +}> + +export const Alert = (props: ChakraAlertProps) => { + return ( + + + + {props.title && {props.title}} + {props.description && {props.description}} + {props.children} + + + ); +}; + + /** * Anchor tab component wrapping Chakra's */ @@ -243,10 +286,16 @@ export const MDXComponents: MDXProviderComponents = { PlatformTabs: withRouter(PlatformTabs), + ContentTabs, + table: Table, th: Th, tr: Tr, td: Td, inlineCode: InlineCode, + + Alert, + AlertTitle, + AlertDescription, }; diff --git a/docs/site/config/navigation.ts b/docs/site/config/navigation.ts index 85719d9ea..35c13e714 100644 --- a/docs/site/config/navigation.ts +++ b/docs/site/config/navigation.ts @@ -48,6 +48,10 @@ const navigation: Navigation = { title: 'Writing Plugins', path: '/writing-plugins', }, + { + title: 'Plugin Implementation', + path: '/plugin-implementation', + }, { title: 'Multi-Flow Experiences', path: '/guides/multi-flow-experiences', @@ -65,6 +69,10 @@ const navigation: Navigation = { title: 'Overview', path: '/content', }, + { + title: 'Navigation', + path: '/content/navigation', + }, { title: 'Assets & Views', path: '/content/assets-views', @@ -74,12 +82,25 @@ const navigation: Navigation = { path: '/content/data-expressions', }, { - title: 'Navigation', - path: '/content/navigation', + title: 'Schema', + path: '/content/schema', + }, + ], + }, + { + title: 'Authoring', + routes: [ + { + title: 'Overview', + path: '/dsl', + }, + { + title: 'Views', + path: '/dsl/views', }, { - title: 'Templates', - path: '/content/templates', + title: 'Schema', + path: '/dsl/schema', }, ], }, @@ -102,6 +123,10 @@ const navigation: Navigation = { title: 'Custom Assets', path: '/assets/custom', }, + { + title: 'DSL Components', + path: '/assets/dsl', + }, ], }, { @@ -111,10 +136,6 @@ const navigation: Navigation = { title: 'Storybook', path: '/tools/storybook', }, - { - title: 'TSX Content Authoring', - path: '/tools/dsl', - }, { title: 'CLI', path: '/tools/cli', diff --git a/docs/site/config/team.json b/docs/site/config/team.json index 942b18ec9..e1405bf9a 100644 --- a/docs/site/config/team.json +++ b/docs/site/config/team.json @@ -16,7 +16,7 @@ }, { "name": "Ketan Reddy", - "domain": ["React", "Core"], + "domain": ["Core", "Tools"], "avatar": "https://avatars.githubusercontent.com/u/8703262?v=4" }, { @@ -28,5 +28,25 @@ "name": "Tony Lin", "domain": ["Android"], "avatar": "https://avatars.githubusercontent.com/u/13474011?v=4" + }, + { + "name": "Rafael Campos", + "domain": ["Tools"], + "avatar": "https://avatars.githubusercontent.com/u/26394217?v=4" + }, + { + "name": "Marlon Ercillo", + "domain": ["React"], + "avatar": "https://avatars.githubusercontent.com/u/3643493?v=4" + }, + { + "name": "Alejandro Fimbres", + "domain": ["Tools"], + "avatar": "https://avatars.githubusercontent.com/u/9345943?v=4" + }, + { + "name": "Nancy Wu", + "domain": ["iOS"], + "avatar": "https://avatars.githubusercontent.com/u/66387473?v=4" } ] diff --git a/docs/site/next.config.mjs b/docs/site/next.config.mjs index 518786235..bac1176c0 100644 --- a/docs/site/next.config.mjs +++ b/docs/site/next.config.mjs @@ -7,12 +7,13 @@ import { URL } from 'url'; const __dirname = new URL('.', import.meta.url).pathname; // This will be replaced during the build stamping -export const BASE_PREFIX = process.env.NODE_ENV === 'production' ? '/DOCS_BASE_PATH' : undefined; +export const BASE_PREFIX = + process.env.NODE_ENV === 'production' ? '/DOCS_BASE_PATH' : undefined; export default { reactStrictMode: true, env: { - NEXT_PUBLIC_BASE_PATH: BASE_PREFIX + NEXT_PUBLIC_BASE_PATH: BASE_PREFIX, }, basePath: BASE_PREFIX, assetPrefix: BASE_PREFIX, @@ -20,7 +21,7 @@ export default { webpack: (config, { dev, isServer, ...options }) => ({ ...config, infrastructureLogging: { - level: "error", + level: 'error', }, module: { ...config.module, @@ -28,15 +29,11 @@ export default { ...config.module.rules, { test: /plugin-nav-data.json$/, - use: [ - path.join(__dirname, './plugins/plugin-nav-generator') - ] + use: [path.join(__dirname, './plugins/plugin-nav-generator')], }, { test: /search-index.json$/, - use: [ - path.join(__dirname, './plugins/search-index-loader') - ] + use: [path.join(__dirname, './plugins/search-index-loader')], }, { test: /.mdx?$/, // load both .md and .mdx files @@ -51,11 +48,12 @@ export default { ], rehypePlugins: [ rehypeSlug, // Add ids to headings - [rehypeAutolinkHeadings, { behavior: 'wrap'}] // Make headings links - ] + [rehypeAutolinkHeadings, { behavior: 'wrap' }], // Make headings links + ], }, }, path.join(__dirname, './plugins/md-layout-loader'), + path.join(__dirname, './plugins/mdx-link-append-loader.js'), ], }, ], diff --git a/docs/site/pages/assets/custom.mdx b/docs/site/pages/assets/custom.mdx index 0beb94484..c2ac1720c 100644 --- a/docs/site/pages/assets/custom.mdx +++ b/docs/site/pages/assets/custom.mdx @@ -4,9 +4,10 @@ title: 'Custom Assets' # Custom Assets -One of the conscious design decisions we made when building Player was to abstract away the actual asset implementation and open it up for users to bring their own when using Player. This way you can seamlessly integrate Player into your existing experiences and reuse UI assets you may have already built. Below we've outlined the way to build custom assets on the various platforms Player supports. +One of the conscious design decisions we made when building Player was to abstract away the actual asset implementation and open it up for users to bring their own when using Player. This way you can seamlessly integrate Player into your existing experiences and reuse UI assets you may have already built. Below we've outlined the way to build custom assets on the various platforms Player supports. -## React + + ### Create Your Asset @@ -61,7 +62,8 @@ const CustomAssetComp = (props) => { This would automatically find the appropriate handler for the `props.header` asset and use that to render. -## iOS + + SwiftUI Player assets are made of 3 parts: @@ -190,7 +192,7 @@ In either situation, your asset implementation needs only to override the view p ```swift class ActionAsset: UncontrolledAsset { - public override var view: AnyView { ActionView(model: model) } + public override var view: AnyView { AnyView(ActionView(model: model)) } } ``` @@ -230,11 +232,15 @@ In the latter case, it is recommended to extend the original asset, so as to avo ##### Why Would I Register my Asset as a Variant? + 1. Transform backed assets have functions that are attached to them, through shared JavaScript plugins. This simplifies setting data from the asset, by giving simple functions like `run` in the reference `ActionAsset` for example. Swift only asset types will not have any convenience functions. 2. Registering as a variant allows you to maintain usage of the transform backed asset as well as your new asset, so both can be used by the same `SwiftUIPlayer` instance, including in the same flow. This also maintains the semantics of Player content, an `action` asset is always an `action` type of interaction, but with `metaData`, it can be displayed differently. -## Android + + + + In order to render an asset a renderer for that type must be registered in the Android Player. If a renderer is found, then Player will delegate rendering when that type is encountered, otherwise Player will skip that node. Creating and registering such a renderer requires the following: @@ -278,12 +284,12 @@ In most cases, there is some additional data that is used to make the rendering ```kotlin class TextAsset(assetContext: AssetContext) : DecodableAsset(Data.serializer()) { - + @Serializable data class Data( val value: String ) - + } ``` @@ -335,7 +341,7 @@ A helper is provided to reduce overhead with rendering an asset into a layout. ` ```kotlin // title_container is a view extension referencing a FrameLayout in an XML layout -data.title.render() into title_container +data.title.render() into title_container ``` #### Styling @@ -408,3 +414,7 @@ This asset will only be used when decoding a view in the JSON tree that is type In this scenario, the custom action asset implementation would still have access to the transformed values of a regular action asset, such as the `run` method. When creating a new plugin, remember to register it when building the AndroidPlayer! + + + + diff --git a/docs/site/pages/assets/dsl.mdx b/docs/site/pages/assets/dsl.mdx new file mode 100644 index 000000000..d83da32ca --- /dev/null +++ b/docs/site/pages/assets/dsl.mdx @@ -0,0 +1,207 @@ +--- +title: Writing DSL Components +--- + +# Creating TSX Components + +In order to take advantage of the auto-completion and validation of TypeScript types, asset libraries can export a component library for content authoring. Creating components isn't much different than writing a React component for the web. The primative elements uses the [react-json-reconciler](https://github.com/intuit/react-json-reconciler) to create the JSON content tree, with utilities to make it quick and painless to create new asset-components. + + +## Creating a Basic Component + +The `Asset` component from the `@player-tools/dsl` package is the quickest way to create a new component. The `Asset` component will take all the Asset's properties and convert them to their equivalent JSON representation when serialized. + +In the examples below, we'll be creating a TSX component for the `action` asset in our reference set. + +The `action` asset has a `label` slot (which is typically used as a `text` asset), a `value` (for flow transitions), and an `exp` for evaluating expressions. +For this example we'll use a resemblance of this type, but in practice types should be imported directly from their asset rather than duplicating them. + +```ts +import type { Asset, AssetWrapper, Expression } from '@player-ui/player'; + +export interface ActionAsset extends Asset<'action'> { + /** The transition value of the action in the state machine */ + value?: string; + + /** A text-like asset for the action's label */ + label?: AssetWrapper; + + /** An optional expression to execute before transitioning */ + exp?: Expression; +} +``` + +_Note: The `Asset` type we're importing here from the `@player-ui/player` package is different than the `Asset` component from the `@player-tools/dsl` package. The former is the basic TypeScript definition for what an Asset in Player is while the latter is a helper function for allowing DSL components to be created. Fundamentally they share a name to reinforce the abstraction of foundational capabilities to core libraries_ + +To turn this interface into a usable component, create a new React component that _renders_ an Asset: + +```tsx +import { Asset, AssetPropsWithChildren } from '@player-tools/dsl'; + +export const Action = (props: AssetPropsWithChildren) => { + return ; +} +``` + +This would allow users to import the `Action` component, and _render_ it to JSON: + +```tsx +const myView = +``` + +which when compiled would look like + +```json +{ + "id": "root", + "type": "action", + "value": "next" +} +``` + +The `AssetPropsWithChildren` type is a utility type to help convert the `Asset` type (which has a required `id` and `type` properties) to a type more suited for components. It changes the `id` to be optional, and adds a `applicability` property automatically. + +## Slots + +Continuing the example fo the `ActionAsset`, we need a way for users to users to specify the nested `label` property, which itself is another asset. This can be accomplished using the `createSlot` utility function. The `createSlot` function also accept components to enable automatically creating `text` and `collection` assets when they aren't specified where needed. If these components aren't passed into the slot when used, the resulting content may be invalid. Let's add a `Label` slot to our `Action` component to allow it to be easily authored. Lets assume we already have a `Text` and `Collection` component. + + +```tsx +import React from 'react'; +import { Asset, AssetPropsWithChildren, createSlot } from '@player-tools/dsl'; + +export const Action = (props: AssetPropsWithChildren) => { + return ; +} + +Action.Label = createSlot({ + name: 'label', + wrapInAsset: true, + TextComp: SomeTextComponent + CollectionComp: SomeCollectionComponent +}) +``` + +This adds component (`Action.Label`) that will automatically place any nested children under the `label` property of the parent asset: + +```tsx +const myView = ( + + + + + +); +``` + + +```tsx +import React from 'react'; + +const myView = ( + + Continue + +); +``` + +which when compiled would look like (note the auto injection of the `Text` asset and corresponding Asset Wrapper): + +```json +{ + "id": "root", + "type": "action", + "value": "next", + "label": { + "asset": { + "id": "root-label-text", + "type": "text", + "value": "Continue" + } + } +} +``` + +And if we wanted to have the `label` property to have to text assets we could write the following DSL + +```tsx +const myView = ( + + + Some + Text + + +); +``` + +which when compiled would look like the following (note the automatic insertion of the `Collection` Asset): + +```json +{ + "id": "root", + "type": "action", + "value": "next", + "label": { + "asset": { + "id": "root-collection", + "type": "text", + "values": [ + { + "asset": { + "id": "root-collection-1-text", + "type": "text", + "value": "Some" + } + },{ + "asset": { + "id": "root-collection-2-text", + "type": "text", + "value": "Text" + } + } + ] + } + } +} +``` + +## Creating a Complex Component + +While a majority of Assets can be described simply via the base `Action` Component, there are certain cases where DSL components need to contain a bit more logic. This section aims to describe further tools that are offered in the `@player-tools/dsl` package. + +### Components with Specially Handled Properties + +In the previous example, we covered how to create a DSL Component for our reference `Action` Asset. Our actual Action Asset however looks a little bit different. + +```tsx +import React from 'react'; + +export const Action = ( + props: Omit, 'exp'> & { + /** An optional expression to execute before transitioning */ + exp?: ExpressionTemplateInstance; + } +) => { + const { exp, children, ...rest } = props; + + return ( + + {exp?.toValue()} + {children} + + ); +}; +``` + +Crucially, the difference is in how the `exp` property is handled. As the `exp` property is an `Expression`, if we just allowed the `Action` component to process this property, we would end up with an `ExpressionTemplate` instance _not_ an `Expression` instance. While technically they are equivalent, there is no need to wrap the final string in the Expression Template tags (`@[]@`) since we know the string will be an `Expression` and it will just lead to additonal procssing at runtime. Therefore, we need to do a few things to properly construct this DSL component. + +The first is to modify the type for the commponent. In the above code snippit we are using the `Omit` type to remove the base `exp` property from the source type and replacing it with an `exp` property that expects a `ExpressionTemplateInstance` which allows an DSL expression to be passed in. + +The second is to extract out the `exp` property from the props and use a `property` component to manually control how that property will get serialized. This component is exposed by the underlying `react-json-reconciler` library which also supplies an `array`, `obj` and `value` component to allow full control over more complicated data structures. The `@player-tools/dsl` package also exposes the `toJsonProperties` function to process whole non-Asset objects. + +### View Components + +For Assets that are intended to be Views, a `View` component is exported from the `@player-tools/dsl` package. Its usage is exactly the same as the `Asset` component, however it correctly handles the serialization of any Crossfield Validations that exist on the View. + + diff --git a/docs/site/pages/assets/reference.mdx b/docs/site/pages/assets/reference.mdx index c9ca72c49..d85e2ef72 100644 --- a/docs/site/pages/assets/reference.mdx +++ b/docs/site/pages/assets/reference.mdx @@ -4,9 +4,10 @@ title: 'Reference Assets' # Reference Assets -To help users get started with Player, we have created a minimal set of useable assets for React, iOS and Android. These reference assets showcase the design philosophy we encourage when building your own asset sets. While these components are functional we _do not_ recommend shipping them to production as they have not been tested to be production ready. +To help users get started with Player, we have created a minimal set of useable assets for React, iOS and Android. These reference assets showcase the design philosophy we encourage when building your own asset sets. While these components are functional we _do not_ recommend shipping them to production as they have not been tested to be production ready. -## React + + For the React Player, we ship a package (`@player-ui/reference-assets-plugin-react`) that provides the reference set of assets. Each asset package exposes a `Component`, type, and optional transform. They also export hooks for easily consuming the transformed component and supplying your own UI for a custom look and feel. These components are all then exposed via a plugin that can be added to Player like so: @@ -23,8 +24,8 @@ const reactPlayer = new ReactPlayer({ The reference assets + React player can be viewed in [Storybook](/storybook-demo) - -## iOS + + Alongside distributing the `PlayerUI` pod, there is a `PlayerUI/ReferenceAssets` subspec used as examples of how Player APIs work for building assets. This pod uses the shared TypeScript transform functions that the React and Android players use for their reference assets, ensuring consistent behavior across platforms. These assets can be loaded into Player like so: @@ -44,7 +45,8 @@ struct MyApp: View { } ``` -## Android + + Alongside distributing the Android Player framework, there is a reference assets library used as an example of how Player APIs work for building assets. These are intended to serve as a reference, but can be used early on in development as a means of understanding how to work with Player. This asset set uses the shared TypeScript transform functions that the Web and Android players use for their reference assets, ensuring consistent behavior across platforms. These assets can be loaded into Player like so: @@ -52,4 +54,7 @@ Alongside distributing the Android Player framework, there is a reference assets import com.intuit.player.android.reference.assets.ReferenceAssetsPlugin val player = AndroidPlayer(context, ReferenceAssetsPlugin()) -``` \ No newline at end of file +``` + + + diff --git a/docs/site/pages/content/assets-views.mdx b/docs/site/pages/content/assets-views.mdx index 9a3907786..364ed2743 100644 --- a/docs/site/pages/content/assets-views.mdx +++ b/docs/site/pages/content/assets-views.mdx @@ -33,10 +33,6 @@ Nested assets are represented as objects containing an `asset` property. For exa The `label` of the parent contains a nested asset reference. These are _slots_ that can usually contain any asset type. -## Applicability - -Any object in the tree (including _assets_) may contain an `applicability` property. This is an _expression_ that may conditionally show or hide an asset (and all of it's children) from the view tree. Applicability is dynamically calculated and will automatically update as data changes on the page. - # Views Views are _assets_ that exist at the top level of the tree. They typically include the navigation actions, a title, or other top-level information. @@ -69,3 +65,284 @@ Example: ``` They follow the same guidelines for normal validation references, with the addition of a `ref` property that points to the binding that this validation is tied to. + +## Applicability + +Any object in the tree (including _assets_) may contain an `applicability` property. This is an _expression_ that may conditionally show or hide an asset (and all of it's children) from the view tree. Applicability is dynamically calculated and will automatically update as data changes on the page. + +# Switches + +Switches are ways of dynamically changing the structure of the view based on data. There are 2 types of switches: `static` and `dynamic`, but their structures are identical. `switches` can appear anywhere you'd find a normal asset, and (similar to [templates](./templates)) are removed from the view before it reaches the UI layer. + +## Usage + +The switch is simply a list of objects with `case` and `asset` properties: + +- `asset` - The asset that will replace the switch if the case is true +- `case` - An [expression](./expression) to evaluate. + +The switch will run through each _case_ statement until the first case expression evaluates to true. For the _default_ case, simple use a value of `true` at the end of the array. + +## Static v Dynamic Switches + +The only difference between a `static` and `dynamic` switch is the timing update behavior after the first rendering of a view. + +A `staticSwitch` calculates the applicable case when a view first renders. It will not re-calculate any of the case statements as data in the view is updated. If you transition away from view-node, and revisit it later-on in the flow, the switch will re-compute the appropriate case statement. + +A `dynamicSwitch` will always update the applicable case statement whenever data changes. If data is changed while a view is still showing, the switch will be updated to reflect the new case. + +## Example + +Anywhere you can place an `asset` node, a `dynamicSwitch` or `staticSwitch` can be placed instead. + +```json +{ + "staticSwitch": [ + { + "case": "{{name.first}} == 'adam'", + "asset": { + "id": "name", + "type": "text", + "value": "Yay" + } + }, + { + "case": "{{name.first}} == 'notadam'", + "asset": { + "id": "name", + "type": "text", + "value": "Nay" + } + }, + { + "case": true, + "asset": { + "id": "name", + "type": "text", + "value": "🤷" + } + } + ] +} +``` + +# Templates + +Templates provide a way to dynamically create a list of assets, or _any_ object, based on data from the model. All of the templating semantics are removed by the time it reaches an asset's transform or UI layer. + +## Usage + +Within any asset, specify a `template` property as an array of: + +- `data` - A binding that points to an array in the model +- `output` - A property to put the mapped objects +- `value` - The template to use for each object/item in the data array. +- `dynamic` - (optional, false by default) A boolean that specifies whether template should be recomputed when data changes + +Within a template, the `_index_` string can be used to substitute the array-index of the item being mapped. + +### Example + +**Authored** + +```json +{ + "asset": { + "id": "top-level", + "type": "collection", + "template": [ + { + "data": "list.of.names", + "output": "values", + "value": { + "asset": { + "id": "value-_index_", + "type": "text", + "value": "{{list.of.names._index_}}" + } + } + } + ] + } +} +``` + +**Output** + +```json +{ + "asset": { + "id": "top-level", + "type": "collection", + "values": [ + { + "asset": { + "id": "value-0", + "type": "text", + "value": "Adam" + } + }, + { + "asset": { + "id": "value-1", + "type": "text", + "value": "Not Adam" + } + } + ] + } +} +``` + +## Multiple templates + +There's a few ways to leverage multiple templates within a single asset. Templates can be _nested_ or multiple used on a single node. These can also be combined to build out complicated nested expansion. + +### Nested Templates + +Templates can contain other templates. When referencing a nested template, append the template depth to the `_index_` string to reference the correct data-item. + +For example, if 1 template contains another, use `_index_` to reference the outer-loop, and `_index1_` to reference the inner loop. Furthermore, if templates are nested three levels deep, the first level loop will still be referenced by `_index_`, the second level will be referenced by `_index1_` and the bottom most loop will be referenced by `_index2_`. + +### Multiple Templates - Single Output + +Templates will, by default, create an array, if needed, for the `output` property of each template. If that array already exits (either by manually writing it in the JSON, or from a previous template run), each item will be appended to the end of the existing array. + +This can be leveraged by combining multiple template directives that use the same `output` property, or by having an `output` use an existing array: + +**_Example_** + +Both templates in the example below output to the `values` array on the parent object. Since no `values` array exists, the first template will create said array, and the second will append to that. + +```json +{ + "asset": { + "id": "top-level", + "type": "collection", + "template": [ + { + "data": "list.of.names", + "output": "values", + "value": { + "asset": { + "id": "name-_index_", + "type": "text", + "value": "{{list.of.names._index_}}" + } + } + }, + { + "data": "list.of.other-names", + "output": "values", + "value": { + "asset": { + "id": "other-name-_index_", + "type": "text", + "value": "{{list.of.names._index_}}" + } + } + } + ] + } +} +``` + +**_Example_** + +The template below will append it's values to the pre-existing `values` array. + +```json +{ + "asset": { + "id": "top-level", + "type": "collection", + "values": [ + { + "asset": { + "id": "existing-name", + "type": "text", + "value": "Something hard-coded" + } + } + ], + "template": [ + { + "data": "list.of.names", + "output": "values", + "value": { + "asset": { + "id": "name-_index_", + "type": "text", + "value": "{{list.of.names._index_}}" + } + } + } + ] + } +} +``` + +## Dynamic and Static Templates + +Like switches, the only difference between a `static` and `dynamic` template is the timing update behavior after the first rendering of a view. If not defined, the value of `dynamic` is default to `false`. + +If `dynamic` is `false`, the template will be parsed when a view first renders. The template will not be parsed again as data in the view is updated. + +If `dynamic` is `true`, template will be always updated whenever data changes. If data is changed while a view is still showing, the template will be updated to reflect the new data. + +**_Example_** + +```json +{ + "asset": { + "id": "top-level", + "type": "collection", + "template": [ + { + "dynamic": true, + "data": "list.of.names", + "output": "values", + "value": { + "asset": { + "id": "value-_index_", + "type": "text", + "value": "{{list.of.names._index_}}" + } + } + } + ] + } +} +``` + +```typescript +model.set([['list.of.names', ['Jain']]]); +model.set([['list.of.names', ['Jain', 'Erica']]]); +``` + +**Output** + +```json +{ + "asset": { + "id": "top-level", + "type": "collection", + "values": [ + { + "asset": { + "id": "value-0", + "type": "text", + "value": "Jain" + } + }, + { + "asset": { + "id": "value-1", + "type": "text", + "value": "Erica" + } + } + ] + } +} +``` diff --git a/docs/site/pages/content/data-expressions.mdx b/docs/site/pages/content/data-expressions.mdx index 2f277e68d..988d9fabd 100644 --- a/docs/site/pages/content/data-expressions.mdx +++ b/docs/site/pages/content/data-expressions.mdx @@ -177,6 +177,37 @@ There are a few expressions built into Player. These are pretty basic, so if you | name | description | arguments | | --------------- | ------------------------------------------------------------------------------------- | ------------------ | -| `getDataval` | Fetches a value from the model. This is equivalent to using the `{{foo.bar}}` syntax. | `binding` | +| `getDataVal` | Fetches a value from the model. This is equivalent to using the `{{foo.bar}}` syntax. | `binding` | | `setDataVal` | Sets a value from the model. This is equivalent to using `{{foo.bar}} = 'value'` | `binding`, `value` | | `deleteDataVal` | Clears a value from the model. | `binding` | +| `conditional` | Execute expressions, or return data based on an expression condition | `condition`, `valueIfTrue`, `valueIfFalse` | + +### Examples + +#### `getDataVal` +```json +{ + "value": "Hello @[getDataVal('user.name')]@" +} +``` + +#### `setDataVal` +```json +{ + "exp": "setDataVal('user.name', 'Test User')" +} +``` + +#### `deleteDataVal` +```json +{ + "exp": "deleteDataVal('user.name')" +} +``` + +#### `conditional` +```json +{ + "value": "It is @[ conditional({{foo.bar}} == 'DAY', 'daytime', 'nighttime') ]@." +} +``` \ No newline at end of file diff --git a/docs/site/pages/content/dsl.mdx b/docs/site/pages/content/dsl.mdx deleted file mode 100644 index f4e308edf..000000000 --- a/docs/site/pages/content/dsl.mdx +++ /dev/null @@ -1,404 +0,0 @@ ---- -title: TSX DSL ---- - -# TSX/JSX Content Authoring - -While Player content _can_ be written directly in JSON, it's not always the preferable authoring format. To take advantage of existing developer tool-chains, Player provides a mechanism for authoring content in (J/T)SX as React components. This is paired with a `cli` to transpile the React tree into a JSON content. - -One thing to note is that the paths in the input folder should match the expected paths in your config file. E.g if the `pages` option is set to `topics`, the source TSX/JSX files should be under a `topics` directory in your input directory. Similarly if the schema option is set to `topic_schema.json` the ts file containing the schema object should be in the root directory and named `topic_schema.ts`. - -## Writing JSX Content - -In order to use the JSX-variant to write content, your asset library should ship a JSX component package to leverage. These will define the primitive _components_ to use to build up the tree. - -In the examples below, we will assume one exists. - -### Bindings and Expressions - -Both `binding` and `expression` in the JSX authoring leverages a tagged template, typically abbreviated as `b` and `e` respectively. In a similar fashion to using `css` or `graphql` in a JS file, this enables syntax-highlighting and validation of bindings and expressions within a JS file. - -To create a binding, or expression: - -```tsx -import { binding as b, expression as e } from '@player-tools/dsl'; - -const myBinding = b`foo.bar`; -const myExpression = e`foo()`; -``` - -The binding and expression instances can also automatically dereference themselves when used inside of another string: - -```tsx -const stringWithBinding = `Some text: ${myBinding}`; // 'Some text: {{foo.bar}}' -const stringWithExp = `Some expr: ${myExpression}`; // 'Some expr: @[foo()]@' -``` - -### Assets/Views - -Writing assets or views is as simple as creating a React element: - -```tsx -import { Input, Text, Collection } from 'my-assets'; - -const view = ( - - Some value - - Some label - - -); -``` - -this would generate something similar to: - -```json -{ - "id": "root", - "type": "collection", - "values": [ - { - "asset": { - "id": "root-values-1", - "type": "text", - "value": "Some value" - } - }, - { - "asset": { - "id": "root-values-2", - "type": "input", - "label": { - "asset": { - "id": "root-values-2-label", - "type": "text", - "value": "Some label" - } - } - } - } - ] -} -``` - -#### IDs - -Any asset can accept an `id` property (just like the JSON version), however ids will automatically be generated for assets missing them. - -#### Collection/Text Creation - -In the event that an asset object is expected, but a `string` or `number` is found, Player will attempt to automatically create a text node, provided the asset-library has a text-asset-factory configured. - -Similarly, if a single asset is expected but a list of them is found instead, Player will attempt to create a _collection_ asset provided the library has the proper configuration set. - -### Templates - -Templates are included via the `@player-tools/dsl` package. This can be used in any asset slot: - -```tsx - - - - Value 1 - - -``` - -Templates can be nested within one another, and the auto-id generation will handle adding the `_index_` information to any generated `id`. - -### Switches - -The `@player-tools/dsl` module also includes support for _static_ and _dynamic_ switches. - -Use the `isDynamic` flag to denote this should be a `dynamicSwitch` instead of a `staticSwitch`: - -```tsx - - - - - Text 1 - - - Text 1 - - - - -``` - -### Navigation - -At this time the `navigation` section is a basic JS object. The `@player-ui/types` package provides typescript typings for these. - -```tsx -import { Navigation, Schema } from '@player-ui/types'; - -const navigation: Navigation = { - BEGIN: 'Start', - Start: { - startState: 'VIEW_1', - VIEW_1: { - state_type: 'VIEW', - ref: 'view-1', - transitions: { - '*': 'END_Done', - }, - }, - END_Done: { - state_type: 'END', - outcome: 'done', - }, - }, -}; -``` - -### Schema - -To author a schema object you should first start by constructing a standard typescript object where the nested paths correlate to the paths on your desired schema. At the final conversion to a Player `Schema` object during the serialization phase the intermediate types and ROOT elements will automatically be constructed. A basic example would be: - -```typescript -const mySchema = { - foo: { - bar: { - baz: //somevalue - faz: //somevalue - } - } -} -``` - -which correlates to a schema of: - -```json -{ - "ROOT": { - "foo": { - "type": "fooType" - } - }, - "fooType": { - "bar": { - "type": "barType" - } - }, - "barType": { - "baz": { - "type": "" - }, - "faz": { - "type": "" - } - } -} -``` - -#### Arrays - -A single object array can be used to indicate an array type, for example: - -```typescript -const mySchema = { - foo: [ - { - bar: //some type - } - ] -} -``` - -will generate the schema: - -```json -{ - "ROOT": { - "foo": { - "type": "fooType", - "isArray": true - } - }, - "fooType": { - "bar": { - "type": "barType" - } - }, - "barType": { - "baz": { - "type": "" - }, - "faz": { - "type": "" - } - } -} -``` - -#### Changing the Name of a Generated Type - -To change the name of the generated type at any point in the tree, import the `SchemaTypeName` symbol from the `@player-tools/dsl` and use it as a key to change the name like so: - -```typescript -const mySchema = { - foo: { - bar: { - [SchemaTypeName]: "buzz", - baz: //somevalue - faz: //somevalue - } - } -} -``` - -#### Defining Data Types - -The leaf nodes of the data structure will need some concrete definition of what data exists at that point of the schema. There are two ways to provide this data. - -##### Data Refs - -The `common-types-plugin` package exposes the types it provides to Player when used and _references_ to those types as well. Using these `Language.DataTypeRef` values you can indicate what the data type will be at that node and that it will be a type explicitly defined in the Player so no additional information needs to be provided (e.g. validations nor formats) - -Various plugins in the `@cg-player` scope also expose similar `DataTypeRef` objects that include common tax types. For convenience the `@cg-player/components` package reexports all the data references for one convenient import. It is important to note that there will be an issue if you use a `DataTypeRef` from a plugin but run the content in a Player that doesn't have that plugin loaded. - -##### Local Data Types - -Sometimes you need to define specific data types that extend existing types for certain pieces of data in a schema, whether that be for specific validations, formatting or both. In this case, in your DSL project you can define an object of type `Schema.DataType` and provide that value to a leaf node. That will indicate that this unique type needs to be included in its entirety to Player as it has information not already contained in Player. - -##### What that Looks Like - -Using our previous example we can fill in the values with some types now to look like this in the ts object: - -```typescript - -import { Schema, BooleanTypeRef } from '@player-ui/types' - -const mycustombooleantype: Schema.DataType = { - type: "BooleanType", - validation: [ - { - type: 'oneOf', - message: 'Value has to be true or false', - options: [true, false], - }, - ], -} - -const mySchema = { - foo: { - bar: { - baz: BooleanTypeRef - faz: mycustombooleantype - } - } -} - -export default mySchema -``` - -and like this in the final schema: - -```json -{ - "ROOT":{ - "foo":{ - "type": "fooType" - } - }, - "fooType":{ - "bar": { - "type":"barType" - } - }, - "barType":{ - "baz":{ - "type": "BooleanType" - }, - "faz":{ - "type": "BooleanType", - "validation": [ - { - "type": "oneOf", - "message": "Value has to be true or false", - "options": [true, false], - }, - ], - } - } -} - -It should be noted that unless the schema object is a default export the `schema.json` will not be created in the output folder. -``` - -#### Using the Schema Object in JSX/TSX Content - -There is one important function that enables us to use our schema object in content.`makeBindingsForObject()` takes your schema object and constructs the bindings opaquely so that you can use the native object path with functions like `.toString()`, `.toValue()`, and `toRefString()` like you could with regular string template bindings. - -Using these functions we can use the schema directly in content: - -```jsx - -const schema = makeBindings(mySchema) - -const baz = schema.foo.bar.baz - -const view = ( - - - - The current value is {baz.toString()} - - - -) - -const validations = [ - { - type: "requiredIf", - ref: baz.toRefString(), - message: "This is required", - }, -]; - -const navigation = {...} - -export default { - id: "example-topic", - topic: "exampletopic", - views: [view], - navigation, -}; - -``` - -### Custom Assets - -If you need to make use of a custom asset or an asset that is in Player but not yet implemented in the Player DSL there are a couple of ways you can do this. - -Define Asset -```typescript -const customAsset = { - asset: { - id: 'id', - type: 'custom', (use the type of the asset you want to use) - ... - } -} -``` -Pass asset into an existing asset -``` - -``` -Pass asset in as child -```jsx - - {toJsonProperties(customAsset)} - -``` -OR - -You can create an Asset using the `` component from `@player-tools/dsl`. -```jsx - -``` diff --git a/docs/site/pages/content/navigation.mdx b/docs/site/pages/content/navigation.mdx index 2e0cdc629..fdb040844 100644 --- a/docs/site/pages/content/navigation.mdx +++ b/docs/site/pages/content/navigation.mdx @@ -94,11 +94,15 @@ The flow state executes the referenced flow, and its `outcome` determines the tr Reaching this state will execute the `FLOW_2` flow -- and if `FLOW_2` reaches an `END` state with an `outcome` of `next`, will transition to the `VIEW_2` state +## Expressions + State types can also contain `onStart` and `onEnd` properties for evaluating expressions. Order of operations: 1. `onStart` - Evaluated at the start of a node's lifecycle; useful for updating data before it's resolved 2. `exp` -3. `onEnd` - Evaluated last, right before transition. If a transition is halted (by validation or otherwise), the expression won't be executed. +3. `onEnd` - Evaluated last, right before transition. + 1. For an `onEnd` expression defined on an individual state, if a transition is halted (by validation or otherwise), the `onEnd` expressions for that state won't be executed. + 2. As Player's navigation is a state machine, `onEnd` expressions defined for the entire flow will only execute when the state machine ends the flow, by reaching an `END` state. Terminating the flow by unmounting Player (on any given platform) will not execute flow defined `onEnd` expressions as it would not have reached an `END` state. ## Examples @@ -128,7 +132,7 @@ This is the simplest of flows. The navigation begins with executing `FLOW_1`. `F ![Single Flow Example](/simple-flow.png?darkModeInvert) -### Flow with `onStart` expression +### Flow with `onStart` expression on a `VIEW` state ```json { @@ -153,7 +157,7 @@ This is the simplest of flows. The navigation begins with executing `FLOW_1`. `F The `view` node in this flow utilizes an `onStart` expression to update the `ref` property dynamically. The expression is evaluated before the data is resolved, and the node references the updated `id`. -### Flow with multiple expressions +### Flow with multiple expression types on a `VIEW` state ```json { diff --git a/docs/site/pages/content/schema.mdx b/docs/site/pages/content/schema.mdx index 0f059253b..3530d73ee 100644 --- a/docs/site/pages/content/schema.mdx +++ b/docs/site/pages/content/schema.mdx @@ -114,7 +114,7 @@ To attach a validation to a path in the data-model, add a reference to a validat } ``` -Each _validation_ reference must include a `type` property which corresponds to the name of the validator to run. Player includes some validators out-of-the-box, and custom `validators` can be registered as well. See [here]() for more details around which validators are supported, and how to add custom ones. +Each _validation_ reference must include a `type` property which corresponds to the name of the validator to run. Player includes some validators out-of-the-box, and custom `validators` can be registered as well. See [the Common Types Plugin](../plugins/common-types) docs for more details around which validators are supported, and how to add custom ones. Any additional properties on the validation reference are passed as _options_ to the `validator`. In the example above, a hypothetical `length` validator can take a `min` and `max` as the boundaries for the length of a string. diff --git a/docs/site/pages/content/switches.mdx b/docs/site/pages/content/switches.mdx deleted file mode 100644 index 3fb6fb13c..000000000 --- a/docs/site/pages/content/switches.mdx +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Switches ---- - -# Switches - -Switches are ways of dynamically changing the structure of the view based on data. There are 2 types of switches: `static` and `dynamic`, but their structures are identical. `switches` can appear anywhere you'd find a normal asset, and (similar to [templates](./templates)) are removed from the view before it reaches the UI layer. - -## Schema - -The switch is simply a list of objects with `case` and `asset` properties: - -- `asset` - The asset that will replace the switch if the case is true -- `case` - An [expression](./expression) to evaluate. - -The switch will run through each _case_ statement until the first case expression evaluates to true. For the _default_ case, simple use a value of `true` at the end of the array. - -## `Static` vs `Dynamic` - -The only difference between a `static` and `dynamic` switch is the timing update behavior after the first rendering of a view. - -A `staticSwitch` calculates the applicable case when a view first renders. It will not re-calculate any of the case statements as data in the view is updated. If you transition away from view-node, and revisit it later-on in the flow, the switch will re-compute the appropriate case statement. - -A `dynamicSwitch` will always update the applicable case statement whenever data changes. If data is changed while a view is still showing, the switch will be updated to reflect the new case. - -## Example - -Anywhere you can place an `asset` node, a `dynamicSwitch` or `staticSwitch` can be placed instead. - -```json -{ - "staticSwitch": [ - { - "case": "{{name.first}} == 'adam'", - "asset": { - "id": "name", - "type": "text", - "value": "Yay" - } - }, - { - "case": "{{name.first}} == 'margie'", - "asset": { - "id": "name", - "type": "text", - "value": "Nay" - } - }, - { - "case": true, - "asset": { - "id": "name", - "type": "text", - "value": "🤷" - } - } - ] -} -``` diff --git a/docs/site/pages/content/templates.mdx b/docs/site/pages/content/templates.mdx deleted file mode 100644 index cea5e0acf..000000000 --- a/docs/site/pages/content/templates.mdx +++ /dev/null @@ -1,224 +0,0 @@ ---- -title: Templates ---- - -# Templates - -Templates provide a way to dynamically create a list of assets, or _any_ object, based on data from the model. All of the templating semantics are removed by the time it reaches an asset's transform or UI layer. - -## Usage - -Within any asset, specify a `template` property as an array of: - -- `data` - A binding that points to an array in the model -- `output` - A property to put the mapped objects -- `value` - The template to use for each object/item in the data array. -- `dynamic` - (optional, false by default) A boolean that specifies whether template should be recomputed when data changes - -Within a template, the `_index_` string can be used to substitute the array-index of the item being mapped. - -### Example - -**Authored** - -```json -{ - "asset": { - "id": "top-level", - "type": "collection", - "template": [ - { - "data": "list.of.names", - "output": "values", - "value": { - "asset": { - "id": "value-_index_", - "type": "text", - "value": "{{list.of.names._index_}}" - } - } - } - ] - } -} -``` - -**Output** - -```json -{ - "asset": { - "id": "top-level", - "type": "collection", - "values": [ - { - "asset": { - "id": "value-0", - "type": "text", - "value": "Adam" - } - }, - { - "asset": { - "id": "value-1", - "type": "text", - "value": "Margie" - } - } - ] - } -} -``` - -## Multiple templates - -There's a few ways to leverage multiple templates within a single asset. Templates can be _nested_ or multiple used on a single node. These can also be combined to build out super complicated nested expansion. - -### Nested Templates - -Templates can contain other templates. When referencing a nested template, append the template depth to the `_index_` string to reference the correct data-item. - -For example, if 1 template contains another, use `_index_` to reference the outer-loop, and `_index1_` to reference the inner loop. - -### Multiple Templates - Single Output - -Templates will, by default, create an array, if needed, for the `output` property of each template. If that array already exits (either by manually writing it in the JSON, or from a previous template run), each item will be appended to the end of the existing array. - -This can be leveraged by combining multiple template directives that use the same `output` property, or by having an `output` use an existing array: - -**_Example_** - -Both templates in the example below output to the `values` array on the parent object. Since no `values` array exists, the first template will create said array, and the second will append to that. - -```json -{ - "asset": { - "id": "top-level", - "type": "collection", - "template": [ - { - "data": "list.of.names", - "output": "values", - "value": { - "asset": { - "id": "name-_index_", - "type": "text", - "value": "{{list.of.names._index_}}" - } - } - }, - { - "data": "list.of.other-names", - "output": "values", - "value": { - "asset": { - "id": "other-name-_index_", - "type": "text", - "value": "{{list.of.names._index_}}" - } - } - } - ] - } -} -``` - -**_Example_** - -The template below will append it's values to the pre-existing `values` array. - -```json -{ - "asset": { - "id": "top-level", - "type": "collection", - "values": [ - { - "asset": { - "id": "existing-name", - "type": "text", - "value": "Something hard-coded" - } - } - ], - "template": [ - { - "data": "list.of.names", - "output": "values", - "value": { - "asset": { - "id": "name-_index_", - "type": "text", - "value": "{{list.of.names._index_}}" - } - } - } - ] - } -} -``` - -## Dynamic and Static Templates - -As in like switches, the only difference between a `static` and `dynamic` template is the timing update behavior after the first rendering of a view. If not defined, the value of `dynamic` is default to `false`. - -If `dynamic` is `false`, the template will be parsed when a view first renders. The template will not be parsed again as data in the view is updated. - -If `dynamic` is `true`, template will be always updated whenever data changes. If data is changed while a view is still showing, the template will be updated to reflect the new data. - -**_Example_** - -```json -{ - "asset": { - "id": "top-level", - "type": "collection", - "template": [ - { - "dynamic": true, - "data": "list.of.names", - "output": "values", - "value": { - "asset": { - "id": "value-_index_", - "type": "text", - "value": "{{list.of.names._index_}}" - } - } - } - ] - } -} -``` - -```typescript -model.set([['list.of.names', ['Jain']]]); -model.set([['list.of.names', ['Jain', 'Erica']]]); -``` - -**Output** - -```json -{ - "asset": { - "id": "top-level", - "type": "collection", - "values": [ - { - "asset": { - "id": "value-0", - "type": "text", - "value": "Jain" - } - }, - { - "asset": { - "id": "value-1", - "type": "text", - "value": "Erica" - } - } - ] - } -} -``` diff --git a/docs/site/pages/dsl/index.mdx b/docs/site/pages/dsl/index.mdx new file mode 100644 index 000000000..bf8036720 --- /dev/null +++ b/docs/site/pages/dsl/index.mdx @@ -0,0 +1,172 @@ +--- +title: DSL Content Overview +--- + +# TSX/JSX Content Authoring (Player DSL) + +While Player content _can_ be written directly in JSON, it's definitely not the preferable authoring format. To take advantage of existing developer tool-chains, Player provides a mechanism for authoring content in (J/T)SX as React components and simple TypeScript objects. The Player CLI can then be used to transpile the React tree into a JSON content. + +## DSL Benefits + +At a high level, the benefits to writing Player content in the DSL can be summarized by three key factors: + +#### Easier maintainability +Simply put, DSL code more concise than its JSON equivalent. That means there is less code for you to have to maintain. Also, as its easier to read than JSON, when you do need to make updates to it, its much more wieldy to work with. + +#### Better development experience +Since the DSL leverages a lot of standard TypeScript language features, most editors will offer quality of life features like typechecking, suggestions, and code generation. All of this is in service of shortening the feedback loop of writing content and ensuring it is what you intended for it. + +#### Easier to extend +The DSL now offers a easily accessible programatic hook into Player content. This allows custom tooling to be created around your DSL integration much easier that before. Common patterns can be extracted into higher level compoennts, functions can be created to generate code, and code generation can be integrated into almost any process where relevant data is present. + +For a further explination on the benefits, see the DSL Benefits section in the [DSL Views](./views.mdx#dsl-benefits-in-views) and the [DSL Schema](./schema.mdx#dsl-benefit-in-schema) + +## Writing DSL Content + +In order to use the DSL to write content, your plugin library should ship a DSL component package. These will define the primitive _components_ to use to build up the tree. Authorship of these components is covered in the [Writing DSL Components](../assets/dsl) secton. The Player Reference Assets ship their own DSL Components via the `@player-ui/reference-assets-components` pacakge. + +In the examples below, we will use the Player Reference Assets Components. + +### Basic Setup + +To get started, you'll need the following dependencies in your `package.json`: + +```json +{ + "dependencies": { + "@player-tools/dsl": "0.4.1", + "@player-tools/cli": "0.4.1", + "@player-ui/reference-assets-components": "0.6.0", + "@types/react": "17.0.39", + "react": "17.0.2" + } +} +``` + +Next, you'll need to configure your environment for DSL Compilation and JSON validation. Below is a basic configuration that can be added in your `package.json`. For a more detailed explination and examples on further customization please refer to the [CLI](../tools/cli) section. + +```json +{ + "player": { + "dsl": { + "src": "./src/main/tsx", + "outDir": "./out" + }, + "json": { + "src": "./out/*.json" + }, + } +} +``` + +### Basic Format and File Layout + +By default, all files that contain a Player Flow should be exported as a `.tsx` file and the schema should be in a `.ts` file. For how to change this behavior, please refer to the [DSL Plugins](./plugins) section of the docs. Each of these files should contain a default export of their appropriate object. For example a file that exports a flow should look like the following: + +```tsx +export default { + id: 'my-flow', + views: [....], + navigation: {....} +} +``` + +and a file that exports the schema should look like: + +```typescript +const mySchema = {...} + +export default mySchema + +``` + +### Navigation + +At this time the `navigation` section is a basic JS object. The `@player-ui/types` package provides typescript typings for this. + +```tsx +import { Navigation } from '@player-ui/types'; + +const navigation: Navigation = { + BEGIN: 'Start', + Start: { + startState: 'VIEW_1', + VIEW_1: { + state_type: 'VIEW', + ref: 'view-1', + transitions: { + '*': 'END_Done', + }, + }, + END_Done: { + state_type: 'END', + outcome: 'done', + }, + }, +}; +``` + +One convenience feature is the auto injection of the the `ref` property for a `VIEW` type state if the corresponding view is a React tree. + +```tsx +import { Navigation } from '@player-ui/types'; + +const view = ( + + Some value + + Some label + + +); + +const navigation: Navigation = { + BEGIN: 'Start', + Start: { + startState: 'VIEW_1', + VIEW_1: { + state_type: 'VIEW', + ref: view, + transitions: { + '*': 'END_Done', + }, + }, + END_Done: { + state_type: 'END', + outcome: 'done', + }, + }, +}; +``` + +_Note: The `Navigation` type we're importing here from the `@player-ui/types` package is different than the `Navigation` type from the `@player-tools/dsl` package. The former is the core definition for what the Navigation section of Player content is. The latter has specific replacements to take DSL constructs where normal objects would be defined._ + +### Bindings and Expressions + +Both `binding` and `expression` in the JSX authoring leverages a tagged template, typically abbreviated as `b` and `e` respectively. In a similar fashion to using `css` or `graphql` in a JS file, this enables syntax-highlighting and validation of bindings and expressions within a JS file. + +```tsx +import { binding as b, expression as e } from '@player-tools/dsl'; + +const myBinding = b`foo.bar`; +const myExpression = e`foo()`; +``` + +The binding and expression instances can also automatically dereference themselves when used inside of another string: + +```tsx +const stringWithBinding = `Some text: ${myBinding}`; // 'Some text: {{foo.bar}}' +const stringWithExp = `Some expr: ${myExpression}`; // 'Some expr: @[foo()]@' +``` + +### View + +Please refer to the [Views](../dsl/views) section for a detailed overview of how to write DSL Views + +### Schema + +Please refer to the [Schema](../dsl/schema) section for a detailed overview of how to write DSL Schemas + +## Compiling DSL Content + +Once your DSL content is authored, you can use the Player CLI to compile and validate your content. For documentation on this functionality, please refer to the [Player CLI](../tools/cli) section \ No newline at end of file diff --git a/docs/site/pages/dsl/plugins.mdx b/docs/site/pages/dsl/plugins.mdx new file mode 100644 index 000000000..1e81c7058 --- /dev/null +++ b/docs/site/pages/dsl/plugins.mdx @@ -0,0 +1,46 @@ +--- +title: DSL Plugins +--- + +# DSl Plugins + +Much like the rest of Player, DSL compilation supports plugins that can influce how content gets compiled and generated. DSL Plugins are a subset of CLI Plugins that use either the hooks available on the CLI itself or on the DSL compiler instance created by the CLI. This section will cover the hooks that are available for use and why you might want to tap them. + +## CLI Hooks + +The `createCompilerContext` function available to plugins that extend the `PlayerCLIPlugin` class gives access to the `CompilationContext` instance. This class manages the context around DSL compilation and exposes two related hooks. + +### `identifyContentType` + +The `identifyContentType` hooks's purpose is to allow plugins to inject custom behavior around detecting what kind of file is being compiled. By default there are three types of content the CLI is aware of (`view`, `flow`, and `schema`). Its methods for detecting which kind of content is contained within a file is very rudimentary (the logic can be found [here](https://github.com/player-ui/tools/blob/main/language/dsl/src/compiler/utils.ts#L5)). In order to allow desired convention or orchestrate the compilation of custom file types, this hook provides a mechanism for allowing that custom logic to be injected. The result of this hook is used in the next hook + +### `compileContent` + +The `compileContent` hook's purpose is to allow the custom compilation logic for any identified file type. As it is an `AsyncSeriesBailHook` it will take the first result returned from a tap who was able to return a result for the compilation for the given file of the identified type. In the case where no external logic is added, the hook will attempt to compile any of its known content types with the built in compiler instance. + +## Compilation Hooks + +The CLI will initialize an instance of the `DSLCompiler` and provide a reference to it via the `onCreateDSLCompiler` function available to plugins that extend the `PlayerCLIPlugin` class. On the compiler itself, the following hook are available to modify the behavior of how DSL content is compiled. + +### `preProcessFlow` +_Note: Only called for `view` or `flow` content_ + +This hook allows transformations on the content before it is compiled. This enables the injection of additonal data or resolving any integration specific convention into something that may be understood by the compiler. This hook can also be used to collate information on what is being compiled for use later. + +### `postProcessFlow` +_Note: Only called for `view` or `flow` content_ + +This hook allows transformations on the content after it is compiled. This allows modifications to the compiled content which in some cases may be preferable as manipulating JSON may be easier than a React Tree. + +### `schemaGenerator` + +This hook gives access to the internal `SchemaGenerator` object which is responsible for compiling the schema. On this generator there are the following hooks. + +#### `createSchemaNode` + +This hook allows custom logic for processing schema nodes as they are generated. This enables arbitrary properties to be statically or dynamically added based on the authored schema node. One potential usecase of this is to allow integration specific semantic conventions to be defined and injected into the final schema. For example, the presence of a specific `Symbol` might mean that a property needs to be injected or even that the schema tree from this point on needs to be modified. + + +### `onEnd` + +This hook is called to signal that the compilation of all files has been completed. This allows any post processing on the output as a whole to take place as a part of the build process. This may include actions like moving or bundling the compilation results or writing new files based on information collected via other hooks on the files that were processed. \ No newline at end of file diff --git a/docs/site/pages/dsl/schema.mdx b/docs/site/pages/dsl/schema.mdx new file mode 100644 index 000000000..f0b69272b --- /dev/null +++ b/docs/site/pages/dsl/schema.mdx @@ -0,0 +1,308 @@ +--- +title: Writing DSL Schemas +--- + +# Basic Schema + +To author a schema object you should first start by constructing a standard typescript object where the nested paths correlate to the paths on your desired schema. When compiled to the final Player `Schema` object, the intermediate types and ROOT elements will automatically be constructed. A basic example would be: + +```typescript +export default { + foo: { + bar: { + baz: {...} + faz: {...} + } + } +} +``` + +which correlates to a schema of: + +```json +{ + "ROOT": { + "foo": { + "type": "fooType" + } + }, + "fooType": { + "bar": { + "type": "barType" + } + }, + "barType": { + "baz": {...}, + "faz": {...} + } +} +``` + +## Arrays + +A single object array can be used to indicate an array type, for example: + +```typescript +export default { + foo: [ + { + bar: {...} + } + ] +} +``` + +will generate the schema: + +```json +{ + "ROOT": { + "foo": { + "type": "fooType", + "isArray": true + } + }, + "fooType": { + "bar": { + "type": "barType" + } + }, + "barType": { + "baz": {...}, + "faz": {...} + } +} +``` + +## Changing the Name of a Generated Type + +To change the name of the generated type at any point in the tree, import the `SchemaTypeName` symbol from the `@player-tools/dsl` and use it as a key on the object whos name you want to change: + +```typescript +import { SchemaTypeName } from "@player-tools/dsl" +export default { + foo: { + bar: { + [SchemaTypeName]: "buzz", + baz: {...} + faz: {...} + } + } +} +``` + +will generate the schema: + +```json +{ + "ROOT": { + "foo": { + "type": "fooType", + "isArray": true + } + }, + "fooType": { + "buzz": { + "type": "buzzType" + } + }, + "buzzType": { + "baz": { + "type": "" + }, + "faz": { + "type": "" + } + } +} +``` + +# Defining Data Types + +The leaf nodes of the schema will need some concrete definition of what data exists at that point of the schema. There are two ways to provide this data. + +## Data Refs + +The `@player-ui/common-types-plugin` package exposes the types it provides to Player when used and _references_ to those types as well. Using these `Language.DataTypeRef` values you can indicate what the data type will be at that node and that it will be a type explicitly defined in Player so no additional information needs to be provided (e.g. validations nor formats) as at runtime it will use the type loaded into Player by the plugin. + +It is recommended that if your player integration loads additional types, to export similar references to those types to make authorship easier. + +##### Local Data Types + +Sometimes you need to define specific data types that extend existing types for certain pieces of data in a schema, whether that be for specific validations, formatting or both. In this case, in your DSL project you can define an object of type `Schema.DataType` and provide that value to a leaf node. That will indicate that this unique type needs to be included in its entirety to Player as it has information not already contained in Player. + +##### What that Looks Like + +Using our previous example we can fill in the values with some types now to look like this in the ts object: + +```typescript +import { dataTypes } from '@player-ui/common-types-plugin'; +import type { Schema } from '@player-ui/types'; + +const mycustombooleantype = { + type: "BooleanType", + validation: [ + { + type: 'oneOf', + message: 'Value has to be true or false', + options: [true, false], + }, + ], +} satisfies Schema.DataType + +const mySchema = { + foo: { + bar: { + baz: dataTypes.BooleanTypeRef + faz: mycustombooleantype + } + } +} + +export default mySchema +``` + +and like this in the final schema: + +```json +{ + "ROOT":{ + "foo":{ + "type": "fooType" + } + }, + "fooType":{ + "bar": { + "type":"barType" + } + }, + "barType":{ + "baz":{ + "type": "BooleanType" + }, + "faz":{ + "type": "BooleanType", + "validation": [ + { + "type": "oneOf", + "message": "Value has to be true or false", + "options": [true, false], + }, + ], + } + } +} +``` + +# DSLSchema Type + +A `DSLSchema` Type is provided in order be able to customize a set of the acceptable data types and validator functions to be used for authoring the DSL data schema in your workspace. This is useful as it allows content authors to get realtime feedback on their data schema. It can catch any structural issues that may produce an invalid schema at compile time or produce a schema that uses functionality thats not available at runtime. + +_Note: A ready-to-use DSLSchema type is shipped with `@player-ui/reference-assets-components`. This type is predefined with the `DataType` and `ValidatorFunction` references inferred from the `@player-ui/common-types-plugin`. Next, you'll be presented the steps in its creation for reference._ + +The first step to fill in the `DSLSchema` type with your integration specific values is importing the `DSLSchema` type and the relevant helper types and utilities from `@player-tools/dsl`. For this example we are importing the `@player-ui/common-types-plugin` in order to use its data types and validators. Our first step is to generate the `DataType` and `ValidatorFunction` object types and references: + +```typescript +import { + DSLSchema, + DataTypeReference + DataTypeRefs, + ValidatorFunctionRefs, + getObjectReferences +} from "@player-tools/dsl" +import { + dataTypes as commonDataTypes, + validators as commonValidators +} from "@player-ui/common-types-plugin"; + +/** Abstracting the types from commonDataTypes to be passed as generic to the DataTypeRefs type for dynamically generating the data type reference Types */ +type myCommonDataTypesRefs = DataTypeRefs + +/** Using getObjectReferences helper to generate the actual data type references to be used in your schema by passing inferred types from commonDataTypes and myCommonDataTypesRefs */ +export const dataRefs = getObjectReferences( + coreDataSet +); +``` + +We'll proceed generating the validation function types: + +```typescript +/** Abstracting types from coreValidators and using as generic of ValidatorFunctionRefs for dynamically generating the data validation function reference Types */ +type commonValidatorRefs = ValidatorFunctionRefs +``` + +The final step is to provide the data Types set and validator function reference Types as generics for the `DataTypeReference` type which is the sole generic type passed into the `DSLSchema` instance: +```typescript +type CommonDSLSchema = DSLSchema< + DataTypeReference +> +``` + +Finally, this is how to use the custom schema type to type check your schema. By adding the `satisfies` keyword followed by your `DSLSchema` generated type, your editor's LSP will show if there is anything not compliant with the data types and validation functions you defined in the schema: + +```typescript +import { CommonDSLSchema, dataRefs } from "./MyTypes" + +const { BooleanTypeRef } = dataRefs + +const exampleSchema = { + myDataSet = { + /** Simply using the BooleanTypeReference to define "firstPath" type to Boolean */ + firstPath: BooleanTypeRef + secondPath: { + /** For adding custom validation for "secondPath", define an object definition with the data "type" property, which is "TextType" for this example */ + type: "TextType", + /** In the validation array open another object definition specifying the use of the "required" validator with the "type" property, with a custom "message" value */ + validation: [ + { + type: "required", + message: "This field is required" + } + ] + } + } +} satisfies CommonDSLSchema +``` + +_Note: The `satisfies` Typescript operator is used instead of type assignment (`exampleSchema:DSLSChema`), because `satisfies` doesn't lose the inferred type of your value by changing and broadening the type unlike using assignment, it simply type-checks, creating localised feedback if anything is incorrect._ + + +# Using the Schema Object in JSX/TSX Content + +As the schema is now a TypeScript obejct, you can now directly reference the schema anywhere in content. The `makeBindingsForObject()` function takes your schema object and constructs the bindings opaquely within the object. This allows the use of the native path in your authored content and for the actual underlying binding to be used when the content is compiled. Additionally, as the underlying bindings are exposed, can you can use the native object path with functions like `.toString()`, `.toValue()`, and `toRefString()` like you could with regular string template bindings. + +```jsx +import { makeBindingsForObject } from '@player-tools/dsl'; +import { mySchema } from './schema' + +const schema = makeBindingsForObject(mySchema) + +const baz = schema.foo.bar.baz + +const view = ( + + + + The current value is {baz.toString()} + + + +) + +const navigation = {...} + +export default { + id: "example", + views: [view], + navigation, +} +``` + +# DSL Benefits in Schema +Player's schema has a few issues that makes it both hard to write and very susceptible to experience breaking mistakes. The DSL Schema aims to mitigate all of these pitfalls. + +## Format +The flattened nature of Player's schema makes it confusing to write for content authors new to Player and tedius to write for more experienced content authors. By allowing the schema to be represented in a tree format, the schema is both easier to write and more closely resembles the shape that the data will take in the data model. + +## Ease of Use +The biggest quality of life improvement the DSL schema brings is that the schema object can be directly used in content. This almost completely eliminates the positiblity of mistyping a schema path leading to runtime errors and massively reduces how verbose content needs to be. Additionally, with plugins able to export references to the data types and validation functions they export, content can now directly reference those artifacts further reducing the possibility of producing invalid content. Lastly, given the number of features that are available in the schema, it can be overwhelming to remember what can/should be used where. By making the schema a normal TypeScript object and assigning it the proper type, content authors are given autocomplete suggestions and a cursory level of validation in their development environment while they are writing their schema. \ No newline at end of file diff --git a/docs/site/pages/dsl/views.mdx b/docs/site/pages/dsl/views.mdx new file mode 100644 index 000000000..13457d727 --- /dev/null +++ b/docs/site/pages/dsl/views.mdx @@ -0,0 +1,133 @@ +--- +title: Writing DSL Views +--- + +# Overview +Writing assets or views is as simple as creating a React element using your base DSL components: + +```tsx +import React from 'react'; +import { Input, Text, Collection } from '@player-ui/reference-assets-components'; + +const view = ( + + Some value + + Some label + + +); +``` + +When compiled, this would produce the following JSON. + +```json +{ + "id": "root", + "type": "collection", + "values": [ + { + "asset": { + "id": "root-values-1", + "type": "text", + "value": "Some value" + } + }, + { + "asset": { + "id": "root-values-2", + "type": "input", + "label": { + "asset": { + "id": "root-values-2-label", + "type": "text", + "value": "Some label" + } + } + } + } + ] +} +``` + +Not only is the source DSL content a fraction of the output object's size (making it easier to read and maintain) as the base components use the same TypeScript types as the assets themselves, you will receive in editor suggestions and type checks as you author your content. + +# View Concepts in DSL + +## Templates + +Templates are included via the `@player-tools/dsl` package. This can be used in any asset slot: + +```tsx +import React from 'react'; +import { dataTypes } from '@player-ui/common-types-plugin'; +import { makeBindingsForObject, Template } from '@player-tools/dsl'; + +const schema = { + foo: [{ + bar: dataTypes.StringType, + }], +}; + +const bindings = makeBindingsForObject(schema); + + + + + + +``` + +Templates can be nested within one another, and the auto-id generation will handle adding the `_index_` information to any generated `id`. + +## Switches + +The `@player-tools/dsl` module also includes support for _static_ and _dynamic_ switches. + +Use the `isDynamic` flag to denote this should be a `dynamicSwitch` instead of a `staticSwitch`: + +```tsx +import React from 'react'; +import { Switch } from '@player-tools/dsl'; + + + + + + Text 1 + + + Text 1 + + + + +``` + +# DSL Benefits in Views + +## IDs + +Any asset can accept an `id` property, however automatic ID creation is supported out of the box by the base `Asset` component and it's generation behavior can be further customized via your component's implementation. + +## Collection/Text Creation + +In the event that an asset object is expected, but a `string` or `number` is found, Player will attempt to automatically create a text node, provided the asset-library has a text-asset-factory configured. + +Similarly, if a single asset is expected but a list of them is found instead, Player will attempt to create a _collection_ asset provided the library has the proper configuration set. + +## Meta Components + +As DSL components are React component, they can be composed into reusable building blocks to simplify and abstract away common UI patterns. By centralizing these patterns, code duplication can be minimized and updates across multiple sets of content can be simplified. These composed components don't just have to be built on top of the base set of DSL components, DSL components themselves can offer common shortcuts for behavior. For example, if we wanted to offer an out of the box `Action` component that could be used as a `Next` action asset, we could export the following from the DSL components library. + +```tsx +import React from 'react'; + +Action.Next = () => ( + + Continue + +); +``` \ No newline at end of file diff --git a/docs/site/pages/faqs.mdx b/docs/site/pages/faqs.mdx index f6ab3ed32..05516cfeb 100644 --- a/docs/site/pages/faqs.mdx +++ b/docs/site/pages/faqs.mdx @@ -9,6 +9,10 @@ While it definitely helps to understand how it works under the hood, as long as If you are an engineer integrating Player into your experience you should probably be familiar with what [Plugins](./plugins) are available and how to [write your own](./writing-plugins) to support the folks who will be authoring the content. The more advanced your use case is you can explore topics as they become applicable. +## Where can I see Player how player is being used? +One of the easiest ways to see Player in action is through our [Storybook](https://player-ui.github.io/latest/tools/storybook). This will allow you to take a look at how some of the JSON is passed into player, and what that might look like with our set of reference assets. +If you wanted to get started on using player, please check out our [Getting Started Guide](https://player-ui.github.io/latest/getting-started). + ## How is Player versioned? Player follows [semantic versioning](https://semver.org/). We will also publish changelogs for every release. @@ -21,5 +25,8 @@ Player follows [semantic versioning](https://semver.org/). We will also publish ## I am having issues using Player, how can I get help? Head over to our [issues page](https://github.com/player-ui/player/issues) on Github and feel free to open a ticket with the bug report template and we'll do our best to get back to you. The more detail you include the easier it will be for us to help troubleshoot. +### Contributing +Check out the [Contributing Guide](https://github.com/player-ui/player/blob/main/CONTRIBUTING.md) as this will show you all of the requirements to get started using Player, how to contribute, and some platform specific guides. + ## I need Player to do _____, can we contribute back to Player? We love and highly encourage open source contributions! One thing that we ask to keep in mind is that Player is supposed to be generic and flexible. We try to keep any application/implementation specific plugins and assets in a separate codebase. While **most** functionality can be added though plugins we know that sometimes changes in Player itself might be required. We are definitely open to these changes but take great effort to ensure that there are minimal breaking changes and we aren't narrowing Player's functionality. diff --git a/docs/site/pages/getting-started.mdx b/docs/site/pages/getting-started.mdx index 2283195c8..e8f5f03bc 100644 --- a/docs/site/pages/getting-started.mdx +++ b/docs/site/pages/getting-started.mdx @@ -4,27 +4,66 @@ title: 'Getting Started' # Getting Started -Getting started with Player is simple. Below we have guides per platform on how to integrate Player into your platform of choice. - -## React +Getting started with Player is simple. ### Install Dependencies -The first dependency you'll need to pull in in the React Player itself. You can do this by running +The first dependency you'll need to pull in is the Player itself. Additionally, you'll need an assets plugin to define any UI -- we'll use the reference assets as an example. + + + + +You can do this by running ```bash yarn add @player-ui/react +yarn add @player-ui/reference-assets-plugin-react ``` -or +or ```bash npm install @player-ui/react +npm install @player-ui/reference-assets-plugin-react +``` + + + +In your `Podfile` you'll need to add `PlayerUI` as a dependency and the `ReferenceAssets` plugin to use the base set of assets. + +```ruby +source 'https://github.com/player-ui/player' + +target 'MyApp' do + # Add the pod to your target + pod 'PlayerUI' + pod 'PlayerUI/ReferenceAssets' # For example only +end ``` + + + +Configure the Android Player dependencies in your `build.gradle(.kts)` files. + +```kotlin +dependencies { + // Android Player + implementation("com.intuit.player.android:player:$playerVersion") + // Optional - reference assets + implementation("com.intuit.player.android:assets:$playerVersion") +} +``` + + + + ### Configuration -Next, in your code you'll need to initialize Player. This is where you would also initialize any plugins you want to use with Player and create the configuration for Player itself. Below is a minimal example of this. +Next, in your code you'll need to initialize Player. This is where you would also initialize any plugins you want to use with Player and create the configuration for Player itself. Below is a minimal example of this. + + + ```javascript import { ReactPlayer } from '@player-ui/react'; @@ -36,45 +75,54 @@ const reactPlayer = new ReactPlayer({ plugins: [new ReferenceAssetsPlugin()], }); ``` + + + +`SwiftUIPlayer` is just a normal SwiftUI View and can be inserted anywhere in your hierarchy. + +```swift +import PlayerUI + +var body: some View { + SwiftUIPlayer( + plugins: [ReferenceAssetsPlugin()], + ) +} +``` + + + + +```kotlin +// create Android Player with reference assets plugin +val player = AndroidPlayer(ReferenceAssetsPlugin()) +``` + + ### Render Content +Now that you have a Player instance created. You'll need to start it with some [content](/content). + + -Now that you have a Player instance created. You'll need to start it with some content. + ```javascript const content = {/* your content here */} reactPlayer.start(content); ``` -With Player running your content, you'll need to actually render out what is processes. To do this, you can use the React Player's component API to inster it into your React tree. +With Player running your content, you'll need to actually render out what is processes. To do this, you can use the React Player's component API to insert it into your React tree. ```javascript const MyApp = () => { return ; }; ``` + + -Congrats! You've got Player up and running. If you need additional functionality you can add more plugins to extend Player's functionality. Head over to the [Plugins](./plugins) section to take a look at the Plugins we've developed or take a look at the [Architecture](./architecture) section to see how you can write your own. - -## iOS (Swift) - -### Install Dependencies - -In your `Podfile` you'll need to add `PlayerUI` as a dependency and the `ReferenceAssets` plugin to use the base set of assets. - -```ruby -source 'https://github.com/player-ui/player' - -target 'MyApp' do - # Add the pod to your target - pod 'PlayerUI' - pod 'PlayerUI/ReferenceAssets' # For example only -end -``` - -### Configuration - -The `SwiftUIPlayer` is just a normal SwiftUI View and can be inserted anywhere in your hierarchy. The flow will start automatically, and the result will update the `Binding?>` that you pass to it: +Expanding on the `SwiftUIPlayer` creation above, providing a `flow` will start the Player automatically, and the `result` will update the `Binding?>` that you pass to it: ```swift import PlayerUI @@ -91,80 +139,19 @@ struct MyApp: View { } } ``` + + -## JVM/Android - -### Install Dependencies - -Configure the Android Player dependencies in your Gradle files. You'll also need to add a plugin that provides asset definitions and components. To get started, we are going to use the reference set provided by the Android Player. _Note: The reference asset set isn't intended for production use. It exists as an example of how to create an Android asset and can be used to help get started. _ - - - - -```kotlin -// Add Android Player dependencies -dependencies { - // Android Player - implementation("com.intuit.player.android", "player", androidPlayerVersion) - // Optional - reference asset set - implementation("com.intuit.player.android", "assets", referenceAssetsVersion) -} -``` - - - - -```groovy -// Add Android Player dependencies -dependencies { - // Android Player - implementation "com.intuit.player.android:player:$androidPlayerVersion" - // Optional - reference asset set - implementation "com.intuit.player.android:assets:$referenceAssetsVersion" -} -``` - - - - -```xml - - com.intuit.player.jvm - core - 3.2.1 - -``` - - - - -### Configuration - -All you need to create an `AndroidPlayer` instance is the Android Context and any plugins you want. - -```kotlin -// create Android Player with reference assets plugin -val player = AndroidPlayer(context, ReferenceAssetsPlugin()) -``` - -Once you have a Player, you must add a handler for view updates, which are represented as RenderableAssets. +To receive view updates, you must add a handler to render the views which are represented as `RenderableAsset`s. ```kotlin -// handle view updates +// handle view updates - should be done before starting the Player player.onUpdate { asset: RenderableAsset? -> - // render asset as View - val view: View = asset?.render() - // add view to screen + // render asset as View `into` view tree + asset?.render() into binding.playerContainer } -``` - -### Usage -#### Basic -With the Android Player and handler configured, all that's left is starting a flow. This is done by calling -start with a JSON string containing your Player content. - -```kotlin +// start Player player.start(content) ``` @@ -174,29 +161,36 @@ When you're done with Player, release any native runtime memory used to instanti player.release() ``` +Creating your own `AndroidPlayer` instance is fairly simple, but it grows in complexity when considering proper resources management and app orchestration. We provide a Fragment/ViewModel integration to make it easier to properly integrate into your app and account for these concerns. + #### ViewModel With the `PlayerViewModel` and `PlayerFragment`, the above orchestration is done for you. All that is needed is to provide concrete implementations of each and add the fragment to your app. ##### PlayerViewModel -The `PlayerViewModel` is an `AndroidViewModel` meaning it requires the actual Android `Application` as well as an `AsyncFlowIterator` to be supplied in the constructor. The AsyncFlowIterator is what tells Player which flows to run. This can be hardcoded into the view model or expected as an argument, as shown below. + +The `PlayerViewModel` requires an `AsyncFlowIterator` to be supplied in the constructor. The AsyncFlowIterator is what tells Player which flows to run. This can be hardcoded into the view model or expected as an argument, as shown below. ```kotlin -class SimplePlayerViewModel(application: Application, flows: AsyncFlowIterator) : PlayerViewModel(application, flows) { +class SimplePlayerViewModel(flows: AsyncFlowIterator) : PlayerViewModel(flows) { override val plugins = listOf(ReferenceAssetsPlugin()) } ``` ##### PlayerFragment -The `PlayerFragment` is a simple Android `Fragment` that only requires a specific `PlayerViewModel` to be defined. If your view model requires the AsyncFlowIterator to be passed as part of the constructor, you can leverage the `PlayerViewModel.Factory` to produce it, as shown below. +The `PlayerFragment` is a simple Android `Fragment` that only requires a specific `PlayerViewModel` to be defined. If your view model requires the `AsyncFlowIterator` to be passed as part of the constructor, you can leverage the `PlayerViewModel.Factory` to produce it, as shown below. Specifically, this fragment takes a flow as an argument to the constructor and creates a single-flow `AsyncFlowIterator` instance using the pseudo-constructor helper. ```kotlin class SimplePlayerFragment(override val flow: String) : PlayerFragment() { override val playerViewModel by viewModels { - PlayerViewModel.Factory(requireActivity().application, AsyncFlowIterator(flow), ::SimplePlayerViewModel) + PlayerViewModel.Factory(AsyncFlowIterator(flow), ::SimplePlayerViewModel) } } ``` + + + +Congrats! You've got Player up and running. If you need additional functionality you can add more plugins to extend Player's functionality. Head over to the [Plugins](./plugins) section to take a look at the Plugins we've developed or take a look at the [Architecture](./architecture) section to see how you can write your own. diff --git a/docs/site/pages/guides/designing-semantic-assets.mdx b/docs/site/pages/guides/designing-semantic-assets.mdx index 56e37330f..5a5f7cce3 100644 --- a/docs/site/pages/guides/designing-semantic-assets.mdx +++ b/docs/site/pages/guides/designing-semantic-assets.mdx @@ -6,7 +6,7 @@ title: Designing Semantic Assets While not a _hard_ requirement by Player, the API design for assets plays an important role in it's adoption, especially if the intent is to re-use content across platforms. In many cases, Player content is written, and edited many more times than assets are created, and thus it's schema plays an important role in it's effective adoption. -Player ships with a set of [Reference Assets](assets/reference) to get started, but intentionally doesn't include anything beyond some basics. We believe it's up to each consumer to define their own semantics (if at all), that best suites their applications. +Player ships with a set of [Reference Assets](../assets/reference) to get started, but intentionally doesn't include anything beyond some basics. We believe it's up to each consumer to define their own semantics (if at all), that best suites their applications. ## Intent Based Schema diff --git a/docs/site/pages/plugin-implementation.mdx b/docs/site/pages/plugin-implementation.mdx new file mode 100644 index 000000000..575c46d25 --- /dev/null +++ b/docs/site/pages/plugin-implementation.mdx @@ -0,0 +1,92 @@ +--- +title: Plugin Implementation +--- + +# Plugin Implementation + +The main purpose of a Plugin is to extend or add new functionality by tapping into Player's pipeline of components via hooks. In this section we'll go over the steps to implement a plugin. + +We'll use the [stage-revert-data](./plugins/stage-revert-data) plugin as example. After creating our plugin class, we'll use the `apply` method which provides access to the Player instance, which then gives access to the necessary hooks. + +```typescript +export default class StageRevertDataPlugin implements PlayerPlugin { + name = 'stage-revert-data-plugin'; + + apply(player: Player) { + let dataController: DataController; + let stageData: String; + let commitTransitions: String[]; + let commitShadowModel: Boolean = false; + + const GatedDataMiddleware = new ValidationMiddleware( + () => + commitShadowModel + ? undefined + : { + message: 'staging data', + severity: 'error', + }, + { shouldIncludeInvalid: () => true } + ); +``` +For this case we needed to define variables that will store references for the scope the hooks will share: +- `dataController`: Reference to the data controller hook in the Player instance, can be used to read, update or commit new changes to the data model +- `stageData`: View attribute that comes from the view state, used for enabling the staging of data +- `commitTransitions`: The list of view names which the shadow model should be committed if transitioned to. Comes from the view state attribute as well. +- `commitShadowModel`: Flag that enables committing shadow model into data model after the transition, only if `stageData` is set to true and the next target view name is included in `commitTransitions`. +- `GatedDataMiddleware` Instance from `ValidationMiddleware` to be used as a middleware to intercept the data-model pipeline before any data is committed and cache the data instead, only if `stageData` is set to true. + +The next step is to tap into the necessary Player hooks. First we tap into the `viewController` which we can then intercept the `resolveView` hook. Player hooks are implemented with the [Tapable](https://github.com/webpack/tapable) package, so we can use the interception API's `call` method, this triggers everytime the hook is triggered, then we get access to the current view state and read the `stageData` and `commitTransitions` attributes. + +```typescript + player.hooks.viewController.tap(this.name, (vc) => { + vc.hooks.resolveView.intercept({ + call: (view, id, state) => { + stageData = state?.attributes?.stageData; + commitTransitions = state?.attributes?.commitTransitions; + }, + }); + }); +``` +`Note`: notice how each time we tap into a hook, we use the `this.name` property as the name of the plugin. This is important to avoid conflicts with other plugins. + +Next we tap into the `dataController`, so we can scope the data controller instance for future use. Then we tap into the `resolveDataStages` plugin in this data controller instance. If the `stage` property is set to true, we add our `GatedDataMiddleware` to the data pipeline. If not, we return the data pipeline as is. + +```typescript + player.hooks.dataController.tap(this.name, (dc: DataController) => { + dataController = dc; + + dc.hooks.resolveDataStages.tap(this.name, (dataPipeline) => { + return stageData + ? [...dataPipeline, GatedDataMiddleware] + : [...dataPipeline]; + }); + }); +``` +Finally, we tap into the `flowController` so we can intercept the `flow` hook. We then tap into the `transition` hook, which is called every time the player transitions from one view to another. If the `commitTransitions` includes the next view name, we set the `commitShadowModel` flag to true, and commit the data stored in the shadow model through `GatedDataMiddleware` into the data model. Whether the data was committed from the shadow model or not, we clear the shadow model paths in the `GatedDataMiddleware` instance and set the `commitShadowModel` flag to false as final steps. + +```typescript + player.hooks.flowController.tap(this.name, (flowController) => { + flowController.hooks.flow.tap(this.name, (flow) => { + flow.hooks.transition.tap(this.name, (from, to) => { + if (from) { + if (commitTransitions.includes(to.name)) { + commitShadowModel = true; + player.logger.debug( + 'Shadow Model Data to be committed %s', + GatedDataMiddleware.shadowModelPaths + ); + dataController.set(GatedDataMiddleware.shadowModelPaths); + } + + commitShadowModel = false; + GatedDataMiddleware.shadowModelPaths.clear(); + } + }); + }); + }); +``` + +And this is how we implement a plugin that manages the staging of data based on the view state attributes. + +Code Snippets Reference: [StageRevertDataPlugin](https://github.com/player-ui/player/blob/main/plugins/stage-revert-data/core/src/index.ts) \ No newline at end of file diff --git a/docs/site/pages/plugins/asset-provider.mdx b/docs/site/pages/plugins/asset-provider.mdx index 426bc2630..67b610174 100644 --- a/docs/site/pages/plugins/asset-provider.mdx +++ b/docs/site/pages/plugins/asset-provider.mdx @@ -7,8 +7,9 @@ platform: react The Asset Provider Plugin enables users to easily register UI components to render their assets. It's used internally by the [Reference Assets](../assets/reference). The matches follow the same rules as asset transforms (more specific matches take priority). -### Usage +## Usage +### Platform @@ -35,7 +36,84 @@ const player = new ReactPlayer({ }) ``` -This will register a match on `{ type: 'custom-asset' }` and `{ type: 'custom', key: 'asset' }` in the view to use your React components. - + This will register a match on `{ type: 'custom-asset' }` and `{ type: 'custom', key: 'asset' }` in the view to use your React components. + +### Content + +In this example, when your content has assets of type `custom-asset` and `custom`, they will render `
Hello World!
` and `
Other Custom Asset
`. + + + + +```tsx +import { Custom, CustomAsset, Collection } from 'my-assets'; + +const view = ( + + + + +); +``` + + + + +```json +{ + "id": "test-flow", + "views": [ + { + "id": "view-1", + "type": "collection", + "label": { + "asset": { + "id": "title", + "type": "text", + "value": "Collections are used to group assets." + } + }, + "values": [ + { + "asset": { + "id": "custom-1", + "type": "custom-asset", + } + }, + { + "asset": { + "id": "custom-2", + "type": "custom", + } + } + ] + } + ], + "navigation": { + "BEGIN": "FLOW_1", + "FLOW_1": { + "startState": "VIEW_1", + "VIEW_1": { + "state_type": "VIEW", + "ref": "view-1", + "transitions": { + "*": "END_Done" + } + }, + "END_Done": { + "state_type": "END", + "outcome": "DONE" + } + } + } +} +``` + + This will register a match on `{ type: 'custom-asset' }` and `{ type: 'custom', key: 'asset' }` in the view to use your React components. + + + + + diff --git a/docs/site/pages/plugins/async-node.mdx b/docs/site/pages/plugins/async-node.mdx new file mode 100644 index 000000000..89c4f1089 --- /dev/null +++ b/docs/site/pages/plugins/async-node.mdx @@ -0,0 +1,196 @@ +--- +title: AsyncNode Plugin +platform: core,react,ios,android +--- + +# Async Node Plugin + +The AsyncNode Plugin is used to enable streaming additional content into a flow that has already been loaded and rendered. +A common use case for this plugin is conversational UI, as the users input more dialogue, new content must be streamed into Player in order to keep the UI up to date. + +The pillar that makes this possible is the concept of an `AsyncNode`. An `AsyncNode` is any tree node with the property `async: true`, it represents a placeholder node that will be replaced by a concrete node in the future. + +In the example below, node with the id "some-async-node" will not be rendered on first render, but will be replaced with a UI asset node at a later time: + +```json +{ + "id": "flow-1", + "views": [ + { + "id": 'action', + "actions": [ + { + "id": "some-async-node", + "async": true, + }, + ], + }, + ], + ... +} +``` + +The `AsyncNodePlugin` exposes an `onAsyncNode` hook on all platforms. The `onAsyncNode` hook will be invoked with the current node when the plugin is available and an `AsyncNode` is detected during the resolve process. The node used to call the hook with could contain metadata according to content spec. + +User should tap into the `onAsyncNode` hook to examine the node's metadata before making a decision on what to replace the async node with. The return could be a single asset node or an array of asset nodes. + + +### Continuous Streaming + +In order to keep streaming in new content, there must be at least 1 or more `AsyncNode`s in the view tree at all times. +This means there must be a constant renewal of new `AsyncNode`s after the previous ones are resolved by the user. + +## Usage + + + + +Add the plugin to Player: + +```js +import { Player } from '@player-ui/player'; +import { AsyncNodePlugin } from '@player-ui/async-node-plugin'; + +const asyncNodePlugin = new AsyncNodePlugin(); + +// Configuring async node behaviour +asyncNodePlugin.hooks.onAsyncNode.tap('handleAsync', async (node: Node.Node) => { + ... + // Determine what to return to be parsed into a concrete UI asset +}); + +const player = new Player({ + plugins: [ + asyncNodePlugin + ] +}) +``` + + + +The `react` version of the AsyncNodePlugin is identical to using the core plugin. Refer to core usage for handler configuration: + +```js +import { ReactPlayer } from '@player-ui/react'; +import { MetricsPlugin } from '@player-ui/async-node-plugin'; + +const asyncNodePlugin = new AsyncNodePlugin(); + +const player = new ReactPlayer({ + plugins: [ + asyncNodePlugin + ] +}) +``` + + + + +### CocoaPods + +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/AsyncNodePlugin' +``` + +### Swift Usage + +In integration code + +```swift +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + AsyncNodePlugin { node in + // Determine what to return either using the singleNode or multiNode case + // Then JSON can be provided using the concrete case, see below for using the encodable case + return .singleNode(.concrete(jsContext?.evaluateScript(""" + ({"asset": {"id": "text", "type": "text", "value":"new node from the hook"}}) + """) ?? JSValue())) + + // OR + return .multiNode([ + .concrete(jsContext?.evaluateScript(""" + ({"asset": {"id": "text", "type": "text", "value":"1st value in the multinode"}}) + """) ?? JSValue()), + .concrete(jsContext?.evaluateScript(""" + ({"asset": {"id": "async-node-2", "async": "true" }}) + """) ?? JSValue()) + ]) + + } + ], + result: $resultBinding + ) +} +``` + +The plugin also provides a default asset placeholder struct that is encodable, instead of passing in the JSON string users can use +`AssetPlaceholderNode` which includes an `asset` key that takes any user defined Encodable struct as the value. Assuming the following encodable struct is defined: + +```swift +struct PlaceholderNode: Codable, Equatable, AssetData { + public var id: String + public var type: String + var value: String? + + public init(id: String, type: String, value: String? = nil) { + self.id = id + self.type = type + self.value = value + } +} +``` + +Instead of using the JSON string above, the following can be used: + +```swift +return .singleNode(.encodable(PlaceholderNode(id: "text", type: "text", value: "new node from the hook"))) + +// OR + +return .multiNode([ + ReplacementNode.encodable(PlaceholderNode(id: "text", type: "text", value: "1st value in the multinode")), + ReplacementNode.encodable(AsyncNode(id: "id"))]) +``` + +Note: the AsyncNode struct is already defined in the plugin with the `async` property defaulted to true so only `id` needs to be passed in + +As a convenience to the user, the AsyncNodePlugin just takes a callback which has the content to be returned, this is provided to the plugin which calls the the `onAsyncNode` hook tap method + + + + +In build.gradle +```kotlin +implementation "com.intuit.player.plugins:async-node:$PLAYER_VERSION" +``` + +In integration code +```kotlin +import com.intuit.player.plugins.asyncnode.AsyncNodePlugin + +val asyncNodePlugin = AsyncNodePlugin() + +// Configuring async node behaviour +asyncNodePlugin.hooks.onAsyncNode.tap("handleAsync") { hookContext, node -> + ... + // Determine what to return in the form of a list of maps representing UI asset to be parsed + // e.g. + // listOf( + // mapOf( + // "asset" to mapOf( + // "id" to "asset-1", + // "type" to "text", + // "value" to "new asset!" + // ) + // ) + // ) +} + +AndroidPlayer(asyncNodePlugin) +``` + + diff --git a/docs/site/pages/plugins/beacon.mdx b/docs/site/pages/plugins/beacon.mdx index 7b6f9e527..fb1d39a24 100644 --- a/docs/site/pages/plugins/beacon.mdx +++ b/docs/site/pages/plugins/beacon.mdx @@ -32,6 +32,8 @@ interface DefaultBeacon { } ``` +## Usage + @@ -94,6 +96,15 @@ This will add additional React Context to the running player for the producers f +### CocoaPods +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/BeaconPlugin' +``` + +### Swift Usage + To receive Beacon events from Player in iOS, add the `BeaconPlugin` to your plugin array: ```swift @@ -102,7 +113,7 @@ var body: some View { flow: flow, plugins: [ BeaconPlugin { (beacon: DefaultBeacon) in - // Process beacon into the format you need for Segment/Trinity and send it on + // Process beacon into the format you need for your analytics platform } ] ) @@ -175,12 +186,12 @@ For convenience, there are several extension methods for utilizing a pre-install ```kotlin // registering handler -player.onBeacon { beacon: String -> +androidPlayer.onBeacon { beacon: String -> // Process beacon and send to analytics platform } // fire beacon -player.beacon( +androidPlayer.beacon( "clicked", "button", assetThatIsBeaconing, @@ -190,10 +201,7 @@ player.beacon( The base `RenderableAsset` class provides an additional helper to make beaconing less verbose from an asset perspective. -```kotlin -// within RenderableAsset implementation -beacon(BeaconAction.clicked, BeaconElement.button) -``` +[Helper in RenderableAsset](https://github.com/player-ui/player/blob/2a2110caf87ab752207c82ec9ab8ce72316d20f5/android/player/src/main/java/com/intuit/player/android/asset/RenderableAsset.kt#L253) @@ -264,6 +272,8 @@ export const Component = (props) => { +The SwiftUIBeaconPlugin attaches a BeaconContext to the root of the SwiftUI view tree as an environment value, so when included any asset can use that context to send a beacon, if the context is in the environment: + ```swift struct ActionAssetView: View { @ObservedObject var viewModel: AssetViewModel @@ -275,4 +285,40 @@ struct ActionAssetView: View { ``` - \ No newline at end of file + + + + Using the base `RenderableAsset`'s helper function in this example fires off a basic beacon with no adjustment to format as well as empty data upon button click + +**Example** + +```kotlin +class MyAsset(assetContext: AssetContext) : + DecodableAsset<>(assetContext, Data.serializer()) { + + @Serializable + data class Data( + ... + ) : AssetData() + + override fun createView(): View { + ... + } + + override fun View.hydrate() { + val binding = MyCustomViewBinding.bind(this) + binding.button.setOnClickListener { + this@MyAsset.beacon( + action = BeaconAction.clicked, + element = BeaconElement.button, + asset = this@MyAsset, + data = null + ) + } + ... + } +} +``` + + + diff --git a/docs/site/pages/plugins/check-path.mdx b/docs/site/pages/plugins/check-path.mdx index 8a20154c0..91968acc7 100644 --- a/docs/site/pages/plugins/check-path.mdx +++ b/docs/site/pages/plugins/check-path.mdx @@ -1,6 +1,6 @@ --- title: Check Path Plugin -platform: react,core +platform: react,core,android --- # Check Path Plugin @@ -48,7 +48,7 @@ Install the plugin: ```bash yarn add @player-ui/check-path-plugin-react ``` - +6474792156 Add it to Player: ```js @@ -63,6 +63,51 @@ const rp = new ReactPlayer({ This will automatically create the underlying _core_ version of the `CheckPathPlugin` to be made available via `React` Context for the hooks. + + + +### CocoaPods + +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/SwiftUICheckPathPlugin' +``` + +### Swift Usage + +This plugin takes no parameters, and the configuration comes from content, it can just be added to the plugin array: + +```swift +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + SwiftUICheckPathPlugin() + ], + result: $resultBinding + ) +} +``` + + + +In build.gradle + +```kotlin +implementation "com.intuit.player.plugins:check-path:$PLAYER_VERSION" +``` + +In Player constructor + +```kotlin +import com.intuit.player.plugins.checkpath.CheckPathPlugin + +val plugins = listOf(CheckPathPlugin()) +AndroidPlayer(plugins) +``` + + @@ -149,6 +194,39 @@ export const MyAsset = ({ id }) => { ``` + + +### Swift Usage + +The SwiftUICheckPathPlugin attaches the `BaseCheckPathPlugin` named `checkPath` to the root of the SwiftUI view tree as an environment value, so when included any asset can use that environment value to access the base functionality + +```swift + +struct MyAsset: View { + @ObservedObject var viewModel: AssetViewModel + @Environment(\.checkPath) var checkPath + + var body: some View { + let isInForm = checkPath?.hasParentContext(id: viewModal.data.id, query: "form") + if isInForm { + SelectChoice() + } else { + RadioGroup() + } +} + +``` + + + + +The JVM CheckPathPlugin API contains partial functionality in comparison to the core plugin, ideally this should not be used, and all logic where pathing is concerned should be handled in transforms +If the situation arises and this plugin is absolutely necessary, the API from the following source are available at the moment. This is subject to change in future releases: + +[CheckPathPlugin](https://github.com/player-ui/player/blob/main/plugins/check-path/jvm/src/main/kotlin/CheckPathPlugin.kt) + + + diff --git a/docs/site/pages/plugins/common-expressions.mdx b/docs/site/pages/plugins/common-expressions.mdx index e1fd256b8..17b2566e6 100644 --- a/docs/site/pages/plugins/common-expressions.mdx +++ b/docs/site/pages/plugins/common-expressions.mdx @@ -9,6 +9,64 @@ This plugin exposes some basic expressions into Player content. It also serves as a good reference to adding your own custom expressions into Player. +## Usage + + + + +Install the plugin: + +```bash +yarn add @player-ui/common-types-plugin +``` + +Add it to Player: +```js +import CommonExpressionsPlugin from '@player-ui/common-expressions-plugin'; + +const commonExpressionsPlugin = new CommonExpressionsPlugin(); +const player = new Player({ plugins: [commonExpressionsPlugin] }); + +// Start your flow +player.start(myFlow); +``` + +This will allow any included expressions or custom expressions to be used in the content + + + + + +### CocoaPods + +Add the subspec to your `Podfile` + +```ruby + +pod 'PlayerUI/CommonExpressionsPlugin' + +``` + +### Swift Usage + +This plugin takes no parameters, and the configuration comes from content, it can just be added to the plugin array: + +```swift + +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + CommonExpressionsPlugin() + ], + result: $resultBinding + ) +} +``` + + + + --- ## General diff --git a/docs/site/pages/plugins/common-types.mdx b/docs/site/pages/plugins/common-types.mdx index 456fc0777..881d7ba8b 100644 --- a/docs/site/pages/plugins/common-types.mdx +++ b/docs/site/pages/plugins/common-types.mdx @@ -9,7 +9,65 @@ This plugin exposes some basic `DataTypes`, `validations`, and `formats` into Pl It also serves as a good reference to adding your own custom types into Player. ---- +## Usage + + + + +Install the plugin: + +```bash +yarn add @player-ui/common-types-plugin +``` + +Add it to Player: +```js +import CommonTypesPlugin from '@player-ui/common-types-plugin'; + +const commonTypesPlugin = new CommonTypesPlugin(); +const player = new Player({ plugins: [commonTypesPlugin] }); + +// Start your flow +player.start(myFlow); +``` + +This will allow any `DataTypes`, `formats`, `validations` and custom data types to be used in the content + + + + + +### CocoaPods + +Add the subspec to your `Podfile` + +```ruby + +pod 'PlayerUI/CommonTypesPlugin' + +``` + +### Swift Usage + +This plugin takes no parameters, and the configuration comes from content, it can just be added to the plugin array: + +```swift + +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + CommonTypesPlugin() + ], + result: $resultBinding + ) +} +``` + + + + + ## Formats @@ -313,4 +371,4 @@ Options: > A value representing a phone number * **validations**: `phone` -* **format**: `phone` (`(###) ###-####`) \ No newline at end of file +* **format**: `phone` (`(###) ###-####`) diff --git a/docs/site/pages/plugins/computed-properties.mdx b/docs/site/pages/plugins/computed-properties.mdx index c7c4b0dcc..709887501 100644 --- a/docs/site/pages/plugins/computed-properties.mdx +++ b/docs/site/pages/plugins/computed-properties.mdx @@ -25,6 +25,33 @@ const player = new Player({ ``` + + + +### CocoaPods + +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/ComputedPropertiesPlugin' +``` + +### Swift Usage + +This plugin takes no parameters, and the configuration comes from content, it can just be added to the plugin array: + +```swift +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + ComputedPropertiesPlugin() + ], + result: $resultBinding + ) +} +``` + ## Expression Data Type @@ -62,4 +89,4 @@ Any data-lookup for that binding path will evaluate the given expression and ret Using the above schema, any reference to `{{foo.computedValue}}` will compute the `1 + 2 + 3` expression and use that as the underlying value for that path. -Any write or set operation to `{{foo.computedValue}}` will result in a thrown exception for writing to a read-only path. \ No newline at end of file +Any write or set operation to `{{foo.computedValue}}` will result in a thrown exception for writing to a read-only path. diff --git a/docs/site/pages/plugins/console-logger.mdx b/docs/site/pages/plugins/console-logger.mdx index b8458f0cb..d50835e00 100644 --- a/docs/site/pages/plugins/console-logger.mdx +++ b/docs/site/pages/plugins/console-logger.mdx @@ -34,23 +34,28 @@ consoleLogger.setSeverity('warn'); -Add the subspec: +### CocoaPods +Add the subspec to your `Podfile` ```ruby pod 'PlayerUI/PrintLoggerPlugin' ``` -Construct the plugin: +### Swift Usage ```swift -SwiftUIPlayer( - flow: flow, - plugins: [PrintLoggerPlugin()], - result: $result -) +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + PrintLoggerPlugin() + ], + result: $resultBinding + ) +} ``` -To change the severity: +To change the severity, supply it as an argument to the constructor: ```swift PrintLoggerPlugin(level: .warn) ``` diff --git a/docs/site/pages/plugins/coroutines.mdx b/docs/site/pages/plugins/coroutines.mdx new file mode 100644 index 000000000..efdcff109 --- /dev/null +++ b/docs/site/pages/plugins/coroutines.mdx @@ -0,0 +1,83 @@ +--- +title: Coroutines +platform: android +--- + +# Kotlin Coroutines + +Kotlin coroutines is a popular Kotlin library to handle asynchronous programming and concurrency in the JVM. More information on Coroutines can be found [here](https://kotlinlang.org/docs/coroutines-overview.html) +This module contains several JVM exclusive plugins to aid various tooling needs. + +## UpdatesPlugin + +Plugin using a `ReceiveChannel` to provide an updated asset whenever the Player View is updated. + +### Usage + + + +In build.gradle + +```kotlin +implementation "com.intuit.player.plugins:coroutines:$PLAYER_VERSION" +``` + +In Player constructor +```kotlin +import com.intuit.player.plugins.coroutines.UpdatesPlugin + +val plugins = listOf(UpdatesPlugin()) +AndroidPlayer(plugins) +``` + + + + +### API + + + +The API for this plugin revolves around the `ReceiveChannel` for asset updates. +Public API available from sources here: + +[UpdatesPlugin](https://github.com/player-ui/player/blob/main/plugins/coroutines/jvm/src/main/kotlin/com/intuit/player/plugins/coroutines/UpdatesPlugin.kt) + + + + +## FlowScopePlugin + +Plugin that provides a CoroutineScope that can be used to perform various async operations with the context of the current flow. +All operations launched using the scope will be cancelled when player changes state. + +### Usage + + + +In build.gradle + +```kotlin +implementation "com.intuit.player.plugins:coroutines:$PLAYER_VERSION" +``` + +In Player constructor +```kotlin +import com.intuit.player.plugins.coroutines.FlowScopePlugin + +val plugins = listOf(FlowScopePlugin()) +AndroidPlayer(plugins) +``` + + + + +### API + + + + +Public API available from sources here: + +[FlowScopePlugin](https://github.com/player-ui/player/blob/main/plugins/coroutines/jvm/src/main/kotlin/com/intuit/player/plugins/coroutines/FlowScopePlugin.kt) + + \ No newline at end of file diff --git a/docs/site/pages/plugins/expression.mdx b/docs/site/pages/plugins/expression.mdx index feec6e1d8..042b47498 100644 --- a/docs/site/pages/plugins/expression.mdx +++ b/docs/site/pages/plugins/expression.mdx @@ -52,7 +52,14 @@ Any calls to `myCustomFunction()` within the flow will utilize the newly registe -### Use +### CocoaPods +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/ExpressionPlugin' +``` + +### Swift Usage The ExpressionPlugin lets you register custom expressions to run native code: @@ -71,7 +78,7 @@ let expressionPlugin = ExpressionPlugin(expressions: [ } ]) ``` -### Arguments +#### Arguments Arguments can be passed to custom expressions, and your handler receives the arguments as an array of Any: @@ -97,9 +104,16 @@ let expressionPlugin = ExpressionPlugin(expressions: [ The `ExpressionPlugin` enables consumers to register custom expressions in native JVM code. Simply supply a map of expression name to handler on instantiation, and the expressions will be available within the content. Handlers receive arguments are as a `List` and are permitted to return `Any?`. +## Usage +In build.gradle ```kotlin -// A convenience constructor is provided that takes -// varargs pairs of the expressions to register +implementation "com.intuit.player.plugins:expression:$PLAYER_VERSION" +``` + +In Player constructor +```kotlin +import com.intuit.player.plugins.expression.ExpressionPlugin + val expressionPlugin = ExpressionPlugin( "hello" to { args: List -> when (val name = args.firstOfNull()) { @@ -108,7 +122,10 @@ val expressionPlugin = ExpressionPlugin( } } ) +AndroidPlayer(expressionPlugin) ``` + +In Player content ```json { "id": "hello-world-text", diff --git a/docs/site/pages/plugins/external-action-view-modifier.mdx b/docs/site/pages/plugins/external-action-view-modifier.mdx index 380d0b0b6..4c1240656 100644 --- a/docs/site/pages/plugins/external-action-view-modifier.mdx +++ b/docs/site/pages/plugins/external-action-view-modifier.mdx @@ -7,7 +7,14 @@ platform: ios This plugin is used to handle EXTERNAL states, allowing you to asynchronously tell Player when, and what to transition with once you have finished processing the external state request. -## Example +### CocoaPods +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/ExternalActionViewModifierPlugin' +``` + +### Swift Usage For an example flow with an external state such as: @@ -43,7 +50,7 @@ For an example flow with an external state such as: } ``` -The plugin can be declared to handle this external state: +The plugin can be declared to handle this external state by showing some user interface, and letting the user trigger the transition to the next state: ```swift let plugin = ExternalActionViewModifierPlugin { state, options, transition in @@ -55,10 +62,18 @@ let plugin = ExternalActionViewModifierPlugin { stat ) } -let player = SwiftUIPlayer(flow: json, plugins: [plugin]) +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + plugin + ], + result: $resultBinding + ) +} ``` -## ExternalStateViewModifier +#### ExternalStateViewModifier `ExternalStateSheetModifier` is a provided modifier to present the external content with the `.sheet` SwiftUI ViewModifier, however, you can easily define your own. diff --git a/docs/site/pages/plugins/external-action.mdx b/docs/site/pages/plugins/external-action.mdx index 420f63ab7..538562cbe 100644 --- a/docs/site/pages/plugins/external-action.mdx +++ b/docs/site/pages/plugins/external-action.mdx @@ -48,6 +48,15 @@ This will transition any `EXTERNAL` state in Player's navigation, with a `ref` p +### CocoaPods +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/ExternalActionPlugin' +``` + +### Swift Usage + For an example flow with an external state such as: ```json @@ -82,7 +91,7 @@ For an example flow with an external state such as: } ``` -The plugin can be declared to handle this external state: +The plugin can be declared to handle this external state, whether that means integrating an existing platform native experience, or doing headless processing: ```swift let plugin = ExternalActionPlugin { state, options, transition in @@ -96,7 +105,15 @@ let plugin = ExternalActionPlugin { state, options, transition in transition(transitionValue ?? "Next") } -let player = Player(plugins: [plugin]) +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + plugin + ], + result: $resultBinding + ) +} ``` @@ -108,6 +125,38 @@ The `ExternalActionPlugin` adds support for handling `EXTERNAL` navigation state 2. options: `ControllerState` provides access to the current player controllers 3. transition: `(String) -> Unit` is provided to enable the handler to "complete" the `EXTERNAL` state and transition using the provided `String` value +## Usage +In build.gradle +```kotlin +implementation "com.intuit.player.plugins:external-action:$PLAYER_VERSION" +``` + +In Player constructor +```kotlin +import com.intuit.player.plugins.externalAction.ExternalActionPlugin + +val externalActionPlugin = ExternalActionPlugin { state, options, transition -> + // access external state node + val extraProperty = state["extraProperty"] + // access data model + options.data.get("{{foo.bar}}") + // evaluate expression + options.expression.evaluate("{{foo.bar}} = 1") + // do other processing or show non-player experience + + // transition to the next node using "Next" + transition("Next") +} +AndroidPlayer(externalActionPlugin) + +// handler can be configured after instantiation +externalActionPlugin.onExternalAction { /** handle external action */ } + +// extension method for configuring plugin with player instance +androidPlayer.onExernalAction { /** handle external action */ } +``` + +In Player content ```json { "navigation": { @@ -135,27 +184,11 @@ The `ExternalActionPlugin` adds support for handling `EXTERNAL` navigation state } } ``` -```kotlin -val externalActionPlugin = ExternalActionPlugin { state, options, transition -> - // access external state node - val extraProperty = state["extraProperty"] - // access data model - options.data.get("{{foo.bar}}") - // evaluate expression - options.expression.evaluate("{{foo.bar}} = 1") - // do other processing or show non-player experience - // transition to the next node using "Next" - transition("Next") -} +## API -// handler can be configured after instantiation -externalActionPlugin.onExternalAction { /** handle external action */ } - -val player = AndroidPlayer(context, externalActionPlugin) -// extension method for configuring plugin with player instance -player.onExernalAction { /** handle external action */ } -``` +Public API available from sources here: +[ExternalActionPlugin](https://github.com/player-ui/player/blob/main/plugins/external-action/jvm/src/main/kotlin/com/intuit/player/plugins/externalAction/ExternalActionPlugin.kt) \ No newline at end of file diff --git a/docs/site/pages/plugins/index.mdx b/docs/site/pages/plugins/index.mdx index 27b435f7f..010ea8d8f 100644 --- a/docs/site/pages/plugins/index.mdx +++ b/docs/site/pages/plugins/index.mdx @@ -9,4 +9,4 @@ Internally they allow access to many of the core sub-systems, which can add feat ![Plugins Overview Diagram](/plugin_overview.png?darkModeInvert) -The scope of what a plugin is capable of is pretty broad, but are typically broken down into smaller reusable modules. Some are more end-user focused ([Common Expression Plugin](./common-expression) and [Common Types Plugin](./common-types)) while others are more relavant for other plugin developers ([Expression Plugin](./expression) and [Types Provider Plugin](./types-provider)) +The scope of what a plugin is capable of is pretty broad, but are typically broken down into smaller reusable modules. Some are more end-user focused ([Common Expression Plugin](../plugins/common-expressions) and [Common Types Plugin](../plugins/common-types)) while others are more relavant for other plugin developers ([Expression Plugin](../plugins/expression) and [Types Provider Plugin](../plugins/types-provider)) diff --git a/docs/site/pages/plugins/metrics.mdx b/docs/site/pages/plugins/metrics.mdx index 0d4cdee03..b9dfe82db 100644 --- a/docs/site/pages/plugins/metrics.mdx +++ b/docs/site/pages/plugins/metrics.mdx @@ -1,6 +1,6 @@ --- title: Metrics -platform: core,react,ios +platform: core,react,ios,android --- # Metrics Plugin @@ -68,23 +68,51 @@ const player = new ReactPlayer({ -The `ios` version of the Metrics Plugin will track initial render time for each view in a flow. Due to current SwiftUI limitations, update time can't be tracked yet. It should be used in conjunction with a core plugin that utilizes the events. +The `ios` version of the Metrics Plugin will track initial render time for each view in a flow. Due to current SwiftUI limitations, update time can't be tracked yet. It can be used in conjunction with a core plugin that utilizes the events, through `findPlugin`, or standalone. + +### CocoaPods +Add the subspec to your `Podfile` -Add the subspec: ```ruby pod 'PlayerUI/MetricsPlugin' +``` -Construct the plugin: - +### Swift Usage ```swift -SwiftUIPlayer( - flow: flow, - plugins: [MetricsPlugin()], - result: $result -) +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + // Tracking render time can be controlled with a parameter + MetricsPlugin(trackRenderTime: true) { timing, nodeMetrics, flowMetrics in + // Handle metrics payload + log(timing.duration ?? -1) + } + ], + result: $resultBinding + ) +} ``` - + + +The `JVM` Metrics plugin can track render time for views in a flow. + +In build.gradle +```kotlin +implementation "com.intuit.player.plugins:metrics:$PLAYER_VERSION" +``` + +In Player constructor +```kotlin +import com.intuit.player.plugins.expression.ExpressionPlugin + +val metricsPlugin = MetricsPlugin { timing, renderMetrics, flowMetrics -> + ... +} +AndroidPlayer(metricsPlugin) +``` + @@ -103,7 +131,7 @@ import { BeaconPluginPlugin } from '@player-ui/beacon-plugin'; class MyBeaconPluginPlugin implements BeaconPluginPlugin { apply(beaconPlugin: BeaconPlugin) { - beaconPlugin.hooks.buildBeacon.tapPromise( + beaconPlugin.hooks.buildBeacon.tap( { name: 'my-beacon-plugin', context: true } as Tap, async (context, beacon) => { const { renderTime } = diff --git a/docs/site/pages/plugins/partial-match-fingerprint.mdx b/docs/site/pages/plugins/partial-match-fingerprint.mdx index 27387b042..67c0ed8c9 100644 --- a/docs/site/pages/plugins/partial-match-fingerprint.mdx +++ b/docs/site/pages/plugins/partial-match-fingerprint.mdx @@ -1,6 +1,6 @@ --- title: Partial Match -platform: core +platform: core, ios --- # Partial Match Plugin @@ -43,4 +43,27 @@ Query the plugin for matches: const value = matchPlugin.get('asset-id') // 'ABC' ``` + + +This plugin is used by `BaseAssetRegistry` to handle matching resolved asset nodes with their native implementation, to decode for rendering. It is unlikely to be needed to be used directly in iOS use cases. + +### CocoaPods +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI' +``` + +### Swift Usage +```swift +let plugin = PartialMatchFingerprintPlugin() + +// iOS usage links the array position of a registered asset to its requested match object +// The JavaScript part of this plugin will populate matches when the view is resolving +plugin.register(match: ["type": "text"], index: 1) + +// get the index for the asset with matching ID if it was resolved in the core plugin +plugin.get(assetId: "test") +``` + \ No newline at end of file diff --git a/docs/site/pages/plugins/pub-sub.mdx b/docs/site/pages/plugins/pub-sub.mdx index d724d53b7..0061bc2ab 100644 --- a/docs/site/pages/plugins/pub-sub.mdx +++ b/docs/site/pages/plugins/pub-sub.mdx @@ -38,8 +38,16 @@ pubsub.unsubscribe(token); -If your content uses the `@[ publish() ]@` expression for actions, you can subscribe to these events by adding the `PubSubPlugin` to your plugin array: +If your content uses the `@[ publish() ]@` expression for actions, you can subscribe to these events by using the `PubSubPlugin`. +### CocoaPods +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/PubSubPlugin' +``` + +### Swift Usage ```swift let eventHandler: (String, AnyType?) -> Void = { (eventName, eventData) in // Handle event @@ -47,12 +55,25 @@ let eventHandler: (String, AnyType?) -> Void = { (eventName, eventData) in let plugin = PubSubPlugin([("eventName", eventHandler)]) ``` -If your content uses a different name for publishing (such as publishEvent) you can customize the expression name that the plugin uses: +If your content uses a different name for publishing (such as `publishEvent`) you can customize the expression name that the plugin uses: ```swift let plugin = PubSubPlugin([("eventName", eventHandler)], options: PubSubPluginOptions(expressionName: "publishEvent")) ``` +Then, just include it in your `plugins` to a Player instance: +```swift +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + plugin + ], + result: $resultBinding + ) +} +``` + _Note: AnyType is a custom enum type to handle the arbitrary types that can be received from these events, as the data is set in your Player Content, ensure that it matches either String, [String], or [String: String]._ diff --git a/docs/site/pages/plugins/set-timeout.mdx b/docs/site/pages/plugins/set-timeout.mdx index 4d8ce1438..9f6bc7edd 100644 --- a/docs/site/pages/plugins/set-timeout.mdx +++ b/docs/site/pages/plugins/set-timeout.mdx @@ -1,5 +1,5 @@ --- -title: setTimeout Plugin +title: SetTimeout Plugin platform: android --- @@ -9,6 +9,12 @@ The only explicit runtime plugin in the core Android plugin set, the `SetTimeout ## Usage +In build.gradle +```kotlin +implementation "com.intuit.player.plugins:set-time-out:$PLAYER_VERSION" +``` + +In plugin implementation ```kotlin class PluginThatNeedsSetTimeout : RuntimePlugin { diff --git a/docs/site/pages/plugins/stage-revert-data.mdx b/docs/site/pages/plugins/stage-revert-data.mdx index 8eab2303a..1b02ee8cc 100644 --- a/docs/site/pages/plugins/stage-revert-data.mdx +++ b/docs/site/pages/plugins/stage-revert-data.mdx @@ -1,9 +1,9 @@ --- title: Stage Revert Data -platform: core +platform: core,ios --- -# Stage Rvert Data +# Stage Revert Data Plugin This plugin enables users to temporarily stage data changes before committing to the actual data model @@ -44,4 +44,27 @@ const player = new Player({ ``` + + +### CocoaPods +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/StageRevertDataPlugin' +``` + +### Swift Usage +This plugin takes no parameters, and the configuration comes from content, it can just be added to the plugin array: +```swift +var body: some View { + SwiftUIPlayer( + flow: flow, + plugins: [ + StageRevertDataPlugin() + ], + result: $resultBinding + ) +} +``` + \ No newline at end of file diff --git a/docs/site/pages/plugins/swiftui-pending-transaction.mdx b/docs/site/pages/plugins/swiftui-pending-transaction.mdx index 3afdea9ab..cceb54f61 100644 --- a/docs/site/pages/plugins/swiftui-pending-transaction.mdx +++ b/docs/site/pages/plugins/swiftui-pending-transaction.mdx @@ -1,4 +1,3 @@ - --- title: SwiftUIPendingTransactionPlugin platform: ios @@ -8,6 +7,12 @@ platform: ios The `SwiftUIPendingTransactionPlugin` allows you to register pending transactions (callbacks) in the userInfo on the decoder. Users can decide when to register, commit and clear transactions based on the use case. Anytime there is a scenario where we want a native transaction to happen while a view update is taking place, we can make use of this plugin. Below is an example used in the sample app where we can see this take place: +### CocoaPods +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/SwiftUIPendingTransactionPlugin' +``` ## **The Issue:** diff --git a/docs/site/pages/plugins/transition.mdx b/docs/site/pages/plugins/transition.mdx index a0b768ad8..c7c26a38b 100644 --- a/docs/site/pages/plugins/transition.mdx +++ b/docs/site/pages/plugins/transition.mdx @@ -7,18 +7,19 @@ platform: ios The `TransitionPlugin` allows for specifying transitions for when Player loads a flow, and for transition between views in the same flow. -## Usage - -Add the optional subspec to your Podfile or podspec: +### CocoaPods +Add the subspec to your `Podfile` ```ruby pod 'PlayerUI/TransitionPlugin' ``` + +### Swift Usage ```swift SwiftUIPlayer(flow: flowString, plugins: [TransitionPlugin(popTransition: .pop)], result: $resultBinding) ``` -## Customizing Transitions +#### Customizing Transitions To specify different transition, just supply a `PlayerViewTransition` to the plugin initializer: @@ -34,7 +35,7 @@ let plugin = TransitionPlugin( ) ``` -## Default Transitions +#### Default Transitions By default there are 5 transitions included with the `SwiftUIPlayer`: diff --git a/docs/site/pages/plugins/types-provider.mdx b/docs/site/pages/plugins/types-provider.mdx index ea38634a9..809dc613e 100644 --- a/docs/site/pages/plugins/types-provider.mdx +++ b/docs/site/pages/plugins/types-provider.mdx @@ -76,12 +76,21 @@ Given a data-type reference to `CustomType` in the content, your new validation -A wrapper is provided for the types-provider plugin for Core Player. This allows the use of Swift code to create custom types, validators, and formatters. +The swift `TypesProviderPlugin` enables adding custom data types, formatters and validation purely through swift code. While in general, the recommendation would be to share a single JavaScript implementation to multiple platforms, some use cases may need a native integration. -## Custom Validator +### CocoaPods +Add the subspec to your `Podfile` + +```ruby +pod 'PlayerUI/TypesProviderPlugin' +``` + +### Swift Usage + +#### Custom Validator ```swift -let validationFunction = {context, value, options in +let validationFunction = { context, value, options in if value == goodValue { return nil // Return nil to pass the validation } else { @@ -111,7 +120,7 @@ then in the JSON schema for your type:'' } ``` -## Custom Formatter +#### Custom Formatter ```swift let formatFunction = {value, options in @@ -144,7 +153,7 @@ then in the JSON schema for your type: } ``` -### Formatting Options +##### Formatting Options The second parameter passed to the format/deformat functions is for additional options, it is of type `[String: Any]` and contains any other keys that were passed alongside the `type` of the formatter: @@ -167,7 +176,7 @@ let formatFunction = {value, options in } ``` -## Custom Types +#### Custom Types Just as you can define custom formats and validation, you can define a custom type that encapsulates that functionality into a type, to avoid the need to keep specifying options, this is how the [common-types](./plugins/common-types) are defined, so when you choose a type like `DateType` the formatting is already set up. @@ -196,7 +205,7 @@ then in your JSON schema: } ``` -### Options in the CustomType +##### Options in the CustomType You can supply options to formatters of your custom type in the `ValidationReference` or `FormatReference`: diff --git a/docs/site/pages/tools/cli.mdx b/docs/site/pages/tools/cli.mdx index 3936be02a..c040ec8f9 100644 --- a/docs/site/pages/tools/cli.mdx +++ b/docs/site/pages/tools/cli.mdx @@ -45,7 +45,7 @@ Plugins are the way to change runtime behavior of the CLI actions. This includes # Commands - [`player dsl compile`](#player-dsl-compile) -Compile Player DSL files into JSON +Compile Player DSL files into JSON after running TSC compiler against Typescript files ``` USAGE @@ -60,7 +60,23 @@ FLAGS --skip-validation Option to skip validating the generated JSON DESCRIPTION - Compile Player DSL files into JSON + Compile Player DSL files into JSON after running TSC compiler against Typescript files +``` + +- [`player dsl validate`](#player-dsl-validate) +Runs isolated TSC compiler on authored Player DSL Typescript files. + +``` +USAGE + $ player dsl validate [-f ] [-c ] + +FLAGS + -c, --config= Path to a specific config file to load. + By default, will automatically search for an rc or config file to load + -f, --files=... A list of files or globs to validate + +DESCRIPTION + Runs isolated TSC compiler on authored Player DSL Typescript files. ``` - [`player json validate`](#player-json-validate) diff --git a/docs/site/pages/tools/dsl.mdx b/docs/site/pages/tools/dsl.mdx deleted file mode 100644 index 5e4529d4f..000000000 --- a/docs/site/pages/tools/dsl.mdx +++ /dev/null @@ -1,245 +0,0 @@ ---- -title: Writing content using TSX ---- - - -# TSX Content Authoring - -While JSON content makes for a decent transport layer, it's not always the preferable authoring format. To take advantage of existing developer tool-chains, Player provides a mechanism for authoring content in (J/T)SX as React components. This is paired with a cli to transpile the React tree into a JSON content. - -## Writing TSX Content - -In order to use the TSX-variant to write content, your asset library should ship a TSX component package to leverage. Read more [here](#creating-tsx-components) for more details on creating one. - -In the examples below, we will assume one already exists. - -## Assets/Views - -Assets and views are treated as basic React elements: - -```tsx -import { Input, Text, Collection } from '@player-ui/reference-assets-components'; - -const view = ( - - Some value - - Some label - - -); -``` - -which would generate something equivalent to: - -```json -{ - "id": "root", - "type": "collection", - "values": [ - { - "asset": { - "id": "root-values-1", - "type": "text", - "value": "Some value" - } - }, - { - "asset": { - "id": "root-values-2", - "type": "input", - "label": { - "asset": { - "id": "root-values-2-label", - "type": "text", - "value": "Some label" - } - } - } - } - ] -} -``` - -### Automatic ID generation - -Any component leveraging the base `Asset` component will automatically generate an `id` if one isn't provided. - -### Automatic Text/Collection generation - -In thee event that an asset is expected, but a `string` or `number` is found, a `Text` node will automatically be created (provided the asset-library as a _text-asset-factory_ configured). - -Similarly, if a single asset is expected, but a list is found, a `Collection` node will be created. - -## Bindings/Expressions - -Both binding and expression in the TSX authoring leverages a tagged template, typically abbreviated as `b` and `e` respectively. In a similar fashion to using `css` or `graphql` in a TS/JS file, this enables syntax-highlighting and validation of bindings and expressions within a JS file. - -To create a binding or expression: - -```tsx -import { binding as b, expression as e } from '@player-tools/dsl'; - -const myBinding = b`foo.bar`; -const myExpression = e`foo()`; -``` - -Bindings and expressions can also be used within template strings and automatically dereference themselves: - -```tsx -const stringWithBinding = `Some text: ${myBinding}`; // 'Some text: {{foo.bar}}' -const stringWithExp = `Some expr: ${myExpression}`; // 'Some expr: @[foo()]@' -``` - -## Templates - -Template support is included via the `@player-tools/dsl` package. This can be used in place of an asset slot: - -```tsx - - - - Value 1 - - -``` - -Templates can be nested within one another, and the auto-id generation will handle adding the `_index_` information to any generated `id`. - -## Switches - -Included as well is the ability to specify switches: - -```tsx - - - - - Text 1 - - - Text 1 - - - - -``` - -Use the `isDynamic` flag to denote if it should be a `static` or `dynamic` switch. - -## Schema - -The detailed format for the schema section is described [here](../content/dsl###Schema). The schema object is a mix of JS object structure with leaf nodes that are Player data types or type references. At DSL compile time, this data sctructure will be compiled down into the schema format required in JSON. While you can write the schema in the native format, using this noation allows it to be referenced directly in the TSX content in addition to being much more intuitive to write. - -## Navigation - -The `navigation` content segment is a basic JS object. The `@player-ui/player` package exports TypeScript interfaces which can provide typings for these: - -```tsx -import { Navigation } from '@player-ui/player'; - -const navigation: Navigation = { - BEGIN: 'Start', - Start: { - startState: 'VIEW_1', - VIEW_1: { - state_type: 'VIEW', - ref: 'view-1', - transitions: { - '*': 'END_Done', - }, - }, - END_Done: { - state_type: 'END', - outcome: 'done', - }, - }, -}; -``` - -# Creating TSX Components - -In order to take advantage of the auto-completion and validation of TypeScript types, asset libraries can export a component library for content authoring. Creating components isn't much different than writing a React component for the web. The primative elements uses the [react-json-reconciler](https://github.com/intuit/react-json-reconciler) to create the JSON content tree, with utilities to make it quick and painless to create new asset-components. - - -## Creating a basic component - -The `Asset` component from the `@player-tools/dsl` package is the quickest way to create a new component. - -In the examples below, we'll be creating a TSX component for the `action` asset in our reference set. - -The `action` asset has a `label` slot (which is typically used as a `text` asset), a `value` (for flow transitions), and an `exp` for evaluating expressions. -It's type resembles something akin to: - -```ts -import { Asset, AssetWrapper, Expression } from '@player-ui/player'; - -export interface ActionAsset extends Asset<'action'> { - /** The transition value of the action in the state machine */ - value?: string; - - /** A text-like asset for the action's label */ - label?: AssetWrapper; - - /** An optional expression to execute before transitioning */ - exp?: Expression; -} -``` - -To turn this interface into a usable component, create a new React component that _renders_ an Asset: - -```tsx -import { Asset, AssetPropsWithChildren } from '@player-tools/dsl'; - -export const Action = (props: AssetPropsWithChildren) => { - return ; -} -``` - -This would allow users to import the `Action` component, and _render_ it to JSON: - -```tsx -const myView = -``` - -The `AssetPropsWithChildren` type is a utility type to help convert the `Asset` type (which has a required `id` and `type` properties) to a type more suited for components. It changes the `id` to be optional, and adds a `applicability` property automatically. - -## Slots - -Continuing the example fo the `ActionAsset`, we need a way for users to users to specify the nested `label` property, which itself is another asset. This can be accomplished using the `createSlot` utility function. - -```tsx -import { Asset, AssetPropsWithChildren } from '@player-tools/dsl'; - -export const Action = (props: AssetPropsWithChildren) => { - return ; -} - -Action.Label = createSlot({ - name: 'label' -}) -``` - -This adds component (`Action.Label`) that will automatically place any nested children under the `label` property of the parent asset: - -```tsx -const myView = ( - - - - - -); -``` - -It also accepts components for automatically creating `text` and `collection` assets, allowing users to shortcut the `Text` component: - -```tsx -const myView = ( - - Continue - -); -``` diff --git a/docs/site/pages/writing-plugins.mdx b/docs/site/pages/writing-plugins.mdx index 613d91c42..d6c93b18b 100644 --- a/docs/site/pages/writing-plugins.mdx +++ b/docs/site/pages/writing-plugins.mdx @@ -13,6 +13,22 @@ platform: core,react Core plugins are the easiest way to extend Player functionality regardless of what platform you are using Player on. To make writing core plugins easy `@player-ui/player` exposes an interface `PlayerPlugin` that denotes everything needed. The two mandatory features are a `name` property which is lets Player know how to refer to the plugin and an implemented `apply` function that takes a `player` object. Optionally a `symbol` property can be used to provide a unique identifier that can be used to retrieve the plugin from Player. +The first step for creating a plugin is making our plugin class, making sure it implements the `PlayerPlugin` interface from `@player-ui/player`. By convention, a name attribute with the dash-cased name of your plugin should be defined. + +```typescript +export default class ExamplePlayerPlugin implements PlayerPlugin { + name = 'example-player-plugin'; + + {/* A constructor can go here */} + + apply(player: Player) { + {/* Your logic here */} + } + + {/* Helper methods can go here */} +} +``` + The `apply` function is where the actual logic of the plugin lives. By tapping the hooks exposed via `player.hooks` you gain access to the internal pipeline of components that comprise Player and can inject your functionality into their exposed hooks. For example if you want to do something any time Player's state changes you could do the following: ```javascript @@ -36,12 +52,14 @@ apply(player: Player) { It is not uncommon for core plugins to have constructors for cases where the plugin needs to take some configuration. In cases where plugin configs are more complicated than basic feature flags, it is recommended to make an interface to represent the config object. As an added benefit it also makes it easier to down stream consumers to use your plugin. +For a more comprehensive guide on plugins, check out this [Plugin Implementation](./plugin-implementation) example. + _Note: For the React Player you can import and load the plugin the same way you would a React Player Plugin but for the iOS and Android Players you will need to wrap the javascript bundle in a iOS/Android plugin to ensure it is available on your platform._ -React Player Plugins are very similar to core plugins in both their composition and use. The `@player-ui/react` package exposes an interface `ReactPlayerPlugin` that, much like the `PlayerPlugin` interface provides the necessary attributes that are required for a React Player plugin. Again a `name` attribute is required and a function `applyReact` is required that takes a `ReactPlayer` instance. Similarly to core plugins in the `applyReact` function you have access to the React Player object and access to the three exposed hooks: +React Player Plugins are very similar to core plugins in both their composition and use. The `@player-ui/react` package exposes an interface `ReactPlayerPlugin` that, much like the `PlayerPlugin` interface provides the necessary attributes that are required for a React Player plugin. Again a dash-cased `name` attribute should be used by convention, and a function `applyReact` is required that takes a `ReactPlayer` instance. Similarly to core plugins in the `applyReact` function you have access to the React Player object and access to the three exposed hooks: - The `webComponent` hook allows you to modify a React component that is stored in the React Player for use when it renders content. This happens during the initialization phase and ise useful if you want to wrap components in various content providers. - The `playerComponent` hook allows you to modify a component or execute functionality when the React Player is rendering a component after the view has been reconciled in Player. This is useful if you want to inject additional props to components or collect data on which component was rendered. @@ -78,19 +96,28 @@ export class FunctionPlugin implements ReactPlayerPlugin { ); }); } + + {/* Helper methods can go here */} +} ``` Lastly React plugins can also act as a core plugin in cases where core functionality needs to be extended for the React plugin to work. Since both the `PlayerPlugin` and `ReactPlayerPlugin` are typescript interfaces a plugin can implement both and be considered a valid plugin. -iOS Player Plugins are very similar to core and react plugins in both their composition and use. The `PlayerUI/Core` subspec exposes an interface `NativePlugin` that, much like the core `PlayerPlugin` interfaces, provides the necessary attributes that are required for an iOS Player plugin. A `pluginName` attributed is required, and a function `apply` is required that takes an instance of a Player implementation. Similarly to core plugins, in the `apply` function you have access to the Player object and access to the hooks. `apply` uses generics to future proof so plugins can be used for multiple Player implementations should they be created. +iOS Player Plugins are very similar to core and react plugins in both their composition and use. + +### NativePlugin + +The `PlayerUI/Core` subspec exposes an interface `NativePlugin` that, much like the core `PlayerPlugin` interfaces, provides the necessary attributes that are required for an iOS Player plugin. A `pluginName` attributed is required, and a function `apply` is required that takes an instance of a Player implementation. Similarly to core plugins, in the `apply` function you have access to the Player object and access to the hooks. `apply` uses generics to future proof so plugins can be used for multiple Player implementations should they be created. The `player` passed to `apply` exposes hooks from the core player, as well as hooks specific to that player implementation. For the current state of this project, the `SwiftUIPlayer` is the primary iOS Player, and exposes two hooks for the SwiftUI layer specifically: - The `view` hook allows you to modify the root view that will be displayed in the SwiftUIPlayer body. This is useful for applying changes to the environment for the SwiftUI view tree, or apply ViewModifiers and such. - The `transition` hook allows you to specify a `PlayerViewTransition` object to be applied when the flow transitions from one view to another, to animate the transition. + +#### Basic Example Below is an example of a basic `NativePlugin` that sets a value in the EnvironmentValues when the plugin is included: ```swift @@ -107,5 +134,76 @@ class EnvironmentPlugin: NativePlugin { } } ``` +#### Asset Registration +Likely the most common usecase for plugins is to register assets: + +```swift +import PlayerUI + +class ExampleAssetPlugin: NativePlugin { + let pluginName = "ExampleAssetPlugin" + + func apply

(player: P) where P: HeadlessPlayer { + guard let player = player as? SwiftUIPlayer else { return } + player.assetRegistry.register("text", asset: TextAsset.self) + player.assetRegistry.register("action", asset: ActionAsset.self) + } +} +``` + +### JSBasePlugin +Building native features on top of shared functionality is one of the primary benefits of using player. As such we expose convenience utilities to enable loading JavaScript Player Plugins as the base for your `NativePlugin`. + + +#### Basic Setup +This example will load the `SharedJSPlugin` in the JavaScript layer when included as a plugin to `SwiftUIPlayer`. +```swift +import PlayerUI + +class SharedJSPlugin: JSBasePlugin { + convenience init() { + // pluginName must match the exported class name in the JavaScript plugin + self.init(fileName: 'shared-js-plugin-bundle', pluginName: 'SharedJSPlugin') + } + + // Construct the URL to load the JS bundle + override open func getUrlForFile(fileName: String) -> URL? { + ResourceUtilities.urlForFile( + fileName: fileName, + ext: "js", + bundle: Bundle(for: YourPlugin.self), + pathComponent: "YOUR_POD.bundle" + ) + } +} +``` +#### Arguments for constructing the JavaScript plugins +To simplify the ease of use, the `JSContext` for `JSBasePlugin` implementations is provided when the underlying resources for the core `player` are being setup. This means that we need to provide the arguments for the JavaScript constructor late. To do this, override the `getArguments` function: + +```swift +import PlayerUI + +class SharedJSPlugin: JSBasePlugin { + var option: Boolean + + convenience init(option: Boolean) { + // pluginName must match the exported class name in the JavaScript plugin + self.init(fileName: 'shared-js-plugin-bundle', pluginName: 'SharedJSPlugin') + self.option = option + } + + override open func getArguments() -> [Any] { + // plugin just takes a boolean arguments + return [option] + // More common in JavaScript, constructor takes an object + return [["enable": option]] + } +} +``` + +**Note**: As `JavaScriptCore` cannot resolve dependencies at runtime, using a module bundler such as [tsup](https://github.com/egoist/tsup) is required. + +**Note**: `JSBasePlugin` implementations do not necessarily need to be a `PlayerPlugin`, for example, the [BeaconPlugin](https://github.com/player-ui/player/blob/main/ios/plugins/BaseBeaconPlugin/Sources/BaseBeaconPlugin.swift#L63) can take plugins in it's constructor, that are not `PlayerPlugin`. + diff --git a/docs/site/plugins/mdx-link-append-loader.js b/docs/site/plugins/mdx-link-append-loader.js new file mode 100644 index 000000000..f27e598b5 --- /dev/null +++ b/docs/site/plugins/mdx-link-append-loader.js @@ -0,0 +1,9 @@ +const path = require('path'); + +module.exports = function (source) { + const filePath = this.resourcePath; + const rootDir = this.rootContext; + const relativePath = path.relative(rootDir, filePath); + const newContent = `${source}\n\n---\n\n[Help to improve this page](https://github.dev/player-ui/player/blob/main/docs/site/${relativePath})`; + return newContent; +}; diff --git a/docs/storybook/src/flows/managed.ts b/docs/storybook/src/flows/managed.ts index 1f4ea8f48..fc8de3ce1 100644 --- a/docs/storybook/src/flows/managed.ts +++ b/docs/storybook/src/flows/managed.ts @@ -1,38 +1,8 @@ -import { FlowManager, Flow, CompletedState } from "@player-ui/react"; -import { makeFlow } from "@player-ui/make-flow"; - -const firstFlow = makeFlow({ - id: "text-1", - type: "action", - value: "Flow 1", - label: { - asset: { id: "action-label-1", type: "text", value: "End Flow 1" }, - }, -}); - -const secondFlow = makeFlow({ - id: "text-2", - type: "action", - value: "Flow 2", - label: { - asset: { id: "action-label-2", type: "text", value: "End Flow 2" }, - }, -}); - -const errorFlow = makeFlow({ - id: "text-2", - type: "action", - value: "Flow Error", - exp: "{{foo.bar..}", - label: { - asset: { id: "action-label-2", type: "text", value: "End Flow 2" }, - }, -}); - -const assetErrorFlow = makeFlow({ - id: "text-3", - type: "error", -}); +import { FlowManager, Flow, CompletedState } from '@player-ui/react'; +import firstFlow from '@player-ui/reference-assets-plugin-mocks/flow-manager/first-flow.json'; +import secondFlow from '@player-ui/reference-assets-plugin-mocks/flow-manager/second-flow.json'; +import errorFlow from '@player-ui/reference-assets-plugin-mocks/flow-manager/error-flow.json'; +import assetErrorFlow from '@player-ui/reference-assets-plugin-mocks/flow-manager/asset-error-flow.json'; export const SIMPLE_FLOWS = [firstFlow, secondFlow]; export const ERROR_CONTENT_FLOW = [firstFlow, errorFlow]; diff --git a/ios/core/Sources/CorePlugins/JSBasePlugin.swift b/ios/core/Sources/CorePlugins/JSBasePlugin.swift index 46eedce74..f3537f06e 100644 --- a/ios/core/Sources/CorePlugins/JSBasePlugin.swift +++ b/ios/core/Sources/CorePlugins/JSBasePlugin.swift @@ -80,7 +80,7 @@ open class JSBasePlugin { */ private func getPlugin(context: JSContext, fileName: String, pluginName: String, arguments: [Any] = []) -> JSValue { guard - let plugin = context.getClassReference(pluginName, load: {loadJSResource(into: $0, fileName: fileName)}), + let plugin = context.getClassReference(pluginName, load: {loadJSResource(into: $0, fileName: fileName)}), !plugin.isUndefined, let pluginValue = plugin.construct(withArguments: arguments) else { fatalError("Unable To Construct \(pluginName)") diff --git a/ios/core/Sources/Player/HeadlessPlayer.swift b/ios/core/Sources/Player/HeadlessPlayer.swift index f8857efa3..3399f340a 100644 --- a/ios/core/Sources/Player/HeadlessPlayer.swift +++ b/ios/core/Sources/Player/HeadlessPlayer.swift @@ -187,7 +187,7 @@ public extension HeadlessPlayer { let warn = JSValue(object: logger.getJSLogFor(level: .warning), in: context) let error = JSValue(object: logger.getJSLogFor(level: .error), in: context) for plugin in plugins { - if let plugin = plugin as? JSBasePlugin { + if let plugin = plugin as? JSBasePlugin, plugin.context != context { plugin.context = context } } diff --git a/ios/core/Sources/Types/Core/BindingParser.swift b/ios/core/Sources/Types/Core/BindingParser.swift index e59e32829..349455047 100644 --- a/ios/core/Sources/Types/Core/BindingParser.swift +++ b/ios/core/Sources/Types/Core/BindingParser.swift @@ -34,7 +34,7 @@ public class BindingParser { } /// A path in the data model -public class BindingInstance { +public class BindingInstance: Decodable { internal var value: JSValue? /// Create a BindingInstance, a path to data in the data model @@ -53,6 +53,11 @@ public class BindingInstance { self.value = value } + ///Create a BindingInstance from a decoder + required public init(from decoder: Decoder) throws { + value = try decoder.getJSValue() + } + /// Retrieve this Binding as an array /// - Returns: The array of segments in the binding public func asArray() -> [String]? { diff --git a/ios/core/Sources/Types/Core/DataController.swift b/ios/core/Sources/Types/Core/DataController.swift index 74b5c2787..65eb1cab4 100644 --- a/ios/core/Sources/Types/Core/DataController.swift +++ b/ios/core/Sources/Types/Core/DataController.swift @@ -16,6 +16,9 @@ open class BaseDataController { /// The JSValue that backs this wrapper public let value: JSValue + /// The hooks that can be tapped into + public let hooks: DataControllerHooks + /** Construct a DataController from a JSValue - parameters: @@ -23,6 +26,9 @@ open class BaseDataController { */ public init(_ value: JSValue) { self.value = value + self.hooks = DataControllerHooks( + onUpdate: HookDecode<[Update]>(baseValue: self.value, name: "onUpdate") + ) } /** diff --git a/ios/core/Sources/Types/Core/Flow.swift b/ios/core/Sources/Types/Core/Flow.swift index ed101cddd..ae31cb619 100644 --- a/ios/core/Sources/Types/Core/Flow.swift +++ b/ios/core/Sources/Types/Core/Flow.swift @@ -1,6 +1,6 @@ // // Flow.swift -// +// // // Created by Borawski, Harris on 2/13/20. // diff --git a/ios/core/Sources/Types/Core/FlowController.swift b/ios/core/Sources/Types/Core/FlowController.swift index 9eb5f249c..1c192ff7b 100644 --- a/ios/core/Sources/Types/Core/FlowController.swift +++ b/ios/core/Sources/Types/Core/FlowController.swift @@ -52,4 +52,3 @@ public class FlowController: CreatedFromJSValue { try self.value.objectForKeyedSubscript("transition").tryCatch(args: [action]) } } - diff --git a/ios/core/Sources/Types/Core/NavigationStates.swift b/ios/core/Sources/Types/Core/NavigationStates.swift index 0e167a80d..cc65b05a4 100644 --- a/ios/core/Sources/Types/Core/NavigationStates.swift +++ b/ios/core/Sources/Types/Core/NavigationStates.swift @@ -43,8 +43,8 @@ public class NavigationFlowViewState: NavigationFlowTransitionableState { public var ref: String { rawValue.objectForKeyedSubscript("ref").toString() } /// View meta-properties - public var attributes: [String: String]? { - rawValue.objectForKeyedSubscript("attributes").toObject() as? [String: String] + public var attributes: [String: Any]? { + rawValue.objectForKeyedSubscript("attributes").toObject() as? [String: Any] } public subscript(dynamicMember member: String) -> T? { diff --git a/ios/core/Sources/Types/Hooks/DataControllerHooks.swift b/ios/core/Sources/Types/Hooks/DataControllerHooks.swift new file mode 100644 index 000000000..5c2c12c2b --- /dev/null +++ b/ios/core/Sources/Types/Hooks/DataControllerHooks.swift @@ -0,0 +1,29 @@ +// +// DataControllerHooks.swift +// PlayerUI +// +// Created by Zhao Xia Wu on 2024-04-08. +// + +import Foundation +import JavaScriptCore + +/** +Hooks that can be tapped into for the DataController +This lets users tap into events in the JS environment +*/ +public struct DataControllerHooks { + /// Fired when the data changes + public var onUpdate: HookDecode<[Update]> +} + +public struct Update: Decodable { + /** The updated binding */ + var binding: BindingInstance + /** The old value */ + var oldValue: AnyType + /** The new value */ + var newValue: AnyType + /** Force the Update to be included even if no data changed */ + var force: Bool? +} diff --git a/ios/core/Sources/Types/Hooks/Hook.swift b/ios/core/Sources/Types/Hooks/Hook.swift index ca2dbf588..618117620 100644 --- a/ios/core/Sources/Types/Hooks/Hook.swift +++ b/ios/core/Sources/Types/Hooks/Hook.swift @@ -82,3 +82,97 @@ public class Hook2: BaseJSHook where T: CreatedFromJSValue, U: CreatedFrom self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) } } + +/** + This class represents an object in the JS runtime that can be tapped into + to receive JS events + */ +public class HookDecode: BaseJSHook where T: Decodable { + /** + Attach a closure to the hook, so when the hook is fired in the JS runtime + we receive the event in the native runtime + + - parameters: + - hook: A function to run when the JS hook is fired + */ + public func tap(_ hook: @escaping (T) -> Void) { + let tapMethod: @convention(block) (JSValue?) -> Void = { value in + guard + let val = value, + let hookValue = try? JSONDecoder().decode(T.self, from: val) + else { return } + hook(hookValue) + } + + self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) + } +} + +/** + This class represents an object in the JS runtime that can be tapped into + to receive JS events that has 2 parameters + */ +public class Hook2Decode: BaseJSHook where T: Decodable, U: Decodable { + /** + Attach a closure to the hook, so when the hook is fired in the JS runtime + we receive the event in the native runtime + + - parameters: + - hook: A function to run when the JS hook is fired + */ + public func tap(_ hook: @escaping (T, U) -> Void) { + let tapMethod: @convention(block) (JSValue?, JSValue?) -> Void = { value, value2 in + + let decoder = JSONDecoder() + guard + let val = value, + let val2 = value2, + let hookValue = try? decoder.decode(T.self, from: val), + let hookValue2 = try? decoder.decode(U.self, from: val2) + else { return } + hook(hookValue, hookValue2) + } + + self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) + } +} + +/** + This class represents an object in the JS runtime that can be tapped into + and returns a promise that resolves when the asynchronous task is completed + */ +public class AsyncHook: BaseJSHook where T: CreatedFromJSValue { + private var handler: AsyncHookHandler? + + public typealias AsyncHookHandler = (T) async throws -> JSValue? + + /** + Attach a closure to the hook, so when the hook is fired in the JS runtime + we receive the event in the native runtime + + - parameters: + - hook: A function to run when the JS hook is fired + */ + public func tap(_ hook: @escaping AsyncHookHandler) { + let tapMethod: @convention(block) (JSValue?) -> JSValue = { value in + guard + let val = value, + let hookValue = T.createInstance(value: val) as? T + else { return JSValue() } + + let promise = + JSUtilities.createPromise(context: self.context, handler: { (resolve, _) in + Task { + let result = try await hook(hookValue) + DispatchQueue.main.async { + resolve(result as Any) + } + } + }) + + return promise ?? JSValue() + } + + self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) + } +} diff --git a/ios/core/Tests/FlowStateTests.swift b/ios/core/Tests/FlowStateTests.swift index 8f0f85917..576bf903d 100644 --- a/ios/core/Tests/FlowStateTests.swift +++ b/ios/core/Tests/FlowStateTests.swift @@ -22,7 +22,7 @@ class FlowStateTests: XCTestCase { XCTAssertNotNil(inProgress.controllers?.flow.current?.currentState?.value as? NavigationFlowViewState) let view = inProgress.controllers?.flow.current?.currentState?.value as? NavigationFlowViewState - XCTAssertEqual(view?.attributes?["test"], "value") + XCTAssertEqual(view?.attributes?["test"] as? String, "value") } func testExternalFlowState() { diff --git a/ios/core/Tests/HeadlessPlayerTests.swift b/ios/core/Tests/HeadlessPlayerTests.swift index fc3ec8155..82f5230a9 100644 --- a/ios/core/Tests/HeadlessPlayerTests.swift +++ b/ios/core/Tests/HeadlessPlayerTests.swift @@ -251,6 +251,27 @@ class HeadlessPlayerTests: XCTestCase { } } } + + func testDataControllerOnUpdate() { + let player = HeadlessPlayerImpl(plugins: []) + + let updateExp = expectation(description: "Data Updated") + + XCTAssertNotNil(player.state as? NotStartedState) + player.hooks?.dataController.tap({ dataController in + dataController.hooks.onUpdate.tap { updates in + XCTAssertEqual(updates.first?.binding.asString(), "count") + XCTAssertEqual(updates.first?.oldValue, AnyType.number(data: 0)) + XCTAssertEqual(updates.first?.newValue, AnyType.number(data: 5)) + updateExp.fulfill() + } + + dataController.set(transaction: ["count": 5]) + }) + + player.start(flow: FlowData.COUNTER) { _ in} + wait(for: [updateExp], timeout: 1) + } } class FakePlugin: JSBasePlugin, NativePlugin { diff --git a/ios/packages/reference-assets/UITests/BeaconPluginUITest.swift b/ios/packages/reference-assets/UITests/BeaconPluginUITest.swift new file mode 100644 index 000000000..1ad0cb199 --- /dev/null +++ b/ios/packages/reference-assets/UITests/BeaconPluginUITest.swift @@ -0,0 +1,20 @@ +import Foundation +import XCTest + +class BeaconPluginUITests: BaseTestCase { + override func navigateToAssetCollection() { + app.otherElements.buttons["Plugins + Managed Player"].firstMatch.tap() + } + + func testBeaconPluginAction() { + openFlow("beacon action") + XCTAssertTrue(app.alerts["Info"].staticTexts.element(boundBy: 1).label.contains("action")) + + app.buttons["OK"].firstMatch.tap() + let button = app.buttons["action"].firstMatch + waitFor(button) + button.tap() + + XCTAssertTrue(app.alerts["Info"].staticTexts.element(boundBy: 1).label.contains("some-data")) + } +} diff --git a/ios/packages/reference-assets/UITests/ExternalActionUITests.swift b/ios/packages/reference-assets/UITests/ExternalActionUITests.swift new file mode 100644 index 000000000..24490af96 --- /dev/null +++ b/ios/packages/reference-assets/UITests/ExternalActionUITests.swift @@ -0,0 +1,15 @@ +import Foundation +import XCTest + +class ExternalActionUITests: BaseTestCase { + override func navigateToAssetCollection() { + app.otherElements.buttons["Plugins + Managed Player"].firstMatch.tap() + } + + func testExternalAction() { + openFlow("external-action external-action") + let alert = app.alerts["FlowCompleted"] + waitFor(alert) + XCTAssertTrue(alert.staticTexts.element(boundBy: 1).label.contains("FWD")) + } +} diff --git a/ios/packages/reference-assets/UITests/ManagedPlayerUITests.swift b/ios/packages/reference-assets/UITests/ManagedPlayerUITests.swift new file mode 100644 index 000000000..131aea5c9 --- /dev/null +++ b/ios/packages/reference-assets/UITests/ManagedPlayerUITests.swift @@ -0,0 +1,62 @@ +import Foundation +import XCTest + +class ManagedPlayerUITests: BaseTestCase { + override func navigateToAssetCollection() { + app.otherElements.buttons["Plugins + Managed Player"].firstMatch.tap() + } + + func testSimpleFlow() { + openFlow("Simple Flows") + let button1 = app.buttons["first_view"].firstMatch + waitFor(button1) + button1.tap() + + let button2 = app.buttons["second_view"].firstMatch + waitFor(button2) + button2.tap() + + let completedText = app.staticTexts["Flow Completed"] + + waitFor(completedText) + } + + func testErrorContentFlow() { + openFlow("Error Content Flow") + + let button1 = app.buttons["first_view"].firstMatch + waitFor(button1) + button1.tap() + + let button2 = app.buttons["second_view"].firstMatch + + waitFor(button2) + button2.tap() + + let errorText = app.staticTexts["Unclosed brace after \"foo.bar..}\" at character 12"].firstMatch + waitFor(errorText) + + + let retryButton = app.buttons["Retry"].firstMatch + retryButton.tap() + + waitFor(button2) + } + + func testErrorAssetFlow() { + openFlow("Error Asset Flow") + let button1 = app.buttons["first_view"].firstMatch + waitFor(button1) + button1.tap() + + let errorText = app.staticTexts["PlayerUI.DecodingError.typeNotRegistered(type: \"error\")"].firstMatch + + waitFor(errorText) + + + let resetButton = app.buttons["Reset"] + resetButton.tap() + + waitFor(button1) + } +} diff --git a/ios/packages/reference-assets/UITests/PubsubUITests.swift b/ios/packages/reference-assets/UITests/PubsubUITests.swift new file mode 100644 index 000000000..a6d7134ef --- /dev/null +++ b/ios/packages/reference-assets/UITests/PubsubUITests.swift @@ -0,0 +1,23 @@ +import Foundation +import XCTest + +class PubSubUITests: BaseTestCase { + override func navigateToAssetCollection() { + app.otherElements.buttons["Plugins + Managed Player"].firstMatch.tap() + } + + func testPubsubPluginAction() { + openFlow("pub sub basic") + XCTAssertTrue(app.alerts["Info"].staticTexts.element(boundBy: 1).label.contains("action")) + + app.buttons["OK"].firstMatch.tap() + + let button = app.buttons["action"].firstMatch + waitFor(button) + button.tap() + + XCTAssertTrue(app.alerts["Info"].staticTexts.element(boundBy: 1).label.contains("Published: `some-event`")) + + XCTAssertTrue(app.alerts["Info"].staticTexts.element(boundBy: 1).label.contains("event published message")) + } +} diff --git a/ios/packages/reference-assets/UITests/SwiftUIPendingTransactionPluginUITests.swift b/ios/packages/reference-assets/UITests/SwiftUIPendingTransactionPluginUITests.swift new file mode 100644 index 000000000..f8ff9ce5e --- /dev/null +++ b/ios/packages/reference-assets/UITests/SwiftUIPendingTransactionPluginUITests.swift @@ -0,0 +1,44 @@ +import Foundation +import XCTest + +class SwiftUIPendingTransactionPluginUITests: BaseTestCase { + override func navigateToAssetCollection() { + app.otherElements.buttons["Plugins + Managed Player"].firstMatch.tap() + } + + func testInputAssetPendingTransaction() { + openFlow("input asset pending transaction") + + waitFor(app.alerts.buttons["OK"].firstMatch) + + app.alerts.buttons["OK"].firstMatch.tap() + + var enteredValue = "-1" + + let input = app.textFields["input-required"] + waitFor(input) + + input.tap() + input.typeText(enteredValue) + + let button = app.buttons["action-1"].firstMatch + + button.tap() + XCTAssertTrue(app.staticTexts["Must be at least 1"].firstMatch.exists) + + input.typeText(XCUIKeyboardKey.delete.rawValue) + input.typeText(XCUIKeyboardKey.delete.rawValue) + + enteredValue = "1" + + input.tap() + input.typeText(enteredValue) + + button.tap() + + XCTAssertTrue(app.alerts["Info"].staticTexts.element(boundBy: 1).label.contains("view-2")) + + app.buttons["OK"].firstMatch.tap() + XCTAssertTrue(app.staticTexts["You made it!"].firstMatch.exists) + } +} diff --git a/ios/plugins/AsyncNodePlugin/Sources/AsyncNodePlugin.swift b/ios/plugins/AsyncNodePlugin/Sources/AsyncNodePlugin.swift new file mode 100644 index 000000000..fe38d46ea --- /dev/null +++ b/ios/plugins/AsyncNodePlugin/Sources/AsyncNodePlugin.swift @@ -0,0 +1,142 @@ +// +// AsyncNodePlugin.swift +// PlayerUI +// +// Created by Zhao Xia Wu on 2024-02-05. +// + +import Foundation +import JavaScriptCore + +public typealias AsyncHookHandler = (JSValue) async throws -> AsyncNodeHandlerType + +public enum AsyncNodeHandlerType { + case multiNode([ReplacementNode]) + case singleNode(ReplacementNode) +} + +/** + Wraps the core AsyncNodePlugin and taps into the `onAsyncNode` hook to allow asynchronous replacement of the node object that contains `async` + */ +public class AsyncNodePlugin: JSBasePlugin, NativePlugin { + public var hooks: AsyncNodeHook? + + private var asyncHookHandler: AsyncHookHandler? + + /** + Constructs the AsyncNodePlugin + - Parameters: + - handler: The callback that is used to tap into the core `onAsyncNode` hook + exposed to users of the plugin allowing them to supply the replacement node used in the tap callback + */ + public convenience init(_ handler: @escaping AsyncHookHandler) { + self.init(fileName: "async-node-plugin.prod", pluginName: "AsyncNodePlugin.AsyncNodePlugin") + self.asyncHookHandler = handler + } + + override public func setup(context: JSContext) { + super.setup(context: context) + + if let pluginRef = self.pluginRef { + self.hooks = AsyncNodeHook(onAsyncNode: AsyncHook(baseValue: pluginRef, name: "onAsyncNode")) + } + + hooks?.onAsyncNode.tap({ node in + // hook value is the original node + guard let asyncHookHandler = self.asyncHookHandler else { + return JSValue() + } + + let replacementNode = try await (asyncHookHandler)(node) + + switch replacementNode { + case .multiNode(let replacementNodes): + let jsValueArray = replacementNodes.compactMap({ node in + switch node { + case .concrete(let jsValue): + return jsValue + case .encodable(let encodable): + let encoder = JSONEncoder() + do { + let res = try encoder.encode(encodable) + return context.evaluateScript("(\(String(data: res, encoding: .utf8) ?? ""))") as JSValue + } catch { + return nil + } + } + }) + + return context.objectForKeyedSubscript("Array").objectForKeyedSubscript("from").call(withArguments: [jsValueArray]) + + case .singleNode(let replacementNode): + switch replacementNode { + + case .encodable(let encodable): + let encoder = JSONEncoder() + do { + let res = try encoder.encode(encodable) + return context.evaluateScript("(\(String(data: res, encoding: .utf8) ?? ""))") as JSValue + } catch { + break + } + case .concrete(let jsValue): + return jsValue + } + } + + return nil + }) + } + + override open func getUrlForFile(fileName: String) -> URL? { + ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: AsyncNodePlugin.self), pathComponent: "PlayerUI_AsyncNodePlugin.bundle") + } +} + +public struct AsyncNodeHook { + public let onAsyncNode: AsyncHook +} + +/** + Replacement node that the callback of this plugin expects, users can supply either a JSValue or an Encodable object that gets converted to a JSValue in the `setup` + */ +public enum ReplacementNode: Encodable { + case concrete(JSValue) + case encodable(Encodable) + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .encodable(let value): + try container.encode(value) + case .concrete( _): + break + } + } +} + +public struct AssetPlaceholderNode: Encodable { + public enum CodingKeys: String, CodingKey { + case asset + } + + var asset: Encodable + public init(asset: Encodable) { + self.asset = asset + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try? container.encode(asset, forKey: .asset) + } +} + +public struct AsyncNode: Codable, Equatable { + var id: String + var async: Bool = true + + public init(id: String) { + self.id = id + } +} diff --git a/ios/plugins/AsyncNodePlugin/ViewInspector/AsynNodePluginTests.swift b/ios/plugins/AsyncNodePlugin/ViewInspector/AsynNodePluginTests.swift new file mode 100644 index 000000000..32e07567e --- /dev/null +++ b/ios/plugins/AsyncNodePlugin/ViewInspector/AsynNodePluginTests.swift @@ -0,0 +1,367 @@ +// +// AsyncNodePluginTests.swift +// PlayerUI +// +// Created by Zhao Xia Wu on 2024-02-05. +// + +import Foundation +import XCTest +import SwiftUI +import ViewInspector +import JavaScriptCore +@testable import PlayerUI + +class AsyncNodePluginTests: SwiftUIAssetUnitTestCase { + + func testConstruction() { + let context = JSContext() + let plugin = AsyncNodePlugin { _ in + return .singleNode(.concrete(JSValue())) + } + plugin.context = context + + XCTAssertNotNil(plugin.pluginRef) + } + + func testAsyncNodeWithSwiftUIPlayerUsingJSValue() { + let handlerExpectation = XCTestExpectation(description: "handler called") + let jsContext = JSContext() + + let plugin = AsyncNodePlugin { _ in + handlerExpectation.fulfill() + + return .singleNode(.concrete(jsContext?.evaluateScript(""" + ({"asset": {"id": "text", "type": "text", "value":"new node from the hook"}}) + """) ?? JSValue())) + } + + plugin.context = jsContext + + let context = SwiftUIPlayer.Context { jsContext ?? JSContext() } + + let player = SwiftUIPlayer( + flow: .asyncNodeJson, plugins: [ReferenceAssetsPlugin(), plugin], context: context) + + ViewHosting.host(view: player) + + let viewExpectation = player.inspection.inspect(after: 0.5) { view in + _ = try view.vStack().first?.anyView().find(text: "new node from the hook") + } + + wait(for: [handlerExpectation, viewExpectation], timeout: 1) + } + + func testAsyncNodeWithAnotherAsyncNodeDelay() { + let handlerExpectation = XCTestExpectation(description: "first data did not change") + + let context = JSContext() + + var count = 0 + + let resolveHandler: AsyncHookHandler = { _ in + handlerExpectation.fulfill() + + sleep(3) + return .singleNode(.concrete(context?.evaluateScript(""" + ([ + {"asset": {"id": "text", "type": "text", "value":"new node from the hook 1"}} + ]) + """) ?? JSValue())) + } + + let plugin = AsyncNodePlugin(resolveHandler) + + plugin.context = context + + let player = HeadlessPlayerImpl(plugins: [ReferenceAssetsPlugin(), plugin], context: context ?? JSContext()) + + let textExpectation = XCTestExpectation(description: "newText1 found") + + var expectedMultiNode1Text: String = "" + + player.hooks?.viewController.tap({ (viewController) in + viewController.hooks.view.tap { (view) in + view.hooks.onUpdate.tap { val in + count += 1 + + if count == 2 { + let newText1 = val + .objectForKeyedSubscript("values") + .objectAtIndexedSubscript(1) + .objectForKeyedSubscript("asset") + .objectForKeyedSubscript("value") + guard let textString1 = newText1?.toString() else { return XCTFail("newText1 was not a string") } + + expectedMultiNode1Text = textString1 + textExpectation.fulfill() + } + } + } + }) + + player.start(flow: .asyncNodeJson, completion: {_ in}) + + wait(for: [handlerExpectation, textExpectation], timeout: 5) + + XCTAssert(count == 2) + XCTAssertEqual(expectedMultiNode1Text, "new node from the hook 1") + } + + func testReplaceAsyncNodeWithChainedMultiNodes() { + let handlerExpectation = XCTestExpectation(description: "first data did not change") + + let context = JSContext() + var count = 0 + + let resolve: AsyncHookHandler = { _ in + handlerExpectation.fulfill() + + if count == 1 { + return .multiNode([ + ReplacementNode.concrete(context?.evaluateScript(""" + ( + {"asset": {"id": "text", "type": "text", "value":"1st value in the multinode"}} + ) + """) ?? JSValue()), + ReplacementNode.encodable(AsyncNode(id: "id"))]) + } else if count == 2 { + return .multiNode([ + ReplacementNode.encodable(AssetPlaceholderNode(asset: PlaceholderNode(id: "text-2", type: "text", value: "2nd value in the multinode"))), + ReplacementNode.encodable(AsyncNode(id: "id-1"))]) + } else if count == 3 { + return .singleNode(ReplacementNode.encodable( + AssetPlaceholderNode(asset: PlaceholderNode(id: "text", type: "text", value: "3rd value in the multinode")) + )) + } + + return .singleNode(ReplacementNode.concrete(context?.evaluateScript("") ?? JSValue())) + } + + let plugin = AsyncNodePlugin(resolve) + + plugin.context = context + + let player = HeadlessPlayerImpl(plugins: [ReferenceAssetsPlugin(), plugin], context: context ?? JSContext()) + + let textExpectation = XCTestExpectation(description: "newText found") + let textExpectation2 = XCTestExpectation(description: "newText found") + let textExpectation3 = XCTestExpectation(description: "newText found") + + var expectedMultiNode1Text: String = "" + var expectedMultiNode2Text: String = "" + var expectedMultiNode3Text: String = "" + + player.hooks?.viewController.tap({ (viewController) in + viewController.hooks.view.tap { (view) in + view.hooks.onUpdate.tap { val in + count += 1 + + if count == 2 { + let newText1 = val + .objectForKeyedSubscript("values") + .objectAtIndexedSubscript(1) + .objectForKeyedSubscript("asset") + .objectForKeyedSubscript("value") + guard let textString1 = newText1?.toString() else { return XCTFail("newText was not a string") } + + expectedMultiNode1Text = textString1 + textExpectation.fulfill() + } + + if count == 3 { + let newText2 = val + .objectForKeyedSubscript("values") + .objectAtIndexedSubscript(2) + .objectForKeyedSubscript("asset") + .objectForKeyedSubscript("value") + guard let textString2 = newText2?.toString() else { return XCTFail("newText was not a string") } + + expectedMultiNode2Text = textString2 + + textExpectation2.fulfill() + } + + if count == 4 { + let newText3 = val + .objectForKeyedSubscript("values") + .objectAtIndexedSubscript(3) + .objectForKeyedSubscript("asset") + .objectForKeyedSubscript("value") + guard let textString3 = newText3?.toString() else { return XCTFail("newText was not a string") } + + expectedMultiNode3Text = textString3 + textExpectation3.fulfill() + } + } + } + }) + + player.start(flow: .asyncNodeJson, completion: { _ in}) + + wait(for: [handlerExpectation, textExpectation], timeout: 5) + + XCTAssert(count == 2) + XCTAssertEqual(expectedMultiNode1Text, "1st value in the multinode") + + wait(for: [textExpectation2], timeout: 6) + + XCTAssert(count == 3) + XCTAssertEqual(expectedMultiNode2Text, "2nd value in the multinode") + + wait(for: [textExpectation3], timeout: 7) + + XCTAssert(count == 4) + XCTAssertEqual(expectedMultiNode3Text, "3rd value in the multinode") + } + + func testAsyncNodeReplacementWithChainedMultiNodesSinglular() { + let handlerExpectation = XCTestExpectation(description: "first data did not change") + + let context = JSContext() + + var count = 0 + + let resolve: AsyncHookHandler = { _ in + handlerExpectation.fulfill() + + if count == 1 { + return .multiNode([ + ReplacementNode.encodable(AssetPlaceholderNode(asset: PlaceholderNode(id: "text", type: "text", value: "new node from the hook 1"))), + ReplacementNode.encodable(AsyncNode(id: "id")) + ]) + } else if count == 2 { + return .singleNode(.concrete(context?.evaluateScript(""" + ( + {"asset": {"id": "text", "type": "text", "value":"new node from the hook 2"}} + ) + """) ?? JSValue())) + } + + return .singleNode(ReplacementNode.concrete(context?.evaluateScript("") ?? JSValue())) + } + + let plugin = AsyncNodePlugin(resolve) + + plugin.context = context + + let player = HeadlessPlayerImpl(plugins: [ReferenceAssetsPlugin(), plugin], context: context ?? JSContext()) + + let textExpectation = XCTestExpectation(description: "newText found") + let textExpectation2 = XCTestExpectation(description: "newText found") + + var expectedMultiNode1Text: String = "" + var expectedMultiNode2Text: String = "" + + player.hooks?.viewController.tap({ (viewController) in + viewController.hooks.view.tap { (view) in + view.hooks.onUpdate.tap { val in + count += 1 + + if count == 2 { + let newText1 = val + .objectForKeyedSubscript("values") + .objectAtIndexedSubscript(1) + .objectForKeyedSubscript("asset") + .objectForKeyedSubscript("value") + guard let textString1 = newText1?.toString() else { return XCTFail("newText was not a string") } + + expectedMultiNode1Text = textString1 + textExpectation.fulfill() + } + + if count == 3 { + let newText2 = val + .objectForKeyedSubscript("values") + .objectAtIndexedSubscript(2) + .objectForKeyedSubscript("asset") + .objectForKeyedSubscript("value") + guard let textString2 = newText2?.toString() else { return XCTFail("newText was not a string") } + + expectedMultiNode2Text = textString2 + textExpectation2.fulfill() + + } + } + } + }) + + player.start(flow: .asyncNodeJson, completion: { _ in}) + + wait(for: [handlerExpectation, textExpectation], timeout: 5) + + XCTAssert(count == 2) + XCTAssertEqual(expectedMultiNode1Text, "new node from the hook 1") + + wait(for: [textExpectation2], timeout: 5) + + XCTAssert(count == 3) + XCTAssertEqual(expectedMultiNode2Text, "new node from the hook 2") + } +} + +extension String { + static let asyncNodeJson = """ + { + "id": "generated-flow", + "views": [ + { + "id": "collection", + "type": "collection", + "values": [ + { + "asset": { + "id": "action", + "type": "action", + "exp": "{{count}} = {{count}} + 1", + "label": { + "asset": { + "id": "test", + "type": "text", + "value": "test" + } + } + } + }, + { + "id": "async", + "async": true + } + ] + } + ], + "data": { + "count": 0 + }, + "navigation": { + "BEGIN": "FLOW_1", + "FLOW_1": { + "startState": "VIEW_1", + "VIEW_1": { + "state_type": "VIEW", + "ref": "collection", + "transitions": { + "*": "END_Done" + } + }, + "END_Done": { + "state_type": "END", + "outcome": "done" + } + } + } + } + """ +} + +struct PlaceholderNode: Codable, Equatable, AssetData { + public var id: String + public var type: String + var value: String? + + public init(id: String, type: String, value: String? = nil) { + self.id = id + self.type = type + self.value = value + } +} diff --git a/ios/swiftui/Sources/ManagedPlayer/ManagedPlayer.swift b/ios/swiftui/Sources/ManagedPlayer/ManagedPlayer.swift index 9e6e61de3..ba8f05ed7 100644 --- a/ios/swiftui/Sources/ManagedPlayer/ManagedPlayer.swift +++ b/ios/swiftui/Sources/ManagedPlayer/ManagedPlayer.swift @@ -248,4 +248,3 @@ internal final class Inspection where V: View { } } } - diff --git a/ios/swiftui/Sources/SwiftUIPlayer.swift b/ios/swiftui/Sources/SwiftUIPlayer.swift index d2697bc69..fb7e66d9f 100644 --- a/ios/swiftui/Sources/SwiftUIPlayer.swift +++ b/ios/swiftui/Sources/SwiftUIPlayer.swift @@ -63,6 +63,7 @@ public struct SwiftUIPlayer: View, HeadlessPlayer { } let context: JSContext = contextBuilder() + let allPlugins = plugins + [partialMatchPlugin] guard let playerValue = player.setupPlayer(context: context, plugins: allPlugins) else { return logger.e("Failed to load player") diff --git a/ios/test-utils-core/Sources/ui-test/AssetCollection.swift b/ios/test-utils-core/Sources/ui-test/AssetCollection.swift index f48bade24..98d426f7e 100644 --- a/ios/test-utils-core/Sources/ui-test/AssetCollection.swift +++ b/ios/test-utils-core/Sources/ui-test/AssetCollection.swift @@ -27,6 +27,7 @@ public struct AssetCollection: View { - parameters: - plugins: Plugins to add to Player instance that is created - sections: The `[FlowSection]` to display + - padding: Padding around the AssetFlowView - completion: A handler for when a flow reaches an end state */ public init( diff --git a/ios/test-utils/Sources/ui-test/AssetUITestCase.swift b/ios/test-utils/Sources/ui-test/AssetUITestCase.swift index c15527ff1..9759b266b 100644 --- a/ios/test-utils/Sources/ui-test/AssetUITestCase.swift +++ b/ios/test-utils/Sources/ui-test/AssetUITestCase.swift @@ -51,6 +51,7 @@ open class AssetUITestCase: XCTestCase { */ open func openFlow(_ mockName: String) { navigateToAssetCollection() + app.otherElements.buttons[mockName].firstMatch.tap() } diff --git a/jvm/BUILD b/jvm/BUILD index 1b6024ac7..1c13c81eb 100644 --- a/jvm/BUILD +++ b/jvm/BUILD @@ -1,8 +1,12 @@ -load("@io_bazel_rules_kotlin//kotlin:core.bzl", "kt_compiler_plugin", "kt_kotlinc_options") +load("@io_bazel_rules_kotlin//kotlin:core.bzl", "define_kt_toolchain", "kt_compiler_plugin", "kt_kotlinc_options") load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") load("@io_bazel_rules_kotlin//kotlin:lint.bzl", "ktlint_config") load("//jvm/dependencies:versions.bzl", "versions") +exports_files([ + "pom.tpl", +]) + kt_compiler_plugin( name = "serialization_plugin", compile_phase = True, @@ -26,7 +30,7 @@ kt_jvm_library( optins = [ "kotlin.RequiresOptIn", - "com.intuit.player.jvm.core.utils.InternalPlayerApi", + "com.intuit.playerui.core.utils.InternalPlayerApi", "kotlinx.serialization.ExperimentalSerializationApi", "kotlinx.coroutines.ExperimentalCoroutinesApi", "kotlin.contracts.ExperimentalContracts", @@ -49,6 +53,10 @@ kt_kotlinc_options( ktlint_config( name = "lint_config", - editorconfig = "//:.editorconfig", visibility = ["//visibility:public"], ) + +define_kt_toolchain( + name = "kotlin_toolchain", + kotlinc_options = ":test_options", +) diff --git a/jvm/build.bzl b/jvm/build.bzl index 08f834df3..f2a6aef39 100644 --- a/jvm/build.bzl +++ b/jvm/build.bzl @@ -1,13 +1,14 @@ load("@rules_player//kotlin:kt_jvm.bzl", _kt_jvm = "kt_jvm") load("@rules_player//kotlin:distribution.bzl", _distribution = "distribution") +load("@build_constants//:constants.bzl", "VERSION") load("//jvm/dependencies:common.bzl", common_main_deps = "main_deps", common_test_deps = "test_deps") -load("//:index.bzl", "GIT_REPO", "DOCS_URL") +DEFAULT_GROUP = "com.intuit.playerui" DEFAULT_PROJECT_NAME = "Player" DEFAUTL_PROJECT_DESCRIPTION = "A cross-platform semantic rendering engine" DEFAULT_DEVELOPERS = { - "sugarmanz": ["name=Jeremiah Zucker", "email=zucker.jeremiah@gmail.com"], - "brocollie08": ["name=Tony Lin"] + "sugarmanz": ["name=Jeremiah Zucker", "email=zucker.jeremiah@gmail.com"], + "brocollie08": ["name=Tony Lin", "email=sentony93@gmail.com"], } DEFAULT_RELEASE_REPO = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" DEFAULT_SNAPSHOT_REPO = "https://oss.sonatype.org/content/repositories/snapshots/" @@ -22,14 +23,9 @@ def kt_player_module( include_common_deps = True, # Distribution config - group = "com.intuit.player", - - # (optional) - project_name = DEFAULT_PROJECT_NAME, - project_description = DEFAUTL_PROJECT_DESCRIPTION, - project_url = DOCS_URL, - scm_url = GIT_REPO, - developers = DEFAULT_DEVELOPERS, + group = DEFAULT_GROUP, + deploy_env = None, + excluded_workspaces = None, # Package level config module_name = None, @@ -53,15 +49,10 @@ def kt_player_module( name = name, lint_config = "//jvm:lint_config", group = group, - release_repo = DEFAULT_RELEASE_REPO, - snapshot_repo = DEFAULT_SNAPSHOT_REPO, - version_file = "//:VERSION", - project_name = project_name, - project_description = project_description, - project_url = project_url, - scm_url = scm_url, - developers = developers, - workspace_refs = "@plugin_workspace_refs//:refs.json", + version = VERSION, + deploy_env = deploy_env, + excluded_workspaces = excluded_workspaces, + pom_template = "//jvm:pom.tpl", module_name = module_name, main_opts = "//jvm:main_options", main_srcs = main_srcs, @@ -86,22 +77,14 @@ def kt_player_module( def distribution( *, name, - - # (optional) - project_name = DEFAULT_PROJECT_NAME, - project_description = DEFAUTL_PROJECT_DESCRIPTION, - project_url = DOCS_URL, - scm_url = GIT_REPO, - developers = DEFAULT_DEVELOPERS,): + maven_coordinates, + lib_name = None, + pom_template = "//jvm:pom.tpl", + **kwargs): _distribution( name = name, - release_repo = DEFAULT_RELEASE_REPO, - snapshot_repo = DEFAULT_SNAPSHOT_REPO, - version_file = "//:VERSION", - project_name = project_name, - project_description = project_description, - project_url = project_url, - scm_url = scm_url, - developers = developers, - workspace_refs = "@plugin_workspace_refs//:refs.json", + maven_coordinates = maven_coordinates, + lib_name = lib_name, + pom_template = pom_template, + **kwargs ) diff --git a/jvm/core/BUILD b/jvm/core/BUILD index 1f7ece996..258e040b3 100644 --- a/jvm/core/BUILD +++ b/jvm/core/BUILD @@ -4,8 +4,11 @@ load(":deps.bzl", "main_deps", "main_exports", "test_deps") # TODO: Sources jar for src/main/kotlin includes main/kotlin kt_player_module( name = "core", + excluded_workspaces = { + "com_github_jetbrains_kotlin": None, + }, main_deps = main_deps, main_exports = main_exports, test_deps = test_deps, - test_package = "com.intuit.player.jvm.core", + test_package = "com.intuit.playerui.core", ) diff --git a/jvm/core/api/core.api b/jvm/core/api/core.api index 8d4a2ce77..fe294c0ff 100644 --- a/jvm/core/api/core.api +++ b/jvm/core/api/core.api @@ -1,1584 +1,1596 @@ public class com/intuit/player/jvm/core/asset/Asset : com/intuit/player/jvm/core/bridge/Node, com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/asset/Asset$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public fun clear ()V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lcom/intuit/player/jvm/core/asset/MetaData; - public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun compute (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; - public fun computeIfAbsent (Ljava/lang/String;Ljava/util/function/Function;)Ljava/lang/Object; - public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun computeIfPresent (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public final fun containsKey (Ljava/lang/Object;)Z - public fun containsKey (Ljava/lang/String;)Z - public fun containsValue (Ljava/lang/Object;)Z - public fun deserialize (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public final fun entrySet ()Ljava/util/Set; - public fun equals (Ljava/lang/Object;)Z - public final fun get (Ljava/lang/Object;)Ljava/lang/Object; - public fun get (Ljava/lang/String;)Ljava/lang/Object; - public final fun getAsset (Ljava/lang/String;)Lcom/intuit/player/jvm/core/asset/AssetWrapper; - public fun getBoolean (Ljava/lang/String;)Ljava/lang/Boolean; - public fun getDouble (Ljava/lang/String;)Ljava/lang/Double; - public fun getEntries ()Ljava/util/Set; - public fun getFormat ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; - public fun getFunction (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Invokable; - public final fun getId ()Ljava/lang/String; - public fun getInt (Ljava/lang/String;)Ljava/lang/Integer; - public fun getKeys ()Ljava/util/Set; - public fun getList (Ljava/lang/String;)Ljava/util/List; - public fun getLong (Ljava/lang/String;)Ljava/lang/Long; - public final fun getMetaData ()Lcom/intuit/player/jvm/core/asset/MetaData; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getObject (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; - public fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; - public fun getSerializable (Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public fun getSize ()I - public fun getString (Ljava/lang/String;)Ljava/lang/String; - public final fun getType ()Ljava/lang/String; - public fun getValues ()Ljava/util/Collection; - public fun hashCode ()I - public fun isEmpty ()Z - public fun isReleased ()Z - public fun isUndefined ()Z - public final fun keySet ()Ljava/util/Set; - public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun merge (Ljava/lang/String;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun nativeReferenceEquals (Ljava/lang/Object;)Z - public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public fun put (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; - public fun putAll (Ljava/util/Map;)V - public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public fun putIfAbsent (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; - public fun remove (Ljava/lang/Object;)Ljava/lang/Object; - public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z - public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z - public fun replace (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; - public fun replace (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)Z - public fun replaceAll (Ljava/util/function/BiFunction;)V - public final fun size ()I - public fun toString ()Ljava/lang/String; - public final fun values ()Ljava/util/Collection; +public static final field Companion Lcom/intuit/player/jvm/core/asset/Asset$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public fun clear ()V +public final fun component1 ()Ljava/lang/String; +public final fun component2 ()Ljava/lang/String; +public final fun component3 ()Lcom/intuit/player/jvm/core/asset/MetaData; +public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun compute (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; +public fun computeIfAbsent (Ljava/lang/String;Ljava/util/function/Function;)Ljava/lang/Object; +public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun computeIfPresent (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public final fun containsKey (Ljava/lang/Object;)Z +public fun containsKey (Ljava/lang/String;)Z +public fun containsValue (Ljava/lang/Object;)Z +public fun deserialize (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public final fun entrySet ()Ljava/util/Set; +public fun equals (Ljava/lang/Object;)Z +public final fun get (Ljava/lang/Object;)Ljava/lang/Object; +public fun get (Ljava/lang/String;)Ljava/lang/Object; +public final fun getAsset (Ljava/lang/String;)Lcom/intuit/player/jvm/core/asset/AssetWrapper; +public fun getBoolean (Ljava/lang/String;)Ljava/lang/Boolean; +public fun getDouble (Ljava/lang/String;)Ljava/lang/Double; +public fun getEntries ()Ljava/util/Set; +public fun getFormat ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; +public fun getFunction (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Invokable; +public final fun getId ()Ljava/lang/String; +public fun getInt (Ljava/lang/String;)Ljava/lang/Integer; +public fun getKeys ()Ljava/util/Set; +public fun getList (Ljava/lang/String;)Ljava/util/List; +public fun getLong (Ljava/lang/String;)Ljava/lang/Long; +public final fun getMetaData ()Lcom/intuit/player/jvm/core/asset/MetaData; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getObject (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; +public fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public fun getSerializable (Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public fun getSize ()I +public fun getString (Ljava/lang/String;)Ljava/lang/String; +public final fun getType ()Ljava/lang/String; +public fun getValues ()Ljava/util/Collection; +public fun hashCode ()I +public fun isEmpty ()Z +public fun isReleased ()Z +public fun isUndefined ()Z +public final fun keySet ()Ljava/util/Set; +public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun merge (Ljava/lang/String;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun nativeReferenceEquals (Ljava/lang/Object;)Z +public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +public fun put (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; +public fun putAll (Ljava/util/Map;)V +public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +public fun putIfAbsent (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; +public fun remove (Ljava/lang/Object;)Ljava/lang/Object; +public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z +public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z +public fun replace (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; +public fun replace (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)Z +public fun replaceAll (Ljava/util/function/BiFunction;)V +public final fun size ()I +public fun toString ()Ljava/lang/String; +public final fun values ()Ljava/util/Collection; } public final class com/intuit/player/jvm/core/asset/Asset$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/asset/AssetKt { - public static final fun getValue (Lcom/intuit/player/jvm/core/asset/Asset;)Ljava/lang/String; +public static final fun getValue (Lcom/intuit/player/jvm/core/asset/Asset;)Ljava/lang/String; } public final class com/intuit/player/jvm/core/asset/AssetWrapper : com/intuit/player/jvm/core/bridge/NodeWrapper { - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public final fun getAsset ()Lcom/intuit/player/jvm/core/asset/Asset; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public final fun getAsset ()Lcom/intuit/player/jvm/core/asset/Asset; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public class com/intuit/player/jvm/core/asset/MetaData : com/intuit/player/jvm/core/bridge/NodeWrapper { - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public final fun getBeacon ()Lkotlinx/serialization/json/JsonElement; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getRef ()Ljava/lang/String; - public final fun getRole ()Ljava/lang/String; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public final fun getBeacon ()Lkotlinx/serialization/json/JsonElement; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getRef ()Ljava/lang/String; +public final fun getRole ()Ljava/lang/String; } public abstract interface class com/intuit/player/jvm/core/bridge/Completable { - public abstract fun asFlow (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun onComplete (Lkotlin/jvm/functions/Function1;)V +public abstract fun asFlow (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +public abstract fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +public abstract fun onComplete (Lkotlin/jvm/functions/Function1;)V } public final class com/intuit/player/jvm/core/bridge/Completed : com/intuit/player/jvm/core/bridge/Completable { - public fun (Ljava/lang/Object;)V - public fun asFlow (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getValue ()Ljava/lang/Object; - public fun onComplete (Lkotlin/jvm/functions/Function1;)V +public fun (Ljava/lang/Object;)V +public fun asFlow (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +public final fun getValue ()Ljava/lang/Object; +public fun onComplete (Lkotlin/jvm/functions/Function1;)V } public abstract interface class com/intuit/player/jvm/core/bridge/Invokable : kotlin/Function { - public abstract fun invoke ([Ljava/lang/Object;)Ljava/lang/Object; +public abstract fun invoke ([Ljava/lang/Object;)Ljava/lang/Object; } public final class com/intuit/player/jvm/core/bridge/InvokableKt { - public static final fun invokeVararg (Lkotlin/Function;[Ljava/lang/Object;)Ljava/lang/Object; - public static final fun toFunction (Lcom/intuit/player/jvm/core/bridge/Invokable;Ljava/lang/String;)Lkotlin/Function; +public static final fun invokeVararg (Lkotlin/Function;[Ljava/lang/Object;)Ljava/lang/Object; +public static final fun toFunction (Lcom/intuit/player/jvm/core/bridge/Invokable;Ljava/lang/String;)Lkotlin/Function; } public final class com/intuit/player/jvm/core/bridge/JSErrorException : com/intuit/player/jvm/core/player/PlayerException, com/intuit/player/jvm/core/bridge/NodeWrapper { - public fun (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/Throwable;)V - public synthetic fun (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getMessage ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/Throwable;)V +public synthetic fun (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public fun getMessage ()Ljava/lang/String; +public final fun getName ()Ljava/lang/String; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public abstract interface class com/intuit/player/jvm/core/bridge/Node : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { - public abstract fun deserialize (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public abstract fun getBoolean (Ljava/lang/String;)Ljava/lang/Boolean; - public abstract fun getDouble (Ljava/lang/String;)Ljava/lang/Double; - public abstract fun getFormat ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; - public abstract fun getFunction (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Invokable; - public abstract fun getInt (Ljava/lang/String;)Ljava/lang/Integer; - public abstract fun getList (Ljava/lang/String;)Ljava/util/List; - public abstract fun getLong (Ljava/lang/String;)Ljava/lang/Long; - public abstract fun getObject (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; - public abstract fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; - public abstract fun getSerializable (Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public abstract fun getString (Ljava/lang/String;)Ljava/lang/String; - public abstract fun isReleased ()Z - public abstract fun isUndefined ()Z - public abstract fun nativeReferenceEquals (Ljava/lang/Object;)Z +public abstract fun deserialize (Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public abstract fun getBoolean (Ljava/lang/String;)Ljava/lang/Boolean; +public abstract fun getDouble (Ljava/lang/String;)Ljava/lang/Double; +public abstract fun getFormat ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; +public abstract fun getFunction (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Invokable; +public abstract fun getInt (Ljava/lang/String;)Ljava/lang/Integer; +public abstract fun getList (Ljava/lang/String;)Ljava/util/List; +public abstract fun getLong (Ljava/lang/String;)Ljava/lang/Long; +public abstract fun getObject (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; +public abstract fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public abstract fun getSerializable (Ljava/lang/String;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public abstract fun getString (Ljava/lang/String;)Ljava/lang/String; +public abstract fun isReleased ()Z +public abstract fun isUndefined ()Z +public abstract fun nativeReferenceEquals (Ljava/lang/Object;)Z } public final class com/intuit/player/jvm/core/bridge/Node$DefaultImpls { - public static fun getBoolean (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/Boolean; - public static fun getDouble (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/Double; - public static fun getFormat (Lcom/intuit/player/jvm/core/bridge/Node;)Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; - public static fun getFunction (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Invokable; - public static fun getInt (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/Integer; - public static fun getList (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/util/List; - public static fun getLong (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/Long; - public static fun getObject (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; - public static fun getString (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/String; +public static fun getBoolean (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/Boolean; +public static fun getDouble (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/Double; +public static fun getFormat (Lcom/intuit/player/jvm/core/bridge/Node;)Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; +public static fun getFunction (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Invokable; +public static fun getInt (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/Integer; +public static fun getList (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/util/List; +public static fun getLong (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/Long; +public static fun getObject (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; +public static fun getString (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Ljava/lang/String; } public final class com/intuit/player/jvm/core/bridge/NodeKt { - public static final fun getFormat (Lcom/intuit/player/jvm/core/bridge/NodeWrapper;)Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; - public static final fun getJson (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement; - public static final fun getRuntime (Lcom/intuit/player/jvm/core/bridge/NodeWrapper;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; - public static final fun toJson (Lcom/intuit/player/jvm/core/bridge/Node;)Lkotlinx/serialization/json/JsonElement; +public static final fun getFormat (Lcom/intuit/player/jvm/core/bridge/NodeWrapper;)Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; +public static final fun getJson (Lcom/intuit/player/jvm/core/bridge/Node;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement; +public static final fun getRuntime (Lcom/intuit/player/jvm/core/bridge/NodeWrapper;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public static final fun toJson (Lcom/intuit/player/jvm/core/bridge/Node;)Lkotlinx/serialization/json/JsonElement; } public abstract interface class com/intuit/player/jvm/core/bridge/NodeWrapper { - public abstract fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public abstract fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/bridge/PlayerRuntimeException : com/intuit/player/jvm/core/player/PlayerException { - public fun (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;Ljava/lang/Throwable;)V - public synthetic fun (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public fun (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;Ljava/lang/Throwable;)V +public synthetic fun (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; } public final class com/intuit/player/jvm/core/bridge/PlayerRuntimeExceptionKt { - public static final fun PlayerRuntimeException (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;Ljava/lang/Throwable;)Lcom/intuit/player/jvm/core/bridge/PlayerRuntimeException; - public static synthetic fun PlayerRuntimeException$default (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/PlayerRuntimeException; +public static final fun PlayerRuntimeException (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;Ljava/lang/Throwable;)Lcom/intuit/player/jvm/core/bridge/PlayerRuntimeException; +public static synthetic fun PlayerRuntimeException$default (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/PlayerRuntimeException; } public abstract interface class com/intuit/player/jvm/core/bridge/Promise$Api { - public abstract fun reject ([Ljava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/Promise; - public abstract fun resolve ([Ljava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/Promise; +public abstract fun reject ([Ljava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/Promise; +public abstract fun resolve ([Ljava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/Promise; } public final class com/intuit/player/jvm/core/bridge/Promise$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/bridge/Promise$Serializer : kotlinx/serialization/KSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/bridge/Promise$Serializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/Promise; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/bridge/Promise;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public static final field INSTANCE Lcom/intuit/player/jvm/core/bridge/Promise$Serializer; +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/Promise; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/bridge/Promise;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V } public final class com/intuit/player/jvm/core/bridge/PromiseException : com/intuit/player/jvm/core/player/PlayerException { - public fun (Ljava/lang/String;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public fun (Ljava/lang/String;Ljava/lang/Throwable;)V +public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public final class com/intuit/player/jvm/core/bridge/PromiseKt { - public static final fun Promise (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Lkotlin/jvm/functions/Function2;)Lcom/intuit/player/jvm/core/bridge/Promise; - public static final fun getPromise (Lcom/intuit/player/jvm/core/bridge/Node;)Lcom/intuit/player/jvm/core/bridge/Promise$Api; - public static final fun getPromise (Lcom/intuit/player/jvm/core/bridge/NodeWrapper;)Lcom/intuit/player/jvm/core/bridge/Promise$Api; - public static final fun getPromise (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)Lcom/intuit/player/jvm/core/bridge/Promise$Api; +public static final fun Promise (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Lkotlin/jvm/functions/Function2;)Lcom/intuit/player/jvm/core/bridge/Promise; +public static final fun getPromise (Lcom/intuit/player/jvm/core/bridge/Node;)Lcom/intuit/player/jvm/core/bridge/Promise$Api; +public static final fun getPromise (Lcom/intuit/player/jvm/core/bridge/NodeWrapper;)Lcom/intuit/player/jvm/core/bridge/Promise$Api; +public static final fun getPromise (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)Lcom/intuit/player/jvm/core/bridge/Promise$Api; } public final class com/intuit/player/jvm/core/bridge/global/JSIterator : com/intuit/player/jvm/core/bridge/NodeWrapper, java/util/Iterator, kotlin/jvm/internal/markers/KMappedMarker { - public static final field Companion Lcom/intuit/player/jvm/core/bridge/global/JSIterator$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;)V - public final fun getItemSerializer ()Lkotlinx/serialization/KSerializer; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun hasNext ()Z - public fun next ()Ljava/lang/Object; - public fun remove ()V +public static final field Companion Lcom/intuit/player/jvm/core/bridge/global/JSIterator$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;)V +public final fun getItemSerializer ()Lkotlinx/serialization/KSerializer; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun hasNext ()Z +public fun next ()Ljava/lang/Object; +public fun remove ()V } public final class com/intuit/player/jvm/core/bridge/global/JSIterator$Companion { - public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; +public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/bridge/global/JSIterator$Serializer : com/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer { - public fun (Lkotlinx/serialization/KSerializer;)V - public final fun getItemSerializer ()Lkotlinx/serialization/KSerializer; +public fun (Lkotlinx/serialization/KSerializer;)V +public final fun getItemSerializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/bridge/global/JSMap : com/intuit/player/jvm/core/bridge/NodeWrapper, java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { - public static final field Companion Lcom/intuit/player/jvm/core/bridge/global/JSMap$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V - public fun clear ()V - public fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; - public fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun containsKey (Ljava/lang/Object;)Z - public fun containsValue (Ljava/lang/Object;)Z - public final fun entrySet ()Ljava/util/Set; - public fun get (Ljava/lang/Object;)Ljava/lang/Object; - public fun getEntries ()Ljava/util/Set; - public fun getKeys ()Ljava/util/Set; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getSize ()I - public fun getValues ()Ljava/util/List; - public fun isEmpty ()Z - public final fun keySet ()Ljava/util/Set; - public fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public fun putAll (Ljava/util/Map;)V - public fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public fun remove (Ljava/lang/Object;)Ljava/lang/Object; - public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z - public fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z - public fun replaceAll (Ljava/util/function/BiFunction;)V - public final fun size ()I - public synthetic fun values ()Ljava/util/Collection; - public final fun values ()Ljava/util/List; +public static final field Companion Lcom/intuit/player/jvm/core/bridge/global/JSMap$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V +public fun clear ()V +public fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; +public fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun containsKey (Ljava/lang/Object;)Z +public fun containsValue (Ljava/lang/Object;)Z +public final fun entrySet ()Ljava/util/Set; +public fun get (Ljava/lang/Object;)Ljava/lang/Object; +public fun getEntries ()Ljava/util/Set; +public fun getKeys ()Ljava/util/Set; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getSize ()I +public fun getValues ()Ljava/util/List; +public fun isEmpty ()Z +public final fun keySet ()Ljava/util/Set; +public fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +public fun putAll (Ljava/util/Map;)V +public fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +public fun remove (Ljava/lang/Object;)Ljava/lang/Object; +public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z +public fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +public fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z +public fun replaceAll (Ljava/util/function/BiFunction;)V +public final fun size ()I +public synthetic fun values ()Ljava/util/Collection; +public final fun values ()Ljava/util/List; } public final class com/intuit/player/jvm/core/bridge/global/JSMap$Companion { - public final fun serializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; +public final fun serializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/bridge/hooks/NodeHookKt { - public static final fun getCallingStackTraceElement ()Ljava/lang/StackTraceElement; +public static final fun getCallingStackTraceElement ()Ljava/lang/StackTraceElement; } public final class com/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1 : com/intuit/player/jvm/core/bridge/hooks/SyncHook1, com/intuit/player/jvm/core/bridge/hooks/NodeHook { - public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;)V - public synthetic fun call (Ljava/util/HashMap;[Ljava/lang/Object;)Ljava/lang/Object; - public fun call (Ljava/util/HashMap;[Ljava/lang/Object;)V - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun init ([Lkotlinx/serialization/KSerializer;)V +public static final field Companion Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;)V +public synthetic fun call (Ljava/util/HashMap;[Ljava/lang/Object;)Ljava/lang/Object; +public fun call (Ljava/util/HashMap;[Ljava/lang/Object;)V +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun init ([Lkotlinx/serialization/KSerializer;)V +} + +public final class com/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1$Companion { +public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/bridge/hooks/NodeSyncHook2 : com/intuit/hooks/SyncHook, com/intuit/player/jvm/core/bridge/hooks/NodeHook { - public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V - public synthetic fun call (Ljava/util/HashMap;[Ljava/lang/Object;)Ljava/lang/Object; - public fun call (Ljava/util/HashMap;[Ljava/lang/Object;)V - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun init ([Lkotlinx/serialization/KSerializer;)V - public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Ljava/lang/String; - public final fun tap (Lkotlin/jvm/functions/Function2;)Ljava/lang/String; - public final fun tap (Lkotlin/jvm/functions/Function3;)Ljava/lang/String; +public static final field Companion Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook2$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V +public synthetic fun call (Ljava/util/HashMap;[Ljava/lang/Object;)Ljava/lang/Object; +public fun call (Ljava/util/HashMap;[Ljava/lang/Object;)V +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun init ([Lkotlinx/serialization/KSerializer;)V +public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Ljava/lang/String; +public final fun tap (Lkotlin/jvm/functions/Function2;)Ljava/lang/String; +public final fun tap (Lkotlin/jvm/functions/Function3;)Ljava/lang/String; +} + +public final class com/intuit/player/jvm/core/bridge/hooks/NodeSyncHook2$Companion { +public final fun serializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook1 : com/intuit/hooks/SyncWaterfallHook, com/intuit/player/jvm/core/bridge/hooks/NodeHook { - public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;)V - public fun call (Ljava/util/HashMap;[Ljava/lang/Object;)Ljava/lang/Object; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun init ([Lkotlinx/serialization/KSerializer;)V - public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; - public final fun tap (Lkotlin/jvm/functions/Function1;)Ljava/lang/String; - public final fun tap (Lkotlin/jvm/functions/Function2;)Ljava/lang/String; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;)V +public fun call (Ljava/util/HashMap;[Ljava/lang/Object;)Ljava/lang/Object; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun init ([Lkotlinx/serialization/KSerializer;)V +public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; +public final fun tap (Lkotlin/jvm/functions/Function1;)Ljava/lang/String; +public final fun tap (Lkotlin/jvm/functions/Function2;)Ljava/lang/String; } public final class com/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook2 : com/intuit/hooks/SyncWaterfallHook, com/intuit/player/jvm/core/bridge/hooks/NodeHook { - public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V - public fun call (Ljava/util/HashMap;[Ljava/lang/Object;)Ljava/lang/Object; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun init ([Lkotlinx/serialization/KSerializer;)V - public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Ljava/lang/String; - public final fun tap (Lkotlin/jvm/functions/Function2;)Ljava/lang/String; - public final fun tap (Lkotlin/jvm/functions/Function3;)Ljava/lang/String; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V +public fun call (Ljava/util/HashMap;[Ljava/lang/Object;)Ljava/lang/Object; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun init ([Lkotlinx/serialization/KSerializer;)V +public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Ljava/lang/String; +public final fun tap (Lkotlin/jvm/functions/Function2;)Ljava/lang/String; +public final fun tap (Lkotlin/jvm/functions/Function3;)Ljava/lang/String; } public abstract class com/intuit/player/jvm/core/bridge/hooks/SyncHook1 : com/intuit/hooks/SyncHook { - public fun ()V - public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; - public final fun tap (Lkotlin/jvm/functions/Function1;)Ljava/lang/String; - public final fun tap (Lkotlin/jvm/functions/Function2;)Ljava/lang/String; +public fun ()V +public final fun tap (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; +public final fun tap (Lkotlin/jvm/functions/Function1;)Ljava/lang/String; +public final fun tap (Lkotlin/jvm/functions/Function2;)Ljava/lang/String; } public class com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeConfig { - public fun ()V +public fun ()V } public abstract interface class com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeContainer { - public abstract fun getFactory ()Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory; +public abstract fun getFactory ()Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory; } public abstract interface class com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory { - public abstract fun create (Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public abstract fun create (Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; } public final class com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory$DefaultImpls { - public static synthetic fun create$default (Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public static synthetic fun create$default (Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; } public final class com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactoryKt { - public static final fun config (Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory; - public static final fun getRuntimeContainers ()Ljava/util/List; - public static final fun getRuntimeFactory ()Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory; +public static final fun config (Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory; +public static final fun getRuntimeContainers ()Ljava/util/List; +public static final fun getRuntimeFactory ()Lcom/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory; } public abstract interface class com/intuit/player/jvm/core/bridge/runtime/Runtime : com/intuit/player/jvm/core/bridge/Node { - public abstract fun add (Ljava/lang/String;Ljava/lang/Object;)V - public abstract fun execute (Ljava/lang/String;)Ljava/lang/Object; - public abstract fun getFormat ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; - public abstract fun getScope ()Lkotlinx/coroutines/CoroutineScope; - public abstract fun release ()V - public abstract fun serialize (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; +public abstract fun add (Ljava/lang/String;Ljava/lang/Object;)V +public abstract fun execute (Ljava/lang/String;)Ljava/lang/Object; +public abstract fun getFormat ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; +public abstract fun getScope ()Lkotlinx/coroutines/CoroutineScope; +public abstract fun release ()V +public abstract fun serialize (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; } public final class com/intuit/player/jvm/core/bridge/runtime/Runtime$DefaultImpls { - public static fun getBoolean (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/Boolean; - public static fun getDouble (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/Double; - public static fun getFunction (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Invokable; - public static fun getInt (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/Integer; - public static fun getList (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/util/List; - public static fun getLong (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/Long; - public static fun getObject (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; - public static fun getString (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/String; +public static fun getBoolean (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/Boolean; +public static fun getDouble (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/Double; +public static fun getFunction (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Invokable; +public static fun getInt (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/Integer; +public static fun getList (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/util/List; +public static fun getLong (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/Long; +public static fun getObject (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; +public static fun getString (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Ljava/lang/String; } public abstract interface class com/intuit/player/jvm/core/bridge/serialization/encoding/FunctionDecoder : kotlinx/serialization/encoding/Decoder { - public abstract fun decodeFunction ()Lcom/intuit/player/jvm/core/bridge/Invokable; +public abstract fun decodeFunction ()Lcom/intuit/player/jvm/core/bridge/Invokable; } public final class com/intuit/player/jvm/core/bridge/serialization/encoding/FunctionDecoder$DefaultImpls { - public static fun decodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public static fun decodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public static fun decodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public static fun decodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; } public abstract interface class com/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder : kotlinx/serialization/encoding/Encoder { - public abstract fun encodeFunction (Lcom/intuit/player/jvm/core/bridge/Invokable;)V - public abstract fun encodeFunction (Ljava/lang/Object;)V - public abstract fun encodeFunction (Lkotlin/Function;)V - public abstract fun encodeFunction (Lkotlin/reflect/KCallable;)V +public abstract fun encodeFunction (Lcom/intuit/player/jvm/core/bridge/Invokable;)V +public abstract fun encodeFunction (Ljava/lang/Object;)V +public abstract fun encodeFunction (Lkotlin/Function;)V +public abstract fun encodeFunction (Lkotlin/reflect/KCallable;)V } public final class com/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder$DefaultImpls { - public static fun beginCollection (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; - public static fun encodeFunction (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;Ljava/lang/Object;)V - public static fun encodeNotNullMark (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;)V - public static fun encodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V - public static fun encodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V +public static fun beginCollection (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; +public static fun encodeFunction (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;Ljava/lang/Object;)V +public static fun encodeNotNullMark (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;)V +public static fun encodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V +public static fun encodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V } public abstract interface class com/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoder : com/intuit/player/jvm/core/bridge/serialization/encoding/FunctionDecoder, kotlinx/serialization/encoding/Decoder { - public abstract fun decodeNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public abstract fun decodeValue ()Ljava/lang/Object; +public abstract fun decodeNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public abstract fun decodeValue ()Ljava/lang/Object; } public final class com/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoder$DefaultImpls { - public static fun decodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public static fun decodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public static fun decodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public static fun decodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; } public final class com/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecodersKt { - public static final fun requireNodeDecoder (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoder; - public static final fun requireNodeEncoder (Lkotlinx/serialization/encoding/Encoder;)Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder; +public static final fun requireNodeDecoder (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoder; +public static final fun requireNodeEncoder (Lkotlinx/serialization/encoding/Encoder;)Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder; } public abstract interface class com/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder : com/intuit/player/jvm/core/bridge/serialization/encoding/FunctionEncoder, kotlinx/serialization/encoding/Encoder { - public abstract fun encodeNode (Lcom/intuit/player/jvm/core/bridge/Node;)V - public abstract fun encodeValue (Ljava/lang/Object;)V +public abstract fun encodeNode (Lcom/intuit/player/jvm/core/bridge/Node;)V +public abstract fun encodeValue (Ljava/lang/Object;)V } public final class com/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder$DefaultImpls { - public static fun beginCollection (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; - public static fun encodeFunction (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;Ljava/lang/Object;)V - public static fun encodeNotNullMark (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;)V - public static fun encodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V - public static fun encodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V +public static fun beginCollection (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/CompositeEncoder; +public static fun encodeFunction (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;Ljava/lang/Object;)V +public static fun encodeNotNullMark (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;)V +public static fun encodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V +public static fun encodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/NodeEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V } public final class com/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueCompositeDecoder$DefaultImpls { - public static fun decodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueCompositeDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public static fun decodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueCompositeDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public static fun getCurrentValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueCompositeDecoder;)Ljava/lang/Object; +public static fun decodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueCompositeDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public static fun decodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueCompositeDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public static fun getCurrentValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueCompositeDecoder;)Ljava/lang/Object; } public final class com/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueDecoder$DefaultImpls { - public static fun decodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public static fun decodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; - public static fun getCurrentValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueDecoder;)Ljava/lang/Object; +public static fun decodeNullableSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public static fun decodeSerializableValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object; +public static fun getCurrentValue (Lcom/intuit/player/jvm/core/bridge/serialization/encoding/RuntimeValueDecoder;)Ljava/lang/Object; } public abstract class com/intuit/player/jvm/core/bridge/serialization/format/AbstractRuntimeFormat : com/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat, kotlinx/serialization/StringFormat { - public fun (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormatConfiguration;)V - public fun decodeFromString (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/String;)Ljava/lang/Object; - public fun encodeToString (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/lang/String; - public final fun getConfig ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormatConfiguration; - public fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; - public final fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; - public fun registerSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +public fun (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormatConfiguration;)V +public fun decodeFromString (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/String;)Ljava/lang/Object; +public fun encodeToString (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/lang/String; +public final fun getConfig ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormatConfiguration; +public fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public final fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; +public fun registerSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V } public abstract interface class com/intuit/player/jvm/core/bridge/serialization/format/Builder { - public abstract fun getFormat ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; +public abstract fun getFormat ()Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat; } public final class com/intuit/player/jvm/core/bridge/serialization/format/BuildersKt { - public static final fun runtimeArray (Lcom/intuit/player/jvm/core/bridge/serialization/format/Builder;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; - public static final fun runtimeArray (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; - public static final fun runtimeObject (Lcom/intuit/player/jvm/core/bridge/serialization/format/Builder;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; - public static final fun runtimeObject (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +public static final fun runtimeArray (Lcom/intuit/player/jvm/core/bridge/serialization/format/Builder;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +public static final fun runtimeArray (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +public static final fun runtimeObject (Lcom/intuit/player/jvm/core/bridge/serialization/format/Builder;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; +public static final fun runtimeObject (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } public abstract interface annotation class com/intuit/player/jvm/core/bridge/serialization/format/RuntimeBuilderDsl : java/lang/annotation/Annotation { } public final class com/intuit/player/jvm/core/bridge/serialization/format/RuntimeDecodingException : com/intuit/player/jvm/core/bridge/serialization/format/RuntimeSerializationException { - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public final class com/intuit/player/jvm/core/bridge/serialization/format/RuntimeEncodingException : com/intuit/player/jvm/core/bridge/serialization/format/RuntimeSerializationException { - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract interface class com/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat : kotlinx/serialization/SerialFormat { - public abstract fun decodeFromRuntimeValue (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; - public abstract fun encodeToRuntimeValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; - public abstract fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; - public abstract fun parseToRuntimeValue (Ljava/lang/String;)Ljava/lang/Object; - public abstract fun registerSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V +public abstract fun decodeFromRuntimeValue (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; +public abstract fun encodeToRuntimeValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/lang/Object; +public abstract fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public abstract fun parseToRuntimeValue (Ljava/lang/String;)Ljava/lang/Object; +public abstract fun registerSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V } public abstract interface class com/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormatConfiguration { - public abstract fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; - public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; +public abstract fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; } public final class com/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormatKt { - public static final fun registerContextualSerializer (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V - public static final fun registerSerializersModule (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat;Lkotlin/jvm/functions/Function1;)V +public static final fun registerContextualSerializer (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V +public static final fun registerSerializersModule (Lcom/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat;Lkotlin/jvm/functions/Function1;)V } public class com/intuit/player/jvm/core/bridge/serialization/format/RuntimeSerializationException : kotlinx/serialization/SerializationException { - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public final class com/intuit/player/jvm/core/bridge/serialization/json/JsonPrimitiveKt { - public static final fun getValue (Lkotlinx/serialization/json/JsonPrimitive;)Ljava/lang/Object; - public static final fun isJsonElementDescriptor (Lkotlinx/serialization/descriptors/SerialDescriptor;)Z - public static final fun isJsonElementSerializer (Lkotlinx/serialization/DeserializationStrategy;)Z - public static final fun isJsonElementSerializer (Lkotlinx/serialization/KSerializer;)Z - public static final fun isJsonElementSerializer (Lkotlinx/serialization/SerializationStrategy;)Z +public static final fun getValue (Lkotlinx/serialization/json/JsonPrimitive;)Ljava/lang/Object; +public static final fun isJsonElementDescriptor (Lkotlinx/serialization/descriptors/SerialDescriptor;)Z +public static final fun isJsonElementSerializer (Lkotlinx/serialization/DeserializationStrategy;)Z +public static final fun isJsonElementSerializer (Lkotlinx/serialization/KSerializer;)Z +public static final fun isJsonElementSerializer (Lkotlinx/serialization/SerializationStrategy;)Z } public final class com/intuit/player/jvm/core/bridge/serialization/json/PrettyKt { - public static final fun getPrettyJson ()Lkotlinx/serialization/json/Json; +public static final fun getPrettyJson ()Lkotlinx/serialization/json/Json; } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/DeferKt { } public abstract class com/intuit/player/jvm/core/bridge/serialization/serializers/FunctionLikeSerializer : kotlinx/serialization/KSerializer { - public static final field Companion Lcom/intuit/player/jvm/core/bridge/serialization/serializers/FunctionLikeSerializer$Companion; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public static final field Companion Lcom/intuit/player/jvm/core/bridge/serialization/serializers/FunctionLikeSerializer$Companion; +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/FunctionLikeSerializer$Companion { - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/FunctionSerializer : com/intuit/player/jvm/core/bridge/serialization/serializers/FunctionLikeSerializer { - public fun ()V +public fun ()V } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/InvokableSerializer : com/intuit/player/jvm/core/bridge/serialization/serializers/FunctionLikeSerializer { - public fun ()V +public fun ()V } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/KCallableSerializer : com/intuit/player/jvm/core/bridge/serialization/serializers/FunctionLikeSerializer { - public fun ()V +public fun ()V } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/ModuleKt { - public static final fun getPlayerSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; +public static final fun getPlayerSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/NodeSerializer : kotlinx/serialization/KSerializer { - public fun ()V - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/Node; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/bridge/Node;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public fun ()V +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/Node; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/bridge/Node;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V } public class com/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer : kotlinx/serialization/KSerializer { - public static final field Companion Lcom/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer$Companion; - public fun (Lkotlin/jvm/functions/Function1;)V - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/NodeWrapper; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/bridge/NodeWrapper;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public static final field Companion Lcom/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer$Companion; +public fun (Lkotlin/jvm/functions/Function1;)V +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/NodeWrapper; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/bridge/NodeWrapper;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer; +public final fun invoke (Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer; } public abstract class com/intuit/player/jvm/core/bridge/serialization/serializers/PolymorphicNodeWrapperSerializer : com/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer { - public fun ()V - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/NodeWrapper; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public abstract fun selectDeserializer (Lcom/intuit/player/jvm/core/bridge/Node;)Lkotlinx/serialization/KSerializer; +public fun ()V +public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/NodeWrapper; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public abstract fun selectDeserializer (Lcom/intuit/player/jvm/core/bridge/Node;)Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer : kotlinx/serialization/KSerializer { - public static final field Companion Lcom/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$Companion; - public fun ()V - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/player/PlayerException; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Throwable;)V +public static final field Companion Lcom/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$Companion; +public fun ()V +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/player/PlayerException; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Throwable;)V } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$Companion { } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$SerializableStackTraceElement$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$SerializableStackTraceElement$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$SerializableStackTraceElement; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$SerializableStackTraceElement;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +public static final field INSTANCE Lcom/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$SerializableStackTraceElement$$serializer; +public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun childSerializers ()[Lkotlinx/serialization/KSerializer; +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$SerializableStackTraceElement; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$SerializableStackTraceElement;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer$SerializableStackTraceElement$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/data/DataController : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/data/DataController$Companion; - public final fun get (Ljava/lang/String;)Ljava/lang/Object; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun set (Ljava/util/List;)V - public final fun set (Ljava/util/Map;)V +public static final field Companion Lcom/intuit/player/jvm/core/data/DataController$Companion; +public final fun get (Ljava/lang/String;)Ljava/lang/Object; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun set (Ljava/util/List;)V +public final fun set (Ljava/util/Map;)V } public final class com/intuit/player/jvm/core/data/DataController$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/data/DataControllerKt { - public static final fun get (Lcom/intuit/player/jvm/core/data/DataController;)Ljava/util/Map; - public static final fun set (Lcom/intuit/player/jvm/core/data/DataController;[Lkotlin/Pair;)V +public static final fun get (Lcom/intuit/player/jvm/core/data/DataController;)Ljava/util/Map; +public static final fun set (Lcom/intuit/player/jvm/core/data/DataController;[Lkotlin/Pair;)V } public final class com/intuit/player/jvm/core/data/DataModelWithParser : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/data/DataModelWithParser$Companion; - public final fun get (Ljava/lang/String;)Ljava/lang/Object; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun set (Ljava/util/List;)V +public static final field Companion Lcom/intuit/player/jvm/core/data/DataModelWithParser$Companion; +public final fun get (Ljava/lang/String;)Ljava/lang/Object; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun set (Ljava/util/List;)V } public final class com/intuit/player/jvm/core/data/DataModelWithParser$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/data/DataModelWithParserKt { - public static final fun get (Lcom/intuit/player/jvm/core/data/DataModelWithParser;)Ljava/util/Map; - public static final fun set (Lcom/intuit/player/jvm/core/data/DataModelWithParser;[Lkotlin/Pair;)V +public static final fun get (Lcom/intuit/player/jvm/core/data/DataModelWithParser;)Ljava/util/Map; +public static final fun set (Lcom/intuit/player/jvm/core/data/DataModelWithParser;[Lkotlin/Pair;)V } public abstract interface annotation class com/intuit/player/jvm/core/experimental/ExperimentalPlayerApi : java/lang/annotation/Annotation { } public abstract class com/intuit/player/jvm/core/expressions/Expression { - public static final field Companion Lcom/intuit/player/jvm/core/expressions/Expression$Companion; +public static final field Companion Lcom/intuit/player/jvm/core/expressions/Expression$Companion; } public final class com/intuit/player/jvm/core/expressions/Expression$Collection : com/intuit/player/jvm/core/expressions/Expression { - public static final field Companion Lcom/intuit/player/jvm/core/expressions/Expression$Collection$Companion; - public fun (Ljava/util/List;)V - public fun ([Ljava/lang/String;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lcom/intuit/player/jvm/core/expressions/Expression$Collection; - public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/expressions/Expression$Collection;Ljava/util/List;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/expressions/Expression$Collection; - public fun equals (Ljava/lang/Object;)Z - public final fun getExpressions ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public static final field Companion Lcom/intuit/player/jvm/core/expressions/Expression$Collection$Companion; +public fun (Ljava/util/List;)V +public fun ([Ljava/lang/String;)V +public final fun component1 ()Ljava/util/List; +public final fun copy (Ljava/util/List;)Lcom/intuit/player/jvm/core/expressions/Expression$Collection; +public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/expressions/Expression$Collection;Ljava/util/List;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/expressions/Expression$Collection; +public fun equals (Ljava/lang/Object;)Z +public final fun getExpressions ()Ljava/util/List; +public fun hashCode ()I +public fun toString ()Ljava/lang/String; } public final class com/intuit/player/jvm/core/expressions/Expression$Collection$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/expressions/Expression$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/expressions/Expression$Single : com/intuit/player/jvm/core/expressions/Expression { - public static final field Companion Lcom/intuit/player/jvm/core/expressions/Expression$Single$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/intuit/player/jvm/core/expressions/Expression$Single; - public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/expressions/Expression$Single;Ljava/lang/String;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/expressions/Expression$Single; - public fun equals (Ljava/lang/Object;)Z - public final fun getExpression ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; +public static final field Companion Lcom/intuit/player/jvm/core/expressions/Expression$Single$Companion; +public fun (Ljava/lang/String;)V +public final fun component1 ()Ljava/lang/String; +public final fun copy (Ljava/lang/String;)Lcom/intuit/player/jvm/core/expressions/Expression$Single; +public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/expressions/Expression$Single;Ljava/lang/String;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/expressions/Expression$Single; +public fun equals (Ljava/lang/Object;)Z +public final fun getExpression ()Ljava/lang/String; +public fun hashCode ()I +public fun toString ()Ljava/lang/String; } public final class com/intuit/player/jvm/core/expressions/Expression$Single$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/expressions/ExpressionController : com/intuit/player/jvm/core/bridge/NodeWrapper, com/intuit/player/jvm/core/expressions/ExpressionEvaluator { - public static final field Companion Lcom/intuit/player/jvm/core/expressions/ExpressionController$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public fun evaluate (Ljava/util/List;)Ljava/lang/Object; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public static final field Companion Lcom/intuit/player/jvm/core/expressions/ExpressionController$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public fun evaluate (Ljava/util/List;)Ljava/lang/Object; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/expressions/ExpressionController$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class com/intuit/player/jvm/core/expressions/ExpressionEvaluator { - public abstract fun evaluate (Ljava/util/List;)Ljava/lang/Object; +public abstract fun evaluate (Ljava/util/List;)Ljava/lang/Object; } public final class com/intuit/player/jvm/core/expressions/ExpressionEvaluatorKt { - public static final fun evaluate (Lcom/intuit/player/jvm/core/expressions/ExpressionEvaluator;Lcom/intuit/player/jvm/core/expressions/Expression;)Ljava/lang/Object; - public static final fun evaluate (Lcom/intuit/player/jvm/core/expressions/ExpressionEvaluator;Ljava/lang/String;)Ljava/lang/Object; - public static final fun evaluate (Lcom/intuit/player/jvm/core/expressions/ExpressionEvaluator;[Ljava/lang/String;)Ljava/lang/Object; +public static final fun evaluate (Lcom/intuit/player/jvm/core/expressions/ExpressionEvaluator;Lcom/intuit/player/jvm/core/expressions/Expression;)Ljava/lang/Object; +public static final fun evaluate (Lcom/intuit/player/jvm/core/expressions/ExpressionEvaluator;Ljava/lang/String;)Ljava/lang/Object; +public static final fun evaluate (Lcom/intuit/player/jvm/core/expressions/ExpressionEvaluator;[Ljava/lang/String;)Ljava/lang/Object; } public final class com/intuit/player/jvm/core/flow/Flow { - public static final field Companion Lcom/intuit/player/jvm/core/flow/Flow$Companion; - public static final field UNKNOWN_ID Ljava/lang/String; - public fun ()V - public synthetic fun (ILjava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V - public fun (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/List; - public final fun component3 ()Lkotlinx/serialization/json/JsonElement; - public final fun component4 ()Lkotlinx/serialization/json/JsonElement; - public final fun component5 ()Lcom/intuit/player/jvm/core/flow/Navigation; - public final fun copy (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;)Lcom/intuit/player/jvm/core/flow/Flow; - public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/flow/Flow;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/flow/Flow; - public fun equals (Ljava/lang/Object;)Z - public final fun getData ()Lkotlinx/serialization/json/JsonElement; - public final fun getId ()Ljava/lang/String; - public final fun getNavigation ()Lcom/intuit/player/jvm/core/flow/Navigation; - public final fun getSchema ()Lkotlinx/serialization/json/JsonElement; - public final fun getViews ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; - public static final fun write$Self (Lcom/intuit/player/jvm/core/flow/Flow;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +public static final field Companion Lcom/intuit/player/jvm/core/flow/Flow$Companion; +public static final field UNKNOWN_ID Ljava/lang/String; +public fun ()V +public synthetic fun (ILjava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V +public fun (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;)V +public synthetic fun (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final fun component1 ()Ljava/lang/String; +public final fun component2 ()Ljava/util/List; +public final fun component3 ()Lkotlinx/serialization/json/JsonElement; +public final fun component4 ()Lkotlinx/serialization/json/JsonElement; +public final fun component5 ()Lcom/intuit/player/jvm/core/flow/Navigation; +public final fun copy (Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;)Lcom/intuit/player/jvm/core/flow/Flow; +public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/flow/Flow;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/json/JsonElement;Lcom/intuit/player/jvm/core/flow/Navigation;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/flow/Flow; +public fun equals (Ljava/lang/Object;)Z +public final fun getData ()Lkotlinx/serialization/json/JsonElement; +public final fun getId ()Ljava/lang/String; +public final fun getNavigation ()Lcom/intuit/player/jvm/core/flow/Navigation; +public final fun getSchema ()Lkotlinx/serialization/json/JsonElement; +public final fun getViews ()Ljava/util/List; +public fun hashCode ()I +public fun toString ()Ljava/lang/String; +public static final fun write$Self (Lcom/intuit/player/jvm/core/flow/Flow;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class com/intuit/player/jvm/core/flow/Flow$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/Flow$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/flow/Flow; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/flow/Flow;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/Flow$$serializer; +public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun childSerializers ()[Lkotlinx/serialization/KSerializer; +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/flow/Flow; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/flow/Flow;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/Flow$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/FlowController : com/intuit/player/jvm/core/bridge/NodeWrapper, com/intuit/player/jvm/core/flow/Transition { - public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowController$Companion; - public final fun getCurrent ()Lcom/intuit/player/jvm/core/flow/FlowInstance; - public final fun getHooks ()Lcom/intuit/player/jvm/core/flow/FlowController$Hooks; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun transition (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V +public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowController$Companion; +public final fun getCurrent ()Lcom/intuit/player/jvm/core/flow/FlowInstance; +public final fun getHooks ()Lcom/intuit/player/jvm/core/flow/FlowController$Hooks; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun transition (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V } public final class com/intuit/player/jvm/core/flow/FlowController$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/FlowController$Hooks : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowController$Hooks$Companion; - public final fun getFlow ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowController$Hooks$Companion; +public final fun getFlow ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/flow/FlowController$Hooks$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/FlowException : com/intuit/player/jvm/core/player/PlayerException { } public final class com/intuit/player/jvm/core/flow/FlowInstance : com/intuit/player/jvm/core/bridge/NodeWrapper, com/intuit/player/jvm/core/flow/Transition { - public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowInstance$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public final fun getCurrentState ()Lcom/intuit/player/jvm/core/player/state/NamedState; - public final fun getHooks ()Lcom/intuit/player/jvm/core/flow/FlowInstance$Hooks; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun transition (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V +public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowInstance$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public final fun getCurrentState ()Lcom/intuit/player/jvm/core/player/state/NamedState; +public final fun getHooks ()Lcom/intuit/player/jvm/core/flow/FlowInstance$Hooks; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun transition (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V } public final class com/intuit/player/jvm/core/flow/FlowInstance$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/FlowInstance$Hooks : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowInstance$Hooks$Companion; - public final fun getBeforeTransition ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook2; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getOnEnd ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public final fun getOnStart ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public final fun getResolveTransitionNode ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook1; - public final fun getTransition ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook2; +public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowInstance$Hooks$Companion; +public final fun getBeforeTransition ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook2; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getOnEnd ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public final fun getOnStart ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public final fun getResolveTransitionNode ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook1; +public final fun getTransition ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook2; } public final class com/intuit/player/jvm/core/flow/FlowInstance$Hooks$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/FlowResult : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowResult$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public fun equals (Ljava/lang/Object;)Z - public final fun getData ()Lkotlinx/serialization/json/JsonElement; - public final fun getEndState ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowEndState; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun hashCode ()I +public static final field Companion Lcom/intuit/player/jvm/core/flow/FlowResult$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public fun equals (Ljava/lang/Object;)Z +public final fun getData ()Lkotlinx/serialization/json/JsonElement; +public final fun getEndState ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowEndState; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun hashCode ()I } public final class com/intuit/player/jvm/core/flow/FlowResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/FlowResult$Serializer : com/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/FlowResult$Serializer; +public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/FlowResult$Serializer; } public final class com/intuit/player/jvm/core/flow/Navigation { - public static final field Companion Lcom/intuit/player/jvm/core/flow/Navigation$Companion; - public synthetic fun (ILjava/lang/String;Ljava/util/Map;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V - public fun (Ljava/lang/String;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lcom/intuit/player/jvm/core/flow/Navigation; - public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/flow/Navigation;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/flow/Navigation; - public fun equals (Ljava/lang/Object;)Z - public final fun getBEGIN ()Ljava/lang/String; - public final fun getFlows ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; - public static final fun write$Self (Lcom/intuit/player/jvm/core/flow/Navigation;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +public static final field Companion Lcom/intuit/player/jvm/core/flow/Navigation$Companion; +public synthetic fun (ILjava/lang/String;Ljava/util/Map;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V +public fun (Ljava/lang/String;Ljava/util/Map;)V +public synthetic fun (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final fun component1 ()Ljava/lang/String; +public final fun component2 ()Ljava/util/Map; +public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lcom/intuit/player/jvm/core/flow/Navigation; +public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/flow/Navigation;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/flow/Navigation; +public fun equals (Ljava/lang/Object;)Z +public final fun getBEGIN ()Ljava/lang/String; +public final fun getFlows ()Ljava/util/Map; +public fun hashCode ()I +public fun toString ()Ljava/lang/String; +public static final fun write$Self (Lcom/intuit/player/jvm/core/flow/Navigation;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class com/intuit/player/jvm/core/flow/Navigation$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/Navigation$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/flow/Navigation; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/flow/Navigation;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/Navigation$$serializer; +public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun childSerializers ()[Lkotlinx/serialization/KSerializer; +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/flow/Navigation; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/flow/Navigation;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/Navigation$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/NavigationFlow { - public static final field Companion Lcom/intuit/player/jvm/core/flow/NavigationFlow$Companion; - public synthetic fun (ILjava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V - public fun (Ljava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Ljava/util/Map;)V - public synthetic fun (Ljava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lcom/intuit/player/jvm/core/expressions/Expression; - public final fun component3 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Ljava/util/Map;)Lcom/intuit/player/jvm/core/flow/NavigationFlow; - public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/flow/NavigationFlow;Ljava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Ljava/util/Map;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/flow/NavigationFlow; - public fun equals (Ljava/lang/Object;)Z - public final fun getOnStart ()Lcom/intuit/player/jvm/core/expressions/Expression; - public final fun getStartState ()Ljava/lang/String; - public final fun getStates ()Ljava/util/Map; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; - public static final fun write$Self (Lcom/intuit/player/jvm/core/flow/NavigationFlow;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +public static final field Companion Lcom/intuit/player/jvm/core/flow/NavigationFlow$Companion; +public synthetic fun (ILjava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V +public fun (Ljava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Ljava/util/Map;)V +public synthetic fun (Ljava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final fun component1 ()Ljava/lang/String; +public final fun component2 ()Lcom/intuit/player/jvm/core/expressions/Expression; +public final fun component3 ()Ljava/util/Map; +public final fun copy (Ljava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Ljava/util/Map;)Lcom/intuit/player/jvm/core/flow/NavigationFlow; +public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/flow/NavigationFlow;Ljava/lang/String;Lcom/intuit/player/jvm/core/expressions/Expression;Ljava/util/Map;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/flow/NavigationFlow; +public fun equals (Ljava/lang/Object;)Z +public final fun getOnStart ()Lcom/intuit/player/jvm/core/expressions/Expression; +public final fun getStartState ()Ljava/lang/String; +public final fun getStates ()Ljava/util/Map; +public fun hashCode ()I +public fun toString ()Ljava/lang/String; +public static final fun write$Self (Lcom/intuit/player/jvm/core/flow/NavigationFlow;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class com/intuit/player/jvm/core/flow/NavigationFlow$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/NavigationFlow$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/flow/NavigationFlow; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/flow/NavigationFlow;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/NavigationFlow$$serializer; +public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun childSerializers ()[Lkotlinx/serialization/KSerializer; +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/flow/NavigationFlow; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/flow/NavigationFlow;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/NavigationFlow$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class com/intuit/player/jvm/core/flow/Transition { - public abstract fun transition (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V +public abstract fun transition (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V } public final class com/intuit/player/jvm/core/flow/Transition$DefaultImpls { - public static synthetic fun transition$default (Lcom/intuit/player/jvm/core/flow/Transition;Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;ILjava/lang/Object;)V +public static synthetic fun transition$default (Lcom/intuit/player/jvm/core/flow/Transition;Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;ILjava/lang/Object;)V } public final class com/intuit/player/jvm/core/flow/TransitionKt { - public static final fun forceTransition (Lcom/intuit/player/jvm/core/flow/Transition;Ljava/lang/String;)V +public static final fun forceTransition (Lcom/intuit/player/jvm/core/flow/Transition;Ljava/lang/String;)V } public final class com/intuit/player/jvm/core/flow/TransitionOptions { - public static final field Companion Lcom/intuit/player/jvm/core/flow/TransitionOptions$Companion; - public fun ()V - public synthetic fun (IZLkotlinx/serialization/internal/SerializationConstructorMarker;)V - public fun (Z)V - public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z - public final fun copy (Z)Lcom/intuit/player/jvm/core/flow/TransitionOptions; - public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/flow/TransitionOptions;ZILjava/lang/Object;)Lcom/intuit/player/jvm/core/flow/TransitionOptions; - public fun equals (Ljava/lang/Object;)Z - public final fun getForce ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; - public static final fun write$Self (Lcom/intuit/player/jvm/core/flow/TransitionOptions;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +public static final field Companion Lcom/intuit/player/jvm/core/flow/TransitionOptions$Companion; +public fun ()V +public synthetic fun (IZLkotlinx/serialization/internal/SerializationConstructorMarker;)V +public fun (Z)V +public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final fun component1 ()Z +public final fun copy (Z)Lcom/intuit/player/jvm/core/flow/TransitionOptions; +public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/flow/TransitionOptions;ZILjava/lang/Object;)Lcom/intuit/player/jvm/core/flow/TransitionOptions; +public fun equals (Ljava/lang/Object;)Z +public final fun getForce ()Z +public fun hashCode ()I +public fun toString ()Ljava/lang/String; +public static final fun write$Self (Lcom/intuit/player/jvm/core/flow/TransitionOptions;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class com/intuit/player/jvm/core/flow/TransitionOptions$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/TransitionOptions$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/flow/TransitionOptions; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +public static final field INSTANCE Lcom/intuit/player/jvm/core/flow/TransitionOptions$$serializer; +public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun childSerializers ()[Lkotlinx/serialization/KSerializer; +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/flow/TransitionOptions; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/TransitionOptions$Companion { - public final fun getForceTransition ()Lcom/intuit/player/jvm/core/flow/TransitionOptions; - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun getForceTransition ()Lcom/intuit/player/jvm/core/flow/TransitionOptions; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowActionState : com/intuit/player/jvm/core/flow/state/NavigationFlowTransitionableState, com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowActionState$Companion; - public final fun getExp ()Lcom/intuit/player/jvm/core/expressions/Expression; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowActionState$Companion; +public final fun getExp ()Lcom/intuit/player/jvm/core/expressions/Expression; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowActionState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowEndState : com/intuit/player/jvm/core/flow/state/NavigationFlowState, com/intuit/player/jvm/core/bridge/NodeWrapper, java/util/Map, kotlin/jvm/internal/markers/KMappedMarker { - public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowEndState$Companion; - public fun clear ()V - public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun compute (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; - public fun computeIfAbsent (Ljava/lang/String;Ljava/util/function/Function;)Ljava/lang/Object; - public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun computeIfPresent (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public final fun containsKey (Ljava/lang/Object;)Z - public fun containsKey (Ljava/lang/String;)Z - public fun containsValue (Ljava/lang/Object;)Z - public final fun entrySet ()Ljava/util/Set; - public final fun get (Ljava/lang/Object;)Ljava/lang/Object; - public fun get (Ljava/lang/String;)Ljava/lang/Object; - public fun getEntries ()Ljava/util/Set; - public fun getKeys ()Ljava/util/Set; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getOutcome ()Ljava/lang/String; - public fun getSize ()I - public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; - public fun getValues ()Ljava/util/Collection; - public fun isEmpty ()Z - public final fun keySet ()Ljava/util/Set; - public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public fun merge (Ljava/lang/String;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; - public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public fun put (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; - public fun putAll (Ljava/util/Map;)V - public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public fun putIfAbsent (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; - public fun remove (Ljava/lang/Object;)Ljava/lang/Object; - public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z - public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; - public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z - public fun replace (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; - public fun replace (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)Z - public fun replaceAll (Ljava/util/function/BiFunction;)V - public final fun size ()I - public final fun values ()Ljava/util/Collection; +public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowEndState$Companion; +public fun clear ()V +public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun compute (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object; +public fun computeIfAbsent (Ljava/lang/String;Ljava/util/function/Function;)Ljava/lang/Object; +public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun computeIfPresent (Ljava/lang/String;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public final fun containsKey (Ljava/lang/Object;)Z +public fun containsKey (Ljava/lang/String;)Z +public fun containsValue (Ljava/lang/Object;)Z +public final fun entrySet ()Ljava/util/Set; +public final fun get (Ljava/lang/Object;)Ljava/lang/Object; +public fun get (Ljava/lang/String;)Ljava/lang/Object; +public fun getEntries ()Ljava/util/Set; +public fun getKeys ()Ljava/util/Set; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getOutcome ()Ljava/lang/String; +public fun getSize ()I +public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public fun getValues ()Ljava/util/Collection; +public fun isEmpty ()Z +public final fun keySet ()Ljava/util/Set; +public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public fun merge (Ljava/lang/String;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object; +public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +public fun put (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; +public fun putAll (Ljava/util/Map;)V +public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +public fun putIfAbsent (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; +public fun remove (Ljava/lang/Object;)Ljava/lang/Object; +public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z +public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z +public fun replace (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; +public fun replace (Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)Z +public fun replaceAll (Ljava/util/function/BiFunction;)V +public final fun size ()I +public final fun values ()Ljava/util/Collection; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowEndState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowExternalState : com/intuit/player/jvm/core/flow/state/NavigationFlowTransitionableState, com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowExternalState$Companion; - public final fun get (Ljava/lang/String;)Ljava/lang/Object; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowExternalState$Companion; +public final fun get (Ljava/lang/String;)Ljava/lang/Object; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowExternalState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowFlowState : com/intuit/player/jvm/core/flow/state/NavigationFlowTransitionableState, com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowFlowState$Companion; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowFlowState$Companion; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowFlowState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract class com/intuit/player/jvm/core/flow/state/NavigationFlowState : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowState$Companion; - public abstract fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowState$Companion; +public abstract fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowStateType : java/lang/Enum { - public static final field ACTION Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; - public static final field END Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; - public static final field EXTERNAL Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; - public static final field FLOW Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; - public static final field VIEW Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; - public static fun valueOf (Ljava/lang/String;)Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; - public static fun values ()[Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field ACTION Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field END Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field EXTERNAL Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field FLOW Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field VIEW Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static fun valueOf (Ljava/lang/String;)Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static fun values ()[Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; } public abstract class com/intuit/player/jvm/core/flow/state/NavigationFlowTransitionableState : com/intuit/player/jvm/core/flow/state/NavigationFlowState { - public synthetic fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getRef ()Ljava/lang/String; - public final fun getTransitions ()Ljava/util/Map; +public synthetic fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getRef ()Ljava/lang/String; +public final fun getTransitions ()Ljava/util/Map; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowViewState : com/intuit/player/jvm/core/flow/state/NavigationFlowTransitionableState, com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowViewState$Companion; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; +public static final field Companion Lcom/intuit/player/jvm/core/flow/state/NavigationFlowViewState$Companion; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getStateType ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowStateType; } public final class com/intuit/player/jvm/core/flow/state/NavigationFlowViewState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/logger/TapableLogger : com/intuit/player/jvm/core/bridge/NodeWrapper, com/intuit/player/jvm/core/plugins/LoggerPlugin { - public static final field Companion Lcom/intuit/player/jvm/core/logger/TapableLogger$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public final fun addHandler (Lcom/intuit/player/jvm/core/plugins/LoggerPlugin;)V - public fun apply (Lcom/intuit/player/jvm/core/player/Player;)V - public fun debug ([Ljava/lang/Object;)V - public fun error ([Ljava/lang/Object;)V - public final fun getHooks ()Lcom/intuit/player/jvm/core/logger/TapableLogger$Hooks; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun info ([Ljava/lang/Object;)V - public fun trace ([Ljava/lang/Object;)V - public fun warn ([Ljava/lang/Object;)V +public static final field Companion Lcom/intuit/player/jvm/core/logger/TapableLogger$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public final fun addHandler (Lcom/intuit/player/jvm/core/plugins/LoggerPlugin;)V +public fun apply (Lcom/intuit/player/jvm/core/player/Player;)V +public fun debug ([Ljava/lang/Object;)V +public fun error ([Ljava/lang/Object;)V +public final fun getHooks ()Lcom/intuit/player/jvm/core/logger/TapableLogger$Hooks; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun info ([Ljava/lang/Object;)V +public fun trace ([Ljava/lang/Object;)V +public fun warn ([Ljava/lang/Object;)V } public final class com/intuit/player/jvm/core/logger/TapableLogger$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/logger/TapableLogger$Hooks : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/logger/TapableLogger$Hooks$Companion; - public final fun getDebug ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; - public final fun getError ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; - public final fun getInfo ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getTrace ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; - public final fun getWarn ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; +public static final field Companion Lcom/intuit/player/jvm/core/logger/TapableLogger$Hooks$Companion; +public final fun getDebug ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; +public final fun getError ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; +public final fun getInfo ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getTrace ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; +public final fun getWarn ()Lcom/intuit/player/jvm/core/bridge/hooks/SyncHook1; } public final class com/intuit/player/jvm/core/logger/TapableLogger$Hooks$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/managed/AsyncIterationManager { - public fun (Lcom/intuit/player/jvm/core/managed/AsyncIterator;)V - public final fun getIterator ()Lcom/intuit/player/jvm/core/managed/AsyncIterator; - public final fun getState ()Lkotlinx/coroutines/flow/StateFlow; - public final fun next (Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;)Lkotlinx/coroutines/Job; - public static synthetic fun next$default (Lcom/intuit/player/jvm/core/managed/AsyncIterationManager;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;ILjava/lang/Object;)Lkotlinx/coroutines/Job; +public fun (Lcom/intuit/player/jvm/core/managed/AsyncIterator;)V +public final fun getIterator ()Lcom/intuit/player/jvm/core/managed/AsyncIterator; +public final fun getState ()Lkotlinx/coroutines/flow/StateFlow; +public final fun next (Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;)Lkotlinx/coroutines/Job; +public static synthetic fun next$default (Lcom/intuit/player/jvm/core/managed/AsyncIterationManager;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;ILjava/lang/Object;)Lkotlinx/coroutines/Job; } public abstract class com/intuit/player/jvm/core/managed/AsyncIterationManager$State { } public final class com/intuit/player/jvm/core/managed/AsyncIterationManager$State$Done : com/intuit/player/jvm/core/managed/AsyncIterationManager$State { - public static final field INSTANCE Lcom/intuit/player/jvm/core/managed/AsyncIterationManager$State$Done; +public static final field INSTANCE Lcom/intuit/player/jvm/core/managed/AsyncIterationManager$State$Done; } public final class com/intuit/player/jvm/core/managed/AsyncIterationManager$State$Error : com/intuit/player/jvm/core/managed/AsyncIterationManager$State { - public fun (Ljava/lang/Exception;)V - public final fun getError ()Ljava/lang/Exception; +public fun (Ljava/lang/Exception;)V +public final fun getError ()Ljava/lang/Exception; } public final class com/intuit/player/jvm/core/managed/AsyncIterationManager$State$Item : com/intuit/player/jvm/core/managed/AsyncIterationManager$State { - public fun (Ljava/lang/Object;)V - public final fun getValue ()Ljava/lang/Object; +public fun (Ljava/lang/Object;)V +public final fun getValue ()Ljava/lang/Object; } public final class com/intuit/player/jvm/core/managed/AsyncIterationManager$State$NotStarted : com/intuit/player/jvm/core/managed/AsyncIterationManager$State { - public static final field INSTANCE Lcom/intuit/player/jvm/core/managed/AsyncIterationManager$State$NotStarted; +public static final field INSTANCE Lcom/intuit/player/jvm/core/managed/AsyncIterationManager$State$NotStarted; } public final class com/intuit/player/jvm/core/managed/AsyncIterationManager$State$Pending : com/intuit/player/jvm/core/managed/AsyncIterationManager$State { - public static final field INSTANCE Lcom/intuit/player/jvm/core/managed/AsyncIterationManager$State$Pending; +public static final field INSTANCE Lcom/intuit/player/jvm/core/managed/AsyncIterationManager$State$Pending; } public abstract interface class com/intuit/player/jvm/core/managed/AsyncIterator { - public static final field Companion Lcom/intuit/player/jvm/core/managed/AsyncIterator$Companion; - public abstract fun next (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun terminate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +public static final field Companion Lcom/intuit/player/jvm/core/managed/AsyncIterator$Companion; +public abstract fun next (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +public abstract fun terminate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class com/intuit/player/jvm/core/managed/AsyncIterator$Companion { - public final fun invoke (Ljava/util/List;)Lcom/intuit/player/jvm/core/managed/AsyncIterator; - public final fun invoke ([Ljava/lang/Object;)Lcom/intuit/player/jvm/core/managed/AsyncIterator; +public final fun invoke (Ljava/util/List;)Lcom/intuit/player/jvm/core/managed/AsyncIterator; +public final fun invoke ([Ljava/lang/Object;)Lcom/intuit/player/jvm/core/managed/AsyncIterator; } public final class com/intuit/player/jvm/core/managed/AsyncIterator$DefaultImpls { - public static synthetic fun next$default (Lcom/intuit/player/jvm/core/managed/AsyncIterator;Ljava/lang/Object;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public static fun terminate (Lcom/intuit/player/jvm/core/managed/AsyncIterator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +public static synthetic fun next$default (Lcom/intuit/player/jvm/core/managed/AsyncIterator;Ljava/lang/Object;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +public static fun terminate (Lcom/intuit/player/jvm/core/managed/AsyncIterator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class com/intuit/player/jvm/core/managed/AsyncIteratorKt { - public static final fun AsyncFlowIterator (Ljava/util/List;)Lcom/intuit/player/jvm/core/managed/AsyncIterator; - public static final fun AsyncFlowIterator ([Ljava/lang/String;)Lcom/intuit/player/jvm/core/managed/AsyncIterator; +public static final fun AsyncFlowIterator (Ljava/util/List;)Lcom/intuit/player/jvm/core/managed/AsyncIterator; +public static final fun AsyncFlowIterator ([Ljava/lang/String;)Lcom/intuit/player/jvm/core/managed/AsyncIterator; } public final class com/intuit/player/jvm/core/player/HeadlessPlayer : com/intuit/player/jvm/core/player/Player, com/intuit/player/jvm/core/bridge/NodeWrapper { - public fun (Ljava/util/List;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/net/URL;)V - public synthetic fun (Ljava/util/List;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/net/URL;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun ([Lcom/intuit/player/jvm/core/plugins/Plugin;)V - public fun ([Lcom/intuit/player/jvm/core/plugins/Plugin;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)V - public fun ([Lcom/intuit/player/jvm/core/plugins/Plugin;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/net/URL;)V - public synthetic fun ([Lcom/intuit/player/jvm/core/plugins/Plugin;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/net/URL;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getHooks ()Lcom/intuit/player/jvm/core/player/Player$Hooks; - public fun getLogger ()Lcom/intuit/player/jvm/core/logger/TapableLogger; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getPlugins ()Ljava/util/List; - public final fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; - public fun getState ()Lcom/intuit/player/jvm/core/player/state/PlayerFlowState; - public fun release ()V - public final fun start (Lcom/intuit/player/jvm/core/bridge/Node;)Lcom/intuit/player/jvm/core/bridge/Completable; - public final fun start (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/Completable; - public fun start (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Completable; +public fun (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;[Lcom/intuit/player/jvm/core/plugins/Plugin;)V +public fun (Ljava/util/List;)V +public fun (Ljava/util/List;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)V +public fun (Ljava/util/List;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/net/URL;)V +public synthetic fun (Ljava/util/List;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/net/URL;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public fun ([Lcom/intuit/player/jvm/core/plugins/Plugin;)V +public fun ([Lcom/intuit/player/jvm/core/plugins/Plugin;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)V +public fun ([Lcom/intuit/player/jvm/core/plugins/Plugin;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/net/URL;)V +public synthetic fun ([Lcom/intuit/player/jvm/core/plugins/Plugin;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/net/URL;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public fun getHooks ()Lcom/intuit/player/jvm/core/player/Player$Hooks; +public fun getLogger ()Lcom/intuit/player/jvm/core/logger/TapableLogger; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getPlugins ()Ljava/util/List; +public final fun getRuntime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; +public fun getState ()Lcom/intuit/player/jvm/core/player/state/PlayerFlowState; +public fun release ()V +public final fun start (Lcom/intuit/player/jvm/core/bridge/Node;)Lcom/intuit/player/jvm/core/bridge/Completable; +public final fun start (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/Completable; +public fun start (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Completable; } public final class com/intuit/player/jvm/core/player/JSPlayerConfig { - public static final field Companion Lcom/intuit/player/jvm/core/player/JSPlayerConfig$Companion; - public fun ()V - public synthetic fun (ILjava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V - public fun (Ljava/util/List;)V - public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lcom/intuit/player/jvm/core/player/JSPlayerConfig; - public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/player/JSPlayerConfig;Ljava/util/List;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/player/JSPlayerConfig; - public fun equals (Ljava/lang/Object;)Z - public final fun getPlugins ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; - public static final fun write$Self (Lcom/intuit/player/jvm/core/player/JSPlayerConfig;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +public static final field Companion Lcom/intuit/player/jvm/core/player/JSPlayerConfig$Companion; +public fun ()V +public synthetic fun (ILjava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V +public fun (Ljava/util/List;)V +public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final fun component1 ()Ljava/util/List; +public final fun copy (Ljava/util/List;)Lcom/intuit/player/jvm/core/player/JSPlayerConfig; +public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/player/JSPlayerConfig;Ljava/util/List;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/player/JSPlayerConfig; +public fun equals (Ljava/lang/Object;)Z +public final fun getPlugins ()Ljava/util/List; +public fun hashCode ()I +public fun toString ()Ljava/lang/String; +public static final fun write$Self (Lcom/intuit/player/jvm/core/player/JSPlayerConfig;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class com/intuit/player/jvm/core/player/JSPlayerConfig$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/player/JSPlayerConfig$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/player/JSPlayerConfig; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/player/JSPlayerConfig;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +public static final field INSTANCE Lcom/intuit/player/jvm/core/player/JSPlayerConfig$$serializer; +public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun childSerializers ()[Lkotlinx/serialization/KSerializer; +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/player/JSPlayerConfig; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/player/JSPlayerConfig;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/player/JSPlayerConfig$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract class com/intuit/player/jvm/core/player/Player : com/intuit/player/jvm/core/plugins/Pluggable { - public fun ()V - public abstract fun getHooks ()Lcom/intuit/player/jvm/core/player/Player$Hooks; - public abstract fun getLogger ()Lcom/intuit/player/jvm/core/logger/TapableLogger; - public abstract fun getState ()Lcom/intuit/player/jvm/core/player/state/PlayerFlowState; - public abstract fun release ()V - public abstract fun start (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Completable; - public final fun start (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/Completable; - public final fun start (Ljava/net/URL;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/Completable; - public static synthetic fun start$default (Lcom/intuit/player/jvm/core/player/Player;Ljava/net/URL;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/Completable; +public fun ()V +public abstract fun getHooks ()Lcom/intuit/player/jvm/core/player/Player$Hooks; +public abstract fun getLogger ()Lcom/intuit/player/jvm/core/logger/TapableLogger; +public abstract fun getState ()Lcom/intuit/player/jvm/core/player/state/PlayerFlowState; +public abstract fun release ()V +public abstract fun start (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Completable; +public final fun start (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/Completable; +public final fun start (Ljava/net/URL;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/bridge/Completable; +public static synthetic fun start$default (Lcom/intuit/player/jvm/core/player/Player;Ljava/net/URL;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/Completable; } public abstract interface class com/intuit/player/jvm/core/player/Player$Hooks { - public static final field Companion Lcom/intuit/player/jvm/core/player/Player$Hooks$Companion; - public abstract fun getDataController ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public abstract fun getExpressionEvaluator ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public abstract fun getFlowController ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public abstract fun getState ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public abstract fun getValidationController ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public abstract fun getView ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public abstract fun getViewController ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public static final field Companion Lcom/intuit/player/jvm/core/player/Player$Hooks$Companion; +public abstract fun getDataController ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public abstract fun getExpressionEvaluator ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public abstract fun getFlowController ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public abstract fun getState ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public abstract fun getValidationController ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public abstract fun getView ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public abstract fun getViewController ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; } public final class com/intuit/player/jvm/core/player/Player$Hooks$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public class com/intuit/player/jvm/core/player/PlayerException : java/lang/Exception { - public static final field Companion Lcom/intuit/player/jvm/core/player/PlayerException$Companion; - public fun (Ljava/lang/String;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public static final field Companion Lcom/intuit/player/jvm/core/player/PlayerException$Companion; +public fun (Ljava/lang/String;Ljava/lang/Throwable;)V +public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public final class com/intuit/player/jvm/core/player/PlayerException$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/player/PlayerFlowStatus : java/lang/Enum { - public static final field COMPLETED Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; - public static final field Companion Lcom/intuit/player/jvm/core/player/PlayerFlowStatus$Companion; - public static final field ERROR Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; - public static final field IN_PROGRESS Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; - public static final field NOT_STARTED Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; - public static final field RELEASED Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; - public final fun getValue ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; - public static fun values ()[Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field COMPLETED Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field Companion Lcom/intuit/player/jvm/core/player/PlayerFlowStatus$Companion; +public static final field ERROR Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field IN_PROGRESS Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field NOT_STARTED Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field RELEASED Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public final fun getValue ()Ljava/lang/String; +public static fun valueOf (Ljava/lang/String;)Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static fun values ()[Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; } public final class com/intuit/player/jvm/core/player/PlayerFlowStatus$Companion { - public final fun from (Ljava/lang/Object;)Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public final fun from (Ljava/lang/Object;)Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; } public final class com/intuit/player/jvm/core/player/state/CompletedState : com/intuit/player/jvm/core/player/state/PlayerFlowExecutionState { - public static final field Companion Lcom/intuit/player/jvm/core/player/state/CompletedState$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public final fun getData ()Lkotlinx/serialization/json/JsonElement; - public final fun getEndState ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowEndState; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field Companion Lcom/intuit/player/jvm/core/player/state/CompletedState$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public final fun getData ()Lkotlinx/serialization/json/JsonElement; +public final fun getEndState ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowEndState; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; } public final class com/intuit/player/jvm/core/player/state/CompletedState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/player/state/ControllerState : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/player/state/ControllerState$Companion; - public final fun getData ()Lcom/intuit/player/jvm/core/data/DataController; - public final fun getExpression ()Lcom/intuit/player/jvm/core/expressions/ExpressionController; - public final fun getFlow ()Lcom/intuit/player/jvm/core/flow/FlowController; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getValidation ()Lcom/intuit/player/jvm/core/validation/ValidationController; - public final fun getView ()Lcom/intuit/player/jvm/core/view/ViewController; +public static final field Companion Lcom/intuit/player/jvm/core/player/state/ControllerState$Companion; +public final fun getData ()Lcom/intuit/player/jvm/core/data/DataController; +public final fun getExpression ()Lcom/intuit/player/jvm/core/expressions/ExpressionController; +public final fun getFlow ()Lcom/intuit/player/jvm/core/flow/FlowController; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getValidation ()Lcom/intuit/player/jvm/core/validation/ValidationController; +public final fun getView ()Lcom/intuit/player/jvm/core/view/ViewController; } public final class com/intuit/player/jvm/core/player/state/ControllerState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract class com/intuit/player/jvm/core/player/state/ErrorState : com/intuit/player/jvm/core/player/state/PlayerFlowState { - public static final field Companion Lcom/intuit/player/jvm/core/player/state/ErrorState$Companion; - public fun ()V - public abstract fun getError ()Lcom/intuit/player/jvm/core/player/PlayerException; - public abstract fun getFlow ()Lcom/intuit/player/jvm/core/flow/Flow; - public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field Companion Lcom/intuit/player/jvm/core/player/state/ErrorState$Companion; +public fun ()V +public abstract fun getError ()Lcom/intuit/player/jvm/core/player/PlayerException; +public abstract fun getFlow ()Lcom/intuit/player/jvm/core/flow/Flow; +public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; } public final class com/intuit/player/jvm/core/player/state/ErrorState$Companion { - public final fun from (Lcom/intuit/player/jvm/core/player/PlayerException;Lcom/intuit/player/jvm/core/flow/Flow;)Lcom/intuit/player/jvm/core/player/state/ErrorState; - public final fun from (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/Flow;)Lcom/intuit/player/jvm/core/player/state/ErrorState; - public static synthetic fun from$default (Lcom/intuit/player/jvm/core/player/state/ErrorState$Companion;Lcom/intuit/player/jvm/core/player/PlayerException;Lcom/intuit/player/jvm/core/flow/Flow;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/player/state/ErrorState; - public static synthetic fun from$default (Lcom/intuit/player/jvm/core/player/state/ErrorState$Companion;Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/Flow;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/player/state/ErrorState; +public final fun from (Lcom/intuit/player/jvm/core/player/PlayerException;Lcom/intuit/player/jvm/core/flow/Flow;)Lcom/intuit/player/jvm/core/player/state/ErrorState; +public final fun from (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/Flow;)Lcom/intuit/player/jvm/core/player/state/ErrorState; +public static synthetic fun from$default (Lcom/intuit/player/jvm/core/player/state/ErrorState$Companion;Lcom/intuit/player/jvm/core/player/PlayerException;Lcom/intuit/player/jvm/core/flow/Flow;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/player/state/ErrorState; +public static synthetic fun from$default (Lcom/intuit/player/jvm/core/player/state/ErrorState$Companion;Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/Flow;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/player/state/ErrorState; } public final class com/intuit/player/jvm/core/player/state/InProgressState : com/intuit/player/jvm/core/player/state/PlayerFlowExecutionState, com/intuit/player/jvm/core/bridge/NodeWrapper, com/intuit/player/jvm/core/expressions/ExpressionEvaluator, com/intuit/player/jvm/core/flow/Transition { - public static final field Companion Lcom/intuit/player/jvm/core/player/state/InProgressState$Companion; - public fun evaluate (Ljava/util/List;)Ljava/lang/Object; - public final fun fail (Ljava/lang/Throwable;)V - public final fun getControllers ()Lcom/intuit/player/jvm/core/player/state/ControllerState; - public final fun getFlowResult ()Lcom/intuit/player/jvm/core/bridge/Completable; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; - public fun transition (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V +public static final field Companion Lcom/intuit/player/jvm/core/player/state/InProgressState$Companion; +public fun evaluate (Ljava/util/List;)Ljava/lang/Object; +public final fun fail (Ljava/lang/Throwable;)V +public final fun getControllers ()Lcom/intuit/player/jvm/core/player/state/ControllerState; +public final fun getFlowResult ()Lcom/intuit/player/jvm/core/bridge/Completable; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public fun transition (Ljava/lang/String;Lcom/intuit/player/jvm/core/flow/TransitionOptions;)V } public final class com/intuit/player/jvm/core/player/state/InProgressState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/player/state/NamedState : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/player/state/NamedState$Companion; - public final fun getName ()Ljava/lang/String; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getValue ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowState; - public fun toString ()Ljava/lang/String; +public static final field Companion Lcom/intuit/player/jvm/core/player/state/NamedState$Companion; +public final fun getName ()Ljava/lang/String; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getValue ()Lcom/intuit/player/jvm/core/flow/state/NavigationFlowState; +public fun toString ()Ljava/lang/String; } public final class com/intuit/player/jvm/core/player/state/NamedState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/player/state/NotStartedState : com/intuit/player/jvm/core/player/state/PlayerFlowState, com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/player/state/NotStartedState$Companion; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field Companion Lcom/intuit/player/jvm/core/player/state/NotStartedState$Companion; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; } public final class com/intuit/player/jvm/core/player/state/NotStartedState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract class com/intuit/player/jvm/core/player/state/PlayerFlowExecutionState : com/intuit/player/jvm/core/player/state/PlayerFlowState, com/intuit/player/jvm/core/bridge/NodeWrapper { - public synthetic fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getDataModel ()Lcom/intuit/player/jvm/core/data/DataModelWithParser; - public final fun getFlow ()Lcom/intuit/player/jvm/core/flow/Flow; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public synthetic fun (Lcom/intuit/player/jvm/core/bridge/Node;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +public final fun getDataModel ()Lcom/intuit/player/jvm/core/data/DataModelWithParser; +public final fun getFlow ()Lcom/intuit/player/jvm/core/flow/Flow; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public abstract class com/intuit/player/jvm/core/player/state/PlayerFlowState : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/player/state/PlayerFlowState$Companion; - public final fun getRef ()Ljava/lang/String; - public abstract fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field Companion Lcom/intuit/player/jvm/core/player/state/PlayerFlowState$Companion; +public final fun getRef ()Ljava/lang/String; +public abstract fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; } public final class com/intuit/player/jvm/core/player/state/PlayerFlowState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/player/state/PlayerFlowStateKt { - public static final fun fail (Lcom/intuit/player/jvm/core/player/state/InProgressState;Ljava/lang/String;)V - public static final fun getCompletedState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/CompletedState; - public static final fun getCurrentFlowState (Lcom/intuit/player/jvm/core/player/state/InProgressState;)Lcom/intuit/player/jvm/core/player/state/NamedState; - public static final fun getCurrentView (Lcom/intuit/player/jvm/core/player/state/InProgressState;)Lcom/intuit/player/jvm/core/view/View; - public static final fun getErrorState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/ErrorState; - public static final fun getInProgressState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/InProgressState; - public static final fun getLastViewUpdate (Lcom/intuit/player/jvm/core/player/state/InProgressState;)Lcom/intuit/player/jvm/core/asset/Asset; - public static final fun getNotStartedState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/NotStartedState; - public static final fun getReleasedState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/ReleasedState; +public static final fun fail (Lcom/intuit/player/jvm/core/player/state/InProgressState;Ljava/lang/String;)V +public static final fun getCompletedState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/CompletedState; +public static final fun getCurrentFlowState (Lcom/intuit/player/jvm/core/player/state/InProgressState;)Lcom/intuit/player/jvm/core/player/state/NamedState; +public static final fun getCurrentView (Lcom/intuit/player/jvm/core/player/state/InProgressState;)Lcom/intuit/player/jvm/core/view/View; +public static final fun getErrorState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/ErrorState; +public static final fun getInProgressState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/InProgressState; +public static final fun getLastViewUpdate (Lcom/intuit/player/jvm/core/player/state/InProgressState;)Lcom/intuit/player/jvm/core/asset/Asset; +public static final fun getNotStartedState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/NotStartedState; +public static final fun getReleasedState (Lcom/intuit/player/jvm/core/player/Player;)Lcom/intuit/player/jvm/core/player/state/ReleasedState; } public final class com/intuit/player/jvm/core/player/state/ReleasedState : com/intuit/player/jvm/core/player/state/PlayerFlowState, com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field INSTANCE Lcom/intuit/player/jvm/core/player/state/ReleasedState; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; +public static final field INSTANCE Lcom/intuit/player/jvm/core/player/state/ReleasedState; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public fun getStatus ()Lcom/intuit/player/jvm/core/player/PlayerFlowStatus; } public abstract interface class com/intuit/player/jvm/core/plugins/JSPluginWrapper : com/intuit/player/jvm/core/bridge/NodeWrapper, com/intuit/player/jvm/core/plugins/RuntimePlugin { - public abstract fun getInstance ()Lcom/intuit/player/jvm/core/bridge/Node; - public abstract fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public abstract fun getInstance ()Lcom/intuit/player/jvm/core/bridge/Node; +public abstract fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/plugins/JSPluginWrapper$DefaultImpls { - public static fun getNode (Lcom/intuit/player/jvm/core/plugins/JSPluginWrapper;)Lcom/intuit/player/jvm/core/bridge/Node; +public static fun getNode (Lcom/intuit/player/jvm/core/plugins/JSPluginWrapper;)Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/plugins/JSPluginWrapper$Serializer : com/intuit/player/jvm/core/bridge/serialization/serializers/NodeWrapperSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/plugins/JSPluginWrapper$Serializer; +public static final field INSTANCE Lcom/intuit/player/jvm/core/plugins/JSPluginWrapper$Serializer; } public abstract class com/intuit/player/jvm/core/plugins/JSScriptPluginWrapper : com/intuit/player/jvm/core/plugins/JSPluginWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper$Companion; - protected field instance Lcom/intuit/player/jvm/core/bridge/Node; - public fun (Ljava/lang/String;Ljava/lang/String;)V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun apply (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)V - public final fun buildInstance (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; - public static synthetic fun buildInstance$default (Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getInstance ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getName ()Ljava/lang/String; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - protected final fun getScript ()Ljava/lang/String; - protected final fun setInstance (Lcom/intuit/player/jvm/core/bridge/Node;)V +public static final field Companion Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper$Companion; +protected field instance Lcom/intuit/player/jvm/core/bridge/Node; +public fun (Ljava/lang/String;Ljava/lang/String;)V +public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V +public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public fun apply (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)V +public final fun buildInstance (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/Node; +public static synthetic fun buildInstance$default (Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper;Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;Ljava/lang/String;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getInstance ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getName ()Ljava/lang/String; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +protected final fun getScript ()Ljava/lang/String; +protected final fun setInstance (Lcom/intuit/player/jvm/core/bridge/Node;)V } public final class com/intuit/player/jvm/core/plugins/JSScriptPluginWrapper$Companion { - public final fun from (Ljava/lang/String;Ljava/lang/String;)Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper; - public final fun from (Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper; - public static synthetic fun from$default (Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper; +public final fun from (Ljava/lang/String;Ljava/lang/String;)Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper; +public final fun from (Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper; +public static synthetic fun from$default (Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper; } public final class com/intuit/player/jvm/core/plugins/JSScriptPluginWrapperKt { - public static final fun PlayerPluginException (Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper;Ljava/lang/String;)Lcom/intuit/player/jvm/core/plugins/PlayerPluginException; +public static final fun PlayerPluginException (Lcom/intuit/player/jvm/core/plugins/JSScriptPluginWrapper;Ljava/lang/String;)Lcom/intuit/player/jvm/core/plugins/PlayerPluginException; } public abstract interface class com/intuit/player/jvm/core/plugins/LoggerPlugin : com/intuit/player/jvm/core/plugins/PlayerPlugin { - public abstract fun apply (Lcom/intuit/player/jvm/core/player/Player;)V - public abstract fun debug ([Ljava/lang/Object;)V - public abstract fun error ([Ljava/lang/Object;)V - public abstract fun info ([Ljava/lang/Object;)V - public abstract fun trace ([Ljava/lang/Object;)V - public abstract fun warn ([Ljava/lang/Object;)V +public abstract fun apply (Lcom/intuit/player/jvm/core/player/Player;)V +public abstract fun debug ([Ljava/lang/Object;)V +public abstract fun error ([Ljava/lang/Object;)V +public abstract fun info ([Ljava/lang/Object;)V +public abstract fun trace ([Ljava/lang/Object;)V +public abstract fun warn ([Ljava/lang/Object;)V } public final class com/intuit/player/jvm/core/plugins/LoggerPlugin$DefaultImpls { - public static fun apply (Lcom/intuit/player/jvm/core/plugins/LoggerPlugin;Lcom/intuit/player/jvm/core/player/Player;)V +public static fun apply (Lcom/intuit/player/jvm/core/plugins/LoggerPlugin;Lcom/intuit/player/jvm/core/player/Player;)V } public final class com/intuit/player/jvm/core/plugins/LoggerPluginKt { - public static final fun getLogger (Lcom/intuit/player/jvm/core/plugins/Pluggable;)Lcom/intuit/player/jvm/core/plugins/LoggerPlugin; +public static final fun getLogger (Lcom/intuit/player/jvm/core/plugins/Pluggable;)Lcom/intuit/player/jvm/core/plugins/LoggerPlugin; } public abstract interface class com/intuit/player/jvm/core/plugins/PlayerPlugin : com/intuit/player/jvm/core/plugins/Plugin { - public abstract fun apply (Lcom/intuit/player/jvm/core/player/Player;)V +public abstract fun apply (Lcom/intuit/player/jvm/core/player/Player;)V } public final class com/intuit/player/jvm/core/plugins/PlayerPluginException : com/intuit/player/jvm/core/player/PlayerException { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getPluginName ()Ljava/lang/String; +public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V +public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final fun getPluginName ()Ljava/lang/String; } public abstract interface class com/intuit/player/jvm/core/plugins/Pluggable { - public abstract fun getPlugins ()Ljava/util/List; +public abstract fun getPlugins ()Ljava/util/List; } public abstract interface class com/intuit/player/jvm/core/plugins/Plugin { } public final class com/intuit/player/jvm/core/plugins/PluginKt { - public static final fun warnPluginNotFound (Lcom/intuit/player/jvm/core/player/Player;Ljava/lang/String;)Lcom/intuit/player/jvm/core/plugins/Plugin; +public static final fun warnPluginNotFound (Lcom/intuit/player/jvm/core/player/Player;Ljava/lang/String;)Lcom/intuit/player/jvm/core/plugins/Plugin; } public abstract interface class com/intuit/player/jvm/core/plugins/RuntimePlugin : com/intuit/player/jvm/core/plugins/Plugin { - public abstract fun apply (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)V +public abstract fun apply (Lcom/intuit/player/jvm/core/bridge/runtime/Runtime;)V } public class com/intuit/player/jvm/core/plugins/logging/PlayerLoggingConfig { - public fun ()V +public fun ()V } public abstract interface class com/intuit/player/jvm/core/plugins/logging/PlayerLoggingContainer { - public abstract fun getFactory ()Lcom/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory; +public abstract fun getFactory ()Lcom/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory; } public abstract interface class com/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory { - public abstract fun create (Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/plugins/LoggerPlugin; +public abstract fun create (Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/plugins/LoggerPlugin; } public final class com/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory$DefaultImpls { - public static synthetic fun create$default (Lcom/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/plugins/LoggerPlugin; +public static synthetic fun create$default (Lcom/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/plugins/LoggerPlugin; } public final class com/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactoryKt { - public static final fun config (Lcom/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory; - public static final fun getLoggerContainers ()Ljava/util/List; - public static final fun getLoggers ()Ljava/util/List; +public static final fun config (Lcom/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory;Lkotlin/jvm/functions/Function1;)Lcom/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory; +public static final fun getLoggerContainers ()Ljava/util/List; +public static final fun getLoggers ()Ljava/util/List; } public final class com/intuit/player/jvm/core/resolver/ResolveOptions : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/resolver/ResolveOptions$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getValidation ()Lcom/intuit/player/jvm/core/validation/Validation; +public static final field Companion Lcom/intuit/player/jvm/core/resolver/ResolveOptions$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getValidation ()Lcom/intuit/player/jvm/core/validation/Validation; } public final class com/intuit/player/jvm/core/resolver/ResolveOptions$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/resolver/Resolver : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/resolver/Resolver$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public final fun getHooks ()Lcom/intuit/player/jvm/core/resolver/Resolver$Hooks; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public static final field Companion Lcom/intuit/player/jvm/core/resolver/Resolver$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public final fun getHooks ()Lcom/intuit/player/jvm/core/resolver/Resolver$Hooks; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/resolver/Resolver$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/resolver/Resolver$Hooks : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/resolver/Resolver$Hooks$Companion; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getResolveOptions ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook2; +public static final field Companion Lcom/intuit/player/jvm/core/resolver/Resolver$Hooks$Companion; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getResolveOptions ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook2; } public final class com/intuit/player/jvm/core/resolver/Resolver$Hooks$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface annotation class com/intuit/player/jvm/core/utils/InternalPlayerApi : java/lang/annotation/Annotation { } public final class com/intuit/player/jvm/core/validation/BindingInstance : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/validation/BindingInstance$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public final fun asString ()Ljava/lang/String; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun parent ()Lcom/intuit/player/jvm/core/validation/BindingInstance; - public fun toString ()Ljava/lang/String; +public static final field Companion Lcom/intuit/player/jvm/core/validation/BindingInstance$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public final fun asString ()Ljava/lang/String; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun parent ()Lcom/intuit/player/jvm/core/validation/BindingInstance; +public fun toString ()Ljava/lang/String; } public final class com/intuit/player/jvm/core/validation/BindingInstance$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/validation/ErrorValidationResponse : com/intuit/player/jvm/core/validation/ValidationResponse { - public static final field Companion Lcom/intuit/player/jvm/core/validation/ErrorValidationResponse$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public static final field Companion Lcom/intuit/player/jvm/core/validation/ErrorValidationResponse$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/validation/ErrorValidationResponse$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/validation/Validation : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/validation/Validation$Companion; - public final fun getAll ()Lcom/intuit/player/jvm/core/bridge/global/JSMap; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public static final field Companion Lcom/intuit/player/jvm/core/validation/Validation$Companion; +public final fun getAll ()Lcom/intuit/player/jvm/core/bridge/global/JSMap; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/validation/Validation$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/validation/ValidationController : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/validation/ValidationController$Companion; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun validateView ()Lcom/intuit/player/jvm/core/validation/ValidationInfo; +public static final field Companion Lcom/intuit/player/jvm/core/validation/ValidationController$Companion; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun validateView ()Lcom/intuit/player/jvm/core/validation/ValidationInfo; } public final class com/intuit/player/jvm/core/validation/ValidationController$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/validation/ValidationInfo { - public static final field Companion Lcom/intuit/player/jvm/core/validation/ValidationInfo$Companion; - public synthetic fun (IZLcom/intuit/player/jvm/core/bridge/global/JSMap;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V - public fun (ZLcom/intuit/player/jvm/core/bridge/global/JSMap;)V - public synthetic fun (ZLcom/intuit/player/jvm/core/bridge/global/JSMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Z - public final fun component2 ()Lcom/intuit/player/jvm/core/bridge/global/JSMap; - public final fun copy (ZLcom/intuit/player/jvm/core/bridge/global/JSMap;)Lcom/intuit/player/jvm/core/validation/ValidationInfo; - public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/validation/ValidationInfo;ZLcom/intuit/player/jvm/core/bridge/global/JSMap;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/validation/ValidationInfo; - public fun equals (Ljava/lang/Object;)Z - public final fun getCanTransition ()Z - public final fun getValidations ()Lcom/intuit/player/jvm/core/bridge/global/JSMap; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; - public static final fun write$Self (Lcom/intuit/player/jvm/core/validation/ValidationInfo;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V +public static final field Companion Lcom/intuit/player/jvm/core/validation/ValidationInfo$Companion; +public synthetic fun (IZLcom/intuit/player/jvm/core/bridge/global/JSMap;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V +public fun (ZLcom/intuit/player/jvm/core/bridge/global/JSMap;)V +public synthetic fun (ZLcom/intuit/player/jvm/core/bridge/global/JSMap;ILkotlin/jvm/internal/DefaultConstructorMarker;)V +public final fun component1 ()Z +public final fun component2 ()Lcom/intuit/player/jvm/core/bridge/global/JSMap; +public final fun copy (ZLcom/intuit/player/jvm/core/bridge/global/JSMap;)Lcom/intuit/player/jvm/core/validation/ValidationInfo; +public static synthetic fun copy$default (Lcom/intuit/player/jvm/core/validation/ValidationInfo;ZLcom/intuit/player/jvm/core/bridge/global/JSMap;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/validation/ValidationInfo; +public fun equals (Ljava/lang/Object;)Z +public final fun getCanTransition ()Z +public final fun getValidations ()Lcom/intuit/player/jvm/core/bridge/global/JSMap; +public fun hashCode ()I +public fun toString ()Ljava/lang/String; +public static final fun write$Self (Lcom/intuit/player/jvm/core/validation/ValidationInfo;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class com/intuit/player/jvm/core/validation/ValidationInfo$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/intuit/player/jvm/core/validation/ValidationInfo$$serializer; - public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/validation/ValidationInfo; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/validation/ValidationInfo;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +public static final field INSTANCE Lcom/intuit/player/jvm/core/validation/ValidationInfo$$serializer; +public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun childSerializers ()[Lkotlinx/serialization/KSerializer; +public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/intuit/player/jvm/core/validation/ValidationInfo; +public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; +public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; +public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/intuit/player/jvm/core/validation/ValidationInfo;)V +public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/validation/ValidationInfo$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/validation/ValidationKt { - public static final fun getWarningsAndErrors (Lcom/intuit/player/jvm/core/validation/Validation;)Lcom/intuit/player/jvm/core/bridge/global/JSMap; +public static final fun getWarningsAndErrors (Lcom/intuit/player/jvm/core/validation/Validation;)Lcom/intuit/player/jvm/core/bridge/global/JSMap; } public abstract class com/intuit/player/jvm/core/validation/ValidationResponse : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/validation/ValidationResponse$Companion; - public final fun getMessage ()Ljava/lang/String; - public final fun getParameters ()Ljava/util/Map; +public static final field Companion Lcom/intuit/player/jvm/core/validation/ValidationResponse$Companion; +public final fun getMessage ()Ljava/lang/String; +public final fun getParameters ()Ljava/util/Map; } public final class com/intuit/player/jvm/core/validation/ValidationResponse$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/validation/WarningValidationResponse : com/intuit/player/jvm/core/validation/ValidationResponse { - public static final field Companion Lcom/intuit/player/jvm/core/validation/WarningValidationResponse$Companion; - public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V - public final fun dismiss ()V - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public static final field Companion Lcom/intuit/player/jvm/core/validation/WarningValidationResponse$Companion; +public fun (Lcom/intuit/player/jvm/core/bridge/Node;)V +public final fun dismiss ()V +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/validation/WarningValidationResponse$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/view/View : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/view/View$Companion; - public final fun getHooks ()Lcom/intuit/player/jvm/core/view/ViewHooks; - public final fun getLastUpdate ()Lcom/intuit/player/jvm/core/asset/Asset; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public static final field Companion Lcom/intuit/player/jvm/core/view/View$Companion; +public final fun getHooks ()Lcom/intuit/player/jvm/core/view/ViewHooks; +public final fun getLastUpdate ()Lcom/intuit/player/jvm/core/asset/Asset; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/view/View$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/view/ViewController : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/view/ViewController$Companion; - public final fun getCurrentView ()Lcom/intuit/player/jvm/core/view/View; - public final fun getHooks ()Lcom/intuit/player/jvm/core/view/ViewController$Hooks; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public static final field Companion Lcom/intuit/player/jvm/core/view/ViewController$Companion; +public final fun getCurrentView ()Lcom/intuit/player/jvm/core/view/View; +public final fun getHooks ()Lcom/intuit/player/jvm/core/view/ViewController$Hooks; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; } public final class com/intuit/player/jvm/core/view/ViewController$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/view/ViewController$Hooks : com/intuit/player/jvm/core/bridge/NodeWrapper { - public static final field Companion Lcom/intuit/player/jvm/core/view/ViewController$Hooks$Companion; - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getView ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public static final field Companion Lcom/intuit/player/jvm/core/view/ViewController$Hooks$Companion; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getView ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; } public final class com/intuit/player/jvm/core/view/ViewController$Hooks$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; +public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class com/intuit/player/jvm/core/view/ViewHooks : com/intuit/player/jvm/core/bridge/NodeWrapper { - public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; - public final fun getOnUpdate ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; - public final fun getResolver ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public fun getNode ()Lcom/intuit/player/jvm/core/bridge/Node; +public final fun getOnUpdate ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; +public final fun getResolver ()Lcom/intuit/player/jvm/core/bridge/hooks/NodeSyncHook1; } - diff --git a/jvm/core/deps.bzl b/jvm/core/deps.bzl index aac4a82f9..b28d93602 100644 --- a/jvm/core/deps.bzl +++ b/jvm/core/deps.bzl @@ -10,10 +10,12 @@ main_exports = [ "//jvm:kotlin_serialization", ] main_deps = main_exports + [ - "@maven//:org_jetbrains_kotlin_kotlin_reflect" + "@maven//:org_jetbrains_kotlin_kotlin_reflect", ] main_runtime_deps = [] # Test dependencies -test_deps = [] +test_deps = [ + "//plugins/reference-assets/jvm:reference-assets", +] test_runtime_deps = [] diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/asset/Asset.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/asset/Asset.kt deleted file mode 100644 index af3402da1..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/asset/Asset.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.intuit.player.jvm.core.asset - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.player.PlayerException -import kotlinx.serialization.Serializable - -/** Convenience helper to get some [value] from an [Asset] */ -public val Asset.value: String? get() = get("value") as? String - -/** Special [Node] type that also describes a player asset */ -@Serializable(with = Asset.Serializer::class) -public open class Asset(override val node: Node) : Node by node, NodeWrapper { - public val id: String get() = getString("id") ?: throw PlayerException("Asset's must be described with an ID") - public operator fun component1(): String = id - - public val type: String get() = getString("type") ?: throw PlayerException("Asset's must be described with a type") - public operator fun component2(): String = type - - public val metaData: MetaData? get() = getObject("metaData")?.let(::MetaData) - public operator fun component3(): MetaData? = metaData - - public fun getAsset(name: String): AssetWrapper? = getObject(name)?.let(::AssetWrapper) - - override fun equals(other: Any?): Boolean = node == other - - override fun hashCode(): Int = node.hashCode() - - override fun toString(): String = node.toString() - - internal object Serializer : NodeWrapperSerializer(::Asset) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/asset/AssetWrapper.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/asset/AssetWrapper.kt deleted file mode 100644 index 4ce4d9fd2..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/asset/AssetWrapper.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.intuit.player.jvm.core.asset - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.player.PlayerException -import kotlinx.serialization.Serializable - -/** - * Container for an [Asset] definition. [node] is exposed because there might be metaData at the wrapper level. - */ -@Serializable(with = AssetWrapper.Serializer::class) -public class AssetWrapper(override val node: Node) : NodeWrapper { - public val asset: Asset get() = node.getObject("asset") as? Asset - ?: throw PlayerException("AssetWrapper is not wrapping a valid asset") - - internal object Serializer : NodeWrapperSerializer(::AssetWrapper) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/asset/MetaData.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/asset/MetaData.kt deleted file mode 100644 index a594865a4..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/asset/MetaData.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.intuit.player.jvm.core.asset - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.getJson -import kotlinx.serialization.json.JsonElement - -/** - * Base structure containing common metadata fields. - * [node] is exposed to enable retrieval of other fields. - * This could also be done by extending this class. - */ -public open class MetaData(override val node: Node) : NodeWrapper { - - /** [JsonElement] representation of some [beacon] description */ - public val beacon: JsonElement get() = node.getJson("beacon") - - /** Common metadata element used to designate how an asset should present itself by describing the intent */ - public val role: String? get() = node.getString("role") - - /** [ref] is typically used to describe a reference to some external location */ - public val ref: String? get() = node.getString("ref") -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/JSErrorException.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/JSErrorException.kt deleted file mode 100644 index 1c891543b..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/JSErrorException.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.intuit.player.jvm.core.bridge - -import com.intuit.player.jvm.core.player.PlayerException - -public class JSErrorException(override val node: Node, cause: Throwable? = null) : PlayerException(node.getString("message") ?: "", cause), NodeWrapper { - - public val name: String get() = node.getString("name") ?: "Error" - - override val message: String - get() = "$name: ${super.message}" -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeConfig.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeConfig.kt deleted file mode 100644 index 48f92d0b9..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeConfig.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.intuit.player.jvm.core.bridge.runtime - -/** Base configuration for [Runtime] */ -public open class PlayerRuntimeConfig diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/FunctionSerializers.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/FunctionSerializers.kt deleted file mode 100644 index 3eedf724d..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/FunctionSerializers.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.intuit.player.jvm.core.bridge.serialization.serializers - -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.serialization.encoding.requireNodeDecoder -import com.intuit.player.jvm.core.bridge.serialization.encoding.requireNodeEncoder -import kotlinx.serialization.InternalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.PolymorphicKind -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildSerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlin.reflect.KCallable - -public sealed class FunctionLikeSerializer : KSerializer { - - final override val descriptor: SerialDescriptor = FunctionLikeSerializer.descriptor - - override fun deserialize(decoder: Decoder): T = decoder.requireNodeDecoder().decodeFunction() as T - - override fun serialize(encoder: Encoder, value: T) { - encoder.requireNodeEncoder().encodeFunction(value) - } - - public companion object { - @OptIn(InternalSerializationApi::class) - public val descriptor: SerialDescriptor = buildSerialDescriptor("FunctionLike", PolymorphicKind.OPEN) {} - } -} - -public class KCallableSerializer : FunctionLikeSerializer>() -public class InvokableSerializer : FunctionLikeSerializer>() -public class FunctionSerializer : FunctionLikeSerializer>() - -public inline fun > functionSerializer(): KSerializer = object : KSerializer by FunctionSerializer() as KSerializer { - override val descriptor: SerialDescriptor = SerialDescriptor(T::class.qualifiedName!!, FunctionSerializer().descriptor) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/NodeSerializableField.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/NodeSerializableField.kt deleted file mode 100644 index c022b4c36..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/NodeSerializableField.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.intuit.player.jvm.core.bridge.serialization.serializers - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.format.serializer -import com.intuit.player.jvm.core.player.PlayerException -import kotlinx.serialization.KSerializer -import kotlin.reflect.KProperty - -internal class NodeSerializableField(private val provider: () -> Node, private val serializer: KSerializer, private val name: String? = null) { - - operator fun getValue(thisRef: Any?, property: KProperty<*>): T = provider() - .getSerializable(name ?: property.name, serializer) - ?: throw PlayerException("Could not deserialize $name") - - companion object { - fun NodeWrapper.NodeSerializableField(serializer: KSerializer, name: String? = null) = NodeSerializableField(::node, serializer, name) - - inline fun NodeWrapper.NodeSerializableField(name: String? = null) = NodeSerializableField(::node, node.format.serializer(), name) - } -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer.kt deleted file mode 100644 index 76e561e97..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/ThrowableSerializer.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.intuit.player.jvm.core.bridge.serialization.serializers - -import com.intuit.player.jvm.core.bridge.JSErrorException -import com.intuit.player.jvm.core.bridge.serialization.encoding.requireNodeDecoder -import com.intuit.player.jvm.core.bridge.serialization.encoding.requireNodeEncoder -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.core.utils.InternalPlayerApi -import kotlinx.serialization.* -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.builtins.nullable -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* - -// the serializer knows _how_ to construct the `Throwable` type from a JS Node -// and _how_ to deconstruct a `Throwable` into primitives to serialize -@Serializer(forClass = Throwable::class) -public class ThrowableSerializer : KSerializer { - - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("kotlin.Throwable") { - element("serialized", Boolean.serializer().descriptor.nullable, isOptional = true) - element("message", String.serializer().descriptor.nullable, isOptional = true) - element("stack", String.serializer().descriptor.nullable, isOptional = true) - element("stackTrace", serializedStackTraceSerializer.descriptor.nullable, isOptional = true) - element("cause", defer { ThrowableSerializer().descriptor.nullable }, isOptional = true) - } - - override fun deserialize(decoder: Decoder): PlayerException = decoder.decodeStructure(descriptor) { - var serialized = false - var message = "" - var stackTrace: Array = emptyArray() - var cause: Throwable? = null - - fun decodeStackTraceFromStack(stack: String? = decodeNullableSerializableElement(descriptor, 2, String.serializer().nullable)): Array = stack - ?.split("\n") - ?.mapNotNull(errorStackReg::find) - ?.map(MatchResult::destructured) - ?.map { (className, methodName, fileName, lineNumber) -> - StackTraceElement(className, methodName, fileName, lineNumber.toIntOrNull() ?: -2) - }?.toTypedArray() ?: emptyArray() - - fun decodeSerializedStackTrace(): Array = decodeNullableSerializableElement(descriptor, 3, serializedStackTraceSerializer.nullable) - ?.map { (className, methodName, fileName, lineNumber) -> - StackTraceElement(className, methodName, fileName, lineNumber) - }?.toTypedArray() ?: emptyArray() - - if (decodeSequentially()) { - serialized = decodeNullableSerializableElement(descriptor, 0, Boolean.serializer().nullable) ?: false - - if (serialized) { - message = decodeNullableSerializableElement(descriptor, 1, String.serializer().nullable) ?: "" - stackTrace = decodeSerializedStackTrace() - cause = decodeNullableSerializableElement(descriptor, 4, nullable) - } else stackTrace = decodeStackTraceFromStack() - } else while (true) { - when (val index = decodeElementIndex(descriptor)) { - 0 -> serialized = decodeNullableSerializableElement(descriptor, 0, Boolean.serializer().nullable) ?: false - 1 -> message = decodeNullableSerializableElement(descriptor, 1, String.serializer().nullable) ?: "" - 2 -> stackTrace = decodeStackTraceFromStack() - 3 -> stackTrace = decodeSerializedStackTrace() - 4 -> cause = decodeNullableSerializableElement(descriptor, 4, nullable) - CompositeDecoder.DECODE_DONE -> break - else -> error("Unexpected index: $index") - } - } - - if (serialized) PlayerException(message, cause) else { - val error = decoder.requireNodeDecoder().decodeNode() - stackTrace = decodeStackTraceFromStack(error.getString("stack")) - JSErrorException(error) - }.apply { setStackTrace(stackTrace) } - } - - override fun serialize(encoder: Encoder, value: Throwable) { - when (value) { - is JSErrorException -> encoder.requireNodeEncoder().encodeNode(value.node) - else -> encoder.encodeStructure(descriptor) { - encodeBooleanElement(descriptor, 0, true) - encodeNullableSerializableElement(descriptor, 1, String.serializer(), value.message) - encodeStringElement(descriptor, 2, value.stackTraceToString()) - encodeSerializableElement( - descriptor, 3, serializedStackTraceSerializer, - value.stackTrace.map { stackTraceElement: StackTraceElement -> - SerializableStackTraceElement( - stackTraceElement.className, - stackTraceElement.methodName, - stackTraceElement.fileName, - stackTraceElement.lineNumber - ) - } - ) - encodeNullableSerializableElement(descriptor, 4, nullable, value.cause) - } - } - } - - @InternalPlayerApi - @Serializable - public data class SerializableStackTraceElement( - val className: String?, - val methodName: String?, - val fileName: String?, - val lineNumber: Int, - ) - - public companion object { - private val errorStackReg: Regex = - """(?<=at )(?[A-z\d\s.<>$]*(?=\.))?\.?(?[A-z\d\s.<>$]*(?= ))? ?\(?(?:.*, )?(?[A-z\d\s.<>$]*)?:?(?[A-z\d\s.<>$]*)?:?(?[A-z\d\s.<>$]*)?\)?$""".toRegex( - RegexOption.MULTILINE - ) - - private val serializedStackTraceSerializer = ListSerializer(SerializableStackTraceElement.serializer()) - } -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowInstance.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowInstance.kt deleted file mode 100644 index b48158bd9..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowInstance.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.intuit.player.jvm.core.flow - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncHook1 -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncHook2 -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncWaterfallHook1 -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncWaterfallHook2 -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializableField.Companion.NodeSerializableField -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.flow.state.NavigationFlowState -import com.intuit.player.jvm.core.player.state.NamedState -import kotlinx.serialization.Serializable -import kotlinx.serialization.builtins.nullable -import kotlinx.serialization.builtins.serializer - -@Serializable(FlowInstance.Serializer::class) -public class FlowInstance(override val node: Node) : NodeWrapper, Transition { - - public val id: String by NodeSerializableField() - - public val hooks: Hooks by NodeSerializableField(Hooks.serializer()) - - public val currentState: NamedState? get() = node.getSerializable("currentState", NamedState.serializer()) - - override fun transition(state: String, options: TransitionOptions?) { - node.getFunction("transition")?.invoke(state, options) - } - - @Serializable(Hooks.Serializer::class) - public class Hooks internal constructor(override val node: Node) : NodeWrapper { - /** A callback when the onStart node was present */ - public val onStart: NodeSyncHook1 get() = NodeSyncHook1( - node.getObject("onStart")!!, - GenericSerializer() - ) - - /** A callback when the onEnd node was present */ - public val onEnd: NodeSyncHook1 get() = NodeSyncHook1( - node.getObject("onEnd")!!, - GenericSerializer() - ) - - /** A chance to manipulate the flow-node used to calculate the given transition used */ - public val beforeTransition: NodeSyncWaterfallHook2 get() = NodeSyncWaterfallHook2( - node.getObject("beforeTransition")!!, - NavigationFlowState.serializer(), - String.serializer(), - ) - - /** A chance to manipulate the flow-node calculated after a transition */ - public val resolveTransitionNode: NodeSyncWaterfallHook1 get() = NodeSyncWaterfallHook1( - node.getObject("resolveTransitionNode")!!, - NavigationFlowState.serializer(), - ) - - /** A callback when a transition from 1 state to another was made */ - public val transition: NodeSyncHook2 get() = NodeSyncHook2( - node.getObject("transition")!!, - NamedState.serializer().nullable, - NamedState.serializer() - ) - - internal object Serializer : NodeWrapperSerializer(::Hooks) - } - - internal object Serializer : NodeWrapperSerializer(::FlowInstance) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowResult.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowResult.kt deleted file mode 100644 index 42d8bc839..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowResult.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.intuit.player.jvm.core.flow - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.getJson -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.flow.state.NavigationFlowEndState -import com.intuit.player.jvm.core.flow.state.NavigationFlowState -import com.intuit.player.jvm.core.player.PlayerException -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull - -@Serializable(with = FlowResult.Serializer::class) -public class FlowResult(override val node: Node) : NodeWrapper { - - /** End state describing _how_ the flow ended (forwards, backwards, etc) */ - public val endState: NavigationFlowEndState - get() = node.getSerializable("endState", NavigationFlowState.serializer()) - as? NavigationFlowEndState ?: throw PlayerException("flow result not defined") - - /** The serialized data-model */ - public val data: JsonElement get() = node.getJson("data") ?: JsonNull - - override fun equals(other: Any?): Boolean = node == other - - override fun hashCode(): Int = node.hashCode() - - public object Serializer : NodeWrapperSerializer(::FlowResult) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/HeadlessPlayer.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/HeadlessPlayer.kt deleted file mode 100644 index f2664e4e3..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/HeadlessPlayer.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.intuit.player.jvm.core.player - -import com.intuit.player.jvm.core.bridge.Completable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.runtime.add -import com.intuit.player.jvm.core.bridge.runtime.runtimeFactory -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializableField.Companion.NodeSerializableField -import com.intuit.player.jvm.core.logger.TapableLogger -import com.intuit.player.jvm.core.player.HeadlessPlayer.Companion.bundledSource -import com.intuit.player.jvm.core.player.state.CompletedState -import com.intuit.player.jvm.core.player.state.PlayerFlowState -import com.intuit.player.jvm.core.player.state.ReleasedState -import com.intuit.player.jvm.core.plugins.* -import com.intuit.player.jvm.core.plugins.logging.loggers -import java.net.URL - -/** - * Headless [Player] wrapping a core JS player with a [Runtime]. The [player] will - * be instantiated from the [bundledSource] unless supplied with - * a different [source] to read from. - * - * Though this accepts a collection of generic [Plugin]s, this - * will only [Plugin.apply] [plugins] that are known and appropriate - * for this context. Additionally, certain plugins will be filtered - * and applied in a specific order that guarantees safety. - * - * Allowed [Plugin]s: - * - [RuntimePlugin] - * - [JSPluginWrapper] - * - [PlayerPlugin] - */ -public class HeadlessPlayer public constructor( - override val plugins: List, - public val runtime: Runtime<*> = runtimeFactory.create(), - private val source: URL = bundledSource, -) : Player(), NodeWrapper { - - /** Convenience constructor to allow [plugins] to be passed as varargs */ - @JvmOverloads public constructor( - vararg plugins: Plugin, - runtime: Runtime<*> = runtimeFactory.create(), - source: URL = bundledSource - ) : this(plugins.toList(), runtime, source) - - private val player: Node - - override val node: Node by ::player - - override val logger: TapableLogger by NodeSerializableField(TapableLogger.serializer()) - - override val hooks: Hooks by NodeSerializableField(Hooks.serializer()) - - override val state: PlayerFlowState get() = if (player.isReleased()) ReleasedState else - player.getFunction("getState")!!().deserialize(PlayerFlowState.serializer()) - - init { - /** 1. load source into the [runtime] and release lock */ - runtime.execute(source.readText()) - - /** 2. apply JS plugins and build [JSPlayerConfig] */ - val config = plugins - .filterIsInstance() - .onEach { it.apply(runtime) } - .filterIsInstance() - .let(::JSPlayerConfig) - - /** 3. Build JS headless player */ - runtime.add("config", config) - player = runtime.execute("new Player.Player(config)") as? Node - ?: throw PlayerException("Couldn't create backing JS Player w/ config: $config") - - /** 4. apply to player plugins */ - plugins - .filterIsInstance() - .onEach { it.apply(this) } - - /** 5. apply logger plugins */ - loggers - .filterNot { plugins.map { it::class }.contains(it::class) } - .forEach(logger::addHandler) - } - - override fun start(flow: String): Completable = start(runtime.execute("($flow)") as Node) - - public fun start(flow: Node): Completable = PlayerCompletable(player.getFunction("start")!!.invoke(flow)) - - /** Start a [flow] and subscribe to the result */ - public fun start(flow: Node, onComplete: (Result) -> Unit): Completable = - start(flow).apply { - onComplete(onComplete) - } - - override fun release() { - // TODO: Call state hook! - if (!runtime.isReleased()) { - runtime.release() - } - } - - internal companion object { - - private const val bundledSourcePath = "core/player/dist/player.prod.js" - - /** Gets [URL] of the bundled source */ - private val bundledSource get() = this::class.java - .classLoader.getResource(bundledSourcePath)!! - } -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/JSPlayerConfig.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/JSPlayerConfig.kt deleted file mode 100644 index 0cae7081c..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/JSPlayerConfig.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.intuit.player.jvm.core.player - -import com.intuit.player.jvm.core.plugins.JSPluginWrapper -import kotlinx.serialization.Serializable - -/** Expected structure of JS object used to instantiate the core JS player */ -@Serializable -public data class JSPlayerConfig( - val plugins: List = listOf(), -) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerFlowStatus.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerFlowStatus.kt deleted file mode 100644 index d49098848..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerFlowStatus.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.intuit.player.jvm.core.player - -/** All possible enumerations of player states */ -public enum class PlayerFlowStatus(public val value: String) { - NOT_STARTED("not-started"), - IN_PROGRESS("in-progress"), - COMPLETED("completed"), - ERROR("error"), - /** Only a JVM player state */ - RELEASED("released"); - - public companion object { - /** Flexibly and safely get the corresponding [PlayerFlowStatus] from the [value] */ - public fun from(value: Any?): PlayerFlowStatus = when (value) { - NOT_STARTED.value -> NOT_STARTED - IN_PROGRESS.value -> IN_PROGRESS - COMPLETED.value -> COMPLETED - RELEASED.value -> RELEASED - else -> ERROR - } - } -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/state/NamedState.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/state/NamedState.kt deleted file mode 100644 index 8a054347f..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/state/NamedState.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.intuit.player.jvm.core.player.state - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.flow.state.NavigationFlowState -import kotlinx.serialization.Serializable - -@Serializable(NamedState.Serializer::class) -public class NamedState internal constructor(override val node: Node) : NodeWrapper { - /** The name of the navigation node */ - public val name: String - get() = node.getString("name") ?: "" - - /** The navigation node */ - public val value: NavigationFlowState - get() = node.getSerializable("value", NavigationFlowState.serializer())!! - - override fun toString(): String = (name to value).toString() - - internal object Serializer : NodeWrapperSerializer(::NamedState) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/logging/PlayerLoggingConfig.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/logging/PlayerLoggingConfig.kt deleted file mode 100644 index 71d9a5d67..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/logging/PlayerLoggingConfig.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.intuit.player.jvm.core.plugins.logging - -/** Base configuration for [Logging] */ -public open class PlayerLoggingConfig diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/resolver/ResolveOptions.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/resolver/ResolveOptions.kt deleted file mode 100644 index e24380b71..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/resolver/ResolveOptions.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.intuit.player.jvm.core.resolver - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.validation.Validation -import kotlinx.serialization.Serializable - -@Serializable(with = ResolveOptions.Serializer::class) -public class ResolveOptions(override val node: Node) : NodeWrapper { - - public val validation: Validation? get() = node.getSerializable("validation", Validation.serializer()) - - internal object Serializer : NodeWrapperSerializer(::ResolveOptions) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/resolver/Resolver.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/resolver/Resolver.kt deleted file mode 100644 index a89d53f3c..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/resolver/Resolver.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.intuit.player.jvm.core.resolver - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncWaterfallHook2 -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializableField.Companion.NodeSerializableField -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import kotlinx.serialization.Serializable - -@Serializable(with = Resolver.Serializer::class) -public class Resolver(override val node: Node) : NodeWrapper { - - public val hooks: Hooks by NodeSerializableField(Hooks.serializer()) - - @Serializable(with = Hooks.Serializer::class) - public class Hooks internal constructor(override val node: Node) : NodeWrapper { - public val resolveOptions: NodeSyncWaterfallHook2 - get() = NodeSyncWaterfallHook2( - node.getObject("resolveOptions")!!, - ResolveOptions.serializer(), - NodeSerializer() - ) - - internal object Serializer : NodeWrapperSerializer(::Hooks) - } - - internal object Serializer : NodeWrapperSerializer(::Resolver) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/BindingInstance.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/BindingInstance.kt deleted file mode 100644 index 0c7d1f2e1..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/BindingInstance.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.intuit.player.jvm.core.validation - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import kotlinx.serialization.Serializable - -@Serializable(with = BindingInstance.Serializer::class) -public class BindingInstance(override val node: Node) : NodeWrapper { - - public fun asString(): String = node.getFunction("asString")!!() - - public fun parent(): BindingInstance? = node.getFunction("parent")?.invoke() - - override fun toString(): String = asString() - - internal object Serializer : NodeWrapperSerializer(::BindingInstance) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/Validation.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/Validation.kt deleted file mode 100644 index d297781db..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/Validation.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.intuit.player.jvm.core.validation - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.deserialize -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.resolver.Resolver -import kotlinx.serialization.Serializable - -/** Limited definition of the player validation object exposed by [Resolver.Hooks.resolveOptions] */ -@Serializable(with = Validation.Serializer::class) -public class Validation internal constructor(override val node: Node) : NodeWrapper { - /** get all outstanding validations on current flow */ - public fun getAll(): ValidationMapping? = node.getFunction("getAll")?.invoke()?.deserialize() - - internal object Serializer : NodeWrapperSerializer(::Validation) -} - -public fun Validation.getWarningsAndErrors(): ValidationMapping? = getAll() diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/ValidationController.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/ValidationController.kt deleted file mode 100644 index 7ecbc156d..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/ValidationController.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.intuit.player.jvm.core.validation - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.deserialize -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.data.get -import com.intuit.player.jvm.core.data.set -import kotlinx.serialization.Serializable - -/** Limited definition of the player validationController to enable validating the current view */ -@Serializable(with = ValidationController.Serializer::class) -public class ValidationController internal constructor(override val node: Node) : NodeWrapper { - - /** Get information on whether transition is allowed along with potential blocking validations */ - public fun validateView(): ValidationInfo = - node.getFunction("validateView")!!.invoke().deserialize() - - internal object Serializer : NodeWrapperSerializer(::ValidationController) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/ValidationResponse.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/ValidationResponse.kt deleted file mode 100644 index fd23af5f7..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/ValidationResponse.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.intuit.player.jvm.core.validation - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.deserialize -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.PolymorphicNodeWrapperSerializer -import com.intuit.player.jvm.core.player.PlayerException -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable - -@Serializable(with = ValidationResponseSerializer::class) -public sealed class ValidationResponse : NodeWrapper { - /** The validation message to show to the user */ - public val message: String get() = node.getString("message")!! - - /** List of parameters associated with a validation. */ - public val parameters: Map? get() = node.getObject("parameters")?.deserialize() -} - -@Serializable(with = WarningValidationResponse.Serializer::class) -public class WarningValidationResponse(override val node: Node) : ValidationResponse() { - - /** Warning validations can be dismissed without correcting the error */ - public fun dismiss() { - node.getFunction("dismiss")?.invoke() - } - - internal object Serializer : NodeWrapperSerializer(::WarningValidationResponse) -} - -@Serializable(with = ErrorValidationResponse.Serializer::class) -public class ErrorValidationResponse(override val node: Node) : ValidationResponse() { - internal object Serializer : NodeWrapperSerializer(::ErrorValidationResponse) -} - -internal class ValidationResponseSerializer : PolymorphicNodeWrapperSerializer() { - override fun selectDeserializer(node: Node): KSerializer { - return when (node.getString("severity")) { - "warning" -> WarningValidationResponse.serializer() - "error" -> ErrorValidationResponse.serializer() - else -> throw PlayerException("ValidationResponse must be Error or Warning") - } - } -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/View.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/View.kt deleted file mode 100644 index 4a5efa194..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/View.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.intuit.player.jvm.core.view - -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import kotlinx.serialization.Serializable - -/** Limited definition of a stateful view instance from a flow */ -@Serializable(with = View.Serializer::class) -public class View internal constructor(override val node: Node) : NodeWrapper { - public val hooks: ViewHooks get() = ViewHooks(node.getObject("hooks")!!) - - public val lastUpdate: Asset? get() = node.getObject("lastUpdate") as? Asset - - internal object Serializer : NodeWrapperSerializer(::View) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/ViewControllerHooks.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/ViewControllerHooks.kt deleted file mode 100644 index b2e4c6a2e..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/ViewControllerHooks.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.intuit.player.jvm.core.view - -@Deprecated( - "Replaced with ViewController.Hooks", - ReplaceWith("ViewController.Hooks"), - DeprecationLevel.WARNING -) -public typealias ViewControllerHooks = ViewController.Hooks diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/ViewHooks.kt b/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/ViewHooks.kt deleted file mode 100644 index e477de2d0..000000000 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/ViewHooks.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.intuit.player.jvm.core.view - -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncHook1 -import com.intuit.player.jvm.core.resolver.Resolver - -public class ViewHooks internal constructor(override val node: Node) : NodeWrapper { - public val onUpdate: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("onUpdate")!!, Asset.serializer()) - public val resolver: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("resolver")!!, Resolver.serializer()) -} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/asset/Asset.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/asset/Asset.kt new file mode 100644 index 000000000..77778cde4 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/asset/Asset.kt @@ -0,0 +1,41 @@ +package com.intuit.playerui.core.asset + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getSerializable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.player.PlayerException +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer + +/** Convenience helper to get some [value] from an [Asset] */ +public val Asset.value: String? get() = get("value") as? String + +/** Special [Node] type that also describes a player asset */ +@Serializable(with = Asset.Serializer::class) +public open class Asset(override val node: Node) : Node by node, NodeWrapper { + public val id: String by NodeSerializableField(String.serializer(), NodeSerializableField.CacheStrategy.Full) { + throw PlayerException("Asset's must be described with an ID") + } + public operator fun component1(): String = id + + public val type: String by NodeSerializableField(String.serializer(), NodeSerializableField.CacheStrategy.Full) { + throw PlayerException("Asset's must be described with a type") + } + public operator fun component2(): String = type + + public val metaData: MetaData? by NodeSerializableField(MetaData.serializer().nullable) + public operator fun component3(): MetaData? = metaData + + public fun getAsset(name: String): AssetWrapper? = getSerializable(name) + + override fun equals(other: Any?): Boolean = node == other + + override fun hashCode(): Int = node.hashCode() + + override fun toString(): String = node.toString() + + internal object Serializer : NodeWrapperSerializer(::Asset) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/asset/AssetWrapper.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/asset/AssetWrapper.kt new file mode 100644 index 000000000..bdb34e9fb --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/asset/AssetWrapper.kt @@ -0,0 +1,20 @@ +package com.intuit.playerui.core.asset + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.player.PlayerException +import kotlinx.serialization.Serializable + +/** + * Container for an [Asset] definition. [node] is exposed because there might be metaData at the wrapper level. + */ +@Serializable(with = AssetWrapper.Serializer::class) +public class AssetWrapper(override val node: Node) : NodeWrapper { + public val asset: Asset by NodeSerializableField(Asset.serializer()) { + throw PlayerException("AssetWrapper is not wrapping a valid asset") + } + + internal object Serializer : NodeWrapperSerializer(::AssetWrapper) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/asset/MetaData.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/asset/MetaData.kt new file mode 100644 index 000000000..2eb5560b6 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/asset/MetaData.kt @@ -0,0 +1,31 @@ +package com.intuit.playerui.core.asset + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull + +/** + * Base structure containing common metadata fields. + * [node] is exposed to enable retrieval of other fields. + * This could also be done by extending this class. + */ +@Serializable(MetaData.Serializer::class) +public open class MetaData(override val node: Node) : NodeWrapper { + + /** [JsonElement] representation of some [beacon] description */ + public val beacon: JsonElement by NodeSerializableField(JsonElement.serializer()) { JsonNull } + + /** Common metadata element used to designate how an asset should present itself by describing the intent */ + public val role: String? by NodeSerializableField(String.serializer().nullable) + + /** [ref] is typically used to describe a reference to some external location */ + public val ref: String? by NodeSerializableField(String.serializer().nullable) + + internal object Serializer : NodeWrapperSerializer(::MetaData) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Completable.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Completable.kt similarity index 92% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Completable.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Completable.kt index 8d6fa3c50..71526fa7d 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Completable.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Completable.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.bridge +package com.intuit.playerui.core.bridge import kotlinx.coroutines.flow.Flow import kotlin.Result diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Completed.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Completed.kt similarity index 87% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Completed.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Completed.kt index 4ea4daae3..3b7fa1908 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Completed.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Completed.kt @@ -1,8 +1,7 @@ -package com.intuit.player.jvm.core.bridge +package com.intuit.playerui.core.bridge import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -import kotlin.Result import kotlin.Result.Companion.success /** Utility class to wrap a [value] as a [Completable] */ diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Invokable.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Invokable.kt similarity index 71% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Invokable.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Invokable.kt index 2f00cbd9b..42728916e 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Invokable.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Invokable.kt @@ -1,13 +1,46 @@ -package com.intuit.player.jvm.core.bridge +package com.intuit.playerui.core.bridge import kotlin.jvm.functions.FunctionN +public fun Invokable(block: (args: Array) -> R): Invokable = object : Invokable { + override fun invoke(vararg args: Any?): R { + return block(args) + } +} + /** [Function] extension that provides loosely-typed vararg [invoke] signature */ -public fun interface Invokable : Function { +public interface Invokable : Function, Function0, Function1, Function2, Function3, Function4, Function5, Function6, Function7, Function8, Function9, Function10, Function11, Function12, Function13, Function14, Function15, Function16, Function17, Function18, Function19, Function20, Function21, Function22 { public operator fun invoke(vararg args: Any?): R + override fun invoke(): R = invoke(*arrayOf()) + override fun invoke(p1: Any?): R = invoke(*arrayOf(p1)) + override fun invoke(p1: Any?, p2: Any?): R = invoke(*arrayOf(p1, p2)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?): R = invoke(*arrayOf(p1, p2, p3)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?): R = invoke(*arrayOf(p1, p2, p3, p4)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?, p14: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?, p14: Any?, p15: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?, p14: Any?, p15: Any?, p16: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?, p14: Any?, p15: Any?, p16: Any?, p17: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?, p14: Any?, p15: Any?, p16: Any?, p17: Any?, p18: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?, p14: Any?, p15: Any?, p16: Any?, p17: Any?, p18: Any?, p19: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?, p14: Any?, p15: Any?, p16: Any?, p17: Any?, p18: Any?, p19: Any?, p20: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?, p14: Any?, p15: Any?, p16: Any?, p17: Any?, p18: Any?, p19: Any?, p20: Any?, p21: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21)) + override fun invoke(p1: Any?, p2: Any?, p3: Any?, p4: Any?, p5: Any?, p6: Any?, p7: Any?, p8: Any?, p9: Any?, p10: Any?, p11: Any?, p12: Any?, p13: Any?, p14: Any?, p15: Any?, p16: Any?, p17: Any?, p18: Any?, p19: Any?, p20: Any?, p21: Any?, p22: Any?): R = invoke(*arrayOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22)) } /** Extension to convert an [Invokable] to some [functionTypeName] */ +@Deprecated( + "Invokable extend Functions automatically", + level = DeprecationLevel.ERROR, +) public fun Invokable.toFunction(functionTypeName: String): Function = when (functionTypeName) { "Function0" -> object : Function0 { override fun invoke() = this@toFunction() diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/isUndefined.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/IsUndefined.kt similarity index 83% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/isUndefined.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/IsUndefined.kt index 9e887548e..12a568ce1 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/isUndefined.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/IsUndefined.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.bridge +package com.intuit.playerui.core.bridge -import com.intuit.player.jvm.core.bridge.serialization.json.value +import com.intuit.playerui.core.bridge.serialization.json.value import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.contentOrNull diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/JSErrorException.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/JSErrorException.kt new file mode 100644 index 000000000..9bb15e248 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/JSErrorException.kt @@ -0,0 +1,19 @@ +package com.intuit.playerui.core.bridge + +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.ThrowableSerializer +import com.intuit.playerui.core.player.PlayerException +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer + +public class JSErrorException( + override val node: Node, + cause: Throwable? = node.getSerializable("innerErrors", ListSerializer(ThrowableSerializer()))?.first(), +) : PlayerException(node.getString("message") ?: "", cause), NodeWrapper { + + public val name: String by NodeSerializableField(String.serializer()) { + "Error" + } + + override val message: String = "$name: ${super.message}" +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Node.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Node.kt similarity index 66% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Node.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Node.kt index 104e9114b..c3f76e6cc 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Node.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Node.kt @@ -1,10 +1,13 @@ -package com.intuit.player.jvm.core.bridge - -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.core.bridge.serialization.format.serializer +package com.intuit.playerui.core.bridge + +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.core.bridge.serialization.format.serializer +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializer +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.KSerializer import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull @@ -50,6 +53,14 @@ public interface Node : Map { * Returns the value corresponding to the given [key] as a [Invokable], * or `null` if such a key is not present in the node or if the value is not invokable */ + public fun getInvokable(key: String, deserializationStrategy: DeserializationStrategy): Invokable? = get(key).safeCast() + + @Deprecated( + "Replaced with getInvokable, which requires a deserializer for the return type. Either provide a deserializer explicitly, " + + "or use the extension to automatically determine the correct serializer.", + ReplaceWith("getInvokable(key)", "com.intuit.playerui.core.bridge.getInvokable"), + DeprecationLevel.WARNING, + ) public fun getFunction(key: String): Invokable? = get(key).safeCast() /** @@ -91,6 +102,10 @@ public interface Node : Map { public val runtime: Runtime<*> public val format: RuntimeFormat<*> get() = runtime.format + + public companion object { + public fun serializer(): KSerializer = NodeSerializer() + } } public val NodeWrapper.runtime: Runtime<*> get() = node.runtime @@ -115,6 +130,49 @@ public inline fun Node.getSerializable(key: String, serializer: Dese getSerializable(key, it) } ?: getSerializable(key) +public inline fun Node.getInvokable(key: String): Invokable? = getInvokable(key, format.serializer()) + +public inline fun Node.getInvokable(key: String, deserializer: DeserializationStrategy?): Invokable? = deserializer?.let { + getInvokable(key, it) +} ?: getInvokable(key) + +/** +* Get the toString value of a symbol. Due to the current limitations of the +* various supported JS runtimes, we can only reliably get a [String] +* representation of symbols that are contained within a parent [Node]. +* This inherently breaks the uniqueness of symbol representation on the JVM, +* and thus shouldn't be used to verify referential equality between symbols. +*/ +@ExperimentalPlayerApi +public fun Node.getSymbol(key: String): String? { + if (!runtime.containsKey("getSymbol")) { + runtime.execute( + """ + function getSymbol(parent, key) { + const value = parent[key]; + if (typeof value === 'symbol') return value.toString(); + else return null; + } + """, + ) + } + + val getSymbol = runtime.getInvokable("getSymbol") + ?: throw runtime.PlayerRuntimeException("getSymbol doesn't exist in runtime") + + return getSymbol(this, key) +} + +/** Decode a [Node] entirely as a snapshot of its current data. Will allow for additional data access after a [Node]s runtime has been released */ +@ExperimentalPlayerApi +internal fun Node.snapshot(): Map = entries.associate { (key, value) -> + key to when (value) { + is Node -> value.snapshot() + is Function<*> -> null + else -> value + } +} + private inline fun Any?.safeCast(): T? = this as? T internal object EmptyNode : Node { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/NodeWrapper.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/NodeWrapper.kt similarity index 76% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/NodeWrapper.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/NodeWrapper.kt index ccce3d1b3..0326b7bd5 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/NodeWrapper.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/NodeWrapper.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.bridge +package com.intuit.playerui.core.bridge /** Wrapper construct that standardizes how wrapped [Node]s are exposed */ public interface NodeWrapper { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/PlayerRuntimeException.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/PlayerRuntimeException.kt similarity index 74% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/PlayerRuntimeException.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/PlayerRuntimeException.kt index 42dde8da4..85564fa3b 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/PlayerRuntimeException.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/PlayerRuntimeException.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.core.bridge +package com.intuit.playerui.core.bridge -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.player.PlayerException +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.player.PlayerException /** Specific [PlayerException] that denotes an exception that occurred within the context of a [Runtime] */ public class PlayerRuntimeException(public val runtime: Runtime<*>, message: String, cause: Throwable? = null) : diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Promise.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Promise.kt similarity index 75% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Promise.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Promise.kt index dd94059ed..879bf08b1 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/Promise.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/Promise.kt @@ -1,17 +1,19 @@ -package com.intuit.player.jvm.core.bridge - -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.runtime.add -import com.intuit.player.jvm.core.bridge.serialization.format.serializer -import com.intuit.player.jvm.core.bridge.serialization.json.isJsonElementSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.utils.InternalPlayerApi +package com.intuit.playerui.core.bridge + +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.add +import com.intuit.playerui.core.bridge.serialization.format.serializer +import com.intuit.playerui.core.bridge.serialization.json.isJsonElementSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.utils.InternalPlayerApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer @@ -39,7 +41,7 @@ public class Promise(override val node: Node) : NodeWrapper { */ public fun then(deserializer: DeserializationStrategy, block: (Expected?) -> Next?): Promise = Promise( - node.getFunction("then")?.invoke( + node.getInvokable("then")?.invoke( { arg: Any? -> try { block( @@ -51,17 +53,21 @@ public class Promise(override val node: Node) : NodeWrapper { // the value, since that _would_ be wrapped in a holder able to be generically // deserialized is Node -> arg.deserialize(deserializer) - else -> if (deserializer.isJsonElementSerializer) Json.encodeToJsonElement( - GenericSerializer(), - arg, - ) else arg - } as? Expected + else -> if (deserializer.isJsonElementSerializer) { + Json.encodeToJsonElement( + GenericSerializer(), + arg, + ) + } else { + arg + } + } as? Expected, ) } catch (e: Throwable) { Promise.reject(e) } - } - ) ?: throw PromiseException("then did not return valid Promise") + }, + ) ?: throw PromiseException("then did not return valid Promise"), ) /** @@ -74,12 +80,15 @@ public class Promise(override val node: Node) : NodeWrapper { */ public fun catch(block: (Throwable) -> Next?): Promise = Promise( - node.getFunction("catch")?.invoke( + node.getInvokable("catch")?.invoke( { arg: Any? -> try { // Attempt to dynamically build exception from JS error - if (arg is Exception) block(arg) else + if (arg is Exception) { + block(arg) + } else { block((arg as Node).deserialize()) + } } catch (e: Exception) { // fallback to simple String representation of error // if you are debugging and the stacktrace leads you @@ -87,8 +96,8 @@ public class Promise(override val node: Node) : NodeWrapper { // be very helpful block(PromiseException(arg.toString())) } - } - ) ?: throw PromiseException("then did not return valid Promise") + }, + ) ?: throw PromiseException("then did not return valid Promise"), ) /** Converts the [Promise] into a [Completable] to enable awaiting it to resolve or reject */ @@ -106,7 +115,7 @@ public class Promise(override val node: Node) : NodeWrapper { try { onComplete { when { - it.isSuccess -> offer(it.getOrNull()) + it.isSuccess -> trySend(it.getOrNull()) it.isFailure -> close(it.exceptionOrNull()) } } @@ -149,28 +158,33 @@ public val Node.Promise: Promise.Api get() = runtime.Promise public val Runtime<*>.Promise: Promise.Api get() = getObject("Promise")?.let { promise -> object : Promise.Api { override fun resolve(vararg values: T): Promise = promise - .getFunction("resolve")?.invoke(*values)?.let(::Promise) + .getInvokable("resolve")?.invoke(*values)?.let(::Promise) ?: throw PromiseException("Could not resolve with values: $values") override fun reject(vararg values: T): Promise = promise - .getFunction("reject")?.invoke(*values)?.let(::Promise) + .getInvokable("reject")?.invoke(*values)?.let(::Promise) ?: throw PromiseException("Could not reject with values: $values") } } ?: throw PlayerRuntimeException("'Promise' not defined in runtime") /** Helper to bridge complex [Promise] logic with the JS promise constructor */ -public fun Runtime<*>.Promise(block: ((T) -> Unit, (Throwable) -> Unit) -> Unit): Promise { +public fun Runtime<*>.Promise(block: suspend ((T) -> Unit, (Throwable) -> Unit) -> Unit): Promise { val key = "promiseHandler_${UUID.randomUUID().toString().replace("-", "")}" add(key) { resolve: Invokable, reject: Invokable -> - try { - block({ resolve(it) }, { reject(it) }) - } catch (e: Throwable) { - reject(e) + runtime.scope.launch { + try { + block({ runtime.scope.ensureActive(); resolve(it) }, { runtime.scope.ensureActive(); reject(it) }) + } catch (e: Throwable) { + runtime.scope.ensureActive() + reject(e) + } } + + Unit } return Promise( execute("(new Promise($key))") as? Node - ?: throw PromiseException("Error creating promise") + ?: throw PromiseException("Error creating promise"), ) } diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/PromiseException.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/PromiseException.kt similarity index 68% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/PromiseException.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/PromiseException.kt index 50564bffe..319f2b9ca 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/PromiseException.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/PromiseException.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.bridge +package com.intuit.playerui.core.bridge -import com.intuit.player.jvm.core.player.PlayerException +import com.intuit.playerui.core.player.PlayerException /** Specific [PlayerException] that denotes an exception that occurred within the context of a [Promise] */ public class PromiseException(message: String, cause: Throwable? = null) : PlayerException(message, cause) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/global/JSIterator.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/global/JSIterator.kt similarity index 71% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/global/JSIterator.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/global/JSIterator.kt index e5e01a47e..f5dee0f4a 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/global/JSIterator.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/global/JSIterator.kt @@ -1,8 +1,8 @@ -package com.intuit.player.jvm.core.bridge.global +package com.intuit.playerui.core.bridge.global -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -18,8 +18,7 @@ public class JSIterator(override val node: Node, public val itemSerializer: K ) private fun getNext(): NextResult = node - .getFunction("next")!!() - .deserialize(NextResult.serializer(itemSerializer)) + .getInvokable("next", NextResult.serializer(itemSerializer))!!() /** Always keeps track of value to be read */ private var current: NextResult = getNext() diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/global/JSMap.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/global/JSMap.kt similarity index 68% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/global/JSMap.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/global/JSMap.kt index 10a9e0da0..46180eae4 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/global/JSMap.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/global/JSMap.kt @@ -1,8 +1,9 @@ -package com.intuit.player.jvm.core.bridge.global +package com.intuit.playerui.core.bridge.global -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @@ -10,11 +11,11 @@ import kotlinx.serialization.Serializable public class JSMap (override val node: Node, keySerializer: KSerializer, valueSerializer: KSerializer) : Map, NodeWrapper { override val keys: Set by lazy { - JSIterator(node.getFunction("keys")!!(), keySerializer).asSequence().toSet() + JSIterator(node.getInvokable("keys")!!(), keySerializer).asSequence().toSet() } override val values: List by lazy { - JSIterator(node.getFunction("values")!!(), valueSerializer).asSequence().toList() + JSIterator(node.getInvokable("values")!!(), valueSerializer).asSequence().toList() } override val entries: Set> by lazy { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeHook.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeHook.kt similarity index 58% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeHook.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeHook.kt index f81ce6a50..0520009d2 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeHook.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeHook.kt @@ -1,10 +1,11 @@ -package com.intuit.player.jvm.core.bridge.hooks +package com.intuit.playerui.core.bridge.hooks import com.intuit.hooks.HookContext -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.utils.InternalPlayerApi +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.utils.InternalPlayerApi import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.capturedKClass @@ -14,18 +15,23 @@ internal interface NodeHook : NodeWrapper { @OptIn(ExperimentalSerializationApi::class) fun init(vararg serializers: KSerializer<*>) { - node.getFunction("tap")?.invoke( + node.getInvokable("tap")?.invoke( mapOf("name" to "name", "context" to true), Invokable { args -> val context = args[0] as Map val rest = args.drop(1).zip(serializers).map { (value, serializer) -> - if (serializer.descriptor.isNullable && value == null) value - else if (value!!::class == serializer.descriptor.capturedKClass) value - else if (value is Node) value.deserialize(serializer) - else value + if (serializer.descriptor.isNullable && value == null) { + value + } else if (value!!::class == serializer.descriptor.capturedKClass) { + value + } else if (value is Node) { + value.deserialize(serializer) + } else { + value + } } call(HashMap(context), rest.toTypedArray()) - } + }, ) } diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncBailHook1.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncBailHook1.kt new file mode 100644 index 000000000..f5b73a063 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncBailHook1.kt @@ -0,0 +1,36 @@ +package com.intuit.playerui.core.bridge.hooks + +import com.intuit.hooks.BailResult +import com.intuit.hooks.HookContext +import com.intuit.hooks.SyncBailHook +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable + +@Serializable(with = NodeSyncBailHook1.Serializer::class) +public class NodeSyncBailHook1( + override val node: Node, + private val serializer1: KSerializer, +) : SyncBailHook<(HookContext, T) -> BailResult, R>(), NodeHook { + + init { init(serializer1) } + + override fun call(context: HookContext, serializedArgs: Array): R? { + require(serializedArgs.size == 1) + val p1 = serializedArgs[0] as T + return call( + { f, t -> f(context, p1) }, + ) { Unit as R } + } + + public fun tap(name: String, callback: (T?) -> BailResult): String? = super.tap(name) { _, p1 -> callback(p1) } + + public inline fun tap(noinline callback: (T?) -> BailResult): String? = tap(callingStackTraceElement.toString(), callback) + + public inline fun tap(noinline callback: (HookContext, T?) -> BailResult): String? = tap(callingStackTraceElement.toString(), callback) + + internal class Serializer(private val serializer1: KSerializer, private val serializer2: KSerializer) : NodeWrapperSerializer>({ + NodeSyncBailHook1(it, serializer1) + }) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncHook.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncHook.kt similarity index 72% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncHook.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncHook.kt index 30e63a348..cd290a56b 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncHook.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncHook.kt @@ -1,9 +1,11 @@ -package com.intuit.player.jvm.core.bridge.hooks +package com.intuit.playerui.core.bridge.hooks import com.intuit.hooks.HookContext import com.intuit.hooks.SyncHook -import com.intuit.player.jvm.core.bridge.Node +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable public abstract class SyncHook1 : SyncHook<(HookContext, T1) -> Unit>() { public fun tap(name: String, callback: (T1) -> Unit): String? = super.tap(name) { _, p1 -> callback(p1) } @@ -11,22 +13,25 @@ public abstract class SyncHook1 : SyncHook<(HookContext, T1) -> Unit>() { public inline fun tap(noinline callback: (HookContext, T1) -> Unit): String? = tap(callingStackTraceElement.toString(), callback) } -// TODO: Work with serializable +@Serializable(with = NodeSyncHook1.Serializer::class) public class NodeSyncHook1(override val node: Node, private val serializer1: KSerializer) : SyncHook1(), NodeHook { init { init(serializer1) } - override fun call(context: HookContext, serializedArgs: Array) { require(serializedArgs.size == 1) val (p1) = serializedArgs call { f, _ -> f(context, p1 as? T1) } } + + internal class Serializer(private val serializer1: KSerializer) : NodeWrapperSerializer>({ + NodeSyncHook1(it, serializer1) + }) } +@Serializable(with = NodeSyncHook2.Serializer::class) public class NodeSyncHook2(override val node: Node, private val serializer1: KSerializer, private val serializer2: KSerializer) : SyncHook<(HookContext, T1?, T2?) -> Unit>(), NodeHook { init { init(serializer1, serializer2) } - override fun call(context: HookContext, serializedArgs: Array) { require(serializedArgs.size == 2) val (p1, p2) = serializedArgs @@ -38,10 +43,12 @@ public class NodeSyncHook2(override val node: Node, private val serializ ) } } - public fun tap(name: String, callback: (T1?, T2?) -> Unit): String? = super.tap(name) { _, p1, p2 -> callback(p1, p2) } - public inline fun tap(noinline callback: (T1?, T2?) -> Unit): String? = tap(callingStackTraceElement.toString(), callback) public inline fun tap(noinline callback: (HookContext, T1?, T2?) -> Unit): String? = tap(callingStackTraceElement.toString(), callback) + + internal class Serializer(private val serializer1: KSerializer, private val serializer2: KSerializer) : NodeWrapperSerializer>({ + NodeSyncHook2(it, serializer1, serializer2) + }) } diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncWaterfallHook.kt similarity index 75% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncWaterfallHook.kt index 8feeb33b1..564ba86fc 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHook.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncWaterfallHook.kt @@ -1,9 +1,11 @@ -package com.intuit.player.jvm.core.bridge.hooks +package com.intuit.playerui.core.bridge.hooks import com.intuit.hooks.HookContext import com.intuit.hooks.SyncWaterfallHook -import com.intuit.player.jvm.core.bridge.Node +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable /** * A note of caution when using these hooks. Waterfall hooks act as accumulators, applying the tapped functions @@ -12,11 +14,12 @@ import kotlinx.serialization.KSerializer * However, with the immutable principles implemented by the core player runtime, this means that any mutated value * is meant to be a completely new object. Persisting an early value would result in an object that is potentially * _not_ the final accumulated value, meaning it might not even be relevant anymore. In some circumstances, this - * value might be garbage collected and result in a [PlayerRuntimeException][com.intuit.player.jvm.core.bridge.PlayerRuntimeException] + * value might be garbage collected and result in a [PlayerRuntimeException][com.intuit.playerui.core.bridge.PlayerRuntimeException] * representing that the underlying JS instance has been released. If there is a use case to persist this value * outside of the tapped function, this can be done by reading the information and storing it in a non-Node backed * object from within the tapped function. */ +@Serializable(NodeSyncWaterfallHook1.Serializer::class) public class NodeSyncWaterfallHook1(override val node: Node, private val serializer1: KSerializer) : SyncWaterfallHook<(HookContext, T1?) -> T1?, T1?>(), NodeHook { init { init(serializer1) } @@ -27,7 +30,7 @@ public class NodeSyncWaterfallHook1(override val node: Node, private val ser return call( null, { f, _, _ -> f(context, p1) }, - { f, _ -> f(context, p1) } + { f, _ -> f(context, p1) }, ) } @@ -36,12 +39,17 @@ public class NodeSyncWaterfallHook1(override val node: Node, private val ser public inline fun tap(noinline callback: (T1?) -> T1?): String? = tap(callingStackTraceElement.toString(), callback) public inline fun tap(noinline callback: (HookContext, T1?) -> T1?): String? = tap(callingStackTraceElement.toString(), callback) + + internal class Serializer(private val serializer1: KSerializer) : NodeWrapperSerializer>({ + NodeSyncWaterfallHook1(it, serializer1) + }) } +@Serializable(NodeSyncWaterfallHook2.Serializer::class) public class NodeSyncWaterfallHook2( override val node: Node, private val serializer1: KSerializer, - private val serializer2: KSerializer + private val serializer2: KSerializer, ) : SyncWaterfallHook<(HookContext, T1?, T2?) -> T1?, T1?>(), NodeHook { init { init(serializer1, serializer2) } @@ -53,7 +61,7 @@ public class NodeSyncWaterfallHook2( return call( null, { f, _, _ -> f(context, p1, p2) }, - { f, _ -> f(context, p1, p2) } + { f, _ -> f(context, p1, p2) }, ) } @@ -62,4 +70,8 @@ public class NodeSyncWaterfallHook2( public inline fun tap(noinline callback: (T1?, T2?) -> T1?): String? = tap(callingStackTraceElement.toString(), callback) public inline fun tap(noinline callback: (HookContext, T1?, T2?) -> T1?): String? = tap(callingStackTraceElement.toString(), callback) + + internal class Serializer(private val serializer1: KSerializer, private val serializer2: KSerializer) : NodeWrapperSerializer>({ + NodeSyncWaterfallHook2(it, serializer1, serializer2) + }) } diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/PlayerRuntimeConfig.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/PlayerRuntimeConfig.kt new file mode 100644 index 000000000..4fdcc5d20 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/PlayerRuntimeConfig.kt @@ -0,0 +1,9 @@ +package com.intuit.playerui.core.bridge.runtime + +import kotlinx.coroutines.CoroutineExceptionHandler + +/** Base configuration for [Runtime] */ +public open class PlayerRuntimeConfig { + public open var debuggable: Boolean = false + public open var coroutineExceptionHandler: CoroutineExceptionHandler? = null +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeContainer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/PlayerRuntimeContainer.kt similarity index 89% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeContainer.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/PlayerRuntimeContainer.kt index 902353bf2..8a5e017bf 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeContainer.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/PlayerRuntimeContainer.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.bridge.runtime +package com.intuit.playerui.core.bridge.runtime import java.util.* diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/PlayerRuntimeFactory.kt similarity index 92% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/PlayerRuntimeFactory.kt index 501359dc7..9e188264b 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/PlayerRuntimeFactory.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/PlayerRuntimeFactory.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.bridge.runtime +package com.intuit.playerui.core.bridge.runtime import java.util.ServiceLoader @@ -13,7 +13,7 @@ public interface PlayerRuntimeFactory { * with further configurations from the [nested] block. */ public fun PlayerRuntimeFactory.config( - nested: T.() -> Unit + nested: T.() -> Unit, ): PlayerRuntimeFactory { val parent = this @@ -33,5 +33,5 @@ public val runtimeContainers: List = PlayerRuntimeContai /** Default [PlayerRuntimeFactory] to use if none are specified */ public val runtimeFactory: PlayerRuntimeFactory<*> = runtimeContainers.firstOrNull()?.factory ?: error( "Failed to find JS Player runtime implementation in the classpath: consider adding player runtime dependency. " + - "See https://TODO" + "See https://TODO", ) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/Runtime.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/Runtime.kt similarity index 67% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/Runtime.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/Runtime.kt index 199309df4..566d58c7c 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/runtime/Runtime.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/Runtime.kt @@ -1,9 +1,11 @@ -package com.intuit.player.jvm.core.bridge.runtime - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.core.bridge.serialization.format.encodeToRuntimeValue -import com.intuit.player.jvm.core.bridge.serialization.format.serializer +package com.intuit.playerui.core.bridge.runtime + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.core.bridge.serialization.format.encodeToRuntimeValue +import com.intuit.playerui.core.bridge.serialization.format.serializer +import com.intuit.playerui.core.utils.InternalPlayerApi +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationStrategy @@ -11,6 +13,10 @@ import kotlinx.serialization.SerializationStrategy /** Special [Node] that represents the JS runtime */ public interface Runtime : Node { + public val dispatcher: CoroutineDispatcher + + public val config: PlayerRuntimeConfig + /** [CoroutineScope] that represents when the [Runtime] is released and relevant coroutines should cancel */ public val scope: CoroutineScope @@ -19,6 +25,8 @@ public interface Runtime : Node { /** Execute some arbitrary [script] and return the deserialized result */ public fun execute(script: String): Any? + public fun load(scriptContext: ScriptContext): Any? + /** Serialize and assign some [value] to [name] within the [Runtime] */ public fun add(name: String, value: Value) @@ -31,6 +39,10 @@ public interface Runtime : Node { /** Close the [Runtime] and release any resources */ public fun release() + + /** Opportunity to verify the thread to perfom blocking operations on */ + @InternalPlayerApi + public var checkBlockingThread: Thread.() -> Unit } public inline fun Runtime.add(name: String, value: T): Unit = @@ -43,3 +55,8 @@ public inline fun Runtime<*>.serialize(value: T): Any? = public inline fun Runtime<*>.serialize(serializer: SerializationStrategy?, value: T): Any? = serializer?.let { serialize(it, value) } ?: serialize(value) + +public data class ScriptContext( + val script: String, + val id: String, +) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/encoding/AbstractRuntimeValueDecoders.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/encoding/AbstractRuntimeValueDecoders.kt similarity index 90% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/encoding/AbstractRuntimeValueDecoders.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/encoding/AbstractRuntimeValueDecoders.kt index bc524af6f..119a95e45 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/encoding/AbstractRuntimeValueDecoders.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/encoding/AbstractRuntimeValueDecoders.kt @@ -1,13 +1,11 @@ -package com.intuit.player.jvm.core.bridge.serialization.encoding - -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeDecodingException -import com.intuit.player.jvm.core.bridge.serialization.json.isJsonElementSerializer -import com.intuit.player.jvm.core.bridge.serialization.json.value -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializer -import com.intuit.player.jvm.core.bridge.toFunction -import com.intuit.player.jvm.core.utils.InternalPlayerApi +package com.intuit.playerui.core.bridge.serialization.encoding + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.format.RuntimeDecodingException +import com.intuit.playerui.core.bridge.serialization.json.isJsonElementSerializer +import com.intuit.playerui.core.bridge.serialization.json.value +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.utils.InternalPlayerApi import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.PolymorphicSerializer import kotlinx.serialization.descriptors.SerialDescriptor @@ -18,6 +16,7 @@ import kotlinx.serialization.json.JsonNull import kotlinx.serialization.modules.SerializersModule import kotlin.reflect.full.isSubclassOf +// TODO: Better support for nullish values /** Common decoder base implementation to support decoding [T] runtime objects */ @InternalPlayerApi public abstract class AbstractRuntimeValueDecoder : RuntimeValueDecoder { @@ -27,16 +26,14 @@ public abstract class AbstractRuntimeValueDecoder : RuntimeValueDecoder { @Suppress("UNCHECKED_CAST") override fun decodeSerializableValue(deserializer: DeserializationStrategy): T = when { deserializer is PolymorphicSerializer<*> -> when { - deserializer.baseClass.isSubclassOf(Function::class) -> decodeFunction().let { - if (deserializer.baseClass == Invokable::class) it else it.toFunction(deserializer.baseClass.simpleName!!) - } + deserializer.baseClass.isSubclassOf(Function::class) -> decodeFunction(GenericSerializer()) deserializer.baseClass.isSubclassOf(Node::class) -> decodeNode() else -> super.decodeSerializableValue(deserializer) } as T // handle json serializers separately because they don't support custom decoders deserializer.isJsonElementSerializer -> when { - decodeNotNullMark() -> Json.encodeToJsonElement(NodeSerializer(), decodeValue() as Node) + decodeNotNullMark() -> Json.encodeToJsonElement(GenericSerializer(), decodeValue()) else -> JsonNull } as T @@ -54,7 +51,6 @@ public abstract class AbstractRuntimeValueDecoder : RuntimeValueDecoder { override fun decodeShort(): Short = decode().toShort() override fun decodeString(): String = decode() override fun decodeNode(): Node = decode() - override fun decodeFunction(): Invokable<*> = decode() override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = enumDescriptor.getElementIndex(decode()) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoders.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/encoding/NodeDecoders.kt similarity index 78% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoders.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/encoding/NodeDecoders.kt index 281a62e93..f49923e3a 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/encoding/NodeDecoders.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/encoding/NodeDecoders.kt @@ -1,12 +1,13 @@ -package com.intuit.player.jvm.core.bridge.serialization.encoding - -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeDecodingException -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeEncodingException -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.core.bridge.serialization.json.value -import com.intuit.player.jvm.core.utils.InternalPlayerApi +package com.intuit.playerui.core.bridge.serialization.encoding + +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.format.RuntimeDecodingException +import com.intuit.playerui.core.bridge.serialization.format.RuntimeEncodingException +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.core.bridge.serialization.json.value +import com.intuit.playerui.core.utils.InternalPlayerApi +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @@ -57,7 +58,7 @@ public interface FunctionEncoder : Encoder { } public interface FunctionDecoder : Decoder { - public fun decodeFunction(): Invokable<*> + public fun decodeFunction(returnTypeSerializer: KSerializer): Invokable } @InternalPlayerApi diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/format/Builders.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/format/Builders.kt similarity index 93% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/format/Builders.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/format/Builders.kt index 1c851db1f..2c3952019 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/format/Builders.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/format/Builders.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.bridge.serialization.format +package com.intuit.playerui.core.bridge.serialization.format -import com.intuit.player.jvm.core.utils.InternalPlayerApi +import com.intuit.playerui.core.utils.InternalPlayerApi import kotlin.contracts.InvocationKind import kotlin.contracts.contract diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/format/RuntimeFormat.kt similarity index 81% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/format/RuntimeFormat.kt index b33740bc1..e6534ecf6 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/format/RuntimeFormat.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/format/RuntimeFormat.kt @@ -1,14 +1,19 @@ -package com.intuit.player.jvm.core.bridge.serialization.format - -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.serialization.json.PrettyJson -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.KCallableSerializer -import kotlinx.serialization.* +package com.intuit.playerui.core.bridge.serialization.format + +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.serialization.json.PrettyJson +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.KCallableSerializer +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialFormat +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.StringFormat import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModuleBuilder import kotlinx.serialization.modules.contextual import kotlinx.serialization.modules.plus +import kotlinx.serialization.serializer import kotlin.reflect.KCallable import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf @@ -58,7 +63,7 @@ public fun RuntimeFormat<*>.registerContextualSerializer(klass: KClass /** [RuntimeFormat] specific [KSerializer] lookup for [T] type that handles special cases before delegating to the [serializersModule] */ public inline fun RuntimeFormat<*>.serializer(): KSerializer = when { T::class == Any::class -> GenericSerializer().conform() - T::class.isSubclassOf(KCallable::class) -> KCallableSerializer() as KSerializer + T::class.isSubclassOf(KCallable::class) -> KCallableSerializer(GenericSerializer().conform()) as KSerializer else -> serializersModule.serializer() } diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/format/RuntimeSerializationException.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/format/RuntimeSerializationException.kt similarity index 82% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/format/RuntimeSerializationException.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/format/RuntimeSerializationException.kt index 2b7ff0718..bbbe80b31 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/format/RuntimeSerializationException.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/format/RuntimeSerializationException.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.core.bridge.serialization.format +package com.intuit.playerui.core.bridge.serialization.format -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.utils.InternalPlayerApi +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.utils.InternalPlayerApi import kotlinx.serialization.SerializationException /** Generic exception indicating a problem with [Runtime] serialization and deserialization */ diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/json/JsonPrimitive.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/json/JsonPrimitive.kt similarity index 91% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/json/JsonPrimitive.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/json/JsonPrimitive.kt index c216eab87..d35d7401f 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/json/JsonPrimitive.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/json/JsonPrimitive.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.bridge.serialization.json +package com.intuit.playerui.core.bridge.serialization.json -import com.intuit.player.jvm.core.utils.InternalPlayerApi +import com.intuit.playerui.core.utils.InternalPlayerApi import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationStrategy diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/json/pretty.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/json/Pretty.kt similarity index 79% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/json/pretty.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/json/Pretty.kt index a808836db..ddbdeb5cb 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/json/pretty.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/json/Pretty.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.core.bridge.serialization.json +package com.intuit.playerui.core.bridge.serialization.json -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.utils.InternalPlayerApi +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.utils.InternalPlayerApi import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/defer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/Defer.kt similarity index 92% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/defer.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/Defer.kt index be7d03dd9..c2e4016dd 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/defer.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/Defer.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.bridge.serialization.serializers +package com.intuit.playerui.core.bridge.serialization.serializers -import com.intuit.player.jvm.core.utils.InternalPlayerApi +import com.intuit.playerui.core.utils.InternalPlayerApi import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialKind diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/FunctionSerializers.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/FunctionSerializers.kt new file mode 100644 index 000000000..cc67c879d --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/FunctionSerializers.kt @@ -0,0 +1,136 @@ +package com.intuit.playerui.core.bridge.serialization.serializers + +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.serialization.encoding.requireNodeDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.requireNodeEncoder +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.modules.SerializersModule +import kotlin.reflect.KCallable + +public sealed class FunctionLikeSerializer(private val serializer: KSerializer<*>) : KSerializer { + + final override val descriptor: SerialDescriptor = FunctionLikeSerializer.descriptor + + override fun deserialize(decoder: Decoder): T { + return decoder.requireNodeDecoder().decodeFunction(serializer) as T + } + + override fun serialize(encoder: Encoder, value: T) { + encoder.requireNodeEncoder().encodeFunction(value) + } + + public companion object { + @OptIn(InternalSerializationApi::class) + public val descriptor: SerialDescriptor = buildSerialDescriptor("FunctionLike", PolymorphicKind.OPEN) {} + + public val functionSerializerModule: SerializersModule = SerializersModule { + contextual(KCallable::class) { + KCallableSerializer(it.first()) + } + contextual(Invokable::class) { + InvokableSerializer(it.first()) + } + contextual(Function0::class) { + Function0Serializer(it[0]) + } + contextual(Function1::class) { + Function1Serializer(it[0], it[1]) + } + contextual(Function2::class) { + Function2Serializer(it[0], it[1], it[2]) + } + contextual(Function3::class) { + Function3Serializer(it[0], it[1], it[2], it[3]) + } + contextual(Function4::class) { + Function4Serializer(it[0], it[1], it[2], it[3], it[4]) + } + contextual(Function5::class) { + Function5Serializer(it[0], it[1], it[2], it[3], it[4], it[5]) + } + contextual(Function6::class) { + Function6Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6]) + } + contextual(Function7::class) { + Function7Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7]) + } + contextual(Function8::class) { + Function8Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8]) + } + contextual(Function9::class) { + Function9Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9]) + } + contextual(Function10::class) { + Function10Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10]) + } + contextual(Function11::class) { + Function11serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11]) + } + contextual(Function12::class) { + Function12Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12]) + } + contextual(Function13::class) { + Function13Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13]) + } + contextual(Function14::class) { + Function14Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13], it[14]) + } + contextual(Function15::class) { + Function15Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13], it[14], it[15]) + } + contextual(Function16::class) { + Function16Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13], it[14], it[15], it[16]) + } + contextual(Function17::class) { + Function17Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13], it[14], it[15], it[16], it[17]) + } + contextual(Function18::class) { + Function18Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13], it[14], it[15], it[16], it[17], it[18]) + } + contextual(Function19::class) { + Function19Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13], it[14], it[15], it[16], it[17], it[18], it[19]) + } + contextual(Function20::class) { + Function20Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13], it[14], it[15], it[16], it[17], it[18], it[19], it[20]) + } + contextual(Function21::class) { + Function21Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13], it[14], it[15], it[16], it[17], it[18], it[19], it[20], it[21]) + } + contextual(Function22::class) { + Function22Serializer(it[0], it[1], it[2], it[3], it[4], it[5], it[6], it[7], it[8], it[9], it[10], it[11], it[12], it[13], it[14], it[15], it[16], it[17], it[18], it[19], it[20], it[21], it[22]) + } + } + } +} + +public class KCallableSerializer(returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class InvokableSerializer(returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function0Serializer(returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function1Serializer(p1: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function2Serializer(p1: KSerializer<*>, p2: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function3Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function4Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function5Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function6Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function7Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function8Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function9Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function10Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function11serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function12Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function13Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function14Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, p14: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function15Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, p14: KSerializer<*>, p15: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function16Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, p14: KSerializer<*>, p15: KSerializer<*>, p16: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function17Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, p14: KSerializer<*>, p15: KSerializer<*>, p16: KSerializer<*>, p17: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function18Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, p14: KSerializer<*>, p15: KSerializer<*>, p16: KSerializer<*>, p17: KSerializer<*>, p18: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function19Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, p14: KSerializer<*>, p15: KSerializer<*>, p16: KSerializer<*>, p17: KSerializer<*>, p18: KSerializer<*>, p19: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function20Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, p14: KSerializer<*>, p15: KSerializer<*>, p16: KSerializer<*>, p17: KSerializer<*>, p18: KSerializer<*>, p19: KSerializer<*>, p20: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function21Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, p14: KSerializer<*>, p15: KSerializer<*>, p16: KSerializer<*>, p17: KSerializer<*>, p18: KSerializer<*>, p19: KSerializer<*>, p20: KSerializer<*>, p21: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) +public class Function22Serializer(p1: KSerializer<*>, p2: KSerializer<*>, p3: KSerializer<*>, p4: KSerializer<*>, p5: KSerializer<*>, p6: KSerializer<*>, p7: KSerializer<*>, p8: KSerializer<*>, p9: KSerializer<*>, p10: KSerializer<*>, p11: KSerializer<*>, p12: KSerializer<*>, p13: KSerializer<*>, p14: KSerializer<*>, p15: KSerializer<*>, p16: KSerializer<*>, p17: KSerializer<*>, p18: KSerializer<*>, p19: KSerializer<*>, p20: KSerializer<*>, p21: KSerializer<*>, p22: KSerializer<*>, returnTypeSerializer: KSerializer) : FunctionLikeSerializer>(returnTypeSerializer) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/GenericSerializer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/GenericSerializer.kt similarity index 80% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/GenericSerializer.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/GenericSerializer.kt index 12f3fa10d..bfd3a5b22 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/GenericSerializer.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/GenericSerializer.kt @@ -1,12 +1,14 @@ -package com.intuit.player.jvm.core.bridge.serialization.serializers +package com.intuit.playerui.core.bridge.serialization.serializers -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.encoding.NodeDecoder -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeSerializationException -import com.intuit.player.jvm.core.bridge.serialization.json.value -import com.intuit.player.jvm.core.utils.InternalPlayerApi -import kotlinx.serialization.* +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.encoding.NodeDecoder +import com.intuit.playerui.core.bridge.serialization.format.RuntimeSerializationException +import com.intuit.playerui.core.bridge.serialization.json.value +import com.intuit.playerui.core.utils.InternalPlayerApi +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException import kotlinx.serialization.builtins.ArraySerializer import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer @@ -15,7 +17,12 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.serializerOrNull /** * Generic serializer for [Any]? that uses the actual value type to determine the [KSerializer] @@ -47,12 +54,12 @@ public open class GenericSerializer(private val typeSerializers: List encoder.encodeNull() else -> encoder.encodeSerializableValue( diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/module.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/Module.kt similarity index 52% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/module.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/Module.kt index 5e0541895..82890b538 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/module.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/Module.kt @@ -1,8 +1,12 @@ -package com.intuit.player.jvm.core.bridge.serialization.serializers +package com.intuit.playerui.core.bridge.serialization.serializers -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import kotlinx.serialization.modules.* +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.player.state.ErroneousState +import com.intuit.playerui.core.player.state.PlayerFlowState +import kotlinx.serialization.modules.PolymorphicModuleBuilder +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.plus +import kotlinx.serialization.modules.polymorphic public val playerSerializersModule: SerializersModule = SerializersModule { fun PolymorphicModuleBuilder.registerThrowableSerializers() { @@ -19,6 +23,8 @@ public val playerSerializersModule: SerializersModule = SerializersModule { default { NodeSerializer() } } + polymorphic(PlayerFlowState::class, ErroneousState::class, ErroneousState.serializer()) + polymorphic(Any::class) { registerThrowableSerializers() @@ -27,7 +33,4 @@ public val playerSerializersModule: SerializersModule = SerializersModule { contextual(Any::class, ::GenericSerializer) contextual(Throwable::class, ThrowableSerializer()) - contextual(Invokable::class) { - InvokableSerializer() - } -} +} + FunctionLikeSerializer.functionSerializerModule diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/NodeSerializableField.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/NodeSerializableField.kt new file mode 100644 index 000000000..f71ffe5a1 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/NodeSerializableField.kt @@ -0,0 +1,123 @@ +package com.intuit.playerui.core.bridge.serialization.serializers + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.format.serializer +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.player.PlayerException +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.serializer +import kotlin.reflect.KProperty + +/** Delegate for automatic deserialization of [Node] values */ +public class NodeSerializableField private constructor( + private val provider: () -> Node, + private val serializer: KSerializer, + internal val strategy: CacheStrategy, + private val name: String?, + private val defaultValue: Node.(String) -> T, +) { + + /** Caching strategy for determining how to pull the value from [Node] on subsequent attempts */ + public enum class CacheStrategy { + None, + Smart, + Full, + } + + /** Cache of container [Node] that will reset the [value] cache if out-of-date with the [provider] */ + private var cache: Node = provider(); get() { + val provided = provider() + if (provided.nativeReferenceEquals(field)) { + field + } else { + field = provided + value = null + } + + return field + } + + /** Cache of the [T] value, along with the backing [Node] for objects */ + private var value: Pair? = null + + public operator fun getValue(thisRef: Any?, property: KProperty<*>): T { + // early exit if we have a value and explicitly using the cache + value?.takeIf { strategy == CacheStrategy.Full }?.let { (_, value) -> + return value + } + + val key = name ?: property.name + + // will reset cache and value if mismatch + val node = cache + + // early exit if we have a value still and referentially match the backing [Node] + value?.takeIf { strategy == CacheStrategy.Smart } + ?.takeIf { (backing) -> backing?.nativeReferenceEquals(node[key]) == true } + ?.let { (_, value) -> return value } + + // else get and deserialize the value + return node + .getSerializable(key, serializer) + ?.also { value = node.getObject(key) to it } + ?: node.defaultValue(key) + } + + public companion object { + + /** Smart constructor responsible for determining the correct [CacheStrategy] and [defaultValue] from the [serializer], if either are not provided */ + @ExperimentalPlayerApi + public operator fun invoke( + provider: () -> Node, + serializer: KSerializer, + strategy: CacheStrategy? = null, + name: String? = null, + defaultValue: (Node.(String) -> T)? = null, + ): NodeSerializableField = NodeSerializableField( + provider, + serializer, + strategy ?: when (serializer.descriptor.kind) { + is StructureKind, + is PolymorphicKind, + -> CacheStrategy.Smart + else -> CacheStrategy.None + }, + name, + defaultValue ?: { + @Suppress("UNCHECKED_CAST") + if (serializer.descriptor.isNullable) { + null as T + } else { + throw throw PlayerException("""Could not deserialize "$it" as "${serializer.descriptor}"""") + } + }, + ) + + /** Smart constructor to automatically determine the [KSerializer] to use for [T] */ + @ExperimentalPlayerApi + public inline operator fun invoke( + noinline provider: () -> Node, + strategy: CacheStrategy? = null, + name: String? = null, + noinline defaultValue: (Node.(String) -> T)? = null, + ): NodeSerializableField = NodeSerializableField(provider, serializer(), strategy, name, defaultValue) + } +} + +@ExperimentalPlayerApi +public fun NodeWrapper.NodeSerializableField( + serializer: KSerializer, + strategy: NodeSerializableField.CacheStrategy? = null, + name: String? = null, + defaultValue: (Node.(String) -> T)? = null, +): NodeSerializableField = NodeSerializableField(::node, serializer, strategy, name, defaultValue) + +@ExperimentalPlayerApi +public inline fun NodeWrapper.NodeSerializableField( + strategy: NodeSerializableField.CacheStrategy? = null, + name: String? = null, + noinline defaultValue: (Node.(String) -> T)? = null, +): NodeSerializableField = NodeSerializableField(node.format.serializer(), strategy, name, defaultValue) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/NodeSerializers.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/NodeSerializers.kt similarity index 75% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/NodeSerializers.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/NodeSerializers.kt index c7987bcb1..c522cec51 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/bridge/serialization/serializers/NodeSerializers.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/NodeSerializers.kt @@ -1,9 +1,9 @@ -package com.intuit.player.jvm.core.bridge.serialization.serializers +package com.intuit.playerui.core.bridge.serialization.serializers -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.encoding.NodeEncoder -import com.intuit.player.jvm.core.bridge.serialization.encoding.requireNodeDecoder +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.encoding.NodeEncoder +import com.intuit.playerui.core.bridge.serialization.encoding.requireNodeDecoder import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException import kotlinx.serialization.builtins.MapSerializer @@ -29,8 +29,12 @@ public class NodeSerializer : KSerializer { } } -public open class NodeWrapperSerializer(private val factory: (Node) -> T) : KSerializer { - final override val descriptor: SerialDescriptor = buildClassSerialDescriptor("com.intuit.player.jvm.core.bridge.NodeWrapper") +public open class NodeWrapperSerializer( + private val factory: (Node) -> T, + // TODO: Can we pull this from the @SerialName annotation? + private val serialName: String = "com.intuit.playerui.core.bridge.NodeWrapper", +) : KSerializer { + final override val descriptor: SerialDescriptor = buildClassSerialDescriptor(serialName) override fun deserialize(decoder: Decoder): T = NodeSerializer().deserialize(decoder).let(factory) diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/ThrowableSerializer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/ThrowableSerializer.kt new file mode 100644 index 000000000..6df1e71dd --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/ThrowableSerializer.kt @@ -0,0 +1,142 @@ +package com.intuit.playerui.core.bridge.serialization.serializers + +import com.intuit.playerui.core.bridge.JSErrorException +import com.intuit.playerui.core.bridge.serialization.encoding.NodeDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.requireNodeDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.requireNodeEncoder +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.utils.InternalPlayerApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.Serializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.nullable +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.encoding.encodeStructure + +// the serializer knows _how_ to construct the `Throwable` type from a JS Node +// and _how_ to deconstruct a `Throwable` into primitives to serialize +@Serializer(forClass = Throwable::class) +public class ThrowableSerializer : KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("kotlin.Throwable") { + element("serialized", Boolean.serializer().descriptor.nullable, isOptional = true) + element("message", String.serializer().descriptor.nullable, isOptional = true) + element("stack", String.serializer().descriptor.nullable, isOptional = true) + element("stackTrace", serializedStackTraceSerializer.descriptor.nullable, isOptional = true) + element("cause", defer { ThrowableSerializer().descriptor.nullable }, isOptional = true) + } + + override fun deserialize(decoder: Decoder): PlayerException { + // uncaught JVM exceptions can come across as strings or actual exceptions + if (decoder is NodeDecoder) { + when (val value = decoder.decodeValue()) { + is String -> return PlayerException("uncaught exception: $value") + is PlayerException -> return value + is Exception -> return PlayerException("uncaught exception: ${value.message}", value) + } + } + + return decoder.decodeStructure(descriptor) { + var serialized = false + var message = "" + var stackTrace: Array = emptyArray() + var cause: Throwable? = null + + fun decodeStackTraceFromStack(stack: String? = decodeNullableSerializableElement(descriptor, 2, String.serializer().nullable)): Array = stack + ?.split("\n") + ?.mapNotNull(errorStackReg::find) + ?.map(MatchResult::destructured) + ?.map { (className, methodName, fileName, lineNumber) -> + StackTraceElement(className, methodName, fileName, lineNumber.toIntOrNull() ?: -2) + }?.toTypedArray() ?: emptyArray() + + fun decodeSerializedStackTrace(): Array = decodeNullableSerializableElement(descriptor, 3, serializedStackTraceSerializer.nullable) + ?.map { (className, methodName, fileName, lineNumber) -> + StackTraceElement(className, methodName, fileName, lineNumber) + }?.toTypedArray() ?: emptyArray() + + if (decodeSequentially()) { + serialized = decodeNullableSerializableElement(descriptor, 0, Boolean.serializer().nullable) ?: false + + if (serialized) { + message = decodeNullableSerializableElement(descriptor, 1, String.serializer().nullable) ?: "" + stackTrace = decodeSerializedStackTrace() + cause = decodeNullableSerializableElement(descriptor, 4, nullable) + } else { + stackTrace = decodeStackTraceFromStack() + } + } else { + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> serialized = decodeNullableSerializableElement(descriptor, 0, Boolean.serializer().nullable) ?: false + 1 -> message = decodeNullableSerializableElement(descriptor, 1, String.serializer().nullable) ?: "" + 2 -> stackTrace = decodeStackTraceFromStack() + 3 -> stackTrace = decodeSerializedStackTrace() + 4 -> cause = decodeNullableSerializableElement(descriptor, 4, nullable) + CompositeDecoder.DECODE_DONE -> break + else -> error("Unexpected index: $index") + } + } + } + + if (serialized) { + PlayerException(message, cause) + } else { + val error = decoder.requireNodeDecoder().decodeNode() + stackTrace = decodeStackTraceFromStack(error.getString("stack")) + JSErrorException(error) + }.apply { setStackTrace(stackTrace) } + } + } + + override fun serialize(encoder: Encoder, value: Throwable) { + when (value) { + is JSErrorException -> encoder.requireNodeEncoder().encodeNode(value.node) + else -> encoder.encodeStructure(descriptor) { + encodeBooleanElement(descriptor, 0, true) + encodeNullableSerializableElement(descriptor, 1, String.serializer(), value.message) + encodeStringElement(descriptor, 2, value.stackTraceToString()) + encodeSerializableElement( + descriptor, + 3, + serializedStackTraceSerializer, + value.stackTrace.map { stackTraceElement: StackTraceElement -> + SerializableStackTraceElement( + stackTraceElement.className, + stackTraceElement.methodName, + stackTraceElement.fileName, + stackTraceElement.lineNumber, + ) + }, + ) + encodeNullableSerializableElement(descriptor, 4, nullable, value.cause) + } + } + } + + @InternalPlayerApi + @Serializable + public data class SerializableStackTraceElement( + val className: String?, + val methodName: String?, + val fileName: String?, + val lineNumber: Int, + ) + + public companion object { + private val errorStackReg: Regex = + """(?<=at )(?[A-z\d\s.<>$]*(?=\.))?\.?(?[A-z\d\s.<>$]*(?= ))? ?\(?(?:.*, )?(?[A-z\d\s.<>$]*)?:?(?[A-z\d\s.<>$]*)?:?(?[A-z\d\s.<>$]*)?\)?$""".toRegex( + RegexOption.MULTILINE, + ) + + private val serializedStackTraceSerializer = ListSerializer(SerializableStackTraceElement.serializer()) + } +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/data/Binding.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/data/Binding.kt similarity index 68% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/data/Binding.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/data/Binding.kt index 0b43fc7ab..7f4b88cc8 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/data/Binding.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/data/Binding.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.data +package com.intuit.playerui.core.data /** Simplistic representation of player bindings */ public typealias Binding = String diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/data/DataController.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/data/DataController.kt similarity index 62% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/data/DataController.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/data/DataController.kt index 24c50fb6a..7b8c9eb66 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/data/DataController.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/data/DataController.kt @@ -1,24 +1,26 @@ -package com.intuit.player.jvm.core.data +package com.intuit.playerui.core.data -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.data.DataController.Serializer import kotlinx.serialization.Serializable /** Limited definition of the player data controller to enable data modification */ -@Serializable(with = DataController.Serializer::class) +@Serializable(with = Serializer::class) public class DataController internal constructor(override val node: Node) : NodeWrapper { /** Apply [data] to the underlying data model */ public fun set(data: Map) { - node.getFunction("set")?.invoke(data) + node.getInvokable("set")?.invoke(data) } /** [set] each of the [Binding]s contained in the [transaction] */ public fun set(transaction: List>) { - node.getFunction("set")?.invoke(transaction) + node.getInvokable("set")?.invoke(transaction) } - public fun get(binding: Binding): Any? = node.getFunction("get")?.invoke(binding) + public fun get(binding: Binding): Any? = node.getInvokable("get")?.invoke(binding) internal object Serializer : NodeWrapperSerializer(::DataController) } diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/data/DataModelWithParser.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/data/DataModelWithParser.kt similarity index 70% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/data/DataModelWithParser.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/data/DataModelWithParser.kt index c58bff0cf..dc93052c2 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/data/DataModelWithParser.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/data/DataModelWithParser.kt @@ -1,23 +1,25 @@ -package com.intuit.player.jvm.core.data +package com.intuit.playerui.core.data -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.data.DataModelWithParser.Serializer import kotlinx.serialization.Serializable // TODO: Initial support for bindings as [String]s, expand this to support all [BindingLike] types // TODO: Evaluate the advantages between [Map] and [JsonObject] /** Data model handle that provides [get] and [set] functionality w/ binding resolution */ -@Serializable(DataModelWithParser.Serializer::class) +@Serializable(Serializer::class) public class DataModelWithParser internal constructor(override val node: Node) : NodeWrapper { /** Retrieve specific section of the data model resolved from the [binding] */ public fun get(binding: Binding): Any? { - return node.getFunction("get")?.invoke(binding) + return node.getInvokable("get")?.invoke(binding) } /** [set] each of the [Binding]s contained in the [transaction] */ public fun set(transaction: List>) { - node.getFunction("set")?.invoke(transaction) + node.getInvokable("set")?.invoke(transaction) } internal object Serializer : NodeWrapperSerializer(::DataModelWithParser) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/experimental/ExperimentalPlayerApi.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/experimental/ExperimentalPlayerApi.kt similarity index 86% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/experimental/ExperimentalPlayerApi.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/experimental/ExperimentalPlayerApi.kt index 625c6b68f..4615f2650 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/experimental/ExperimentalPlayerApi.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/experimental/ExperimentalPlayerApi.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.experimental +package com.intuit.playerui.core.experimental @Target( AnnotationTarget.CLASS, @@ -11,7 +11,7 @@ package com.intuit.player.jvm.core.experimental AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, - AnnotationTarget.TYPEALIAS + AnnotationTarget.TYPEALIAS, ) @RequiresOptIn("These Player APIs are in active development and may change. Use with caution", RequiresOptIn.Level.WARNING) public annotation class ExperimentalPlayerApi diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/experimental/RuntimeClassDiscriminator.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/experimental/RuntimeClassDiscriminator.kt similarity index 72% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/experimental/RuntimeClassDiscriminator.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/experimental/RuntimeClassDiscriminator.kt index da85e342d..4171427f5 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/experimental/RuntimeClassDiscriminator.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/experimental/RuntimeClassDiscriminator.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.experimental +package com.intuit.playerui.core.experimental import kotlinx.serialization.json.JsonClassDiscriminator diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/expressions/Expression.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/expressions/Expression.kt similarity index 87% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/expressions/Expression.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/expressions/Expression.kt index ca5f5b2b6..02d2065e7 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/expressions/Expression.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/expressions/Expression.kt @@ -1,10 +1,15 @@ -package com.intuit.player.jvm.core.expressions +package com.intuit.playerui.core.expressions +import com.intuit.playerui.core.expressions.Expression.Collection +import com.intuit.playerui.core.expressions.Expression.Serializer +import com.intuit.playerui.core.expressions.Expression.Single import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.* +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.listSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @@ -26,7 +31,7 @@ public sealed class Expression { override val descriptor = PrimitiveSerialDescriptor( Single::class.toString(), - PrimitiveKind.STRING + PrimitiveKind.STRING, ) override fun serialize(encoder: Encoder, value: Single) = @@ -74,7 +79,7 @@ public sealed class Expression { */ override val descriptor = PrimitiveSerialDescriptor( Single::class.toString(), - PrimitiveKind.STRING + PrimitiveKind.STRING, ) override fun serialize(encoder: Encoder, value: Expression) = when (value) { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/expressions/ExpressionEvaluator.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/expressions/ExpressionEvaluator.kt similarity index 81% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/expressions/ExpressionEvaluator.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/expressions/ExpressionEvaluator.kt index ac684fdf2..1751320fc 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/expressions/ExpressionEvaluator.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/expressions/ExpressionEvaluator.kt @@ -1,8 +1,9 @@ -package com.intuit.player.jvm.core.expressions +package com.intuit.playerui.core.expressions -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer import kotlinx.serialization.Serializable /** Exists to provide a consistent interface for any structure that provides expression evaluation */ @@ -30,7 +31,7 @@ public fun ExpressionEvaluator.evaluate(expression: Expression): Any? = when (ex @Serializable(ExpressionController.Serializer::class) public class ExpressionController(override val node: Node) : NodeWrapper, ExpressionEvaluator { override fun evaluate(expressions: List): Any? = node - .getFunction("evaluate")?.invoke(expressions) + .getInvokable("evaluate")?.invoke(expressions) internal object Serializer : NodeWrapperSerializer(::ExpressionController) } diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/Flow.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/Flow.kt similarity index 77% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/Flow.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/Flow.kt index 882cc392c..a48668af6 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/Flow.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/Flow.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.flow +package com.intuit.playerui.core.flow import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement @@ -8,10 +8,10 @@ import kotlinx.serialization.json.JsonNull @Serializable public data class Flow( val id: String = UNKNOWN_ID, - val views: List = emptyList(), + val views: List? = emptyList(), val schema: JsonElement = JsonNull, val data: JsonElement = JsonNull, - val navigation: Navigation? = null + val navigation: Navigation? = null, ) { public companion object { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowController.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowController.kt similarity index 52% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowController.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowController.kt index 8ac60abad..61509c7a1 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowController.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowController.kt @@ -1,27 +1,30 @@ -package com.intuit.player.jvm.core.flow +package com.intuit.playerui.core.flow -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncHook1 -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializableField.Companion.NodeSerializableField -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable /** Limited definition of the player flow controller that enables flow transitions */ @Serializable(with = FlowController.Serializer::class) public class FlowController internal constructor(override val node: Node) : NodeWrapper, Transition { override fun transition(state: String, options: TransitionOptions?) { - node.getFunction("transition")?.invoke(state, options) + node.getInvokable("transition")?.invoke(state, options) } public val hooks: Hooks by NodeSerializableField(Hooks.serializer()) - public val current: FlowInstance? get() = node.getSerializable("current", FlowInstance.serializer()) + public val current: FlowInstance? by NodeSerializableField(FlowInstance.serializer().nullable) @Serializable(Hooks.Serializer::class) public class Hooks internal constructor(override val node: Node) : NodeWrapper { + public val flow: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("flow")!!, FlowInstance.serializer()) + by NodeSerializableField(NodeSyncHook1.serializer(FlowInstance.serializer())) internal object Serializer : NodeWrapperSerializer(::Hooks) } diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowException.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowException.kt similarity index 66% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowException.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowException.kt index 306911bb7..99ec023a6 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/FlowException.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowException.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.flow +package com.intuit.playerui.core.flow -import com.intuit.player.jvm.core.player.PlayerException +import com.intuit.playerui.core.player.PlayerException /** Specific [PlayerException] that denotes an exception that occurred within the context of a flow */ public class FlowException internal constructor(reason: String) : PlayerException(reason) diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowInstance.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowInstance.kt new file mode 100644 index 000000000..a57c44566 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowInstance.kt @@ -0,0 +1,61 @@ +package com.intuit.playerui.core.flow + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.hooks.NodeSyncBailHook1 +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook2 +import com.intuit.playerui.core.bridge.hooks.NodeSyncWaterfallHook1 +import com.intuit.playerui.core.bridge.hooks.NodeSyncWaterfallHook2 +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.flow.state.NavigationFlowState +import com.intuit.playerui.core.player.state.NamedState +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer + +@Serializable(FlowInstance.Serializer::class) +public class FlowInstance(override val node: Node) : NodeWrapper, Transition { + + public val id: String by NodeSerializableField(String.serializer()) + + public val hooks: Hooks by NodeSerializableField(Hooks.serializer()) + + public val currentState: NamedState? by NodeSerializableField(NamedState.serializer().nullable) + + override fun transition(state: String, options: TransitionOptions?) { + node.getInvokable("transition")?.invoke(state, options) + } + + @Serializable(Hooks.Serializer::class) + public class Hooks internal constructor(override val node: Node) : NodeWrapper { + /** A callback when the onStart node was present */ + public val onStart: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(GenericSerializer())) + + /** A callback when the onEnd node was present */ + public val onEnd: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(GenericSerializer())) + + /** A chance to manipulate the flow-node used to calculate the given transition used */ + public val beforeTransition: NodeSyncWaterfallHook2 + by NodeSerializableField(NodeSyncWaterfallHook2.serializer(NavigationFlowState.serializer(), String.serializer())) + + /** A hook to intercept and block a transition */ + public val skipTransition: NodeSyncBailHook1 + by NodeSerializableField(NodeSyncBailHook1.serializer(NamedState.serializer(), Boolean.serializer())) + + /** A chance to manipulate the flow-node calculated after a transition */ + public val resolveTransitionNode: NodeSyncWaterfallHook1 + by NodeSerializableField(NodeSyncWaterfallHook1.serializer(NavigationFlowState.serializer())) + + /** A callback when a transition from 1 state to another was made */ + public val transition: NodeSyncHook2 + by NodeSerializableField(NodeSyncHook2.serializer(NamedState.serializer().nullable, NamedState.serializer())) + + internal object Serializer : NodeWrapperSerializer(::Hooks) + } + + internal object Serializer : NodeWrapperSerializer(::FlowInstance) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowResult.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowResult.kt new file mode 100644 index 000000000..85b00f8c9 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/FlowResult.kt @@ -0,0 +1,26 @@ +package com.intuit.playerui.core.flow + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.flow.state.NavigationFlowEndState +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull + +@Serializable(with = FlowResult.Serializer::class) +public class FlowResult(override val node: Node) : NodeWrapper { + + /** End state describing _how_ the flow ended (forwards, backwards, etc) */ + public val endState: NavigationFlowEndState by NodeSerializableField(NavigationFlowEndState.serializer()) + + /** The serialized data-model */ + public val data: JsonElement by NodeSerializableField(JsonElement.serializer()) { JsonNull } + + override fun equals(other: Any?): Boolean = node == other + + override fun hashCode(): Int = node.hashCode() + + public object Serializer : NodeWrapperSerializer(::FlowResult) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/Navigation.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/Navigation.kt similarity index 81% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/Navigation.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/Navigation.kt index a02dc173f..d507e104f 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/Navigation.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/Navigation.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.flow +package com.intuit.playerui.core.flow import kotlinx.serialization.Serializable @@ -10,5 +10,5 @@ import kotlinx.serialization.Serializable @Serializable public data class Navigation( val BEGIN: String, - val flows: Map = emptyMap() + val flows: Map = emptyMap(), ) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/NavigationFlow.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/NavigationFlow.kt similarity index 53% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/NavigationFlow.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/NavigationFlow.kt index 9e6eebd5f..1ea815d58 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/NavigationFlow.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/NavigationFlow.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.core.flow +package com.intuit.playerui.core.flow -import com.intuit.player.jvm.core.expressions.Expression -import com.intuit.player.jvm.core.flow.state.NavigationFlowState +import com.intuit.playerui.core.expressions.Expression +import com.intuit.playerui.core.flow.state.NavigationFlowState import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -11,5 +11,5 @@ public data class NavigationFlow( val startState: String, val onStart: Expression?, @Transient - val states: Map? = null + val states: Map? = null, ) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/Transition.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/Transition.kt similarity index 82% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/Transition.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/Transition.kt index bdf74ee54..350377ae0 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/Transition.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/Transition.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.flow +package com.intuit.playerui.core.flow -import com.intuit.player.jvm.core.flow.state.NavigationFlowState +import com.intuit.playerui.core.flow.state.NavigationFlowState /** Common interface describing the signature for transitioning between [NavigationFlowState] */ public interface Transition { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/TransitionOptions.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/TransitionOptions.kt similarity index 83% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/TransitionOptions.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/TransitionOptions.kt index fc9d1412b..7009516a5 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/TransitionOptions.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/TransitionOptions.kt @@ -1,11 +1,11 @@ -package com.intuit.player.jvm.core.flow +package com.intuit.playerui.core.flow import kotlinx.serialization.Serializable /** Options used to change how transitions are handled */ @Serializable public data class TransitionOptions( - val force: Boolean = false + val force: Boolean = false, ) { public companion object { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/state/NavigationFlowState.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/state/NavigationFlowState.kt similarity index 61% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/state/NavigationFlowState.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/state/NavigationFlowState.kt index 575062906..12d238460 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/state/NavigationFlowState.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/state/NavigationFlowState.kt @@ -1,43 +1,38 @@ -package com.intuit.player.jvm.core.flow.state - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.PolymorphicNodeWrapperSerializer -import com.intuit.player.jvm.core.expressions.Expression -import com.intuit.player.jvm.core.flow.state.NavigationFlowStateType.* -import kotlinx.serialization.KSerializer +package com.intuit.playerui.core.flow.state + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.snapshot +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.experimental.RuntimeClassDiscriminator +import com.intuit.playerui.core.expressions.Expression +import com.intuit.playerui.core.flow.state.NavigationFlowStateType.ACTION +import com.intuit.playerui.core.flow.state.NavigationFlowStateType.END +import com.intuit.playerui.core.flow.state.NavigationFlowStateType.EXTERNAL +import com.intuit.playerui.core.flow.state.NavigationFlowStateType.FLOW +import com.intuit.playerui.core.flow.state.NavigationFlowStateType.VIEW import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer /** The base representation of a state within a Flow */ -@Serializable(with = NavigationFlowState.NavigationFlowStateSerializer::class) +@Serializable +@RuntimeClassDiscriminator("state_type") public sealed class NavigationFlowState : NodeWrapper { /** A property to determine the type of state this is */ public abstract val stateType: NavigationFlowStateType - - internal class NavigationFlowStateSerializer : PolymorphicNodeWrapperSerializer() { - override fun selectDeserializer(node: Node): KSerializer { - return when (NavigationFlowStateType.valueOf(node.getString("state_type") ?: "")) { - VIEW -> NavigationFlowViewState.serializer() - END -> NavigationFlowEndState.serializer() - FLOW -> NavigationFlowFlowState.serializer() - ACTION -> NavigationFlowActionState.serializer() - EXTERNAL -> NavigationFlowExternalState.serializer() - } - } - } } /** A generic state that can transition to another state */ +@Serializable public sealed class NavigationFlowTransitionableState(override val node: Node) : NavigationFlowState() { /** A mapping of transition-name to FlowState name */ - public val transitions: Map - get() = node.getObject("transitions")?.run { - keys.map { it to (this.getString(it) ?: "") }.toMap() - } ?: emptyMap() + public val transitions: Map by NodeSerializableField(MapSerializer(String.serializer(), String.serializer())) /** An id corresponding to a view from the 'views' array */ - public val ref: String get() = node.getString("ref") ?: "" + public val ref: String by NodeSerializableField(String.serializer()) } /** Action states execute an expression to determine the next state to transition to */ @@ -52,9 +47,9 @@ public class NavigationFlowActionState internal constructor(override val node: N * An expression to execute. * The return value determines the transition to take */ - public val exp: Expression get() = node.getSerializable("exp", Expression.serializer())!! + public val exp: Expression by NodeSerializableField(Expression.serializer()) - internal object Serializer : NodeWrapperSerializer(::NavigationFlowActionState) + internal object Serializer : NodeWrapperSerializer(::NavigationFlowActionState, ACTION.name) } /** An END state of the flow */ @@ -62,7 +57,7 @@ public class NavigationFlowActionState internal constructor(override val node: N public class NavigationFlowEndState internal constructor(override val node: Node) : NavigationFlowState(), NodeWrapper, - Map by node { + Map by node.snapshot() { override val stateType: NavigationFlowStateType = END @@ -70,11 +65,14 @@ public class NavigationFlowEndState internal constructor(override val node: Node * A description of _how_ the flow ended. * If this is a flow started from another flow, the outcome determines the flow transition */ - public val outcome: String get() = node.getString("outcome") ?: "" + public val outcome: String by NodeSerializableField(String.serializer()) { "" } - internal object Serializer : NodeWrapperSerializer(::NavigationFlowEndState) + internal object Serializer : NodeWrapperSerializer(::NavigationFlowEndState, END.name) } +@ExperimentalPlayerApi +public val NavigationFlowEndState.param: Any? get() = get("param") + /** * External Flow states represent states in the FSM that can't be resolved internally in the player. * The flow will wait for the embedded application to manage moving to the next state via a transition @@ -89,7 +87,7 @@ public class NavigationFlowExternalState internal constructor(override val node: /** Getter for any additional properties */ public operator fun get(name: String): Any? = node[name] - internal object Serializer : NodeWrapperSerializer(::NavigationFlowExternalState) + internal object Serializer : NodeWrapperSerializer(::NavigationFlowExternalState, EXTERNAL.name) } @Serializable(with = NavigationFlowFlowState.Serializer::class) @@ -99,7 +97,7 @@ public class NavigationFlowFlowState internal constructor(override val node: Nod override val stateType: NavigationFlowStateType = FLOW - internal object Serializer : NodeWrapperSerializer(::NavigationFlowFlowState) + internal object Serializer : NodeWrapperSerializer(::NavigationFlowFlowState, FLOW.name) } /** A state representing a view */ @@ -110,5 +108,5 @@ public class NavigationFlowViewState internal constructor(override val node: Nod override val stateType: NavigationFlowStateType = VIEW - internal object Serializer : NodeWrapperSerializer(::NavigationFlowViewState) + internal object Serializer : NodeWrapperSerializer(::NavigationFlowViewState, VIEW.name) } diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/state/NavigationFlowStateType.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/state/NavigationFlowStateType.kt similarity index 71% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/state/NavigationFlowStateType.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/state/NavigationFlowStateType.kt index 30ba915bc..dcb493bd5 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/flow/state/NavigationFlowStateType.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/flow/state/NavigationFlowStateType.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.flow.state +package com.intuit.playerui.core.flow.state /** All possible enumerations of navigation flow states */ public enum class NavigationFlowStateType { @@ -6,5 +6,5 @@ public enum class NavigationFlowStateType { END, FLOW, ACTION, - EXTERNAL + EXTERNAL, } diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/logger/TapableLogger.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/logger/TapableLogger.kt similarity index 62% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/logger/TapableLogger.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/logger/TapableLogger.kt index 4c91662b4..9b986d313 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/logger/TapableLogger.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/logger/TapableLogger.kt @@ -1,14 +1,17 @@ -package com.intuit.player.jvm.core.logger +package com.intuit.playerui.core.logger import com.intuit.hooks.HookContext -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncHook1 -import com.intuit.player.jvm.core.bridge.hooks.SyncHook1 -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializableField.Companion.NodeSerializableField -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.plugins.LoggerPlugin +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 +import com.intuit.playerui.core.bridge.hooks.SyncHook1 +import com.intuit.playerui.core.bridge.runtime +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.plugins.LoggerPlugin +import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer @@ -25,7 +28,7 @@ public class TapableLogger(override val node: Node) : LoggerPlugin, NodeWrapper private fun loggerHook(key: String) = NodeSyncHook1( node.getObject(key)!!, - ListSerializer(GenericSerializer()) + ListSerializer(GenericSerializer()), ).toTypedArrayHook() internal object Serializer : NodeWrapperSerializer(::Hooks) @@ -34,23 +37,33 @@ public class TapableLogger(override val node: Node) : LoggerPlugin, NodeWrapper public val hooks: Hooks by NodeSerializableField(Hooks.serializer()) public override fun trace(vararg args: Any?) { - node.getFunction("trace")!!(*args) + runtime.scope.launch { + node.getInvokable("trace")!!(*args) + } } public override fun debug(vararg args: Any?) { - node.getFunction("debug")!!(*args) + runtime.scope.launch { + node.getInvokable("debug")!!(*args) + } } public override fun info(vararg args: Any?) { - node.getFunction("info")!!(*args) + runtime.scope.launch { + node.getInvokable("info")!!(*args) + } } public override fun warn(vararg args: Any?) { - node.getFunction("warn")!!(*args) + runtime.scope.launch { + node.getInvokable("warn")!!(*args) + } } public override fun error(vararg args: Any?) { - node.getFunction("error")!!(*args) + runtime.scope.launch { + node.getInvokable("error")!!(*args) + } } public fun addHandler(logger: LoggerPlugin) { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/managed/AsyncIterationManager.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/managed/AsyncIterationManager.kt similarity index 72% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/managed/AsyncIterationManager.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/managed/AsyncIterationManager.kt index 0945b2b0f..457e5139f 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/managed/AsyncIterationManager.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/managed/AsyncIterationManager.kt @@ -1,8 +1,13 @@ -package com.intuit.player.jvm.core.managed +package com.intuit.playerui.core.managed -import com.intuit.player.jvm.core.player.state.CompletedState -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* +import com.intuit.playerui.core.managed.AsyncIterationManager.State.NotStarted +import com.intuit.playerui.core.player.state.CompletedState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch /** Wrapper of an [AsyncIterator] that captures iterations within a [StateFlow] */ public class AsyncIterationManager(public val iterator: AsyncIterator) { @@ -15,7 +20,7 @@ public class AsyncIterationManager(public val iterator public class Error(public val error: Exception) : State() } - private val _state = MutableStateFlow(State.NotStarted) + private val _state = MutableStateFlow(NotStarted) /** a stateful flow capturing each iteration of the [iterator] and maintaining the last update */ public val state: StateFlow = _state.asStateFlow() @@ -30,13 +35,13 @@ public class AsyncIterationManager(public val iterator next?.let(State::Item) ?: State.Done } catch (exception: Exception) { State.Error(exception) - } + }, ) } } /** - * An [AsyncIterationManager] specifically for a [Player][com.intuit.player.jvm.core.player.Player] + * An [AsyncIterationManager] specifically for a [Player][com.intuit.playerui.core.player.Player] * that consumes [String] flows and a results in a [CompletedState]. */ public typealias FlowManager = AsyncIterationManager diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/managed/AsyncIterator.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/managed/AsyncIterator.kt similarity index 91% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/managed/AsyncIterator.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/managed/AsyncIterator.kt index 6a03bd965..d3c4e13be 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/managed/AsyncIterator.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/managed/AsyncIterator.kt @@ -1,10 +1,8 @@ -package com.intuit.player.jvm.core.managed +package com.intuit.playerui.core.managed -import com.intuit.player.jvm.core.player.state.CompletedState -import kotlinx.coroutines.ExperimentalCoroutinesApi +import com.intuit.playerui.core.player.state.CompletedState /** Generic async iterator that uses some [Result] to determine the next [Item] in the iteration */ -@OptIn(ExperimentalCoroutinesApi::class) public interface AsyncIterator { /** diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt new file mode 100644 index 000000000..821eed4b5 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt @@ -0,0 +1,182 @@ +package com.intuit.playerui.core.player + +import com.intuit.playerui.core.bridge.Completable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.Promise +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeConfig +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.ScriptContext +import com.intuit.playerui.core.bridge.runtime.add +import com.intuit.playerui.core.bridge.runtime.runtimeFactory +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.logger.TapableLogger +import com.intuit.playerui.core.player.HeadlessPlayer.Companion.bundledSource +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.core.player.state.ReleasedState +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.core.plugins.JSPluginWrapper +import com.intuit.playerui.core.plugins.LoggerPlugin +import com.intuit.playerui.core.plugins.PlayerPlugin +import com.intuit.playerui.core.plugins.Plugin +import com.intuit.playerui.core.plugins.RuntimePlugin +import com.intuit.playerui.core.plugins.logging.loggers +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.net.URL + +/** + * Headless [Player] wrapping a core JS player with a [Runtime]. The [player] will + * be instantiated from the [bundledSource] unless supplied with + * a different [source] to read from. + * + * Though this accepts a collection of generic [Plugin]s, this + * will only [Plugin.apply] [plugins] that are known and appropriate + * for this context. Additionally, certain plugins will be filtered + * and applied in a specific order that guarantees safety. + * + * Allowed [Plugin]s: + * - [RuntimePlugin] + * - [JSPluginWrapper] + * - [PlayerPlugin] + */ +public class HeadlessPlayer +@ExperimentalPlayerApi +@JvmOverloads +public constructor( + override val plugins: List, + explicitRuntime: Runtime<*>? = null, + private val source: URL = bundledSource, + config: PlayerRuntimeConfig = PlayerRuntimeConfig(), +) : Player(), NodeWrapper { + + /** Convenience constructor to allow [plugins] to be passed as varargs */ + @ExperimentalPlayerApi @JvmOverloads + public constructor( + vararg plugins: Plugin, + config: PlayerRuntimeConfig = PlayerRuntimeConfig(), + explicitRuntime: Runtime<*>? = null, + source: URL = bundledSource, + ) : this(plugins.toList(), explicitRuntime, source, config) + + public constructor( + explicitRuntime: Runtime<*>, + vararg plugins: Plugin, + ) : this(plugins.toList(), explicitRuntime, config = explicitRuntime.config) + + private val player: Node + + override val node: Node by ::player + + override val logger: TapableLogger by NodeSerializableField(TapableLogger.serializer(), NodeSerializableField.CacheStrategy.Full) + + override val hooks: Hooks by NodeSerializableField(Hooks.serializer(), NodeSerializableField.CacheStrategy.Full) + + override val state: PlayerFlowState get() = if (player.isReleased()) { + ReleasedState + } else { + player.getInvokable("getState")!!().deserialize(PlayerFlowState.serializer()) + } + + public val runtime: Runtime<*> = explicitRuntime ?: runtimeFactory.create { + debuggable = config.debuggable + coroutineExceptionHandler = config.coroutineExceptionHandler ?: CoroutineExceptionHandler { _, throwable -> + inProgressState?.fail(throwable) ?: logger.error( + "Exception caught in Player scope: ${throwable.message}", + throwable.stackTrace.joinToString("\n") { + "\tat $it" + }.replaceFirst("\tat ", "\n"), + ) + } + } + + override val scope: CoroutineScope by runtime::scope + + init { + /** 1. load source into the [runtime] and release lock */ + runtime.load(ScriptContext(if (runtime.config.debuggable) debugSource.readText() else source.readText(), bundledSourcePath)) + + /** 2. merge explicit [LoggerPlugin]s with ones created by service loader */ + val loggerPlugins = plugins.filterIsInstance().let { explicitLoggers -> + val explicitLoggerPlugins = explicitLoggers.map { it::class } + explicitLoggers + loggers { name = this@HeadlessPlayer::class.java.name } + .filterNot { explicitLoggerPlugins.contains(it::class) } + } + + /** 3. apply runtime plugins and build [JSPlayerConfig] */ + val config = plugins + .filterIsInstance() + .onEach { it.apply(runtime) } + .filterIsInstance() + .let { JSPlayerConfig(it, loggerPlugins) } + + /** 4. build JS headless player */ + runtime.add("config", config) + player = runtime.execute("new Player.Player(config)") as? Node + ?: throw PlayerException("Couldn't create backing JS Player w/ config: $config") + + runtime.add("player", player) + + // we only have access to the logger after we have the player instance + if (runtime.config.debuggable) { + runtime.checkBlockingThread = { + if (name == "main") { + scope.launch { + logger.warn( + "Main thread is blocking on JS runtime access: $this", + stackTrace.joinToString("\n") { + "\tat $it" + }.replaceFirst("\tat ", "\n"), + ) + } + } + } + } + + /** 5. apply to player plugins */ + plugins + .filterIsInstance() + .onEach { it.apply(this) } + } + + override fun start(flow: String): Completable = try { + start(runtime.execute("($flow)") as Node) + } catch (exception: Exception) { + val wrapped = PlayerException("Could not load Player content", exception) + inProgressState?.fail(wrapped) ?: hooks.state.call(hashMapOf(), arrayOf(ErrorState.from(wrapped))) + PlayerCompletable(Promise.reject(wrapped)) + } + + public fun start(flow: Node): Completable = PlayerCompletable(player.getInvokable("start")!!.invoke(flow)) + + /** Start a [flow] and subscribe to the result */ + public fun start(flow: Node, onComplete: (Result) -> Unit): Completable = + start(flow).apply { + onComplete(onComplete) + } + + override fun release() { + if (!runtime.isReleased()) { + hooks.state.call(HashMap(), arrayOf(ReleasedState)) + runtime.release() + } + } + + internal companion object { + + private const val bundledSourcePath = "core/player/dist/player.prod.js" + private const val debugSourcePath = "core/player/dist/player.dev.js" + + /** Gets [URL] of the bundled source */ + private val bundledSource get() = this::class.java + .classLoader.getResource(bundledSourcePath)!! + + private val debugSource get() = this::class.java + .classLoader.getResource(debugSourcePath)!! + } +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/JSPlayerConfig.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/JSPlayerConfig.kt new file mode 100644 index 000000000..de9219eab --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/JSPlayerConfig.kt @@ -0,0 +1,22 @@ +package com.intuit.playerui.core.player + +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.plugins.JSPluginWrapper +import com.intuit.playerui.core.plugins.LoggerPlugin +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +/** [JSPluginWrapper]s and [LoggerPlugin]s to configure a JS Player with */ +@Serializable +public data class JSPlayerConfig( + val plugins: List = listOf(), + @Transient val loggers: List = emptyList(), +) { + val logger: Map> = mapOf( + "trace" to Invokable { args -> loggers.forEach { it.trace(*args) } }, + "debug" to Invokable { args -> loggers.forEach { it.debug(*args) } }, + "info" to Invokable { args -> loggers.forEach { it.info(*args) } }, + "warn" to Invokable { args -> loggers.forEach { it.warn(*args) } }, + "error" to Invokable { args -> loggers.forEach { it.error(*args) } }, + ) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/Player.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt similarity index 58% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/Player.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt index c748430f4..aac34ba7c 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/Player.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt @@ -1,24 +1,32 @@ -package com.intuit.player.jvm.core.player - -import com.intuit.player.jvm.core.bridge.Completable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncHook1 -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.data.DataController -import com.intuit.player.jvm.core.experimental.ExperimentalPlayerApi -import com.intuit.player.jvm.core.expressions.ExpressionController -import com.intuit.player.jvm.core.flow.FlowController -import com.intuit.player.jvm.core.flow.FlowResult -import com.intuit.player.jvm.core.logger.TapableLogger -import com.intuit.player.jvm.core.player.state.* -import com.intuit.player.jvm.core.plugins.Pluggable -import com.intuit.player.jvm.core.validation.ValidationController -import com.intuit.player.jvm.core.view.View -import com.intuit.player.jvm.core.view.ViewController +package com.intuit.playerui.core.player + +import com.intuit.playerui.core.bridge.Completable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.data.DataController +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.expressions.ExpressionController +import com.intuit.playerui.core.flow.FlowController +import com.intuit.playerui.core.flow.FlowResult +import com.intuit.playerui.core.logger.TapableLogger +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.core.player.state.ReleasedState +import com.intuit.playerui.core.plugins.Pluggable +import com.intuit.playerui.core.validation.ValidationController +import com.intuit.playerui.core.view.View +import com.intuit.playerui.core.view.ViewController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.job import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import java.net.URL +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** Agnostic [Pluggable] [Player] to provide the core API */ public abstract class Player : Pluggable { @@ -34,7 +42,7 @@ public abstract class Player : Pluggable { /** [Player] hooks that expose underlying controllers for supplementing functionality */ @Serializable(Hooks.Companion.Serializer::class) public interface Hooks { - /** The hook that fires every time we create a new flowController (a new flow is started) */ + /** The hook that fires every time we create a new flowController (a new FRF) */ public val flowController: NodeSyncHook1 /** The hook that updates/handles view things */ @@ -57,22 +65,15 @@ public abstract class Player : Pluggable { public companion object { internal interface HooksByNode : Hooks, NodeWrapper public fun serializer(): KSerializer = Serializer - internal operator fun invoke(node: Node): HooksByNode = object : HooksByNode { + internal operator fun invoke(node: Node): HooksByNode = object : HooksByNode, NodeWrapper { override val node: Node = node - override val flowController: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("flowController")!!, FlowController.serializer()) - override val viewController: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("viewController")!!, ViewController.serializer()) - override val view: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("view")!!, View.serializer()) - override val expressionEvaluator: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("expressionEvaluator")!!, ExpressionController.serializer()) - override val dataController: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("dataController")!!, DataController.serializer()) - override val validationController: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("validationController")!!, ValidationController.serializer()) - override val state: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("state")!!, PlayerFlowState.serializer()) + override val flowController: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(FlowController.serializer())) + override val viewController: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(ViewController.serializer())) + override val view: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(View.serializer())) + override val expressionEvaluator: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(ExpressionController.serializer())) + override val dataController: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(DataController.serializer())) + override val validationController: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(ValidationController.serializer())) + override val state: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(PlayerFlowState.serializer())) } internal object Serializer : KSerializer by NodeWrapperSerializer(Hooks::invoke) as KSerializer @@ -82,6 +83,9 @@ public abstract class Player : Pluggable { /** The current [PlayerFlowState] of the player. */ public abstract val state: PlayerFlowState + /** [CoroutineScope] to be used for launching coroutines that should be cancelled once the Player is released */ + public abstract val scope: CoroutineScope + /** * Asynchronously [start] the [flow] represented as a [String]. The * [FlowResult] can be obtained by subscribing the the [Completable.onComplete] @@ -121,3 +125,13 @@ public abstract class Player : Pluggable { onComplete(onComplete) } } + +/** + * Create a child [CoroutineScope] of the [Player.scope], such that it'll inherit the [CoroutineContext], + * but with its own [SupervisorJob]. Unless overridden by the provided [context], this ensures that the + * sub-scope can be cancelled independently, but still contains other top-level elements, such as the + * dispatcher or coroutine exception handler. + */ +@ExperimentalPlayerApi +public fun Player.subScope(context: CoroutineContext = EmptyCoroutineContext): CoroutineScope = + CoroutineScope(scope.coroutineContext + SupervisorJob(scope.coroutineContext.job) + context) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerCompletable.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerCompletable.kt similarity index 73% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerCompletable.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerCompletable.kt index 28935152a..478a9176d 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerCompletable.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerCompletable.kt @@ -1,10 +1,11 @@ -package com.intuit.player.jvm.core.player - -import com.intuit.player.jvm.core.bridge.Completable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.Promise -import com.intuit.player.jvm.core.bridge.toCompletable -import com.intuit.player.jvm.core.player.state.* +package com.intuit.playerui.core.player + +import com.intuit.playerui.core.bridge.Completable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.Promise +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.core.player.state.PlayerFlowState import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerException.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerException.kt similarity index 70% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerException.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerException.kt index c93ca3454..1ffebe6ca 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerException.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerException.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.player +package com.intuit.playerui.core.player -import com.intuit.player.jvm.core.bridge.serialization.serializers.ThrowableSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.ThrowableSerializer import kotlinx.serialization.Serializable /** Generic exception for any errors encountered in the scope of the [Player] */ diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerFlowStatus.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerFlowStatus.kt new file mode 100644 index 000000000..cc22fe167 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerFlowStatus.kt @@ -0,0 +1,12 @@ +package com.intuit.playerui.core.player + +/** All possible enumerations of player states */ +public enum class PlayerFlowStatus(public val value: String) { + NOT_STARTED("not-started"), + IN_PROGRESS("in-progress"), + COMPLETED("completed"), + ERROR("error"), + + /** Only a JVM player state */ + RELEASED("released"), +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerHooks.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerHooks.kt similarity index 77% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerHooks.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerHooks.kt index 35d3090ef..79ddbf2e2 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/PlayerHooks.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/PlayerHooks.kt @@ -1,8 +1,8 @@ -package com.intuit.player.jvm.core.player +package com.intuit.playerui.core.player @Deprecated( "Abstracted by Player.Hooks interface, the old implementation of Node-backed hooks is within Player.Hooks.Companion as an anonymous object", ReplaceWith("Player.Hooks"), - DeprecationLevel.ERROR + DeprecationLevel.ERROR, ) public typealias PlayerHooks = Player.Hooks diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/state/NamedState.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/state/NamedState.kt new file mode 100644 index 000000000..e89995f23 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/state/NamedState.kt @@ -0,0 +1,22 @@ +package com.intuit.playerui.core.player.state + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.flow.state.NavigationFlowState +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer + +@Serializable(NamedState.Serializer::class) +public class NamedState internal constructor(override val node: Node) : NodeWrapper { + /** The name of the navigation node */ + public val name: String by NodeSerializableField(String.serializer()) + + /** The navigation node */ + public val value: NavigationFlowState by NodeSerializableField(NavigationFlowState.serializer()) + + override fun toString(): String = (name to value).toString() + + internal object Serializer : NodeWrapperSerializer(::NamedState) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/state/PlayerFlowState.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/state/PlayerFlowState.kt similarity index 62% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/state/PlayerFlowState.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/player/state/PlayerFlowState.kt index 3aca2407a..dd56a9a52 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/player/state/PlayerFlowState.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/state/PlayerFlowState.kt @@ -1,52 +1,51 @@ -package com.intuit.player.jvm.core.player.state - -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.* -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializableField.Companion.NodeSerializableField -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.PolymorphicNodeWrapperSerializer -import com.intuit.player.jvm.core.data.DataController -import com.intuit.player.jvm.core.data.DataModelWithParser -import com.intuit.player.jvm.core.expressions.ExpressionController -import com.intuit.player.jvm.core.expressions.ExpressionEvaluator -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.core.flow.FlowController -import com.intuit.player.jvm.core.flow.FlowResult -import com.intuit.player.jvm.core.flow.Transition -import com.intuit.player.jvm.core.flow.state.NavigationFlowEndState -import com.intuit.player.jvm.core.flow.state.NavigationFlowState -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.core.player.PlayerFlowStatus -import com.intuit.player.jvm.core.player.PlayerFlowStatus.* -import com.intuit.player.jvm.core.validation.ValidationController -import com.intuit.player.jvm.core.view.View -import com.intuit.player.jvm.core.view.ViewController -import kotlinx.serialization.KSerializer +package com.intuit.playerui.core.player.state + +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Completable +import com.intuit.playerui.core.bridge.EmptyNode +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.Promise +import com.intuit.playerui.core.bridge.deserialize +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.getSerializable +import com.intuit.playerui.core.bridge.getSymbol +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.data.DataController +import com.intuit.playerui.core.data.DataModelWithParser +import com.intuit.playerui.core.experimental.RuntimeClassDiscriminator +import com.intuit.playerui.core.expressions.ExpressionController +import com.intuit.playerui.core.expressions.ExpressionEvaluator +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.core.flow.FlowController +import com.intuit.playerui.core.flow.FlowResult +import com.intuit.playerui.core.flow.Transition +import com.intuit.playerui.core.flow.state.NavigationFlowEndState +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.PlayerFlowStatus +import com.intuit.playerui.core.player.PlayerFlowStatus.COMPLETED +import com.intuit.playerui.core.player.PlayerFlowStatus.ERROR +import com.intuit.playerui.core.player.PlayerFlowStatus.IN_PROGRESS +import com.intuit.playerui.core.player.PlayerFlowStatus.NOT_STARTED +import com.intuit.playerui.core.player.PlayerFlowStatus.RELEASED +import com.intuit.playerui.core.validation.ValidationController +import com.intuit.playerui.core.view.View +import com.intuit.playerui.core.view.ViewController import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull /** The base representation of the player state */ -@Serializable(with = PlayerFlowStateSerializer::class) +@Serializable +@RuntimeClassDiscriminator("status") public sealed class PlayerFlowState : NodeWrapper { /** The status of the given flow */ public abstract val status: PlayerFlowStatus /** A unique reference for the life-cycle of a flow */ - public val ref: String? get() = node.getString("ref") -} - -internal class PlayerFlowStateSerializer : PolymorphicNodeWrapperSerializer() { - override fun selectDeserializer(node: Node): KSerializer { - return when (PlayerFlowStatus.from(node.getString("status"))) { - NOT_STARTED -> NotStartedState.serializer() - IN_PROGRESS -> InProgressState.serializer() - COMPLETED -> CompletedState.serializer() - ERROR -> ErroneousState.serializer() - RELEASED -> ReleasedState.ReleasedStateSerializer - } - } + public val ref: String? get() = node.getSymbol("ref") } /** Player state describing when a flow completed successfully */ @@ -56,15 +55,18 @@ public class CompletedState(override val node: Node) : override val status: PlayerFlowStatus = COMPLETED - public val endState: NavigationFlowEndState - get() = node.getSerializable("endState", NavigationFlowState.serializer()) - as? NavigationFlowEndState ?: throw PlayerException("flow result not defined") + public val endState: NavigationFlowEndState by NodeSerializableField(NavigationFlowEndState.serializer()) - public val data: JsonElement get() = node.getJson("data") ?: JsonNull + public val data: JsonElement by NodeSerializableField(JsonElement.serializer()) { JsonNull } - public val dataModel: DataModelWithParser get() = node.getSerializable("dataModel")!! + internal val controllers: ControllerState by NodeSerializableField(ControllerState.serializer()) - internal object Serializer : NodeWrapperSerializer(::CompletedState) + // TODO: Completed state dataModel change needs rectification here + public val dataModel: DataModelWithParser by lazy { + DataModelWithParser(controllers.data.node) + } + + internal object Serializer : NodeWrapperSerializer(::CompletedState, COMPLETED.value) } /** Player state describing when a flow finished but not successfully */ @@ -97,17 +99,20 @@ internal class ErroneousState(override val node: Node) : ErrorState(), NodeWrapper { - override val flow: Flow get() = node.getSerializable("flow", Flow.serializer()) ?: Flow() + override val flow: Flow by NodeSerializableField(Flow.serializer()) { Flow() } - override val error: PlayerException get() = when (val rawError = node["error"]) { - is PlayerException -> rawError - is Exception -> PlayerException(rawError.message ?: "", rawError) - is Node -> rawError.deserialize() - is String -> PlayerException(rawError) - else -> PlayerException("unable to determine error") + override val error: PlayerException by NodeSerializableField(PlayerException.serializer()) { + // TODO: Need to test this error handling strategy + when (val rawError = get(it)) { + is PlayerException -> rawError + is Exception -> PlayerException(rawError.message ?: "", rawError) + is Node -> rawError.deserialize() + is String -> PlayerException(rawError) + else -> PlayerException(rawError?.toString() ?: "unable to determine error") + } } - internal object Serializer : NodeWrapperSerializer(::ErroneousState) + internal object Serializer : NodeWrapperSerializer(::ErroneousState, ERROR.value) } /** [InProgressState] is for when a flow is currently executing */ @@ -123,16 +128,16 @@ public class InProgressState internal constructor(override val node: Node) : /** [FlowResult] value that will be available once the flow completes */ // TODO: Make non-nullable if possible - requires Promise change public val flowResult: Completable get() = Promise( - node.getObject("flowResult")!! + node.getObject("flowResult")!!, ).toCompletable(FlowResult.serializer()) public val controllers: ControllerState by NodeSerializableField(ControllerState.serializer()) public fun fail(error: Throwable) { - node.getFunction("fail")!!.invoke(error) + node.getInvokable("fail")!!.invoke(error) } - internal object Serializer : NodeWrapperSerializer(::InProgressState) + internal object Serializer : NodeWrapperSerializer(::InProgressState, IN_PROGRESS.value) } // inline on purpose to capture stack trace of calling site @@ -177,16 +182,17 @@ public class NotStartedState internal constructor(override val node: Node) : override val status: PlayerFlowStatus = NOT_STARTED - internal object Serializer : NodeWrapperSerializer(::NotStartedState) + internal object Serializer : NodeWrapperSerializer(::NotStartedState, NOT_STARTED.value) } /** Terminal player state the signifies when the player resources have been released */ +@Serializable(with = ReleasedState.ReleasedStateSerializer::class) public object ReleasedState : PlayerFlowState(), NodeWrapper { override val node: Node = EmptyNode override val status: PlayerFlowStatus = RELEASED - internal object ReleasedStateSerializer : NodeWrapperSerializer({ ReleasedState }) + internal object ReleasedStateSerializer : NodeWrapperSerializer({ ReleasedState }, RELEASED.value) } /** @@ -198,7 +204,7 @@ public sealed class PlayerFlowExecutionState(override val node: Node) : NodeWrapper { /** The currently executing flow */ - public val flow: Flow get() = node.getSerializable("flow", Flow.serializer()) ?: Flow() + public val flow: Flow by NodeSerializableField(Flow.serializer()) { Flow() } } // Set of *safe* convenience helpers for bounding state to concrete class diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/JSPluginWrapper.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/JSPluginWrapper.kt similarity index 74% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/JSPluginWrapper.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/JSPluginWrapper.kt index 29cd491c1..e35c51c40 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/JSPluginWrapper.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/JSPluginWrapper.kt @@ -1,8 +1,8 @@ -package com.intuit.player.jvm.core.plugins +package com.intuit.playerui.core.plugins -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer import kotlinx.serialization.Serializable /** @@ -23,6 +23,6 @@ public interface JSPluginWrapper : RuntimePlugin, NodeWrapper { @Deprecated( "Replaced with more generic JSPluginWrapper", ReplaceWith("JSPluginWrapper"), - DeprecationLevel.HIDDEN + DeprecationLevel.HIDDEN, ) public typealias JSPlayerPluginWrapper = JSPluginWrapper diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/JSScriptPluginWrapper.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/JSScriptPluginWrapper.kt similarity index 68% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/JSScriptPluginWrapper.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/JSScriptPluginWrapper.kt index 7cd03c621..605288bcc 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/JSScriptPluginWrapper.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/JSScriptPluginWrapper.kt @@ -1,7 +1,8 @@ -package com.intuit.player.jvm.core.plugins +package com.intuit.playerui.core.plugins -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.ScriptContext /** * Convenience construct to instantiate a JS player plugin. By default, this will @@ -10,17 +11,22 @@ import com.intuit.player.jvm.core.bridge.runtime.Runtime * can be passed in directly as a [String], or can be passed as a classpath location * and be read from the provided [ClassLoader]. */ -public abstract class JSScriptPluginWrapper(public val name: String, protected val script: String) : JSPluginWrapper { +public abstract class JSScriptPluginWrapper(public val name: String, protected val script: String, private val sourcePath: String? = null) : JSPluginWrapper { public constructor(name: String, sourcePath: String, classLoader: ClassLoader = JSScriptPluginWrapper::class.java.classLoader) : - this(name, classLoader.getResource(sourcePath)!!.readText()) + this(name, classLoader.getResource(sourcePath)!!.readText(), sourcePath) final override lateinit var instance: Node protected set + protected val debugScript: String + get() = loadDebugScript() ?: script + + private fun loadDebugScript(): String? = JSScriptPluginWrapper::class.java.classLoader.getResource(sourcePath?.substringBeforeLast(".") + ".dev." + sourcePath?.substringAfterLast("."))?.readText() + public val isInstantiated: Boolean get() = ::instance.isInitialized override fun apply(runtime: Runtime<*>) { - runtime.execute(script) + runtime.load(ScriptContext(if (runtime.config.debuggable) debugScript else script, sourcePath ?: "$name.js")) instance = runtime.buildInstance() } @@ -32,7 +38,7 @@ public abstract class JSScriptPluginWrapper(public val name: String, protected v public fun from(name: String, sourcePath: String, classLoader: ClassLoader = JSScriptPluginWrapper::class.java.classLoader): JSScriptPluginWrapper = object : JSScriptPluginWrapper(name, sourcePath, classLoader) {} /** Convenience helper to expose constructor as an anonymous builder */ - public fun from(name: String, script: String): JSScriptPluginWrapper = object : JSScriptPluginWrapper(name, script) {} + public fun from(name: String, script: String): JSScriptPluginWrapper = object : JSScriptPluginWrapper(name, script = script) {} } } diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/LoggerPlugin.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/LoggerPlugin.kt similarity index 69% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/LoggerPlugin.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/LoggerPlugin.kt index 8287d65fc..2d1c5c56b 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/LoggerPlugin.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/LoggerPlugin.kt @@ -1,7 +1,6 @@ -package com.intuit.player.jvm.core.plugins +package com.intuit.playerui.core.plugins -import com.intuit.player.jvm.core.player.Player -import kotlinx.serialization.encoding.* +import com.intuit.playerui.core.player.Player /** Player logger interface */ public interface LoggerPlugin : PlayerPlugin { @@ -11,9 +10,7 @@ public interface LoggerPlugin : PlayerPlugin { public fun warn(vararg args: Any?) public fun error(vararg args: Any?) - override fun apply(player: Player) { - player.logger.addHandler(this) - } + override fun apply(player: Player) {} } /** Convenience getter to find the first [LoggerPlugin] registered to the [Pluggable] */ diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/PlayerPlugin.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/PlayerPlugin.kt similarity index 71% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/PlayerPlugin.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/PlayerPlugin.kt index 014069dd5..4b276fa2b 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/PlayerPlugin.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/PlayerPlugin.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.plugins +package com.intuit.playerui.core.plugins -import com.intuit.player.jvm.core.player.Player +import com.intuit.playerui.core.player.Player /** JVM [Player] plugin that enables additional player configuration on the JVM */ public interface PlayerPlugin : Plugin { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/PlayerPluginException.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/PlayerPluginException.kt similarity index 61% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/PlayerPluginException.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/PlayerPluginException.kt index 1bee9729e..f7970cb38 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/PlayerPluginException.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/PlayerPluginException.kt @@ -1,5 +1,5 @@ -package com.intuit.player.jvm.core.plugins +package com.intuit.playerui.core.plugins -import com.intuit.player.jvm.core.player.PlayerException +import com.intuit.playerui.core.player.PlayerException public class PlayerPluginException(public val pluginName: String, message: String, cause: Throwable? = null) : PlayerException("[$pluginName] $message", cause) diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/Pluggable.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/Pluggable.kt similarity index 93% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/Pluggable.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/Pluggable.kt index e92ff62bc..7dbb37edb 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/Pluggable.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/Pluggable.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.plugins +package com.intuit.playerui.core.plugins /** Describes a construct that is inherently configurable via [Plugin]s */ public interface Pluggable { diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/Plugin.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/Plugin.kt similarity index 88% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/Plugin.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/Plugin.kt index 53e2be7fe..82539b6f0 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/Plugin.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/Plugin.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.plugins +package com.intuit.playerui.core.plugins -import com.intuit.player.jvm.core.player.Player +import com.intuit.playerui.core.player.Player /** Semantic designation that implementations of this will be used to configure some other construct */ public interface Plugin diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/RuntimePlugin.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/RuntimePlugin.kt similarity index 63% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/RuntimePlugin.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/RuntimePlugin.kt index 9f3d4802e..c7773b21e 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/RuntimePlugin.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/RuntimePlugin.kt @@ -1,10 +1,11 @@ -package com.intuit.player.jvm.core.plugins +package com.intuit.playerui.core.plugins -import com.intuit.player.jvm.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.Runtime /** [Plugin] that enables additional configuration of a [Runtime] */ public interface RuntimePlugin : Plugin { /** Invoked with the [Runtime] instance to configure */ + // TODO: Should be suspend? public fun apply(runtime: Runtime<*>) } diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/logging/PlayerLoggingConfig.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/logging/PlayerLoggingConfig.kt new file mode 100644 index 000000000..0f3048002 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/logging/PlayerLoggingConfig.kt @@ -0,0 +1,6 @@ +package com.intuit.playerui.core.plugins.logging + +/** Base configuration for [Logging] */ +public abstract class PlayerLoggingConfig { + public abstract var name: String +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/logging/PlayerLoggingContainer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/logging/PlayerLoggingContainer.kt similarity index 89% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/logging/PlayerLoggingContainer.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/logging/PlayerLoggingContainer.kt index a04a33d7b..d7671676a 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/logging/PlayerLoggingContainer.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/logging/PlayerLoggingContainer.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.plugins.logging +package com.intuit.playerui.core.plugins.logging import java.util.ServiceLoader diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/logging/PlayerLoggingFactory.kt similarity index 76% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/logging/PlayerLoggingFactory.kt index 5bc5f377b..e6aea6731 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/plugins/logging/PlayerLoggingFactory.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/plugins/logging/PlayerLoggingFactory.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.core.plugins.logging +package com.intuit.playerui.core.plugins.logging -import com.intuit.player.jvm.core.plugins.LoggerPlugin -import java.util.ServiceLoader +import com.intuit.playerui.core.plugins.LoggerPlugin +import java.util.* /** Factory of [Logging] with a specific [T] of [PlayerLoggingConfig] */ public interface PlayerLoggingFactory { @@ -14,7 +14,7 @@ public interface PlayerLoggingFactory { * with further configurations from the [nested] block. */ public fun PlayerLoggingFactory.config( - nested: T.() -> Unit + nested: T.() -> Unit, ): PlayerLoggingFactory { val parent = this @@ -33,3 +33,6 @@ public val loggerContainers: List = PlayerLoggingContain /** Default [PlayerLoggingFactory] to use if none are specified */ public val loggers: List = loggerContainers.map { it.factory.create() } + +/** Instantiate and configure all [LoggerPlugin]s */ +public fun loggers(block: PlayerLoggingConfig.() -> Unit): List = loggerContainers.map { it.factory.create(block) } diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/resolver/ResolveOptions.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/resolver/ResolveOptions.kt new file mode 100644 index 000000000..426910dea --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/resolver/ResolveOptions.kt @@ -0,0 +1,17 @@ +package com.intuit.playerui.core.resolver + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.validation.Validation +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable + +@Serializable(with = ResolveOptions.Serializer::class) +public class ResolveOptions(override val node: Node) : NodeWrapper { + + public val validation: Validation? by NodeSerializableField(Validation.serializer().nullable) + + internal object Serializer : NodeWrapperSerializer(::ResolveOptions) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/resolver/Resolver.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/resolver/Resolver.kt new file mode 100644 index 000000000..ac6a476f6 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/resolver/Resolver.kt @@ -0,0 +1,25 @@ +package com.intuit.playerui.core.resolver + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.hooks.NodeSyncWaterfallHook2 +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.resolver.Resolver.Serializer +import kotlinx.serialization.Serializable + +@Serializable(with = Serializer::class) +public class Resolver(override val node: Node) : NodeWrapper { + + public val hooks: Hooks by NodeSerializableField(Hooks.serializer()) + + @Serializable(with = Hooks.Serializer::class) + public class Hooks internal constructor(override val node: Node) : NodeWrapper { + public val resolveOptions: NodeSyncWaterfallHook2 + by NodeSerializableField(NodeSyncWaterfallHook2.serializer(ResolveOptions.serializer(), Node.serializer())) + + internal object Serializer : NodeWrapperSerializer(Resolver::Hooks) + } + + internal object Serializer : NodeWrapperSerializer(::Resolver) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/utils/Future.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/utils/Future.kt new file mode 100644 index 000000000..27ece92d9 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/utils/Future.kt @@ -0,0 +1,13 @@ +package com.intuit.playerui.core.utils + +import kotlinx.coroutines.delay +import java.util.concurrent.Future + +/** + * Method to await on a Java Future in a coroutine context and returning the result + */ +@InternalPlayerApi +public suspend fun Future.await(): T { + while (!isDone) delay(10) + return get() +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/utils/InternalPlayerApi.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/utils/InternalPlayerApi.kt similarity index 85% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/utils/InternalPlayerApi.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/utils/InternalPlayerApi.kt index 7c1f4d696..526da3d9d 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/utils/InternalPlayerApi.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/utils/InternalPlayerApi.kt @@ -1,8 +1,8 @@ -package com.intuit.player.jvm.core.utils +package com.intuit.playerui.core.utils /** * Public API marked with this annotation is effectively **internal**, which means - * it should not be used outside of `player.jvm`. + * it should not be used outside of `playerui.core`. * Signature, semantics, source and binary compatibilities are not guaranteed for this API * and will be changed without any warnings or migration aids. */ diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/BindingInstance.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/BindingInstance.kt new file mode 100644 index 000000000..4ab383019 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/BindingInstance.kt @@ -0,0 +1,20 @@ +package com.intuit.playerui.core.validation + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.validation.BindingInstance.Serializer +import kotlinx.serialization.Serializable + +@Serializable(with = Serializer::class) +public class BindingInstance(override val node: Node) : NodeWrapper { + + public fun asString(): String = node.getInvokable("asString")!!() + + public fun parent(): BindingInstance? = node.getInvokable("parent")?.invoke() + + override fun toString(): String = asString() + + internal object Serializer : NodeWrapperSerializer(::BindingInstance) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/Validation.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/Validation.kt new file mode 100644 index 000000000..bf53555ec --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/Validation.kt @@ -0,0 +1,20 @@ +package com.intuit.playerui.core.validation + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.resolver.Resolver +import com.intuit.playerui.core.validation.Validation.Serializer +import kotlinx.serialization.Serializable + +/** Limited definition of the player validation object exposed by [Resolver.Hooks.resolveOptions] */ +@Serializable(with = Serializer::class) +public class Validation internal constructor(override val node: Node) : NodeWrapper { + /** get all outstanding validations on current flow */ + public fun getAll(): ValidationMapping? = node.getInvokable("getAll")!!() + + internal object Serializer : NodeWrapperSerializer(::Validation) +} + +public fun Validation.getWarningsAndErrors(): ValidationMapping? = getAll() diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/ValidationController.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/ValidationController.kt new file mode 100644 index 000000000..e66ad17b6 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/ValidationController.kt @@ -0,0 +1,19 @@ +package com.intuit.playerui.core.validation + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.validation.ValidationController.Serializer +import kotlinx.serialization.Serializable + +/** Limited definition of the player validationController to enable validating the current view */ +@Serializable(with = Serializer::class) +public class ValidationController internal constructor(override val node: Node) : NodeWrapper { + + /** Get information on whether transition is allowed along with potential blocking validations */ + public fun validateView(): ValidationInfo = + node.getInvokable("validateView")!!() + + internal object Serializer : NodeWrapperSerializer(::ValidationController) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/ValidationInfo.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/ValidationInfo.kt similarity index 58% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/ValidationInfo.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/ValidationInfo.kt index acb22927f..c50f41fe2 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/validation/ValidationInfo.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/ValidationInfo.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.validation +package com.intuit.playerui.core.validation -import com.intuit.player.jvm.core.bridge.global.JSMap +import com.intuit.playerui.core.bridge.global.JSMap import kotlinx.serialization.Serializable public typealias ValidationMapping = JSMap @@ -8,5 +8,5 @@ public typealias ValidationMapping = JSMap @Serializable public data class ValidationInfo( val canTransition: Boolean, - val validations: ValidationMapping? = null + val validations: ValidationMapping? = null, ) diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/ValidationResponse.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/ValidationResponse.kt new file mode 100644 index 000000000..7c1323a08 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/validation/ValidationResponse.kt @@ -0,0 +1,40 @@ +package com.intuit.playerui.core.validation + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.experimental.RuntimeClassDiscriminator +import com.intuit.playerui.core.validation.WarningValidationResponse.Serializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer + +@Serializable +@RuntimeClassDiscriminator("severity") +public sealed class ValidationResponse : NodeWrapper { + /** The validation message to show to the user */ + public val message: String by NodeSerializableField(String.serializer()) + + /** List of parameters associated with a validation. */ + public val parameters: Map? by NodeSerializableField(MapSerializer(String.serializer(), GenericSerializer()).nullable) +} + +@Serializable(with = Serializer::class) +public class WarningValidationResponse(override val node: Node) : ValidationResponse() { + + /** Warning validations can be dismissed without correcting the error */ + public fun dismiss() { + node.getInvokable("dismiss")?.invoke() + } + + internal object Serializer : NodeWrapperSerializer(::WarningValidationResponse, "warning") +} + +@Serializable(with = ErrorValidationResponse.Serializer::class) +public class ErrorValidationResponse(override val node: Node) : ValidationResponse() { + internal object Serializer : NodeWrapperSerializer(::ErrorValidationResponse, "error") +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/View.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/View.kt new file mode 100644 index 000000000..ec2220ada --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/View.kt @@ -0,0 +1,20 @@ +package com.intuit.playerui.core.view + +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.view.View.Serializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable + +/** Limited definition of a stateful view instance from a flow */ +@Serializable(with = Serializer::class) +public class View internal constructor(override val node: Node) : NodeWrapper { + public val hooks: ViewHooks by NodeSerializableField(ViewHooks.serializer()) + + public val lastUpdate: Asset? by NodeSerializableField(Asset.serializer().nullable) + + internal object Serializer : NodeWrapperSerializer(::View) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/ViewController.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/ViewController.kt similarity index 50% rename from jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/ViewController.kt rename to jvm/core/src/main/kotlin/com/intuit/playerui/core/view/ViewController.kt index 1cfcecb70..bbffa88dd 100644 --- a/jvm/core/src/main/kotlin/com/intuit/player/jvm/core/view/ViewController.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/ViewController.kt @@ -1,28 +1,28 @@ -package com.intuit.player.jvm.core.view +package com.intuit.playerui.core.view -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.hooks.NodeSyncHook1 -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeSerializableField.Companion.NodeSerializableField -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.view.ViewController.Serializer import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable /** Limited definition of the player view controller responsible for managing updating/switching views */ -@Serializable(with = ViewController.Serializer::class) +@Serializable(with = Serializer::class) public class ViewController internal constructor(override val node: Node) : NodeWrapper { public val hooks: Hooks by NodeSerializableField(Hooks.serializer()) /** Current view of the flow, if any */ - public val currentView: View? get() = - node.getSerializable("currentView", View.serializer()) + public val currentView: View? by NodeSerializableField(View.serializer().nullable) @Serializable(with = Hooks.Serializer::class) public class Hooks internal constructor(override val node: Node) : NodeWrapper { /** The hook right before the View starts resolving. Attach anything custom here */ - public val view: NodeSyncHook1 - get() = NodeSyncHook1(node.getObject("view")!!, View.serializer()) + public val view: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(View.serializer())) - internal object Serializer : NodeWrapperSerializer(::Hooks) + internal object Serializer : NodeWrapperSerializer(ViewController::Hooks) } internal object Serializer : NodeWrapperSerializer(::ViewController) diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/ViewControllerHooks.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/ViewControllerHooks.kt new file mode 100644 index 000000000..d76d26dc0 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/ViewControllerHooks.kt @@ -0,0 +1,10 @@ +package com.intuit.playerui.core.view + +import com.intuit.playerui.core.view.ViewController.Hooks + +@Deprecated( + "Replaced with ViewController.Hooks", + ReplaceWith("ViewController.Hooks"), + DeprecationLevel.WARNING, +) +public typealias ViewControllerHooks = Hooks diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/ViewHooks.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/ViewHooks.kt new file mode 100644 index 000000000..d79d7266a --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/view/ViewHooks.kt @@ -0,0 +1,19 @@ +package com.intuit.playerui.core.view + +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.resolver.Resolver +import com.intuit.playerui.core.view.ViewHooks.Serializer +import kotlinx.serialization.Serializable + +@Serializable(with = Serializer::class) +public class ViewHooks internal constructor(override val node: Node) : NodeWrapper { + public val onUpdate: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(Asset.serializer())) + public val resolver: NodeSyncHook1 by NodeSerializableField(NodeSyncHook1.serializer(Resolver.serializer())) + + internal object Serializer : NodeWrapperSerializer(::ViewHooks) +} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/PlayerHooksTest.kt b/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/PlayerHooksTest.kt deleted file mode 100644 index 45f8b5599..000000000 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/PlayerHooksTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.intuit.player.jvm.core.player - -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import io.mockk.every -import io.mockk.impl.annotations.MockK -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -internal class PlayerHooksTest : NodeBaseTest() { - @MockK - private lateinit var fc: Node - @MockK - private lateinit var vc: Node - @MockK - private lateinit var dc: Node - @MockK - private lateinit var state: Node - - private val hooks by lazy { - Player.Hooks(node) - } - - @BeforeEach - fun setUpMocks() { - every { node.getObject("flowController") } returns fc - every { node.getObject("viewController") } returns vc - every { node.getObject("dataController") } returns dc - every { node.getObject("state") } returns state - every { fc.getFunction("tap") } returns Invokable {} - every { vc.getFunction("tap") } returns Invokable {} - every { dc.getFunction("tap") } returns Invokable {} - every { state.getFunction("tap") } returns Invokable {} - } - - @Test - fun flowController() { - val fc = hooks.flowController - assertNotNull(fc) - } - - @Test - fun viewController() { - val vc = hooks.viewController - assertNotNull(vc) - } - - @Test - fun dataController() { - val dc = hooks.dataController - assertNotNull(dc) - } - - @Test - fun state() { - assertNotNull(hooks.state) - } -} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/CompletedStateTest.kt b/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/CompletedStateTest.kt deleted file mode 100644 index ca3daa6ec..000000000 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/CompletedStateTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.intuit.player.jvm.core.player.state - -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.getSerializable -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.core.data.DataModelWithParser -import com.intuit.player.jvm.core.expressions.ExpressionController -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.core.flow.FlowController -import com.intuit.player.jvm.core.player.PlayerFlowStatus -import com.intuit.player.jvm.core.view.ViewController -import io.mockk.every -import io.mockk.impl.annotations.MockK -import kotlinx.serialization.modules.EmptySerializersModule -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -internal class CompletedStateTest : NodeBaseTest() { - - private val completedState by lazy { - CompletedState(node) - } - - @MockK - private lateinit var mockDataModel: Node - - @MockK - private lateinit var format: RuntimeFormat<*> - - @BeforeEach - fun setUpMocks() { - every { node.getString("ref") } returns "someRef" - every { node.format } returns format - every { format.serializersModule } returns EmptySerializersModule - every { node.getSerializable("controllers", any()) } returns ControllerState(node) - every { node.getSerializable("view", any()) } returns ViewController(node) - every { node.getSerializable("flow", any()) } returns FlowController(node) - every { node.getSerializable("flow", any()) } returns FlowController(node) - every { node.getSerializable("expression", any()) } returns ExpressionController(node) - every { node.getSerializable("flow", Flow.serializer()) } returns Flow("flowId") - every { node.getSerializable("dataModel", any()) } returns DataModelWithParser(node) - } - - @Test - fun ref() { - assertEquals("someRef", completedState.ref) - } - - @Test - fun status() { - assertEquals(PlayerFlowStatus.COMPLETED, completedState.status) - } - - @Test - fun flow() { - assertEquals("flowId", completedState.flow.id) - } - - @Test - fun dataModel() { - assertNotNull(completedState.dataModel) - } -} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/ErrorStateTest.kt b/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/ErrorStateTest.kt deleted file mode 100644 index 900e8d87a..000000000 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/ErrorStateTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.intuit.player.jvm.core.player.state - -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.core.player.PlayerFlowStatus -import io.mockk.every -import io.mockk.mockk -import kotlinx.serialization.KSerializer -import kotlinx.serialization.modules.EmptySerializersModule -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -internal class ErrorStateTest : NodeBaseTest() { - - private val errorState by lazy { - ErroneousState(node) - } - - @BeforeEach - fun setUpMocks() { - every { node.getString("ref") } returns "someRef" - every { node.getSerializable("flow", Flow.serializer()) } returns Flow("flowId") - - val mockFormat: RuntimeFormat<*> = mockk() - every { node.format } returns mockFormat - every { mockFormat.serializersModule } returns EmptySerializersModule - } - - @Test - fun errorFromObject() { - val someException = PlayerException("hello") - - every { node["error"] } returns node - every { node.deserialize(any>()) } returns someException - - assertEquals(someException, errorState.error) - } - - @Test - fun errorFromString() { - val message = "hello" - every { node["error"] } returns message - assertEquals(message, errorState.error.message) - } - - @Test - fun ref() { - assertEquals("someRef", errorState.ref) - } - - @Test - fun status() { - assertEquals(PlayerFlowStatus.ERROR, errorState.status) - } - - @Test - fun flow() { - assertEquals("flowId", errorState.flow.id) - } -} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/NotStartedStateTest.kt b/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/NotStartedStateTest.kt deleted file mode 100644 index c7d500349..000000000 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/NotStartedStateTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.intuit.player.jvm.core.player.state - -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.player.PlayerFlowStatus -import io.mockk.every -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -internal class NotStartedStateTest : NodeBaseTest() { - - private val notStartedState by lazy { - NotStartedState(node) - } - - @BeforeEach - fun setUpMocks() { - every { node.getString("ref") } returns "someRef" - } - - @Test - fun ref() { - assertEquals("someRef", notStartedState.ref) - } - - @Test - fun status() { - assertEquals(PlayerFlowStatus.NOT_STARTED, notStartedState.status) - } -} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewControllerHooksTest.kt b/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewControllerHooksTest.kt deleted file mode 100644 index 8a8e6db91..000000000 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewControllerHooksTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.intuit.player.jvm.core.view - -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import io.mockk.every -import io.mockk.impl.annotations.MockK -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -internal class ViewControllerHooksTest : NodeBaseTest() { - - @MockK - private lateinit var view: Node - - @BeforeEach - fun setUpMock() { - every { node.getObject("view") } returns view - every { view.getFunction("tap") } returns Invokable {} - } - - @Test - fun view() { - val viewControllerHooks = ViewController.Hooks(node) - assertNotNull(viewControllerHooks.view) - } -} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewHooksTest.kt b/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewHooksTest.kt deleted file mode 100644 index 4cc6d1873..000000000 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewHooksTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.intuit.player.jvm.core.view - -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import io.mockk.every -import io.mockk.impl.annotations.MockK -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -internal class ViewHooksTest : NodeBaseTest() { - - @MockK - private lateinit var assetNode: Node - - @MockK - private lateinit var resolver: Node - - private val viewHooks by lazy { - ViewHooks(node) - } - - @BeforeEach - fun setUpMocks() { - every { node.getObject("onUpdate") } returns assetNode - every { node.getObject("resolver") } returns resolver - every { assetNode.getFunction("tap") } returns Invokable {} - every { resolver.getFunction("tap") } returns Invokable {} - } - - @Test - fun onUpdate() { - assertNotNull(viewHooks.onUpdate) - } - - @Test - fun resolver() { - assertNotNull(viewHooks.resolver) - } -} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewTest.kt b/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewTest.kt deleted file mode 100644 index 2e4a374c7..000000000 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.intuit.player.jvm.core.view - -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Node -import io.mockk.every -import io.mockk.impl.annotations.MockK -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -internal class ViewTest : NodeBaseTest() { - @MockK - private lateinit var hooksNode: Node - - @BeforeEach - fun setUpMocks() { - every { node.getObject("hooks") } returns hooksNode - } - - val view by lazy { - View(node) - } - - @Test - fun hooks() { - assertNotNull(view.hooks) - } -} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/NodeBaseTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/NodeBaseTest.kt similarity index 75% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/NodeBaseTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/NodeBaseTest.kt index 287d642b8..a1baaa53f 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/NodeBaseTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/NodeBaseTest.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core +package com.intuit.playerui.core -import com.intuit.player.jvm.core.bridge.Node +import com.intuit.playerui.core.bridge.Node import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension import org.junit.jupiter.api.extension.ExtendWith diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/asset/AssetTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/asset/AssetTest.kt similarity index 59% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/asset/AssetTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/asset/AssetTest.kt index 64da4ae77..a0da6cf25 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/asset/AssetTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/asset/AssetTest.kt @@ -1,10 +1,11 @@ -package com.intuit.player.jvm.core.asset +package com.intuit.playerui.core.asset -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.player.PlayerException +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.player.PlayerException import io.mockk.every import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test internal class AssetTest : NodeBaseTest() { @@ -18,6 +19,14 @@ internal class AssetTest : NodeBaseTest() { Asset(node) } + @BeforeEach + fun setup() { + every { node.nativeReferenceEquals(any()) } returns false + every { node.getObject(any()) } returns node + every { node.getSerializable("id", any()) } returns ID + every { node.getSerializable("type", any()) } returns TYPE + } + @Test fun testId() { every { node.getString("id") } returns ID @@ -32,9 +41,6 @@ internal class AssetTest : NodeBaseTest() { @Test fun testDestructuring() { - every { node.getString("id") } returns ID - every { node.getString("type") } returns TYPE - val (id, type) = asset assertEquals(ID, id) assertEquals(TYPE, type) @@ -43,7 +49,7 @@ internal class AssetTest : NodeBaseTest() { @Test fun testIdNotPresent() { assertThrows(PlayerException::class.java) { - every { node.getString("id") } returns null + every { node.getSerializable("id", any()) } returns null asset.id } } @@ -51,7 +57,7 @@ internal class AssetTest : NodeBaseTest() { @Test fun testTypeNotPresent() { assertThrows(PlayerException::class.java) { - every { node.getString("type") } returns null + every { node.getSerializable("type", any()) } returns null asset.type } } @@ -59,8 +65,8 @@ internal class AssetTest : NodeBaseTest() { @Test fun testDestructuringNotPresent() { assertThrows(PlayerException::class.java) { - every { node.getString("id") } returns null - every { node.getString("type") } returns null + every { node.getSerializable("id", any()) } returns null + every { node.getSerializable("type", any()) } returns null val (id, type) = asset } } diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/InvokableTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/InvokableTest.kt similarity index 99% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/InvokableTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/InvokableTest.kt index fba4f80a6..17f58ce01 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/InvokableTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/InvokableTest.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.core.bridge +package com.intuit.playerui.core.bridge import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/JsonPromiseTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/JsonPromiseTest.kt similarity index 82% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/JsonPromiseTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/JsonPromiseTest.kt index 20904cba4..bdab03a33 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/JsonPromiseTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/JsonPromiseTest.kt @@ -1,8 +1,16 @@ -package com.intuit.player.jvm.core.bridge +package com.intuit.playerui.core.bridge -import com.intuit.player.jvm.utils.test.RuntimeTest -import com.intuit.player.jvm.utils.test.runBlockingTest -import kotlinx.serialization.json.* +import com.intuit.playerui.utils.test.RuntimeTest +import com.intuit.playerui.utils.test.runBlockingTest +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.add +import kotlinx.serialization.json.addJsonArray +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.TestTemplate @@ -55,7 +63,7 @@ internal class JsonPromiseTest : RuntimeTest() { buildJsonObject { put("a", "b") }, - result + result, ) } @@ -72,10 +80,10 @@ internal class JsonPromiseTest : RuntimeTest() { "c", buildJsonObject { put("d", "e") - } + }, ) }, - result + result, ) } @@ -91,7 +99,7 @@ internal class JsonPromiseTest : RuntimeTest() { add((2 as Number)) add((3 as Number)) }, - result + result, ) } @@ -111,7 +119,7 @@ internal class JsonPromiseTest : RuntimeTest() { add((5 as Number)) } }, - result + result, ) } } diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/NodeGetSymbolTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/NodeGetSymbolTest.kt new file mode 100644 index 000000000..08ac51bb1 --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/NodeGetSymbolTest.kt @@ -0,0 +1,64 @@ +package com.intuit.playerui.core.bridge + +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.utils.test.RuntimeTest +import io.mockk.every +import io.mockk.mockk +import io.mockk.verifySequence +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestTemplate + +class NodeGetSymbolTest : RuntimeTest() { + + @Test + fun `getSymbol injects helper`() { + val runtime: Runtime<*> = mockk() + val node: Node = mockk() + + every { node.runtime } returns runtime + every { runtime.containsKey("getSymbol") } returns false + every { runtime.execute(any()) } returns Unit + every { runtime.getInvokable("getSymbol") } returns Invokable { "Symbol(world)" } + + val symbol = node.getSymbol("hello") + assertEquals("Symbol(world)", symbol) + + verifySequence { + runtime.containsKey("getSymbol") + runtime.execute(any()) + runtime.getInvokable("getSymbol") + } + } + + @Test + fun `getSymbol does not inject helper`() { + val runtime: Runtime<*> = mockk() + val node: Node = mockk() + + every { node.runtime } returns runtime + every { runtime.containsKey("getSymbol") } returns true + every { runtime.getInvokable("getSymbol") } returns Invokable { "Symbol(world)" } + + val symbol = node.getSymbol("hello") + assertEquals("Symbol(world)", symbol) + + verifySequence { + runtime.containsKey("getSymbol") + runtime.getInvokable("getSymbol") + } + } + + @TestTemplate fun `getSymbol works with symbols`() { + val wrapper = runtime.execute("({ ref: Symbol('hello') })") as Node + assertNull(wrapper["ref"]) + assertEquals("Symbol(hello)", wrapper.getSymbol("ref")) + } + + @TestTemplate fun `getSymbol doesn't blow up with non-symbols`() { + val wrapper = runtime.execute("({ ref: 'not a symbol' })") as Node + assertEquals("not a symbol", wrapper["ref"]) + assertNull(wrapper.getSymbol("ref")) + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/PromiseTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/PromiseTest.kt similarity index 77% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/PromiseTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/PromiseTest.kt index 3850439cb..99b0c6e8e 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/PromiseTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/PromiseTest.kt @@ -1,12 +1,15 @@ -package com.intuit.player.jvm.core.bridge - -import com.intuit.player.jvm.core.bridge.runtime.serialize -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.core.player.PlayerFlowStatus -import com.intuit.player.jvm.core.player.state.CompletedState -import com.intuit.player.jvm.core.player.state.PlayerFlowState -import com.intuit.player.jvm.utils.test.PromiseUtils -import com.intuit.player.jvm.utils.test.RuntimeTest +package com.intuit.playerui.core.bridge + +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.PlayerFlowStatus +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.utils.normalizeStackTraceElements +import com.intuit.playerui.utils.test.PromiseUtils +import com.intuit.playerui.utils.test.RuntimeTest +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put @@ -18,8 +21,17 @@ private inline fun currentStackTrace() = Exception().stackTrace internal class PromiseTest : RuntimeTest(), PromiseUtils { - override val thenChain = mutableListOf() - override val catchChain = mutableListOf() + override val thenChain = mutableListOf(); get() = runBlocking { + // TODO: Rework delay into a proper suspension mechanism + delay(100) + field + } + + override val catchChain = mutableListOf(); get() = runBlocking { + // TODO: Rework delay into a proper suspension mechanism + delay(100) + field + } /** * Anti-test case since old test existed to prove the Json conversions forced Ints as Longs. @@ -70,13 +82,13 @@ internal class PromiseTest : RuntimeTest(), PromiseUtils { "flowResult", buildJsonObject { put("outcome", "doneWithTopic") - } + }, ) put( "flow", buildJsonObject { put("id", "some-id") - } + }, ) } val flow = CompletedState( @@ -84,13 +96,13 @@ internal class PromiseTest : RuntimeTest(), PromiseUtils { mapOf( "status" to PlayerFlowStatus.COMPLETED.value, "flowResult" to mapOf( - "outcome" to "doneWithTopic" + "outcome" to "doneWithTopic", ), "flow" to mapOf( - "id" to "some-id" - ) - ) - ) as Node + "id" to "some-id", + ), + ), + ) as Node, ) runtime.Promise.resolve(42) @@ -116,7 +128,7 @@ internal class PromiseTest : RuntimeTest(), PromiseUtils { const promise = new Promise(function(resolve, reject) { resolver = resolve }); return [promise, resolver]; })(); - """.trimIndent() + """.trimIndent(), ) as List<*> Promise(promise as Node) @@ -170,7 +182,7 @@ internal class PromiseTest : RuntimeTest(), PromiseUtils { assertTrue(caught is Throwable) caught as Throwable assertEquals(exception.message, caught.message) - assertEquals(exception.stackTrace.toList(), caught.stackTrace.toList()) + assertEquals(exception.stackTrace.normalizeStackTraceElements(), caught.stackTrace.normalizeStackTraceElements()) } @TestTemplate @@ -188,7 +200,7 @@ internal class PromiseTest : RuntimeTest(), PromiseUtils { caught as Throwable caught.printStackTrace() assertEquals(exception.message, caught.message) - assertEquals(exception.stackTrace.toList(), caught.stackTrace.toList()) + assertEquals(exception.stackTrace.normalizeStackTraceElements(), caught.stackTrace.normalizeStackTraceElements()) } @TestTemplate @@ -205,6 +217,6 @@ internal class PromiseTest : RuntimeTest(), PromiseUtils { assertTrue(caught is Throwable) caught as Throwable assertEquals(exception.message, caught.message) - assertEquals(exception.stackTrace.toList(), caught.stackTrace.toList()) + assertEquals(exception.stackTrace.normalizeStackTraceElements(), caught.stackTrace.normalizeStackTraceElements()) } } diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/global/JSMapTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/global/JSMapTest.kt similarity index 72% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/global/JSMapTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/global/JSMapTest.kt index 40b77f8ba..886ca838d 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/global/JSMapTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/global/JSMapTest.kt @@ -1,9 +1,10 @@ -package com.intuit.player.jvm.core.bridge.global +package com.intuit.playerui.core.bridge.global -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.deserialize -import com.intuit.player.jvm.utils.test.RuntimeTest -import org.junit.jupiter.api.Assertions.* +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.deserialize +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.utils.test.RuntimeTest +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.TestTemplate @kotlinx.serialization.Serializable @@ -28,10 +29,10 @@ internal class JSMapTest : RuntimeTest() { return { generateMap: () => map }; })() - """.trimIndent() + """.trimIndent(), ) as Node - val map: JSMap = node.getFunction("generateMap")!!().deserialize() + val map: JSMap = node.getInvokable("generateMap")!!().deserialize() assertEquals(1, map.size) assertEquals(setOf(Key(1)), map.keys) assertEquals(listOf(Value(2)), map.values) @@ -50,10 +51,10 @@ internal class JSMapTest : RuntimeTest() { return { generateMap: () => map }; })() - """.trimIndent() + """.trimIndent(), ) as Node - val map: JSMap = node.getFunction("generateMap")!!().deserialize() + val map: JSMap = node.getInvokable("generateMap")!!().deserialize() assertEquals(1, map.size) assertEquals(setOf("hello"), map.keys) assertEquals(listOf(1), map.values) diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncBailHookTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncBailHookTest.kt new file mode 100644 index 000000000..85611121c --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncBailHookTest.kt @@ -0,0 +1,105 @@ +package com.intuit.playerui.core.bridge.hooks + +import com.intuit.hooks.BailResult +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.view.View +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.verify +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class NodeSyncBailHookTest : NodeBaseTest() { + @MockK + private lateinit var dummyNode: Node + + @MockK + private lateinit var invokable: Invokable + + private lateinit var map: HashMap + private lateinit var callback: Invokable + + @Suppress("UNCHECKED_CAST") + @BeforeEach + fun setUpMock() { + val callback = Invokable { + map = it[0] as HashMap + callback = it[1] as Invokable + } + every { node.getInvokable("tap") } returns callback + every { node.getInvokable("tap", any()) } returns callback + every { invokable.invoke(*anyVararg()) } + every { dummyNode.deserialize(View.Serializer) } returns View(dummyNode) + } + + @Test + fun `JS Hook Tap On Init`() { + NodeSyncBailHook1(node, View.serializer()) + verify { node.getInvokable("tap", any()) } + } + + @Test + fun `Hook Tap`() { + var output: View? = null + + val nodeHook = NodeSyncBailHook1(node, View.serializer()) + nodeHook.tap { view -> + output = view + BailResult.Continue() + } + + val result = callback.invoke(hashMapOf(), dummyNode) + Assertions.assertTrue(result == Unit) + Assertions.assertEquals(dummyNode, output?.node) + } + + @Test + fun `Tap with Context`() { + var context: HashMap? = null + + val nodeHook = NodeSyncBailHook1(node, View.serializer()) + nodeHook.tap { map, _ -> + context = map + BailResult.Continue() + } + + callback.invoke(hashMapOf("first" to 2), dummyNode) + + Assertions.assertEquals(hashMapOf("first" to 2), context) + } + + @Test + fun `Tap with Bail result, subsequent taps not called`() { + var secondTapCalled = false + val nodeHook = NodeSyncBailHook1(node, View.serializer()) + nodeHook.tap { _ -> + BailResult.Bail(1) + } + nodeHook.tap { _ -> + secondTapCalled = true + BailResult.Continue() + } + val result = callback.invoke(hashMapOf(), dummyNode) + Assertions.assertTrue(result == 1) + Assertions.assertFalse(secondTapCalled) + } + + @Test + fun `Tap with Continue result, subsequent taps called`() { + var secondTapCalled = false + val nodeHook = NodeSyncBailHook1(node, View.serializer()) + nodeHook.tap { _ -> + BailResult.Continue() + } + nodeHook.tap { _ -> + secondTapCalled = true + BailResult.Continue() + } + callback.invoke(hashMapOf(), dummyNode) + Assertions.assertTrue(secondTapCalled) + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncHookTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncHookTest.kt similarity index 72% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncHookTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncHookTest.kt index f386fd45d..9092909f5 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncHookTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncHookTest.kt @@ -1,9 +1,10 @@ -package com.intuit.player.jvm.core.bridge.hooks +package com.intuit.playerui.core.bridge.hooks -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.view.View +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.view.View import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.verify @@ -14,6 +15,7 @@ import org.junit.jupiter.api.Test internal class NodeSyncHookTest : NodeBaseTest() { @MockK private lateinit var dummyNode: Node + @MockK private lateinit var invokable: Invokable @@ -23,10 +25,12 @@ internal class NodeSyncHookTest : NodeBaseTest() { @Suppress("UNCHECKED_CAST") @BeforeEach fun setUpMock() { - every { node.getFunction("tap") } returns Invokable { + val callback = Invokable { map = it[0] as HashMap callback = it[1] as Invokable } + every { node.getInvokable("tap") } returns callback + every { node.getInvokable("tap", any()) } returns callback every { invokable.invoke(*anyVararg()) } every { dummyNode.deserialize(View.Serializer) } returns View(dummyNode) } @@ -34,7 +38,7 @@ internal class NodeSyncHookTest : NodeBaseTest() { @Test fun `JS Hook Tap On Init`() { NodeSyncHook1(node, View.serializer()) - verify { node.getFunction("tap") } + verify { node.getInvokable("tap", any()) } } @Test @@ -46,7 +50,7 @@ internal class NodeSyncHookTest : NodeBaseTest() { callback.invoke(hashMapOf(), dummyNode) - verify { node.getFunction("tap") } + verify { node.getInvokable("tap", any()) } assertEquals(dummyNode, output?.node) } diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHookTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncWaterfallHookTest.kt similarity index 76% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHookTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncWaterfallHookTest.kt index e264ba626..f35066401 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/bridge/hooks/NodeSyncWaterfallHookTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/hooks/NodeSyncWaterfallHookTest.kt @@ -1,10 +1,11 @@ -package com.intuit.player.jvm.core.bridge.hooks - -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.view.View +package com.intuit.playerui.core.bridge.hooks + +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.view.View import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.verify @@ -17,6 +18,7 @@ import org.junit.jupiter.api.Test internal class NodeSyncWaterfallHookTest : NodeBaseTest() { @MockK private lateinit var dummyNode: Node + @MockK private lateinit var invokable: Invokable @@ -28,10 +30,12 @@ internal class NodeSyncWaterfallHookTest : NodeBaseTest() { @Suppress("UNCHECKED_CAST") @BeforeEach fun setUpMock() { - every { node.getFunction("tap") } returns Invokable { + val callback = Invokable { map = it[0] as HashMap callback = it[1] as Invokable } + every { node.getInvokable("tap") } returns callback + every { node.getInvokable("tap", any()) } returns callback every { invokable.invoke(*anyVararg()) } every { dummyNode.deserialize(View.Serializer) } returns View(dummyNode) every { dummyNode.deserialize(mapSerializer) } returns mapOf("key" to "value") @@ -40,7 +44,7 @@ internal class NodeSyncWaterfallHookTest : NodeBaseTest() { @Test fun `JS Hook Tap On Init`() { NodeSyncWaterfallHook1(node, View.serializer()) - verify { node.getFunction("tap") } + verify { node.getInvokable("tap", any()) } } @Test @@ -55,7 +59,7 @@ internal class NodeSyncWaterfallHookTest : NodeBaseTest() { callback.invoke(hashMapOf(), dummyNode) - verify { node.getFunction("tap") } + verify { node.getInvokable("tap", any()) } assertEquals(dummyNode, output?.node) } @@ -68,7 +72,7 @@ internal class NodeSyncWaterfallHookTest : NodeBaseTest() { val result = callback.invoke(hashMapOf(), dummyNode) - verify { node.getFunction("tap") } + verify { node.getInvokable("tap", any()) } assertEquals(mapOf("key" to "value", "anotherKey" to "anotherValue"), result) } diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/runtime/RuntimeBlockingTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/runtime/RuntimeBlockingTest.kt new file mode 100644 index 000000000..867d20cf5 --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/runtime/RuntimeBlockingTest.kt @@ -0,0 +1,150 @@ +package com.intuit.playerui.core.bridge.runtime + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.Promise +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.toCompletable +import com.intuit.playerui.utils.test.RuntimeTest +import com.intuit.playerui.utils.test.runBlockingTest +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.suspendCancellableCoroutine +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.TestTemplate +import org.junit.jupiter.api.assertThrows +import java.util.* +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +internal class RuntimeBlockingTest : RuntimeTest() { + + private lateinit var handles: HashMap> + private lateinit var jobs: HashMap> + private val blockOnJvm by NodeSerializableField<(key: String) -> Unit>(::runtime) + private val promiseOnJvm by NodeSerializableField<(key: String) -> Node>(::runtime) + + private suspend fun Deferred.assertHandled(handle: Continuation) { + handle.resume(Unit) + await() + Assertions.assertTrue(isCompleted) + } + + private fun CoroutineScope.doOnJvmAsync(key: String, block: suspend Deferred.(Continuation) -> Unit, job: suspend () -> Unit): Pair, Deferred>> = async { job() }.also { jobs[key] = it } to async { + while (handles[key] == null) delay(50) + (handles[key] ?: error("handle shouldn't be null")) + .also { (jobs[key] ?: error("job shouldn't be null")).block(it) } + } + + private fun CoroutineScope.blockOnJvmAsync(key: String = UUID.randomUUID().toString(), block: suspend Deferred.(Continuation) -> Unit = {}): Pair, Deferred>> = + doOnJvmAsync(key, block) { blockOnJvm(key) } + + private fun CoroutineScope.promiseOnJvmAsync(key: String = UUID.randomUUID().toString(), block: suspend Deferred.(Continuation) -> Unit = {}): Pair, Deferred>> = + doOnJvmAsync(key, block) { promiseOnJvm(key).let(::Promise).toCompletable().await() ?: Unit } + + private suspend fun suspendOnHandler(key: String) = suspendCancellableCoroutine { continuation -> + synchronized(handles) { + handles[key] = continuation + } + } + + @BeforeEach fun setup() { + handles = hashMapOf() + jobs = hashMapOf() + runtime.add("blockOnJvm") { key: String -> runBlocking { suspendOnHandler(key) } } + runtime.add("promiseOnJvm") { key: String -> + runtime.Promise { resolve, _ -> + suspendOnHandler(key) + resolve(Unit) + } + } + } + + @TestTemplate fun `block during JS execution`() = runBlockingTest { + val (job, handle) = blockOnJvmAsync { assertHandled(it) } + awaitAll(job, handle) + } + + @TestTemplate fun `suspend during JS execution`() = runBlockingTest { + val (job, handle) = promiseOnJvmAsync { assertHandled(it) } + awaitAll(job, handle) + Unit + } + + @TestTemplate fun `multiple blocks during JS execution`() = runBlockingTest { + suspend fun waitForAllJobsToSuspend(num: Int) { + while (jobs.size < num) delay(50) + } + + val (job1, handle1) = blockOnJvmAsync { + waitForAllJobsToSuspend(2) + assertHandled(it) + } + val (job2, handle2) = blockOnJvmAsync { + waitForAllJobsToSuspend(2) + assertHandled(it) + } + + awaitAll(handle1, handle2, job1, job2) + Unit + } + + @TestTemplate fun `multiple suspends during JS execution`() = runBlockingTest { + suspend fun waitForAllJobsToSuspend(num: Int) { + while (jobs.size < num) delay(50) + } + + val (job1, handle1) = promiseOnJvmAsync { + waitForAllJobsToSuspend(2) + assertHandled(it) + } + val (job2, handle2) = promiseOnJvmAsync { + waitForAllJobsToSuspend(2) + assertHandled(it) + } + + awaitAll(handle1, handle2, job1, job2) + Unit + } + + @TestTemplate fun `blocking during JS execution handled gracefully when releasing`() = runBlockingTest { + // This behavior is only really supported by j2v8 w/ a dedicated runtime + if (runtime.toString() != "J2V8") return@runBlockingTest + + val (job, handle) = blockOnJvmAsync() + handle.await() + + val releaseJob = async { + runtime.release() + } + + // ensure release is invoked before we assert on the job + delay(500) + + job.assertHandled(handle.getCompleted()) + + releaseJob.await() + } + + @TestTemplate fun `suspending during JS execution handled gracefully when releasing`() { + assertThrows { + runBlockingTest { + val (job, handle) = promiseOnJvmAsync() + handle.await() + + // ensure completable is awaited on before we release the runtime + delay(500) + + runtime.release() + + // this'll never complete b/c the promise will never resolve + job.assertHandled(handle.getCompleted()) + } + } + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/NodeSerializableFieldTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/NodeSerializableFieldTest.kt new file mode 100644 index 000000000..e0ad035a6 --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/bridge/serialization/serializers/NodeSerializableFieldTest.kt @@ -0,0 +1,173 @@ +package com.intuit.playerui.core.bridge.serialization.serializers + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.PlayerRuntimeException +import com.intuit.playerui.core.bridge.serialization.json.PrettyJson +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField.CacheStrategy +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.utils.test.RuntimeTest +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotSame +import org.junit.jupiter.api.Assertions.assertSame +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.TestTemplate +import org.junit.jupiter.api.assertThrows + +@Serializable +data class Structured( + val primitive: Int, + val nested: Structured? = null, +) + +@Serializable(Wrapper.Serializer::class) +class Wrapper(override val node: Node) : NodeWrapper { + val nested: Structured by NodeSerializableField() + val primitive: Int by NodeSerializableField() + + object Serializer : NodeWrapperSerializer(::Wrapper) +} + +internal class NodeSerializableFieldTest : RuntimeTest() { + + private val data = buildJsonObject { + put("primitive", 10) + put( + "nested", + buildJsonObject { + put("primitive", 20) + }, + ) + } + + lateinit var node: Node + + @BeforeEach + fun setup() { + runtime.execute("var data = ${PrettyJson.encodeToString(data)}") + node = runtime.getObject("data") ?: error("data not defined") + } + + @TestTemplate fun `default chooses no caching for primitive data`() { + val delegate = NodeSerializableField({ node }) + assertEquals(CacheStrategy.None, delegate.strategy) + + val primitive by delegate + assertEquals(10, primitive) + } + + @TestTemplate fun `default chooses smart caching for structured data`() { + val delegate = NodeSerializableField({ node }) + assertEquals(CacheStrategy.Smart, delegate.strategy) + + val nested by delegate + assertEquals(20, nested.primitive) + } + + @TestTemplate fun `smart caching only re-deserializes when JS instances differ`() { + val nested: Structured by NodeSerializableField({ node }, CacheStrategy.Smart) + + assertEquals(20, nested.primitive) + assertSame(nested, nested) + + val inst = nested + runtime.execute("data.nested = { primitive: 30 }") + + assertEquals(30, nested.primitive) + assertNotSame(inst, nested) + + val primitive: Int by NodeSerializableField({ node }, CacheStrategy.Smart) + assertEquals(10, primitive) + + // access check after runtime is released + runtime.release() + assertEquals("[$runtime] Runtime object has been released!", assertThrows { primitive }.message) + assertEquals("[$runtime] Runtime object has been released!", assertThrows { nested }.message) + } + + @TestTemplate fun `full caching doesn't re-access the JS layer`() { + val nested: Structured by NodeSerializableField({ node }, CacheStrategy.Full) + + assertEquals(20, nested.primitive) + assertSame(nested, nested) + + val inst = nested + runtime.execute("data.nested = { primitive: 30 }") + + assertEquals(20, nested.primitive) + assertSame(inst, nested) + + val primitive: Int by NodeSerializableField({ node }, CacheStrategy.Full) + assertEquals(10, primitive) + + // access check after runtime is released + runtime.release() + assertEquals(10, primitive) + assertEquals(Structured(20), nested) + } + + @TestTemplate fun `no caching always re-deserializes`() { + val nested: Structured by NodeSerializableField({ node }, CacheStrategy.None) + + assertEquals(20, nested.primitive) + assertNotSame(nested, nested) + + val inst = nested + runtime.execute("data.nested = { primitive: 30 }") + + assertEquals(30, nested.primitive) + assertNotSame(inst, nested) + + val primitive: Int by NodeSerializableField({ node }, CacheStrategy.None) + assertEquals(10, primitive) + + // access check after runtime is released + runtime.release() + assertEquals("[$runtime] Runtime object has been released!", assertThrows { primitive }.message) + assertEquals("[$runtime] Runtime object has been released!", assertThrows { nested }.message) + } + + @TestTemplate fun `throws when undefined`() { + val nested: Structured by NodeSerializableField({ node }, name = "undefined") + assertEquals( + "Could not deserialize \"undefined\" as \"com.intuit.playerui.core.bridge.serialization.serializers.Structured(primitive: kotlin.Int, nested: com.intuit.playerui.core.bridge.serialization.serializers.Structured?)\"", + assertThrows { + nested + }.message, + ) + } + + @TestTemplate fun `node wrapper helpers automatically provide node`() { + val wrapper: Wrapper by NodeSerializableField({ runtime }, name = "data") + + assertEquals(10, wrapper.primitive) + assertEquals(20, wrapper.nested.primitive) + } + + @TestTemplate fun `default value when non nullable returns null`() { + val delegate = NodeSerializableField({ node }, defaultValue = { 100 }) + val noKeyEntry by delegate + assertEquals(100, noKeyEntry) + } + + @TestTemplate fun `JsonNull is unfortunately not handled automatically`() { + var callCount = 0 + val json: JsonElement by NodeSerializableField({ runtime }) { callCount++; JsonNull } + assertEquals(JsonNull, json) + + runtime.execute("var json = 20;") + assertEquals(JsonPrimitive(20), json) + + runtime.execute("json = { hello: 'world' };") + assertEquals(JsonObject(mapOf("hello" to JsonPrimitive("world"))), json) + assertEquals(1, callCount) + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/data/DataControllerIntegrationTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/data/DataControllerIntegrationTest.kt similarity index 67% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/data/DataControllerIntegrationTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/data/DataControllerIntegrationTest.kt index 9cfef2c58..af7cfd5a0 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/data/DataControllerIntegrationTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/data/DataControllerIntegrationTest.kt @@ -1,9 +1,9 @@ -package com.intuit.player.jvm.core.data +package com.intuit.playerui.core.data -import com.intuit.player.jvm.core.player.state.InProgressState -import com.intuit.player.jvm.core.player.state.dataModel -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.utils.test.simpleFlowString +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.player.state.dataModel +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.simpleFlowString import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.TestTemplate diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/data/DataControllerTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/data/DataControllerTest.kt similarity index 67% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/data/DataControllerTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/data/DataControllerTest.kt index 6c8627990..3898f441a 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/data/DataControllerTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/data/DataControllerTest.kt @@ -1,7 +1,8 @@ -package com.intuit.player.jvm.core.data +package com.intuit.playerui.core.data -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Invokable +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.getInvokable import io.mockk.every import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach @@ -10,7 +11,7 @@ import org.junit.jupiter.api.Test internal class DataControllerTest : NodeBaseTest() { var data: Map = mapOf( - "key" to "initial value" + "key" to "initial value", ) private val dataController by lazy { DataController(node) @@ -18,7 +19,7 @@ internal class DataControllerTest : NodeBaseTest() { @BeforeEach fun setUpMocks() { - every { node.getFunction("set") } returns Invokable { args -> + every { node.getInvokable("set") } returns Invokable { args -> val arg = args[0] as Map data = data + arg.keys.map { it to arg[it] } } @@ -29,14 +30,14 @@ internal class DataControllerTest : NodeBaseTest() { assertEquals("initial value", data["key"]) dataController.set( mapOf( - "key2" to 2 - ) + "key2" to 2, + ), ) assertEquals(2, data["key2"]) dataController.set( mapOf( - "key" to "1" - ) + "key" to "1", + ), ) assertEquals("1", data["key"]) assertEquals(2, data["key2"]) diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/expressions/ExpressionSerializationTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/expressions/ExpressionSerializationTest.kt similarity index 90% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/expressions/ExpressionSerializationTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/expressions/ExpressionSerializationTest.kt index 23392c45f..36983ce32 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/expressions/ExpressionSerializationTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/expressions/ExpressionSerializationTest.kt @@ -1,9 +1,8 @@ -package com.intuit.player.jvm.core.expressions +package com.intuit.playerui.core.expressions -import com.intuit.player.jvm.core.bridge.runtime.serialize -import com.intuit.player.jvm.core.bridge.serialization.format.decodeFromRuntimeValue -import com.intuit.player.jvm.core.bridge.serialization.format.encodeToRuntimeValue -import com.intuit.player.jvm.utils.test.RuntimeTest +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.bridge.serialization.format.encodeToRuntimeValue +import com.intuit.playerui.utils.test.RuntimeTest import kotlinx.serialization.Serializable import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.TestTemplate @@ -48,14 +47,14 @@ internal class ExpressionSerializationTest : RuntimeTest() { val expression: Expression.Single, val genericExpression: Expression, val expressions: Expression.Collection, - val genericExpressions: Expression + val genericExpressions: Expression, ) private val wrapper = ExprWrapper( single, genericSingle, collection, - genericCollection + genericCollection, ) @TestTemplate @@ -66,7 +65,7 @@ internal class ExpressionSerializationTest : RuntimeTest() { "genericExpression" to (genericSingle as Expression.Single).expression, "expressions" to collection.expressions, "genericExpressions" to (genericCollection as Expression.Collection).expressions, - ) + ), ) val wrapper = format.decodeFromRuntimeValue(ExprWrapper.serializer(), node) @@ -85,9 +84,9 @@ internal class ExpressionSerializationTest : RuntimeTest() { "genericExpression" to (genericSingle as Expression.Single).expression, "expressions" to collection.expressions, "genericExpressions" to (genericCollection as Expression.Collection).expressions, - ) + ), ), - runtime.serialize(ExprWrapper.serializer(), wrapper) + runtime.serialize(ExprWrapper.serializer(), wrapper), ) } } diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/expressions/ExpressionTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/expressions/ExpressionTest.kt similarity index 92% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/expressions/ExpressionTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/expressions/ExpressionTest.kt index 87b9b752f..c6fd6d32d 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/expressions/ExpressionTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/expressions/ExpressionTest.kt @@ -1,6 +1,12 @@ -package com.intuit.player.jvm.core.expressions +package com.intuit.playerui.core.expressions -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.decodeFromJsonElement +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonPrimitive import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -48,6 +54,7 @@ internal class ExpressionTest { assertEquals(collectionJson, Json.encodeToJsonElement(Expression.Collection.serializer(), collection)) assertEquals(genericCollectionJson, Json.encodeToJsonElement(Expression.serializer(), genericCollection)) } + @Test fun `test ExpressionType from JSON using implicit serializer`() { /** Uses [Expression.Single.Serializer] */ diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/flow/FlowControllerIntegrationTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/flow/FlowControllerIntegrationTest.kt similarity index 88% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/flow/FlowControllerIntegrationTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/flow/FlowControllerIntegrationTest.kt index c20810b13..bbf09701b 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/flow/FlowControllerIntegrationTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/flow/FlowControllerIntegrationTest.kt @@ -1,11 +1,11 @@ -package com.intuit.player.jvm.core.flow +package com.intuit.playerui.core.flow -import com.intuit.player.jvm.core.flow.state.NavigationFlowTransitionableState -import com.intuit.player.jvm.core.player.state.NamedState -import com.intuit.player.jvm.core.player.state.inProgressState -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.utils.test.runBlockingTest -import com.intuit.player.jvm.utils.test.simpleFlowString +import com.intuit.playerui.core.flow.state.NavigationFlowTransitionableState +import com.intuit.playerui.core.player.state.NamedState +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.runBlockingTest +import com.intuit.playerui.utils.test.simpleFlowString import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.TestTemplate @@ -106,7 +106,7 @@ internal class FlowControllerIntegrationTest : PlayerTest() { } } } -""" +""", ) val controller = player.inProgressState!!.controllers.flow diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/flow/FlowControllerTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/flow/FlowControllerTest.kt similarity index 63% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/flow/FlowControllerTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/flow/FlowControllerTest.kt index a12932060..53472854e 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/flow/FlowControllerTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/flow/FlowControllerTest.kt @@ -1,7 +1,8 @@ -package com.intuit.player.jvm.core.flow +package com.intuit.playerui.core.flow -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Invokable +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.getInvokable import io.mockk.every import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -16,7 +17,7 @@ internal class FlowControllerTest : NodeBaseTest() { @Test fun transition() { - every { node.getFunction("transition") } returns Invokable { + every { node.getInvokable("transition") } returns Invokable { lastTransition = it[0] as String } flowController.transition("Next") diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/managed/AsyncIterationManagerTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/managed/AsyncIterationManagerTest.kt similarity index 89% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/managed/AsyncIterationManagerTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/managed/AsyncIterationManagerTest.kt index 0ac5e4077..2339fa0e8 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/managed/AsyncIterationManagerTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/managed/AsyncIterationManagerTest.kt @@ -1,10 +1,14 @@ -package com.intuit.player.jvm.core.managed +package com.intuit.playerui.core.managed -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.core.player.state.CompletedState +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.state.CompletedState import io.mockk.mockk -import kotlinx.coroutines.* +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.yield import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import kotlin.coroutines.resume @@ -31,7 +35,7 @@ internal class AsyncIterationManagerTest { override suspend fun next(result: Boolean?): String? { throw PlayerException("expected") } - } + }, ) assertEquals(AsyncIterationManager.State.NotStarted, manager.state.value) manager.next() diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/managed/AsyncIteratorTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/managed/AsyncIteratorTest.kt similarity index 94% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/managed/AsyncIteratorTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/managed/AsyncIteratorTest.kt index 9d6c3c9f3..38a23e923 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/managed/AsyncIteratorTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/managed/AsyncIteratorTest.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.core.managed +package com.intuit.playerui.core.managed -import com.intuit.player.jvm.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.CompletedState import io.mockk.mockk import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertEquals diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/HeadlessPlayerTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt similarity index 73% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/HeadlessPlayerTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt index 1fa3ef0b6..ec350db9f 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/HeadlessPlayerTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt @@ -1,32 +1,59 @@ -package com.intuit.player.jvm.core.player - -import com.intuit.player.jvm.core.bridge.JSErrorException -import com.intuit.player.jvm.core.bridge.global.JSMap -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.data.get -import com.intuit.player.jvm.core.data.set -import com.intuit.player.jvm.core.expressions.evaluate -import com.intuit.player.jvm.core.flow.Flow.Companion.UNKNOWN_ID -import com.intuit.player.jvm.core.flow.forceTransition -import com.intuit.player.jvm.core.flow.state.NavigationFlowStateType -import com.intuit.player.jvm.core.flow.state.NavigationFlowViewState -import com.intuit.player.jvm.core.player.state.* -import com.intuit.player.jvm.core.plugins.Plugin -import com.intuit.player.jvm.core.validation.BindingInstance -import com.intuit.player.jvm.core.validation.ValidationResponse -import com.intuit.player.jvm.core.validation.getWarningsAndErrors -import com.intuit.player.jvm.utils.filterKeys -import com.intuit.player.jvm.utils.test.* -import com.intuit.player.plugins.assets.ReferenceAssetsPlugin -import com.intuit.player.plugins.types.CommonTypesPlugin +package com.intuit.playerui.core.player + +import com.intuit.playerui.core.bridge.JSErrorException +import com.intuit.playerui.core.bridge.PlayerRuntimeException +import com.intuit.playerui.core.bridge.global.JSMap +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.data.get +import com.intuit.playerui.core.data.set +import com.intuit.playerui.core.expressions.evaluate +import com.intuit.playerui.core.flow.Flow.Companion.UNKNOWN_ID +import com.intuit.playerui.core.flow.forceTransition +import com.intuit.playerui.core.flow.state.NavigationFlowStateType +import com.intuit.playerui.core.flow.state.NavigationFlowViewState +import com.intuit.playerui.core.flow.state.param +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.player.state.NotStartedState +import com.intuit.playerui.core.player.state.ReleasedState +import com.intuit.playerui.core.player.state.completedState +import com.intuit.playerui.core.player.state.currentFlowState +import com.intuit.playerui.core.player.state.currentView +import com.intuit.playerui.core.player.state.dataModel +import com.intuit.playerui.core.player.state.errorState +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.core.player.state.lastViewUpdate +import com.intuit.playerui.core.plugins.Plugin +import com.intuit.playerui.core.validation.BindingInstance +import com.intuit.playerui.core.validation.ValidationResponse +import com.intuit.playerui.core.validation.getWarningsAndErrors +import com.intuit.playerui.plugins.assets.ReferenceAssetsPlugin +import com.intuit.playerui.plugins.types.CommonTypesPlugin +import com.intuit.playerui.utils.filterKeys +import com.intuit.playerui.utils.normalizeStackTraceElements +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.ThreadUtils +import com.intuit.playerui.utils.test.mocks +import com.intuit.playerui.utils.test.runBlockingTest +import com.intuit.playerui.utils.test.simpleFlow +import com.intuit.playerui.utils.test.simpleFlowString +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.TestTemplate import kotlin.concurrent.thread +import kotlin.coroutines.resume internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { @@ -41,7 +68,7 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { val state = player.state assertTrue(state is NotStartedState) assertEquals(PlayerFlowStatus.NOT_STARTED, state.status) - assertNull(state.ref) + assertEquals("Symbol(not-started)", state.ref) } @TestTemplate @@ -101,17 +128,20 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { } } } -}""".trimMargin() +} + """.trimMargin(), ) player.inProgressState?.transition("Next") val result = flow.await() - assertEquals("done", result.endState.outcome) - assertEquals(mapOf("someKey" to "someValue"), result.endState["param"]) - assertEquals("extraValue", result.endState["extraKey"]) - assertEquals(mapOf("someInt" to 1), result.endState["extraObject"]) assertEquals("counter-flow", result.flow.id) + val endState = result.endState + assertEquals("done", endState.outcome) + player.release() + assertEquals(mapOf("someKey" to "someValue"), endState.param) + assertEquals("extraValue", endState["extraKey"]) + assertEquals(mapOf("someInt" to 1), endState["extraObject"]) } @TestTemplate @@ -166,7 +196,7 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { val state = player.state assertTrue(state is InProgressState) assertEquals(PlayerFlowStatus.IN_PROGRESS, state.status) - assertNull(state.ref) + assertEquals("Symbol(generated-flow)", state.ref) state as InProgressState val flowResultCompletable = state.flowResult @@ -175,8 +205,8 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { // remove evaluated nodes val currentViewJson = Json.decodeFromJsonElement( GenericSerializer(), - simpleFlow.views[0].jsonObject - .filterKeys("applicability") + simpleFlow.views!![0].jsonObject + .filterKeys("applicability"), ) // remove transforms @@ -233,14 +263,15 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { exception.printStackTrace() player.errorState?.error?.printStackTrace() - assertEquals(exception.stackTrace.toList(), player.errorState?.error?.stackTrace?.toList()) + assertEquals(exception.stackTrace.normalizeStackTraceElements(), player.errorState?.error?.stackTrace?.normalizeStackTraceElements()) } @TestTemplate fun `test player error state`() = runBlockingTest { + val completable = player.start("{}") val exception = assertThrows(PlayerException::class.java) { runBlocking { - player.start("""{}""").await() + completable.await() } } assertTrue(exception is JSErrorException) @@ -254,6 +285,22 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { assertEquals(UNKNOWN_ID, state.flow.id) } + @TestTemplate + fun `test invalid JSON content`() = runBlockingTest { + val completable = player.start("hello") + val exception = assertThrows(PlayerException::class.java) { + runBlocking { + completable.await() + } + } + assertTrue(exception is PlayerException) + assertEquals("Could not load Player content", exception.message) + + // this is not started b/c we failed out of content + // evaluation _before_ it's handed to the underlying Player + assertTrue(player.state is NotStartedState) + } + @TestTemplate fun `test player error state from an unhandled JVM exception`() { val message = "oh no!" @@ -270,7 +317,7 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { } assertEquals(message, exception.message) - assertEquals(message, player.errorState?.error?.message) + assertEquals("uncaught exception: $message", player.errorState?.error?.message) } @TestTemplate @@ -339,10 +386,19 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { @TestTemplate fun `test player released state`() = runBlockingTest { + val releasedStateTap: Deferred = async { + suspendCancellableCoroutine { + player.hooks.state.tap { state -> + if (state is ReleasedState) it.resume(state) + } + } + } + assertTrue(player.state is NotStartedState) player.release() assertTrue(player.state is ReleasedState) - val exception = assertThrows(PlayerException::class.java) { + assertEquals(ReleasedState, releasedStateTap.await()) + val exception = assertThrows(PlayerRuntimeException::class.java) { player.start(simpleFlowString) } assertEquals("Runtime object has been released!", exception.message?.split("] ")?.get(1)) @@ -400,7 +456,7 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { } } } - """.trimIndent() + """.trimIndent(), ) assertTrue(player.state is ErrorState) diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/PlayerCompletableTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/PlayerCompletableTest.kt similarity index 83% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/PlayerCompletableTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/player/PlayerCompletableTest.kt index f2c2c6a23..7d0cb6339 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/PlayerCompletableTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/PlayerCompletableTest.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.core.player +package com.intuit.playerui.core.player -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.utils.test.runBlockingTest +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.utils.test.runBlockingTest import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension import kotlinx.coroutines.withTimeout diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/PlayerHooksIntegrationTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/PlayerHooksIntegrationTest.kt similarity index 73% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/PlayerHooksIntegrationTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/player/PlayerHooksIntegrationTest.kt index b3d9b1871..1694cdb0b 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/PlayerHooksIntegrationTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/PlayerHooksIntegrationTest.kt @@ -1,10 +1,10 @@ -package com.intuit.player.jvm.core.player +package com.intuit.playerui.core.player -import com.intuit.player.jvm.core.player.state.CompletedState -import com.intuit.player.jvm.core.player.state.inProgressState -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.utils.test.runBlockingTest -import com.intuit.player.jvm.utils.test.simpleFlowString +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.runBlockingTest +import com.intuit.playerui.utils.test.simpleFlowString import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine import org.junit.jupiter.api.Assertions.assertTrue diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/PlayerHooksTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/PlayerHooksTest.kt new file mode 100644 index 000000000..e86919fbb --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/PlayerHooksTest.kt @@ -0,0 +1,64 @@ +package com.intuit.playerui.core.player + +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 +import com.intuit.playerui.core.data.DataController +import com.intuit.playerui.core.flow.FlowController +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.core.view.ViewController +import io.mockk.every +import io.mockk.impl.annotations.MockK +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class PlayerHooksTest : NodeBaseTest() { + @MockK + private lateinit var fc: NodeSyncHook1 + + @MockK + private lateinit var vc: NodeSyncHook1 + + @MockK + private lateinit var dc: NodeSyncHook1 + + @MockK + private lateinit var state: NodeSyncHook1 + + private val hooks by lazy { + Player.Hooks(node) + } + + @BeforeEach + fun setUpMocks() { + every { node.getObject(any()) } returns node + every { node.getSerializable>("flowController", any()) } returns fc + every { node.getSerializable>("viewController", any()) } returns vc + every { node.getSerializable>("dataController", any()) } returns dc + every { node.getSerializable>("state", any()) } returns state + every { node.nativeReferenceEquals(any()) } returns true + } + + @Test + fun flowController() { + val fc = hooks.flowController + assertNotNull(fc) + } + + @Test + fun viewController() { + val vc = hooks.viewController + assertNotNull(vc) + } + + @Test + fun dataController() { + val dc = hooks.dataController + assertNotNull(dc) + } + + @Test + fun state() { + assertNotNull(hooks.state) + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/CompletedStateTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/CompletedStateTest.kt new file mode 100644 index 000000000..89afef7d4 --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/CompletedStateTest.kt @@ -0,0 +1,73 @@ +package com.intuit.playerui.core.player.state + +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.core.data.DataController +import com.intuit.playerui.core.data.DataModelWithParser +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.core.player.PlayerFlowStatus +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class CompletedStateTest : NodeBaseTest() { + + private val completedState by lazy { + CompletedState(node) + } + + private val controllerState by lazy { + ControllerState(node) + } + + @MockK + private lateinit var mockDataModel: Node + + @MockK + private lateinit var format: RuntimeFormat<*> + + @BeforeEach + fun setUpMocks() { + val runtime: Runtime<*> = mockk() + every { node.runtime } returns runtime + every { runtime.containsKey("getSymbol") } returns true + every { runtime.getInvokable("getSymbol") } returns Invokable { "Symbol(hello)" } + every { node.getObject("controllers") } returns node + every { node.getSerializable("controllers", any()) } returns controllerState + every { node.getObject("data") } returns node + every { node.getSerializable("data", any()) } returns DataController(node) + every { node.getSerializable("flow", Flow.serializer()) } returns Flow("flowId") + every { node.getSerializable("dataModel", DataModelWithParser.serializer()) } returns DataModelWithParser(node) + every { node.getObject("dataModel") } returns mockDataModel + every { node.getObject("flow") } returns null + every { node.nativeReferenceEquals(any()) } returns false + } + + @Test + fun ref() { + assertEquals("Symbol(hello)", completedState.ref) + } + + @Test + fun status() { + assertEquals(PlayerFlowStatus.COMPLETED, completedState.status) + } + + @Test + fun flow() { + assertEquals("flowId", completedState.flow.id) + } + + @Test + fun dataModel() { + assertNotNull(completedState.dataModel) + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/ErrorStateTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/ErrorStateTest.kt new file mode 100644 index 000000000..4fa386ad2 --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/ErrorStateTest.kt @@ -0,0 +1,67 @@ +package com.intuit.playerui.core.player.state + +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.player.PlayerFlowStatus +import io.mockk.every +import io.mockk.mockk +import kotlinx.serialization.KSerializer +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class ErrorStateTest : NodeBaseTest() { + + private val errorState by lazy { + ErroneousState(node) + } + + @BeforeEach + fun setUpMocks() { + val runtime: Runtime<*> = mockk() + every { node.runtime } returns runtime + every { runtime.containsKey("getSymbol") } returns true + every { runtime.getInvokable("getSymbol") } returns Invokable { "Symbol(hello)" } + every { node.getSerializable("flow", Flow.serializer()) } returns Flow("flowId") + every { node.getSerializable("error", any>()) } returns null + every { node.getString("error") } returns "hello" + every { node.getObject("error") } returns null + every { node.getObject("flow") } returns null + every { node.nativeReferenceEquals(any()) } returns false + } + + @Test + fun errorFromObject() { + val someException = PlayerException("hello") + + every { node.getSerializable("error", any>()) } returns someException + + assertEquals(someException, errorState.error) + } + + @Test + fun errorFromString() { + val message = "hello" + every { node["error"] } returns message + assertEquals(message, errorState.error.message) + } + + @Test + fun ref() { + assertEquals("Symbol(hello)", errorState.ref) + } + + @Test + fun status() { + assertEquals(PlayerFlowStatus.ERROR, errorState.status) + } + + @Test + fun flow() { + assertEquals("flowId", errorState.flow.id) + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/InProgressStateTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/InProgressStateTest.kt similarity index 58% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/InProgressStateTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/InProgressStateTest.kt index d418e7c4b..c4f739833 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/player/state/InProgressStateTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/InProgressStateTest.kt @@ -1,22 +1,27 @@ -package com.intuit.player.jvm.core.player.state - -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.core.bridge.serialization.format.serializer -import com.intuit.player.jvm.core.bridge.toJson -import com.intuit.player.jvm.core.data.DataController -import com.intuit.player.jvm.core.expressions.ExpressionController -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.core.flow.FlowController -import com.intuit.player.jvm.core.player.PlayerFlowStatus -import com.intuit.player.jvm.core.view.ViewController +package com.intuit.playerui.core.player.state + +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.core.bridge.toJson +import com.intuit.playerui.core.data.DataController +import com.intuit.playerui.core.data.DataModelWithParser +import com.intuit.playerui.core.expressions.ExpressionController +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.core.flow.FlowController +import com.intuit.playerui.core.player.PlayerFlowStatus +import com.intuit.playerui.core.view.ViewController import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.mockk import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.modules.EmptySerializersModule -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -41,7 +46,12 @@ internal class InProgressStateTest : NodeBaseTest() { every { mockNode.getString("name") } returns "" every { mockNode.getObject("value") } returns mockNode every { mockNode.toJson() } returns JsonPrimitive("") - every { node.getString("ref") } returns "someRef" + every { node.getObject(any()) } returns node + every { node["flow"] } returns node + val runtime: Runtime<*> = mockk() + every { node.runtime } returns runtime + every { runtime.containsKey("getSymbol") } returns true + every { runtime.getInvokable("getSymbol") } returns Invokable { "Symbol(hello)" } every { node.format } returns format every { format.serializersModule } returns EmptySerializersModule every { node.getSerializable("controllers", any()) } returns controllerState @@ -51,15 +61,16 @@ internal class InProgressStateTest : NodeBaseTest() { every { node.getSerializable("current", any()) } returns null every { node.getSerializable("currentView", any()) } returns null every { node.getSerializable("data", any()) } returns DataController(node) - every { node.getFunction("getCurrentView") } returns null - every { node.getFunction("getLastViewUpdate") } returns Invokable { mockNode } - every { node.getFunction("getCurrentFlowState") } returns null + every { node.getInvokable("getCurrentView") } returns null + every { node.getInvokable("getLastViewUpdate") } returns Invokable { mockNode } + every { node.getInvokable("getCurrentFlowState") } returns null every { node.getObject("flowResult") } returns mockNode - every { node.getFunction("transition") } returns Invokable { + every { node.getInvokable("transition") } returns Invokable { lastTransition = it[0] as String } every { node.getSerializable("flow", Flow.serializer()) } returns Flow("flowId") - every { node.getObject("dataModel") } returns mockNode + every { node.getSerializable("dataModel", DataModelWithParser.serializer()) } returns DataModelWithParser(node) + every { node.nativeReferenceEquals(any()) } returns false } @Test @@ -95,7 +106,7 @@ internal class InProgressStateTest : NodeBaseTest() { @Test fun ref() { - assertEquals("someRef", inProgressState.ref) + assertEquals("Symbol(hello)", inProgressState.ref) } @Test diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/NotStartedStateTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/NotStartedStateTest.kt new file mode 100644 index 000000000..053db4e87 --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/state/NotStartedStateTest.kt @@ -0,0 +1,37 @@ +package com.intuit.playerui.core.player.state + +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.player.PlayerFlowStatus +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class NotStartedStateTest : NodeBaseTest() { + + private val notStartedState by lazy { + NotStartedState(node) + } + + @BeforeEach + fun setUpMocks() { + val runtime: Runtime<*> = mockk() + every { node.runtime } returns runtime + every { runtime.containsKey("getSymbol") } returns true + every { runtime.getInvokable("getSymbol") } returns Invokable { "Symbol(hello)" } + } + + @Test + fun ref() { + assertEquals("Symbol(hello)", notStartedState.ref) + } + + @Test + fun status() { + assertEquals(PlayerFlowStatus.NOT_STARTED, notStartedState.status) + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/plugins/PluggableTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/plugins/PluggableTest.kt similarity index 87% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/plugins/PluggableTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/plugins/PluggableTest.kt index 4b5546035..4c9cf9d0e 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/plugins/PluggableTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/plugins/PluggableTest.kt @@ -1,6 +1,8 @@ -package com.intuit.player.jvm.core.plugins +package com.intuit.playerui.core.plugins -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test internal class PluggableTest { @@ -35,7 +37,7 @@ internal class PluggableTest { didWarn = true } override fun error(vararg args: Any?) = throw UnsupportedOperationException() - } + }, ) } diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/serialization/DecodingTests.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/serialization/DecodingTests.kt similarity index 91% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/serialization/DecodingTests.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/serialization/DecodingTests.kt index eefa599e6..b0655ac8d 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/serialization/DecodingTests.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/serialization/DecodingTests.kt @@ -1,16 +1,17 @@ -package com.intuit.player.jvm.core.serialization +package com.intuit.playerui.core.serialization -import com.intuit.player.jvm.core.bridge.serialization.format.decodeFromRuntimeValue -import com.intuit.player.jvm.core.bridge.serialization.format.runtimeArray -import com.intuit.player.jvm.core.bridge.serialization.format.runtimeObject -import com.intuit.player.jvm.core.experimental.RuntimeClassDiscriminator -import com.intuit.player.jvm.utils.test.RuntimeTest +import com.intuit.playerui.core.bridge.serialization.format.decodeFromRuntimeValue +import com.intuit.playerui.core.bridge.serialization.format.runtimeArray +import com.intuit.playerui.core.bridge.serialization.format.runtimeObject +import com.intuit.playerui.core.experimental.RuntimeClassDiscriminator +import com.intuit.playerui.utils.test.RuntimeTest import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.TestTemplate @Serializable @@ -111,7 +112,7 @@ internal class StructureDecoding : RuntimeTest() { "five" to 5, "six" to "six", ), - "seven" to 7.7 + "seven" to 7.7, ) assertEquals(map, format.decodeFromRuntimeValue(runtimeObject)) @@ -159,13 +160,13 @@ internal class StructureDecoding : RuntimeTest() { format.runtimeArray { append(1) append(2) - } + }, ) append( format.runtimeArray { append(3) append(4) - } + }, ) } @@ -185,13 +186,13 @@ internal class StructureDecoding : RuntimeTest() { format.runtimeArray { append(1) append("two") - } + }, ) append( format.runtimeArray { append(3) append(false) - } + }, ) append(7.7) } diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/serialization/EncodingTests.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/serialization/EncodingTests.kt similarity index 91% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/serialization/EncodingTests.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/serialization/EncodingTests.kt index b687d8746..df493f644 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/serialization/EncodingTests.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/serialization/EncodingTests.kt @@ -1,8 +1,10 @@ -package com.intuit.player.jvm.core.serialization +package com.intuit.playerui.core.serialization -import com.intuit.player.jvm.core.bridge.serialization.format.* -import com.intuit.player.jvm.utils.test.RuntimeTest -import com.intuit.player.jvm.utils.test.equals +import com.intuit.playerui.core.bridge.serialization.format.encodeToRuntimeValue +import com.intuit.playerui.core.bridge.serialization.format.runtimeArray +import com.intuit.playerui.core.bridge.serialization.format.runtimeObject +import com.intuit.playerui.utils.test.RuntimeTest +import com.intuit.playerui.utils.test.equals import kotlinx.serialization.Serializable import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.TestTemplate @@ -89,7 +91,7 @@ internal class StructureEncoding : RuntimeTest() { "five" to 5, "six" to "six", ), - "seven" to 7.7 + "seven" to 7.7, ) assertTrue(format.equals(runtimeObject, format.encodeToRuntimeValue(map))) @@ -136,13 +138,13 @@ internal class StructureEncoding : RuntimeTest() { format.runtimeArray { append(1) append(2) - } + }, ) append( format.runtimeArray { append(3) append(4) - } + }, ) } @@ -161,13 +163,13 @@ internal class StructureEncoding : RuntimeTest() { format.runtimeArray { append(1) append("two") - } + }, ) append( format.runtimeArray { append(3) append(false) - } + }, ) append(7.7) } diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/serialization/NodeSerializationTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/serialization/NodeSerializationTest.kt similarity index 71% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/serialization/NodeSerializationTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/serialization/NodeSerializationTest.kt index d479c324f..922bfc04c 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/serialization/NodeSerializationTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/serialization/NodeSerializationTest.kt @@ -1,12 +1,16 @@ -package com.intuit.player.jvm.core.serialization - -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.runtime.serialize -import com.intuit.player.jvm.utils.test.RuntimeTest +package com.intuit.playerui.core.serialization + +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.bridge.serialization.serializers.Function1Serializer +import com.intuit.playerui.utils.test.RuntimeTest import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.TestTemplate @Serializable @@ -16,13 +20,15 @@ data class SomeData( val genericInvokable: Invokable<@Polymorphic Any?>, val specificInvokable: (Int, String) -> Int, val specificInvokableWithNode: (Node) -> Map, + @Serializable(with = Function1Serializer::class) + val specificNonPrimitiveInvokable: (String) -> SomeDataWithDefaults?, val maybeGenericInvokable: Invokable<@Polymorphic Any?>? = null, val maybeNode: Node? = null, ) @Serializable data class SomeDataWithDefaults( - val name: String = "default" + val name: String = "default", ) internal class NodeSerializationTest : RuntimeTest() { @@ -32,6 +38,9 @@ internal class NodeSerializationTest : RuntimeTest() { private val genericInvokable: Invokable get() = Invokable { p1 -> println(p1); 2 } private val specificInvokable: (Int, String) -> Int get() = { p1, p2 -> println("p1: $p1; p2: $p2"); 3 } private val specificInvokableWithNode: (Node) -> Map get() = { p1 -> println(p1); p1 } + private val specificNonPrimitiveInvokable: (String) -> SomeDataWithDefaults? get() = { + SomeDataWithDefaults(it) + } @TestTemplate fun `serializes node wrappers`() { @@ -41,6 +50,7 @@ internal class NodeSerializationTest : RuntimeTest() { genericInvokable, specificInvokable, specificInvokableWithNode, + { null }, null, null, ) @@ -50,14 +60,14 @@ internal class NodeSerializationTest : RuntimeTest() { assertEquals(node, someDataObj["node"]) assertNotEquals(genericInvokable, someDataObj["genericInvokable"]) - assertEquals(2, someDataObj.getFunction("genericInvokable")?.invoke()) + assertEquals(2, someDataObj.getInvokable("genericInvokable")?.invoke()) assertNotEquals(specificInvokable, someDataObj["specificInvokable"]) - assertEquals(3, someDataObj.getFunction("specificInvokable")?.invoke(2, "three")) + assertEquals(3, someDataObj.getInvokable("specificInvokable")?.invoke(2, "three")) val param = mapOf("wut" to "where") assertNotEquals(specificInvokableWithNode, someDataObj["specificInvokableWithNode"]) - assertEquals(param, someDataObj.getFunction("specificInvokableWithNode")?.invoke(param)) + assertEquals(param, someDataObj.getInvokable("specificInvokableWithNode")?.invoke(param)) assertNull(someDataObj["maybeGenericInvokable"]) assertNull(someDataObj["maybeNode"]) @@ -71,8 +81,9 @@ internal class NodeSerializationTest : RuntimeTest() { "node" to node, "genericInvokable" to genericInvokable, "specificInvokableWithNode" to specificInvokableWithNode, + "specificNonPrimitiveInvokable" to specificNonPrimitiveInvokable, "specificInvokable" to specificInvokable, - ) + ), ) as Node val someData = someDataObj.deserialize(SomeData.serializer()) as SomeData assertEquals(name, someData.name) @@ -90,6 +101,11 @@ internal class NodeSerializationTest : RuntimeTest() { assertNull(someData.maybeGenericInvokable) assertNull(someData.maybeNode) + + val function = someData.specificNonPrimitiveInvokable + val data = function.invoke("Foo") + + assertEquals(SomeDataWithDefaults("Foo"), data) } @TestTemplate @@ -101,8 +117,9 @@ internal class NodeSerializationTest : RuntimeTest() { "genericInvokable" to genericInvokable, "specificInvokableWithNode" to specificInvokableWithNode, "specificInvokable" to specificInvokable, + "specificNonPrimitiveInvokable" to specificNonPrimitiveInvokable, "maybeNode" to Unit, - ) + ), ) as Node val someData = someDataObj.deserialize(SomeData.serializer()) as SomeData @@ -113,7 +130,7 @@ internal class NodeSerializationTest : RuntimeTest() { val someDataObj = runtime.serialize( mapOf( "name" to Unit, - ) + ), ) as Node val someData = someDataObj.deserialize(SomeDataWithDefaults.serializer()) diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewControllerHooksTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewControllerHooksTest.kt new file mode 100644 index 000000000..a083a01f9 --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewControllerHooksTest.kt @@ -0,0 +1,32 @@ +package com.intuit.playerui.core.view + +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 +import io.mockk.every +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class ViewControllerHooksTest : NodeBaseTest() { + + private val view by lazy { + NodeSyncHook1(node, View.serializer()) + } + + @BeforeEach + fun setUpMock() { + every { node.getObject("view") } returns node + every { node.getInvokable("tap") } returns Invokable {} + every { node.getInvokable("tap", any()) } returns Invokable {} + every { node.getSerializable>("view", any()) } returns view + every { node.nativeReferenceEquals(any()) } returns false + } + + @Test + fun view() { + val viewControllerHooks = ViewController.Hooks(node) + assertEquals(viewControllerHooks.view, view) + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewControllerTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewControllerTest.kt similarity index 77% rename from jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewControllerTest.kt rename to jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewControllerTest.kt index a1c17ee70..7e30478f7 100644 --- a/jvm/core/src/test/kotlin/com/intuit/player/jvm/core/view/ViewControllerTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewControllerTest.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.core.view +package com.intuit.playerui.core.view -import com.intuit.player.jvm.core.NodeBaseTest -import com.intuit.player.jvm.core.bridge.Node +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.bridge.Node import io.mockk.every import io.mockk.impl.annotations.MockK import org.junit.jupiter.api.Assertions.assertNotNull @@ -12,6 +12,7 @@ internal class ViewControllerTest : NodeBaseTest() { @MockK private lateinit var hookNode: Node + @MockK private lateinit var viewNode: Node @@ -21,8 +22,10 @@ internal class ViewControllerTest : NodeBaseTest() { @BeforeEach fun setUpMock() { + every { node.getObject(any()) } returns node every { node.getSerializable("hooks", any()) } returns ViewController.Hooks(hookNode) every { node.getSerializable("currentView", any()) } returns View(viewNode) + every { node.nativeReferenceEquals(any()) } returns true } @Test diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewHooksTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewHooksTest.kt new file mode 100644 index 000000000..c14d5ffa2 --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewHooksTest.kt @@ -0,0 +1,48 @@ +package com.intuit.playerui.core.view + +import com.intuit.playerui.core.NodeBaseTest +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 +import com.intuit.playerui.core.resolver.Resolver +import io.mockk.every +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class ViewHooksTest : NodeBaseTest() { + + private val onUpdate by lazy { + NodeSyncHook1(node, Asset.serializer()) + } + + private val resolver by lazy { + NodeSyncHook1(node, Resolver.serializer()) + } + + private val viewHooks by lazy { + ViewHooks(node) + } + + @BeforeEach + fun setUpMocks() { + every { node.getObject("onUpdate") } returns node + every { node.getObject("resolver") } returns node + every { node.getInvokable("tap") } returns Invokable {} + every { node.getInvokable("tap", any()) } returns Invokable {} + every { node.getSerializable>("onUpdate", any()) } returns onUpdate + every { node.getSerializable>("resolver", any()) } returns resolver + every { node.nativeReferenceEquals(any()) } returns false + } + + @Test + fun onUpdate() { + assertEquals(viewHooks.onUpdate, onUpdate) + } + + @Test + fun resolver() { + assertEquals(viewHooks.resolver, resolver) + } +} diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewTest.kt new file mode 100644 index 000000000..934380d8d --- /dev/null +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/view/ViewTest.kt @@ -0,0 +1,29 @@ +package com.intuit.playerui.core.view + +import com.intuit.playerui.core.NodeBaseTest +import io.mockk.every +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class ViewTest : NodeBaseTest() { + private val hooks by lazy { + ViewHooks(node) + } + + @BeforeEach + fun setUpMocks() { + every { node.getObject("hooks") } returns node + every { node.getSerializable("hooks", any()) } returns hooks + every { node.nativeReferenceEquals(any()) } returns false + } + + val view by lazy { + View(node) + } + + @Test + fun hooks() { + assertEquals(view.hooks, hooks) + } +} diff --git a/jvm/dependencies/common.bzl b/jvm/dependencies/common.bzl index bae3bae9a..f22761b9f 100644 --- a/jvm/dependencies/common.bzl +++ b/jvm/dependencies/common.bzl @@ -1,6 +1,7 @@ load("//jvm/dependencies:versions.bzl", "versions") maven = [ + "org.jetbrains.kotlin:kotlin-reflect:%s" % "1.7.10", "org.jetbrains.kotlinx:kotlinx-coroutines-core:%s" % versions.kotlin.coroutines, "org.jetbrains.kotlinx:kotlinx-serialization-json:%s" % versions.kotlin.serialization, "com.intuit.hooks:hooks:%s" % versions.hooks, diff --git a/jvm/dependencies/deps.bzl b/jvm/dependencies/deps.bzl index d081e38c0..da280b141 100644 --- a/jvm/dependencies/deps.bzl +++ b/jvm/dependencies/deps.bzl @@ -9,8 +9,7 @@ load("//jvm/testutils:deps.bzl", testutils = "maven") load("//jvm/perf:deps.bzl", perf = "maven") load("//plugins:deps.bzl", plugins = "maven") load("@rules_player//distribution:deps.bzl", distribution = "maven") -load("@grab_bazel_common//:workspace_defs.bzl", grab = "GRAB_BAZEL_COMMON_ARTIFACTS") -tooling = distribution + grab +tooling = distribution maven = remove_duplicates(common + core + graaljs + j2v8 + utils + testutils + perf + plugins + tooling + android) diff --git a/jvm/dependencies/versions.bzl b/jvm/dependencies/versions.bzl index 72f5cf6c4..7d7b5586d 100644 --- a/jvm/dependencies/versions.bzl +++ b/jvm/dependencies/versions.bzl @@ -1,19 +1,21 @@ versions = struct( kotlin = struct( - coroutines = "1.5.2", + coroutines = "1.6.0", serialization = "1.3.0", ), runtimes = struct( graaljs = "21.2.0", j2v8 = "6.1.0", ), + j2v8 = struct( + debugger = "0.2.3", + ), logging = struct( slf4j = "1.7.36", logback = "1.2.10", ), - hooks = "0.11.1", + hooks = "0.15.0", testing = struct( - applitools = "4.7.6", junit = "4.12", jupiter = "5.6.0", kluent = "1.68", @@ -43,4 +45,7 @@ versions = struct( ), material_dialogs = "3.3.0", material = "1.4.0", + facebook = struct( + stetho = "1.5.1", + ), ) diff --git a/jvm/graaljs/BUILD b/jvm/graaljs/BUILD index 14000902b..8cbe87909 100644 --- a/jvm/graaljs/BUILD +++ b/jvm/graaljs/BUILD @@ -7,5 +7,5 @@ kt_player_module( main_exports = main_exports, main_resources = main_resources + glob(["src/main/resources/**"]), test_deps = test_deps, - test_package = "com.intuit.player.jvm.graaljs", + test_package = "com.intuit.playerui.graaljs", ) diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/extensions/handleValue.kt b/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/extensions/handleValue.kt deleted file mode 100644 index 55e2d317c..000000000 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/extensions/handleValue.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.intuit.player.jvm.graaljs.extensions - -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.core.bridge.serialization.format.encodeToRuntimeValue -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.graaljs.bridge.GraalNode -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import org.graalvm.polyglot.Value - -internal fun Any?.handleValue(format: RuntimeFormat): Any? = when (this) { - is Value -> transform(format) - else -> this -} - -private fun Value.transform(format: RuntimeFormat): Any? = when { - isNull -> null - isHostObject -> when (val hostObject = asHostObject()) { - is Unit -> null - is JsonElement -> Json.decodeFromJsonElement(GenericSerializer(), hostObject) - else -> hostObject - } - hasArrayElements() -> toList(format) - canExecute() -> toInvokable(format) - else -> when (this.`as`(Any::class.java)) { - is Int -> asInt() - is Double, is Long -> try { asInt() } catch (e: Exception) { asDouble() } - is String -> asString() - is Boolean -> asBoolean() - /* - * this is kind of awful, Graal's execute returns the result slightly differently - * from J2V8, so if a node is the return result of an execution, the Value passed here - * will have all the map methods instead of the actual map - * Refer to the tests in NodeSerializationTest - * */ - is Map<*, *> -> if (canInvokeMember("getGraalObject")) invokeMember("getGraalObject").toNode(format) else toNode(format) - else -> null - } -} - -internal fun Value.toList(format: RuntimeFormat): List? = if (isNull || !hasArrayElements()) null else lockIfDefined { - (0 until this.arraySize).map(::getArrayElement).map { it.handleValue(format) } -} - -internal fun Value.toNode(format: RuntimeFormat): Node? = if (isNull || !hasMembers()) null else lockIfDefined { - if (this.hasMember("id") && this.hasMember("type")) - Asset(GraalNode(this, format.runtime)) - else GraalNode(this, format.runtime) -} - -internal fun Value.toInvokable(format: RuntimeFormat): Invokable? = if (isNull || !canExecute()) null else lockIfDefined { - Invokable { args -> - blockingLock { - this.execute( - *format.encodeToRuntimeValue( - args as Array - ).`as`(List::class.java).toTypedArray() - ).handleValue(format) as R - } - } -} diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/GraalNode.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/GraalNode.kt similarity index 71% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/GraalNode.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/GraalNode.kt index cb1b39fdf..3cf8df523 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/GraalNode.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/GraalNode.kt @@ -1,16 +1,18 @@ -package com.intuit.player.jvm.graaljs.bridge - -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.graaljs.bridge.runtime.GraalRuntime.Companion.isReleased -import com.intuit.player.jvm.graaljs.bridge.runtime.GraalRuntime.Companion.undefined -import com.intuit.player.jvm.graaljs.extensions.* -import com.intuit.player.jvm.graaljs.extensions.handleValue -import com.intuit.player.jvm.graaljs.extensions.toList -import com.intuit.player.jvm.graaljs.extensions.toNode +package com.intuit.playerui.graaljs.bridge + +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.graaljs.bridge.runtime.GraalRuntime.Companion.isReleased +import com.intuit.playerui.graaljs.bridge.runtime.GraalRuntime.Companion.undefined +import com.intuit.playerui.graaljs.extensions.blockingLock +import com.intuit.playerui.graaljs.extensions.handleValue +import com.intuit.playerui.graaljs.extensions.lockIfDefined +import com.intuit.playerui.graaljs.extensions.toInvokable +import com.intuit.playerui.graaljs.extensions.toList +import com.intuit.playerui.graaljs.extensions.toNode import kotlinx.serialization.DeserializationStrategy import org.graalvm.polyglot.Value @@ -54,9 +56,13 @@ internal open class GraalNode(override val graalObject: Value, override val runt getMember(key) }?.handleValue(format) + override fun getInvokable(key: String, deserializationStrategy: DeserializationStrategy): Invokable? = graalObject.lockIfDefined { + graalObject.getMember(key) + }?.toInvokable(format, deserializationStrategy) + override fun getFunction(key: String): Invokable? = graalObject.lockIfDefined { graalObject.getMember(key) - }?.toInvokable(format) + }?.toInvokable(format, null) override fun getList(key: String): List<*>? = graalObject.lockIfDefined { graalObject.getMember(key) @@ -64,9 +70,9 @@ internal open class GraalNode(override val graalObject: Value, override val runt override fun getSerializable(key: String, deserializer: DeserializationStrategy): T? { return graalObject.blockingLock { - if (memberKeys.contains(key)) - format.decodeFromRuntimeValue(deserializer, graalObject.getMember(key)) - else null + key.takeIf(graalObject::hasMember) + ?.let(graalObject::getMember) + ?.let { format.decodeFromRuntimeValue(deserializer, it) } } } diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/GraalObjectWrapper.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/GraalObjectWrapper.kt similarity index 70% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/GraalObjectWrapper.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/GraalObjectWrapper.kt index 4a734b1ca..cb199809d 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/GraalObjectWrapper.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/GraalObjectWrapper.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.graaljs.bridge +package com.intuit.playerui.graaljs.bridge import org.graalvm.polyglot.Value diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/runtime/GraalRuntime.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/runtime/GraalRuntime.kt similarity index 67% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/runtime/GraalRuntime.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/runtime/GraalRuntime.kt index 2b6c85b59..be131535e 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/runtime/GraalRuntime.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/runtime/GraalRuntime.kt @@ -1,20 +1,22 @@ -package com.intuit.player.jvm.graaljs.bridge.runtime - -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeConfig -import com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeContainer -import com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeFactory -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.serialization.serializers.playerSerializersModule -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.graaljs.bridge.GraalNode -import com.intuit.player.jvm.graaljs.bridge.serialization.format.GraalFormat -import com.intuit.player.jvm.graaljs.bridge.serialization.format.GraalFormatConfiguration -import com.intuit.player.jvm.graaljs.bridge.serialization.serializers.GraalValueSerializer -import com.intuit.player.jvm.graaljs.extensions.blockingLock -import com.intuit.player.jvm.graaljs.extensions.handleValue -import com.intuit.player.jvm.graaljs.player.PlayerContextFactory +package com.intuit.playerui.graaljs.bridge.runtime + +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeConfig +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeContainer +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeFactory +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.ScriptContext +import com.intuit.playerui.core.bridge.serialization.serializers.playerSerializersModule +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.utils.InternalPlayerApi +import com.intuit.playerui.graaljs.bridge.GraalNode +import com.intuit.playerui.graaljs.bridge.serialization.format.GraalFormat +import com.intuit.playerui.graaljs.bridge.serialization.format.GraalFormatConfiguration +import com.intuit.playerui.graaljs.bridge.serialization.serializers.GraalValueSerializer +import com.intuit.playerui.graaljs.extensions.blockingLock +import com.intuit.playerui.graaljs.extensions.handleValue +import com.intuit.playerui.graaljs.player.PlayerContextFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -26,13 +28,17 @@ import kotlinx.serialization.modules.plus import org.graalvm.polyglot.Context import org.graalvm.polyglot.Value import java.util.concurrent.locks.ReentrantLock +import kotlin.coroutines.EmptyCoroutineContext public fun Runtime(runtime: Context, config: GraalRuntimeConfig = GraalRuntimeConfig()): Runtime = GraalRuntime(config) internal class GraalRuntime( - private val config: GraalRuntimeConfig + override val config: GraalRuntimeConfig, ) : Runtime { + override val dispatcher: Nothing + get() = throw UnsupportedOperationException("dispatcher not defined for GraalRuntime") + val context: Context by config::graalContext override val format: GraalFormat = GraalFormat( @@ -40,13 +46,14 @@ internal class GraalRuntime( this, playerSerializersModule + SerializersModule { contextual(Value::class, GraalValueSerializer) - } - ) + }, + ), ) companion object { val Context.isReleased: Boolean - get() = contextRuntimeMap[this]?.released ?: throw PlayerException("Graal Context is not associated with a runtime") + get() = contextRuntimeMap[this]?.released + ?: throw PlayerException("Graal Context is not associated with a runtime") val Context.undefined: Value get() = eval("js", "undefined") private val contextRuntimeMap: MutableMap = hashMapOf() @@ -62,13 +69,17 @@ internal class GraalRuntime( } override val scope: CoroutineScope by lazy { - CoroutineScope(Dispatchers.Default + SupervisorJob()) + CoroutineScope(Dispatchers.Default + SupervisorJob() + (config.coroutineExceptionHandler ?: EmptyCoroutineContext)) } override fun execute(script: String): Any? = context.blockingLock { context.eval("js", script).handleValue(format) } + override fun load(scriptContext: ScriptContext) = context.blockingLock { + context.eval("js", scriptContext.script).handleValue(format) + } + override fun add(name: String, value: Value) { context.blockingLock { getBindings("js").putMember(name, value) @@ -87,6 +98,9 @@ internal class GraalRuntime( } } + @InternalPlayerApi + override var checkBlockingThread: Thread.() -> Unit = {} + override fun toString(): String = "Graal" private val backingNode: Node = GraalNode(context.getBindings("js"), this) @@ -101,6 +115,7 @@ internal class GraalRuntime( override fun isEmpty(): Boolean = backingNode.isEmpty() override fun getSerializable(key: String, deserializer: DeserializationStrategy): T? = backingNode.getSerializable(key, deserializer) + override fun deserialize(deserializer: DeserializationStrategy): T = backingNode.deserialize(deserializer) override fun isReleased(): Boolean = backingNode.isReleased() override fun isUndefined(): Boolean = backingNode.isUndefined() @@ -110,6 +125,7 @@ internal class GraalRuntime( override fun getDouble(key: String): Double? = backingNode.getDouble(key) override fun getLong(key: String): Long? = backingNode.getLong(key) override fun getBoolean(key: String): Boolean? = backingNode.getBoolean(key) + override fun getInvokable(key: String, deserializationStrategy: DeserializationStrategy): Invokable? = backingNode.getInvokable(key, deserializationStrategy) override fun getFunction(key: String): Invokable? = backingNode.getFunction(key) override fun getList(key: String): List<*>? = backingNode.getList(key) override fun getObject(key: String): Node? = backingNode.getObject(key) @@ -123,7 +139,7 @@ public object GraalJS : PlayerRuntimeFactory { } public data class GraalRuntimeConfig( - var graalContext: Context = PlayerContextFactory.context + var graalContext: Context = PlayerContextFactory.context, ) : PlayerRuntimeConfig() public class GraalRuntimeContainer : PlayerRuntimeContainer { diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/encoding/GraalDecoders.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/encoding/GraalDecoders.kt similarity index 77% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/encoding/GraalDecoders.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/encoding/GraalDecoders.kt index a4e72339b..de7c17598 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/encoding/GraalDecoders.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/encoding/GraalDecoders.kt @@ -1,13 +1,20 @@ -package com.intuit.player.jvm.graaljs.bridge.serialization.encoding - -import com.intuit.player.jvm.core.bridge.serialization.encoding.* -import com.intuit.player.jvm.core.experimental.RuntimeClassDiscriminator -import com.intuit.player.jvm.graaljs.bridge.runtime.GraalRuntime.Companion.undefined -import com.intuit.player.jvm.graaljs.bridge.serialization.format.GraalDecodingException -import com.intuit.player.jvm.graaljs.bridge.serialization.format.GraalFormat -import com.intuit.player.jvm.graaljs.extensions.blockingLock -import com.intuit.player.jvm.graaljs.extensions.handleValue +package com.intuit.playerui.graaljs.bridge.serialization.encoding + +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.serialization.encoding.AbstractRuntimeArrayListDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.AbstractRuntimeObjectClassDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.AbstractRuntimeObjectMapDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.AbstractRuntimeValueDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.NodeDecoder +import com.intuit.playerui.core.experimental.RuntimeClassDiscriminator +import com.intuit.playerui.graaljs.bridge.runtime.GraalRuntime.Companion.undefined +import com.intuit.playerui.graaljs.bridge.serialization.format.GraalDecodingException +import com.intuit.playerui.graaljs.bridge.serialization.format.GraalFormat +import com.intuit.playerui.graaljs.extensions.blockingLock +import com.intuit.playerui.graaljs.extensions.handleValue +import com.intuit.playerui.graaljs.extensions.toInvokable import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind @@ -34,6 +41,10 @@ internal sealed class AbstractGraalDecoder( PolymorphicKind.SEALED -> GraalSealedClassDecoder(format, currentValue) else -> error("Runtime format decoders can't decode kinds of (${descriptor.kind}) into structures for $descriptor") } + + override fun decodeFunction(returnTypeSerializer: KSerializer): Invokable { + return currentValue.toInvokable(format, returnTypeSerializer) ?: error("Unable to decode Graal function using return type serializer ${returnTypeSerializer.descriptor}") + } } internal class GraalValueDecoder(format: GraalFormat, value: Value) : AbstractGraalDecoder(format, value) @@ -58,7 +69,7 @@ internal class GraalObjectMapDecoder(override val format: GraalFormat, override override fun buildDecoderForSerializableElement( descriptor: SerialDescriptor, index: Int, - deserializer: DeserializationStrategy + deserializer: DeserializationStrategy, ): Decoder = when (index % 2 == 0) { true -> GraalValueDecoder(format, format.context.asValue(getKeyAtIndex(index))) false -> GraalValueDecoder(format, getElementAtIndex(index)) @@ -80,7 +91,7 @@ internal class GraalArrayListDecoder(override val format: GraalFormat, override override fun buildDecoderForSerializableElement( descriptor: SerialDescriptor, index: Int, - deserializer: DeserializationStrategy + deserializer: DeserializationStrategy, ): Decoder = GraalValueDecoder(format, decodeElement(descriptor, index)) } @@ -104,7 +115,7 @@ internal class GraalObjectClassDecoder(override val format: GraalFormat, overrid override fun buildDecoderForSerializableElement( descriptor: SerialDescriptor, index: Int, - deserializer: DeserializationStrategy + deserializer: DeserializationStrategy, ): Decoder = GraalValueDecoder(format, decodeElement(descriptor, index)) } @@ -118,11 +129,10 @@ internal class GraalSealedClassDecoder(override val format: GraalFormat, overrid override fun buildDecoderForSerializableElement( descriptor: SerialDescriptor, index: Int, - deserializer: DeserializationStrategy + deserializer: DeserializationStrategy, ): Decoder = GraalValueDecoder(format, value) override fun decodeValueElement(descriptor: SerialDescriptor, index: Int): Any? { - val discriminator = ( descriptor.annotations.firstOrNull { it is RuntimeClassDiscriminator diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/encoding/GraalEncoders.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/encoding/GraalEncoders.kt similarity index 76% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/encoding/GraalEncoders.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/encoding/GraalEncoders.kt index 2f0db8c98..08c8db298 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/encoding/GraalEncoders.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/encoding/GraalEncoders.kt @@ -1,23 +1,23 @@ -package com.intuit.player.jvm.graaljs.bridge.serialization.encoding - -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.invokeVararg -import com.intuit.player.jvm.core.bridge.serialization.encoding.FunctionEncoder -import com.intuit.player.jvm.core.bridge.serialization.encoding.NodeEncoder -import com.intuit.player.jvm.core.bridge.serialization.json.isJsonElementSerializer -import com.intuit.player.jvm.core.bridge.serialization.json.value -import com.intuit.player.jvm.core.bridge.serialization.serializers.FunctionLikeSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.ThrowableSerializer -import com.intuit.player.jvm.graaljs.bridge.GraalNode -import com.intuit.player.jvm.graaljs.bridge.GraalObjectWrapper -import com.intuit.player.jvm.graaljs.bridge.serialization.format.GraalEncodingException -import com.intuit.player.jvm.graaljs.bridge.serialization.format.GraalFormat -import com.intuit.player.jvm.graaljs.bridge.serialization.format.encodeToGraalValue -import com.intuit.player.jvm.graaljs.extensions.blockingLock -import com.intuit.player.jvm.graaljs.extensions.handleValue +package com.intuit.playerui.graaljs.bridge.serialization.encoding + +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.invokeVararg +import com.intuit.playerui.core.bridge.serialization.encoding.FunctionEncoder +import com.intuit.playerui.core.bridge.serialization.encoding.NodeEncoder +import com.intuit.playerui.core.bridge.serialization.json.isJsonElementSerializer +import com.intuit.playerui.core.bridge.serialization.json.value +import com.intuit.playerui.core.bridge.serialization.serializers.FunctionLikeSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.ThrowableSerializer +import com.intuit.playerui.graaljs.bridge.GraalNode +import com.intuit.playerui.graaljs.bridge.GraalObjectWrapper +import com.intuit.playerui.graaljs.bridge.serialization.format.GraalEncodingException +import com.intuit.playerui.graaljs.bridge.serialization.format.GraalFormat +import com.intuit.playerui.graaljs.bridge.serialization.format.encodeToGraalValue +import com.intuit.playerui.graaljs.extensions.blockingLock +import com.intuit.playerui.graaljs.extensions.handleValue import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor @@ -56,14 +56,15 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v MAP, LIST, PRIMITIVE, - UNDECIDED + UNDECIDED, } private val currentContent get() = when (mode) { Mode.MAP -> contentMap Mode.LIST -> contentList Mode.PRIMITIVE, - Mode.UNDECIDED -> content + Mode.UNDECIDED, + -> content } private var tag: String? = null @@ -73,7 +74,8 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v } get() = when (mode) { Mode.UNDECIDED, - Mode.PRIMITIVE -> field + Mode.PRIMITIVE, + -> field else -> error("cannot get content unless in PRIMITIVE mode") } @@ -94,22 +96,26 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v } override fun beginStructure( - descriptor: SerialDescriptor + descriptor: SerialDescriptor, ): CompositeEncoder { val consumer = when (mode) { Mode.LIST, - Mode.MAP -> { node -> putContent(node) } + Mode.MAP, + -> { node -> putContent(node) } Mode.PRIMITIVE, - Mode.UNDECIDED -> consumer + Mode.UNDECIDED, + -> consumer } - return if (descriptor == ThrowableSerializer().descriptor) + return if (descriptor == ThrowableSerializer().descriptor) { GraalExceptionEncoder(format, ::putContent) - else when (descriptor.kind) { - StructureKind.CLASS -> GraalValueEncoder(format, Mode.MAP, consumer) - StructureKind.LIST, is PolymorphicKind -> GraalValueEncoder(format, Mode.LIST, consumer) - StructureKind.MAP -> GraalValueEncoder(format, Mode.MAP, consumer) - else -> GraalValueEncoder(format, consumer) + } else { + when (descriptor.kind) { + StructureKind.CLASS -> GraalValueEncoder(format, Mode.MAP, consumer) + StructureKind.LIST, is PolymorphicKind -> GraalValueEncoder(format, Mode.LIST, consumer) + StructureKind.MAP -> GraalValueEncoder(format, Mode.MAP, consumer) + else -> GraalValueEncoder(format, consumer) + } } } @@ -121,7 +127,7 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v is GraalNode -> value.graalObject is Value -> value else -> value - } + }, ) override fun encodeNull() = putContent(null) @@ -144,7 +150,7 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v .toTypedArray() format.encodeToGraalValue(invokable(*encodedArgs)) - } + }, ) override fun encodeFunction(kCallable: KCallable<*>) = putContent( @@ -160,15 +166,17 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v // check if type is nullable and value is null if (( - currValue == null && - // base type or type argument could be marked nullable - (kParam.type.isMarkedNullable || kParam.type.arguments[0].type?.isMarkedNullable == true) - ) || + currValue == null && + // base type or type argument could be marked nullable + (kParam.type.isMarkedNullable || kParam.type.arguments[0].type?.isMarkedNullable == true) + ) || // otherwise check if arg matches type if not null (currValue != null && currValue::class.isSubclassOf(kParam.type.arguments[0].type?.classifier as KClass<*>)) - ) + ) { index++ - else break + } else { + break + } } // only take matching args encodedArgs.slice(start until index).toTypedArray() @@ -178,7 +186,7 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v } }.toTypedArray() format.encodeToGraalValue(kCallable.call(*matchedArgs)) - } + }, ) override fun encodeFunction(function: Function<*>) { @@ -215,7 +223,7 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v is JsonArray -> value.toList() is JsonPrimitive -> value.value else -> value - } as T + } as T, ) else -> super.encodeSerializableValue(serializer, value) @@ -230,7 +238,8 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v else -> putContent(tag, content) } Mode.PRIMITIVE, - Mode.UNDECIDED -> { + Mode.UNDECIDED, + -> { this.content = format.context.asValue(content) endEncode() } @@ -242,7 +251,8 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v Mode.LIST -> contentList.add(content) Mode.MAP -> contentMap[tag] = content Mode.UNDECIDED, - Mode.PRIMITIVE -> { + Mode.PRIMITIVE, + -> { this.content = format.context.asValue(content) endEncode() } @@ -251,8 +261,10 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v } private fun Value.add(content: Any?) { - if (this.hasArrayElements()) blockingLock { - this.setArrayElement(this.arraySize, content) + if (this.hasArrayElements()) { + blockingLock { + this.setArrayElement(this.arraySize, content) + } } val item = "" } @@ -267,7 +279,8 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v is Boolean, is Double, is Int, - is Long -> putMember(key, content) + is Long, + -> putMember(key, content) else -> error("can't set property on Graal Value of type: ${content::class}") } } @@ -275,7 +288,8 @@ internal open class GraalValueEncoder(private val format: GraalFormat, private v internal class GraalExceptionEncoder(format: GraalFormat, consumer: (Value) -> Unit) : GraalValueEncoder( format, - Mode.MAP, consumer + Mode.MAP, + consumer, ) { override val contentMap by lazy { diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/format/GraalFormat.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/format/GraalFormat.kt similarity index 63% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/format/GraalFormat.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/format/GraalFormat.kt index 28f4a501b..ee007219c 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/format/GraalFormat.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/format/GraalFormat.kt @@ -1,12 +1,12 @@ -package com.intuit.player.jvm.graaljs.bridge.serialization.format - -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.serialization.format.AbstractRuntimeFormat -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormatConfiguration -import com.intuit.player.jvm.core.bridge.serialization.format.serializer -import com.intuit.player.jvm.graaljs.bridge.runtime.GraalRuntime -import com.intuit.player.jvm.graaljs.bridge.serialization.encoding.readValue -import com.intuit.player.jvm.graaljs.bridge.serialization.encoding.writeValue +package com.intuit.playerui.graaljs.bridge.serialization.format + +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.serialization.format.AbstractRuntimeFormat +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormatConfiguration +import com.intuit.playerui.core.bridge.serialization.format.serializer +import com.intuit.playerui.graaljs.bridge.runtime.GraalRuntime +import com.intuit.playerui.graaljs.bridge.serialization.encoding.readValue +import com.intuit.playerui.graaljs.bridge.serialization.encoding.writeValue import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.modules.SerializersModule @@ -14,7 +14,7 @@ import org.graalvm.polyglot.Context import org.graalvm.polyglot.Value public class GraalFormat( - config: GraalFormatConfiguration + config: GraalFormatConfiguration, ) : AbstractRuntimeFormat(config) { public val context: Context = (config.runtime as GraalRuntime).context @@ -31,7 +31,7 @@ public class GraalFormat( public data class GraalFormatConfiguration internal constructor( override val runtime: Runtime, - override val serializersModule: SerializersModule + override val serializersModule: SerializersModule, ) : RuntimeFormatConfiguration internal inline fun GraalFormat.encodeToGraalValue(value: T): Value = diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/format/GraalFormatExceptions.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/format/GraalFormatExceptions.kt similarity index 78% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/format/GraalFormatExceptions.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/format/GraalFormatExceptions.kt index ce7197c00..c200da223 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/format/GraalFormatExceptions.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/format/GraalFormatExceptions.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.graaljs.bridge.serialization.format +package com.intuit.playerui.graaljs.bridge.serialization.format -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeSerializationException -import com.intuit.player.jvm.core.utils.InternalPlayerApi +import com.intuit.playerui.core.bridge.serialization.format.RuntimeSerializationException +import com.intuit.playerui.core.utils.InternalPlayerApi /** Generic exception indicating a problem with [GraalFormat] serialization and deserialization */ internal open class GraalSerializationException @InternalPlayerApi constructor(message: String?, cause: Throwable? = null) : RuntimeSerializationException(message, cause) diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/serializers/GraalValueSerializer.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/serializers/GraalValueSerializer.kt similarity index 76% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/serializers/GraalValueSerializer.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/serializers/GraalValueSerializer.kt index f25df8788..0840a49b4 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/serializers/GraalValueSerializer.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/bridge/serialization/serializers/GraalValueSerializer.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.graaljs.bridge.serialization.serializers +package com.intuit.playerui.graaljs.bridge.serialization.serializers -import com.intuit.player.jvm.core.bridge.serialization.encoding.requireNodeEncoder -import com.intuit.player.jvm.graaljs.bridge.serialization.encoding.AbstractGraalDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.requireNodeEncoder +import com.intuit.playerui.graaljs.bridge.serialization.encoding.AbstractGraalDecoder import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializer import kotlinx.serialization.descriptors.SerialDescriptor diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/extensions/HandleValue.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/extensions/HandleValue.kt new file mode 100644 index 000000000..f0a579004 --- /dev/null +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/extensions/HandleValue.kt @@ -0,0 +1,92 @@ +package com.intuit.playerui.graaljs.extensions + +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.core.bridge.serialization.format.encodeToRuntimeValue +import com.intuit.playerui.core.bridge.serialization.format.serializer +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.graaljs.bridge.GraalNode +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import org.graalvm.polyglot.Value + +internal fun Any?.handleValue(format: RuntimeFormat): Any? = when (this) { + is Value -> transform(format) + else -> this +} + +private fun Value.transform(format: RuntimeFormat): Any? = when { + isNull -> null + isHostObject -> when (val hostObject = asHostObject()) { + is Unit -> null + is JsonElement -> Json.decodeFromJsonElement(GenericSerializer(), hostObject) + else -> hostObject + } + hasArrayElements() -> toList(format) + canExecute() -> toInvokable(format, format.serializer()) + metaObject.toString() == "symbol" -> null // this is also awful, but consistent w/ j2v8 + else -> when (this.`as`(Any::class.java)) { + is Int -> asInt() + is Double, is Long -> try { asInt() } catch (e: Exception) { asDouble() } + is String -> asString() + is Boolean -> asBoolean() + /* + * this is kind of awful, Graal's execute returns the result slightly differently + * from J2V8, so if a node is the return result of an execution, the Value passed here + * will have all the map methods instead of the actual map + * Refer to the tests in NodeSerializationTest + * */ + is Map<*, *> -> if (canInvokeMember("getGraalObject")) invokeMember("getGraalObject").toNode(format) else toNode(format) + else -> throw SerializationException("Value ($this) of type (${this::class.java}) is unknown") + } +} + +internal fun Value.toList(format: RuntimeFormat): List? = if (isNull || !hasArrayElements()) { + null +} else { + lockIfDefined { + (0 until this.arraySize).map(::getArrayElement).map { it.handleValue(format) } + } +} + +internal fun Value.toNode(format: RuntimeFormat): Node? = if (isNull || !hasMembers()) { + null +} else { + lockIfDefined { + if (this.hasMember("id") && this.hasMember("type")) { + Asset(GraalNode(this, format.runtime)) + } else { + GraalNode(this, format.runtime) + } + } +} + +internal fun Value.toInvokable(format: RuntimeFormat, deserializationStrategy: DeserializationStrategy?): Invokable? = if (isNull || !canExecute()) { + null +} else { + lockIfDefined { + Invokable { args -> + blockingLock { + when ( + val result = + this.execute( + *format.encodeToRuntimeValue( + args as Array, + ).`as`(List::class.java).toTypedArray(), + ).handleValue(format) + ) { + is Node -> deserializationStrategy?.let { + result.deserialize(deserializationStrategy) + } ?: run { + result as R + } + else -> result as R + } + } + } + } +} diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/extensions/lock.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/extensions/Lock.kt similarity index 89% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/extensions/lock.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/extensions/Lock.kt index 3d25e18bc..aed0f7c43 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/extensions/lock.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/extensions/Lock.kt @@ -1,8 +1,8 @@ -package com.intuit.player.jvm.graaljs.extensions +package com.intuit.playerui.graaljs.extensions -import com.intuit.player.jvm.core.bridge.PlayerRuntimeException -import com.intuit.player.jvm.graaljs.bridge.runtime.GraalRuntime.Companion.isReleased -import com.intuit.player.jvm.graaljs.bridge.runtime.GraalRuntime.Companion.runtime +import com.intuit.playerui.core.bridge.PlayerRuntimeException +import com.intuit.playerui.graaljs.bridge.runtime.GraalRuntime.Companion.isReleased +import com.intuit.playerui.graaljs.bridge.runtime.GraalRuntime.Companion.runtime import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout @@ -71,7 +71,7 @@ internal fun Context.blockingLock(block: Context.() -> T): T = when { /** Special [blockingLock] helper to guard against undefined values */ internal fun Value.lockIfDefined( - block: Value.() -> T + block: Value.() -> T, ): T? = mapUndefinedToNull()?.let { blockingLock(block) } internal fun Value.mapUndefinedToNull() = blockingLock { diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/extensions/unwrap.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/extensions/Unwrap.kt similarity index 53% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/extensions/unwrap.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/extensions/Unwrap.kt index 302389968..1b5904f08 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/extensions/unwrap.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/extensions/Unwrap.kt @@ -1,8 +1,8 @@ -package com.intuit.player.jvm.graaljs.extensions +package com.intuit.playerui.graaljs.extensions -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.graaljs.bridge.GraalNode +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.graaljs.bridge.GraalNode import org.graalvm.polyglot.Value internal fun Node.unwrap(): Value? = when (this) { diff --git a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/player/PlayerContextFactory.kt b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/player/PlayerContextFactory.kt similarity index 91% rename from jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/player/PlayerContextFactory.kt rename to jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/player/PlayerContextFactory.kt index 457c3a07b..2071a3412 100644 --- a/jvm/graaljs/src/main/kotlin/com/intuit/player/jvm/graaljs/player/PlayerContextFactory.kt +++ b/jvm/graaljs/src/main/kotlin/com/intuit/playerui/graaljs/player/PlayerContextFactory.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.graaljs.player +package com.intuit.playerui.graaljs.player import org.graalvm.polyglot.Context diff --git a/jvm/graaljs/src/main/resources/META-INF/services/com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeContainer b/jvm/graaljs/src/main/resources/META-INF/services/com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeContainer deleted file mode 100644 index 0c549014a..000000000 --- a/jvm/graaljs/src/main/resources/META-INF/services/com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeContainer +++ /dev/null @@ -1 +0,0 @@ -com.intuit.player.jvm.graaljs.bridge.runtime.GraalRuntimeContainer diff --git a/jvm/graaljs/src/main/resources/META-INF/services/com.intuit.playeui.core.bridge.runtime.PlayerRuntimeContainer b/jvm/graaljs/src/main/resources/META-INF/services/com.intuit.playeui.core.bridge.runtime.PlayerRuntimeContainer new file mode 100644 index 000000000..e2be9696e --- /dev/null +++ b/jvm/graaljs/src/main/resources/META-INF/services/com.intuit.playeui.core.bridge.runtime.PlayerRuntimeContainer @@ -0,0 +1 @@ +com.intuit.playerui.graaljs.bridge.runtime.GraalRuntimeContainer diff --git a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/base/GraalTest.kt b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/base/GraalTest.kt similarity index 65% rename from jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/base/GraalTest.kt rename to jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/base/GraalTest.kt index 98c345f77..ce386ef70 100644 --- a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/base/GraalTest.kt +++ b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/base/GraalTest.kt @@ -1,16 +1,16 @@ -package com.intuit.player.jvm.graaljs.base +package com.intuit.playerui.graaljs.base -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.graaljs.bridge.runtime.GraalRuntime -import com.intuit.player.jvm.graaljs.bridge.runtime.GraalRuntime.Companion.undefined -import com.intuit.player.jvm.graaljs.bridge.runtime.Runtime -import com.intuit.player.jvm.graaljs.bridge.serialization.format.decodeFromGraalValue -import com.intuit.player.jvm.graaljs.bridge.serialization.format.encodeToGraalValue -import com.intuit.player.jvm.graaljs.extensions.handleValue -import com.intuit.player.jvm.graaljs.player.PlayerContextFactory -import com.intuit.player.jvm.utils.test.PromiseUtils -import com.intuit.player.jvm.utils.test.ThreadUtils +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.graaljs.bridge.runtime.GraalRuntime +import com.intuit.playerui.graaljs.bridge.runtime.GraalRuntime.Companion.undefined +import com.intuit.playerui.graaljs.bridge.runtime.Runtime +import com.intuit.playerui.graaljs.bridge.serialization.format.decodeFromGraalValue +import com.intuit.playerui.graaljs.bridge.serialization.format.encodeToGraalValue +import com.intuit.playerui.graaljs.extensions.handleValue +import com.intuit.playerui.graaljs.player.PlayerContextFactory +import com.intuit.playerui.utils.test.PromiseUtils +import com.intuit.playerui.utils.test.ThreadUtils import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.json.Json @@ -30,8 +30,8 @@ internal abstract class GraalTest(val graal: Context = PlayerContextFactory.buil fun buildV8Object(jsonElement: JsonElement = buildJsonObject {}) = buildV8ObjectFromMap( Json.decodeFromJsonElement( MapSerializer(String.serializer(), GenericSerializer()), - jsonElement - ) + jsonElement, + ), ) fun buildV8ObjectFromMap(map: Map): Value = format.encodeToGraalValue(map) @@ -48,10 +48,9 @@ internal abstract class GraalTest(val graal: Context = PlayerContextFactory.buil fun Value.assertEquivalent(another: Any?) { Assertions.assertTrue( another is Value, - "value to compare is not a Graal Object: $another" + "value to compare is not a Graal Object: $another", ) (another as Value).let { - // verify that all missing keys from another are null or undefined (memberKeys.toSet() - another.memberKeys.toSet()).forEach { missingKey -> val actual = getMember(missingKey) @@ -66,12 +65,14 @@ internal abstract class GraalTest(val graal: Context = PlayerContextFactory.buil if (isNull || !hasMembers() || hasArrayElements()) { if (!canExecute()) Assertions.assertEquals(this.handleValue(format), another.handleValue(format)) - } else memberKeys.forEach { key -> - val (expected, actual) = getMember(key) to another.getMember(key) - if (expected is Value && !expected.isNull) { - expected.assertEquivalent(actual) - } else { - Assertions.assertEquals(expected, actual, "comparing key: $key") + } else { + memberKeys.forEach { key -> + val (expected, actual) = getMember(key) to another.getMember(key) + if (expected is Value && !expected.isNull) { + expected.assertEquivalent(actual) + } else { + Assertions.assertEquals(expected, actual, "comparing key: $key") + } } } } diff --git a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/GraalNodeTest.kt b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/GraalNodeTest.kt similarity index 79% rename from jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/GraalNodeTest.kt rename to jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/GraalNodeTest.kt index f6d5d5035..6a13727a8 100644 --- a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/GraalNodeTest.kt +++ b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/GraalNodeTest.kt @@ -1,14 +1,15 @@ -package com.intuit.player.jvm.graaljs.bridge +package com.intuit.playerui.graaljs.bridge -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.getJson -import com.intuit.player.jvm.core.bridge.toJson -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.graaljs.base.GraalTest -import com.intuit.player.jvm.graaljs.extensions.blockingLock -import com.intuit.player.jvm.graaljs.extensions.handleValue +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.getJson +import com.intuit.playerui.core.bridge.toJson +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.graaljs.base.GraalTest +import com.intuit.playerui.graaljs.extensions.blockingLock +import com.intuit.playerui.graaljs.extensions.handleValue import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.serialization.builtins.serializer @@ -26,18 +27,18 @@ internal class GraalNodeTest : GraalTest() { "string" to "thisisastring", "int" to 1, "object" to mapOf( - "string" to "anotherstring" + "string" to "anotherstring", ), "list" to listOf( 1, "two", mapOf( - "string" to "onemorestring" + "string" to "onemorestring", ), - null + null, ), "function" to Invokable { "classicstring" }, - "null" to null + "null" to null, ) Assertions.assertEquals("thisisastring", node["string"]) @@ -55,7 +56,7 @@ internal class GraalNodeTest : GraalTest() { fun getString() { val node = buildNodeFromMap( "string" to "string", - "notastring" to 1 + "notastring" to 1, ) Assertions.assertEquals("string", node.getString("string")) @@ -68,20 +69,20 @@ internal class GraalNodeTest : GraalTest() { val node = buildNodeFromMap( "function" to Invokable { "classicstring" }, "tuple" to Invokable { (p0, p1) -> listOf(p0, p1) }, - "notafunction" to 1 + "notafunction" to 1, ) - Assertions.assertEquals("classicstring", node.getFunction("function")?.invoke()) - Assertions.assertEquals(listOf("1", 2), node.getFunction("tuple")?.invoke("1", 2)) - Assertions.assertEquals(null, node.getFunction("notafunction")) - Assertions.assertEquals(null, node.getFunction("notthere")) + Assertions.assertEquals("classicstring", node.getInvokable("function")?.invoke()) + Assertions.assertEquals(listOf("1", 2), node.getInvokable("tuple")?.invoke("1", 2)) + Assertions.assertEquals(null, node.getInvokable("notafunction")) + Assertions.assertEquals(null, node.getInvokable("notthere")) } @Test fun getList() { val node = buildNodeFromMap( "list" to listOf(1, 2, 3), - "notalist" to 1 + "notalist" to 1, ) Assertions.assertEquals(listOf(1, 2, 3), node.getList("list")) @@ -93,9 +94,9 @@ internal class GraalNodeTest : GraalTest() { fun getObject() { val node = buildNodeFromMap( "object" to mapOf( - "string" to "thisisastring" + "string" to "thisisastring", ), - "notaobject" to 1234 + "notaobject" to 1234, ) Assertions.assertEquals("thisisastring", node.getObject("object")?.getString("string")) @@ -109,8 +110,8 @@ internal class GraalNodeTest : GraalTest() { val node = buildNodeFromMap( "asset" to mapOf( "id" to "testId", - "type" to "testType" - ) + "type" to "testType", + ), ) val (id, type) = node.getObject("asset") as Asset @@ -124,14 +125,14 @@ internal class GraalNodeTest : GraalTest() { "assets" to listOf( mapOf( "id" to "testId1", - "type" to "testType" + "type" to "testType", ), mapOf( - "id" to "notAnAsset" + "id" to "notAnAsset", ), - 1 + 1, ), - "notassets" to "justastring" + "notassets" to "justastring", ) val assets = node.getList("assets") as List<*> @@ -153,7 +154,7 @@ internal class GraalNodeTest : GraalTest() { fun getInt() { val node = buildNodeFromMap( "int" to 1, - "notanint" to "asdf" + "notanint" to "asdf", ) Assertions.assertEquals(1, node.getInt("int")) @@ -164,7 +165,7 @@ internal class GraalNodeTest : GraalTest() { @Test fun getJson() { val node = buildNodeFromMap( - "beacon" to mapOf("key" to "value") + "beacon" to mapOf("key" to "value"), ) Assertions.assertEquals(JsonNull, node.getJson("notthere")) Assertions.assertEquals(buildJsonObject { put("key", "value") }, node.getJson("beacon")) @@ -185,17 +186,17 @@ internal class GraalNodeTest : GraalTest() { "beacon", buildJsonObject { put("key", "value") - } + }, ) }, - node.toJson() + node.toJson(), ) } @Test fun getBoolean() { val node = buildNodeFromMap( - "isSelected" to true + "isSelected" to true, ) Assertions.assertEquals(true, node.getBoolean("isSelected")) Assertions.assertNull(node.getBoolean("notthere")) @@ -207,18 +208,18 @@ internal class GraalNodeTest : GraalTest() { "string" to "thisisastring", "int" to 1, "object" to mapOf( - "string" to "anotherstring" + "string" to "anotherstring", ), "list" to listOf( 1, "two", mapOf( - "string" to "onemorestring" + "string" to "onemorestring", ), - null + null, ), "function" to Invokable { "classicstring" }, - "null" to null + "null" to null, ) addThreads( @@ -251,7 +252,7 @@ internal class GraalNodeTest : GraalTest() { Assertions.assertEquals(1, node.getInt("int")) } Assertions.assertEquals("thisisastring", node.get("string")) - } + }, ) startThreads() verifyThreads() @@ -260,7 +261,7 @@ internal class GraalNodeTest : GraalTest() { @Test fun getSerializablePrimitive() { val node = buildNodeFromMap( - "number" to 9 + "number" to 9, ) Assertions.assertEquals(9, node.getSerializable("number", Int.serializer())) } @@ -269,8 +270,8 @@ internal class GraalNodeTest : GraalTest() { fun getSerializable() { val node = buildNodeFromMap( "flow" to mapOf( - "id" to "testId" - ) + "id" to "testId", + ), ) Assertions.assertEquals("testId", node.getSerializable("flow", Flow.serializer())?.id) } diff --git a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/format/GraalFormatTest.kt b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/format/GraalFormatTest.kt similarity index 86% rename from jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/format/GraalFormatTest.kt rename to jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/format/GraalFormatTest.kt index 460fa09c2..fdd85147a 100644 --- a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/format/GraalFormatTest.kt +++ b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/format/GraalFormatTest.kt @@ -1,11 +1,12 @@ -package com.intuit.player.jvm.graaljs.bridge.format - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.graaljs.base.GraalTest -import com.intuit.player.jvm.graaljs.bridge.serialization.format.encodeToGraalValue -import com.intuit.player.jvm.graaljs.extensions.blockingLock +package com.intuit.playerui.graaljs.bridge.format + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.graaljs.base.GraalTest +import com.intuit.playerui.graaljs.bridge.serialization.format.encodeToGraalValue +import com.intuit.playerui.graaljs.extensions.blockingLock import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer @@ -86,7 +87,7 @@ internal class GraalFormatTest : GraalTest() { val simple = format.decodeFromRuntimeValue( Simple.serializer(), - graalObject + graalObject, ) Assertions.assertEquals(3, simple.one) @@ -96,7 +97,7 @@ internal class GraalFormatTest : GraalTest() { @Test fun `decode into Node backed serializable`() = format.context.blockingLock { data class Simple(override val node: Node) : NodeWrapper { - fun increment(value: Int) = node.getFunction("increment")!!(value) + fun increment(value: Int) = node.getInvokable("increment")!!(value) } val graalObject = format.context.eval("js", "new Object()") @@ -104,12 +105,12 @@ internal class GraalFormatTest : GraalTest() { "increment", ProxyExecutable { args -> args[0].asInt() + 1 - } + }, ) val simple = format.decodeFromRuntimeValue( NodeWrapperSerializer(::Simple), - graalObject + graalObject, ) Assertions.assertEquals(1, simple.increment(0)) @@ -129,12 +130,12 @@ internal class GraalFormatTest : GraalTest() { "increment", ProxyExecutable { args -> args[0].asInt() + 1 - } + }, ) val simple = format.decodeFromRuntimeValue( Data.serializer(), - graalObject + graalObject, ) Assertions.assertEquals(3, simple.one) diff --git a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/DecodingTest.kt b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/serialization/DecodingTest.kt similarity index 85% rename from jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/DecodingTest.kt rename to jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/serialization/DecodingTest.kt index 1c01b9ff5..4ea651982 100644 --- a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/DecodingTest.kt +++ b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/serialization/DecodingTest.kt @@ -1,9 +1,9 @@ -package com.intuit.player.jvm.graaljs.bridge.serialization +package com.intuit.playerui.graaljs.bridge.serialization -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.graaljs.base.GraalTest -import com.intuit.player.jvm.graaljs.bridge.serialization.format.decodeFromGraalValue -import com.intuit.player.jvm.graaljs.extensions.blockingLock +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.graaljs.base.GraalTest +import com.intuit.playerui.graaljs.bridge.serialization.format.decodeFromGraalValue +import com.intuit.playerui.graaljs.extensions.blockingLock import kotlinx.serialization.Serializable import org.graalvm.polyglot.proxy.ProxyExecutable import org.junit.jupiter.api.Assertions @@ -55,7 +55,7 @@ internal class FunctionDecoding : GraalTest() { Assertions.assertEquals("PLAYER: 1", function.execute(eval("js", "'PLAYER'"), eval("js", "1"))) Assertions.assertEquals( "PLAYER: 2", - format.decodeFromGraalValue>(graalObject.getMember("method"))("PLAYER", 2) + format.decodeFromGraalValue>(graalObject.getMember("method"))("PLAYER", 2), ) } @@ -70,14 +70,14 @@ internal class FunctionDecoding : GraalTest() { Assertions.assertEquals("PLAYER: 1", function.execute(eval("js", "'PLAYER'"), eval("js", "1"))) Assertions.assertEquals( "PLAYER: 2", - format.decodeFromGraalValue>(graalObject.getMember("method"))("PLAYER", 2) + format.decodeFromGraalValue>(graalObject.getMember("method"))("PLAYER", 2), ) } @Test fun `decode kcallable`() = format.context.blockingLock { @Serializable data class Container( - val method: (String, Int) -> String + val method: (String, Int) -> String, ) val function = ProxyExecutable { @@ -90,8 +90,8 @@ internal class FunctionDecoding : GraalTest() { format.decodeFromGraalValue( eval("js", "new Object()").also { it.putMember("method", function) - } - ).method("PLAYER", 2) + }, + ).method("PLAYER", 2), ) } } diff --git a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/EncodingTest.kt b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/serialization/EncodingTest.kt similarity index 87% rename from jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/EncodingTest.kt rename to jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/serialization/EncodingTest.kt index 327198441..c739cf9b2 100644 --- a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/EncodingTest.kt +++ b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/serialization/EncodingTest.kt @@ -1,9 +1,9 @@ -package com.intuit.player.jvm.graaljs.bridge.serialization +package com.intuit.playerui.graaljs.bridge.serialization -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.graaljs.base.GraalTest -import com.intuit.player.jvm.graaljs.bridge.serialization.format.encodeToGraalValue -import com.intuit.player.jvm.graaljs.extensions.blockingLock +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.graaljs.base.GraalTest +import com.intuit.playerui.graaljs.bridge.serialization.format.encodeToGraalValue +import com.intuit.playerui.graaljs.extensions.blockingLock import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @@ -53,7 +53,7 @@ internal class FunctionEncoding : GraalTest() { Assertions.assertEquals("PLAYER: 1", callback("PLAYER", 1)) Assertions.assertEquals( "PLAYER: 2", - format.encodeToGraalValue(callback).execute("PLAYER", 2).asString() + format.encodeToGraalValue(callback).execute("PLAYER", 2).asString(), ) } @@ -63,7 +63,7 @@ internal class FunctionEncoding : GraalTest() { Assertions.assertEquals("PLAYER: 1", callback("PLAYER", 1)) Assertions.assertEquals( "PLAYER: 2", - format.encodeToGraalValue(callback).execute("PLAYER", 2).asString() + format.encodeToGraalValue(callback).execute("PLAYER", 2).asString(), ) } @@ -77,7 +77,7 @@ internal class FunctionEncoding : GraalTest() { Assertions.assertEquals("PLAYER: 1", callback("PLAYER", 1)) Assertions.assertEquals( "PLAYER: 2", - format.encodeToGraalValue(callback).execute("PLAYER", 2).asString() + format.encodeToGraalValue(callback).execute("PLAYER", 2).asString(), ) } } diff --git a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/JsonEncodingTests.kt b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/serialization/JsonEncodingTests.kt similarity index 83% rename from jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/JsonEncodingTests.kt rename to jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/serialization/JsonEncodingTests.kt index 2102ad32d..f6f832b81 100644 --- a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/bridge/serialization/JsonEncodingTests.kt +++ b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/bridge/serialization/JsonEncodingTests.kt @@ -1,10 +1,10 @@ -package com.intuit.player.jvm.graaljs.bridge.serialization +package com.intuit.playerui.graaljs.bridge.serialization -import com.intuit.player.jvm.core.flow.FlowResult -import com.intuit.player.jvm.graaljs.base.GraalTest -import com.intuit.player.jvm.graaljs.bridge.GraalNode -import com.intuit.player.jvm.graaljs.bridge.serialization.format.encodeToGraalValue -import com.intuit.player.jvm.graaljs.extensions.blockingLock +import com.intuit.playerui.core.flow.FlowResult +import com.intuit.playerui.graaljs.base.GraalTest +import com.intuit.playerui.graaljs.bridge.GraalNode +import com.intuit.playerui.graaljs.bridge.serialization.format.encodeToGraalValue +import com.intuit.playerui.graaljs.extensions.blockingLock import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement @@ -21,7 +21,7 @@ internal class JsonEncodingTests : GraalTest() { buildJsonObject { put("state_type", "END") put("outcome", "doneWithTopic") - } + }, ) } private val expectedJsonString = expectedJson.toString() diff --git a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/promise/GraalPromiseTest.kt b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/promise/GraalPromiseTest.kt similarity index 70% rename from jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/promise/GraalPromiseTest.kt rename to jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/promise/GraalPromiseTest.kt index 865b8c1f0..f04a5da9d 100644 --- a/jvm/graaljs/src/test/kotlin/com/intuit/player/jvm/graaljs/promise/GraalPromiseTest.kt +++ b/jvm/graaljs/src/test/kotlin/com/intuit/playerui/graaljs/promise/GraalPromiseTest.kt @@ -1,9 +1,9 @@ -package com.intuit.player.jvm.graaljs.promise +package com.intuit.playerui.graaljs.promise -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.Promise -import com.intuit.player.jvm.graaljs.base.GraalTest -import com.intuit.player.jvm.utils.test.PromiseUtils +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.Promise +import com.intuit.playerui.graaljs.base.GraalTest +import com.intuit.playerui.utils.test.PromiseUtils import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -21,7 +21,7 @@ internal class GraalPromiseTest : GraalTest(), PromiseUtils { const promise = new Promise(function(resolve, reject) { asdf.asdf.asdf.asdf }); return [promise, resolver]; })(); - """.trimIndent() + """.trimIndent(), ) as List<*> Promise(promise as Node).thenRecord.catchRecord @@ -33,13 +33,13 @@ internal class GraalPromiseTest : GraalTest(), PromiseUtils { val exception = catchChain[0] as Throwable assertEquals( - """com.intuit.player.jvm.core.bridge.JSErrorException: ReferenceError: asdf is not defined + """com.intuit.playerui.core.bridge.JSErrorException: ReferenceError: asdf is not defined at .promise(Unnamed:3) at .new Promise(Native Method) at .(Unnamed:3) at .(Unnamed:1) """, - exception.stackTraceToString() + exception.stackTraceToString(), ) } } diff --git a/jvm/j2v8/BUILD b/jvm/j2v8/BUILD index e5b6fa109..b9aa28068 100644 --- a/jvm/j2v8/BUILD +++ b/jvm/j2v8/BUILD @@ -10,13 +10,16 @@ kt_player_module( main_exports = main_exports, main_resources = main_resources + glob(["src/main/resources/**"]), test_deps = test_deps, - test_package = "com.intuit.player.jvm.j2v8", + test_package = "com.intuit.playerui.j2v8", ) j2v8_platform("macos") j2v8_platform("linux") +# TODO: These should probably be AARs? j2v8_platform("android") +j2v8_platform("android-debug") + j2v8_platform("all") diff --git a/jvm/j2v8/api/j2v8.api b/jvm/j2v8/api/j2v8.api index faad36e9b..e8cef6c68 100644 --- a/jvm/j2v8/api/j2v8.api +++ b/jvm/j2v8/api/j2v8.api @@ -29,8 +29,15 @@ public final class com/intuit/player/jvm/j2v8/bridge/runtime/J2V8RuntimeContaine } public final class com/intuit/player/jvm/j2v8/bridge/runtime/V8RuntimeKt { + public static final fun Runtime ()Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; + public static final fun Runtime (Lcom/eclipsesource/v8/V8;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; public static final fun Runtime (Lcom/eclipsesource/v8/V8;Lcom/intuit/player/jvm/j2v8/bridge/runtime/J2V8RuntimeConfig;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; + public static final fun Runtime (Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; + public static final fun Runtime (Ljava/lang/String;Ljava/lang/String;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; + public static final fun Runtime (Ljava/lang/String;Ljava/nio/file/Path;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; public static synthetic fun Runtime$default (Lcom/eclipsesource/v8/V8;Lcom/intuit/player/jvm/j2v8/bridge/runtime/J2V8RuntimeConfig;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; + public static synthetic fun Runtime$default (Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; + public static synthetic fun Runtime$default (Ljava/lang/String;Ljava/nio/file/Path;ILjava/lang/Object;)Lcom/intuit/player/jvm/core/bridge/runtime/Runtime; } public final class com/intuit/player/jvm/j2v8/bridge/serialization/format/BuildersKt { diff --git a/jvm/j2v8/build.bzl b/jvm/j2v8/build.bzl index e353ad225..d414c5800 100644 --- a/jvm/j2v8/build.bzl +++ b/jvm/j2v8/build.bzl @@ -1,17 +1,22 @@ -load("//jvm:build.bzl", "distribution") +load("//jvm:build.bzl", "DEFAULT_GROUP", "distribution") +load("@build_constants//:constants.bzl", "VERSION") deps = { "macos": ["//jvm/j2v8/libs:j2v8_macos"], "linux": ["//jvm/j2v8/libs:j2v8_linux"], - "android": ["@android_j2v8//aar"], + "android": ["@maven//:com_eclipsesource_j2v8_j2v8"], + "android-debug": [ + "//jvm/j2v8:j2v8-android", + "@maven//:com_github_AlexTrotsenko_j2v8_debugger", + ], "all": [ "//jvm/j2v8:j2v8-macos", "//jvm/j2v8:j2v8-linux", "//jvm/j2v8:j2v8-android", - ] + ], } -def j2v8_platform(platform): +def j2v8_platform(platform, group = DEFAULT_GROUP, version = VERSION): if platform not in deps: fail("platform must be defined in " + deps.keys()) @@ -19,10 +24,11 @@ def j2v8_platform(platform): native.java_library( name = name, exports = [":j2v8"] + deps[platform], - tags = ["maven_coordinates=%s:%s:{pom_version}" % ("com.intuit.player", name)], + tags = ["maven_coordinates=%s:%s:%s" % (group, name, version)], visibility = ["//visibility:public"], ) distribution( name = name, + maven_coordinates = "%s:%s:%s" % (group, name, version), ) diff --git a/jvm/j2v8/deps.bzl b/jvm/j2v8/deps.bzl index 1eed9a99f..749604221 100644 --- a/jvm/j2v8/deps.bzl +++ b/jvm/j2v8/deps.bzl @@ -1,18 +1,25 @@ load("//jvm/dependencies:versions.bzl", "versions") +load("@rules_player//maven:parse_coordinates.bzl", "parse_coordinates") -maven = [] +maven_main = [] + +maven = maven_main + [ + "com.github.AlexTrotsenko:j2v8-debugger:%s" % versions.j2v8.debugger, +] main_exports = [ "//jvm/core", ] -main_deps = main_exports + [ +main_deps = main_exports + parse_coordinates(maven_main) + [ "//jvm:kotlin_serialization", - "//jvm/j2v8/libs:j2v8_empty" + "//jvm/j2v8/libs:j2v8_empty_compile_only", + "//jvm/j2v8/libs:j2v8_debugger_compile_only", ] +# TODO: These should probably just be dependencies of headless main_resources = [ - "//core/player:Player_Bundles" + "//core/player:Player_Bundles", ] test_deps = [ diff --git a/jvm/j2v8/libs/BUILD b/jvm/j2v8/libs/BUILD index ca87885b0..27f1cb409 100644 --- a/jvm/j2v8/libs/BUILD +++ b/jvm/j2v8/libs/BUILD @@ -1,20 +1,29 @@ package(default_visibility = ["//jvm/j2v8:__pkg__"]) java_import( - name = "j2v8_empty", + name = "j2v8_empty_compile_only", jars = [":j2v8_empty-6.1.0.jar"], - neverlink = True + neverlink = True, + tags = ["maven:compile-only"], ) java_import( name = "j2v8_macos", jars = [ ":j2v8_macos_x86_64-6.1.0.jar", - ":j2v8_macos_aarch_64-6.1.0.jar" + ":j2v8_macos_aarch_64-6.1.0.jar", ], ) java_import( name = "j2v8_linux", jars = [":j2v8_linux_x86_64-6.1.0.jar"], -) \ No newline at end of file +) + +java_import( + name = "j2v8_debugger_compile_only", + jars = [], + neverlink = True, + tags = ["maven:compile-only"], + deps = ["@maven//:com_github_AlexTrotsenko_j2v8_debugger"], +) diff --git a/jvm/j2v8/src/main/kotlin/com/alexii/j2v8debugger/ScriptSourceProvider.kt b/jvm/j2v8/src/main/kotlin/com/alexii/j2v8debugger/ScriptSourceProvider.kt new file mode 100644 index 000000000..3fc02bfe0 --- /dev/null +++ b/jvm/j2v8/src/main/kotlin/com/alexii/j2v8debugger/ScriptSourceProvider.kt @@ -0,0 +1,8 @@ +package com.alexii.j2v8debugger + +/** Redundant declaration of [ScriptSourceProvider] to ensure it exists on classpath at runtime */ +public interface ScriptSourceProvider { + public val allScriptIds: Collection + + public fun getSource(scriptId: String): String +} diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/runtime/V8Runtime.kt b/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/runtime/V8Runtime.kt deleted file mode 100644 index 89697fc4e..000000000 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/runtime/V8Runtime.kt +++ /dev/null @@ -1,124 +0,0 @@ -package com.intuit.player.jvm.j2v8.bridge.runtime - -import com.eclipsesource.v8.V8 -import com.eclipsesource.v8.V8Array -import com.eclipsesource.v8.V8Object -import com.eclipsesource.v8.V8Value -import com.eclipsesource.v8.utils.MemoryManager -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.runtime.* -import com.intuit.player.jvm.core.bridge.serialization.serializers.playerSerializersModule -import com.intuit.player.jvm.j2v8.V8Null -import com.intuit.player.jvm.j2v8.V8Primitive -import com.intuit.player.jvm.j2v8.addPrimitive -import com.intuit.player.jvm.j2v8.bridge.V8Node -import com.intuit.player.jvm.j2v8.bridge.serialization.format.J2V8Format -import com.intuit.player.jvm.j2v8.bridge.serialization.format.J2V8FormatConfiguration -import com.intuit.player.jvm.j2v8.bridge.serialization.serializers.V8ValueSerializer -import com.intuit.player.jvm.j2v8.extensions.blockingLock -import com.intuit.player.jvm.j2v8.extensions.handleValue -import com.intuit.player.jvm.j2v8.extensions.unlock -import kotlinx.coroutines.* -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.plus - -public fun Runtime(runtime: V8, config: J2V8RuntimeConfig = J2V8RuntimeConfig(runtime)): Runtime = - V8Runtime(config) - -internal class V8Runtime(private val config: J2V8RuntimeConfig) : Runtime { - - val v8: V8 by config::runtime - - override val format: J2V8Format = J2V8Format( - J2V8FormatConfiguration( - this, - playerSerializersModule + SerializersModule { - contextual(V8Value::class, V8ValueSerializer) - contextual(V8Object::class, V8ValueSerializer.conform()) - contextual(V8Array::class, V8ValueSerializer.conform()) - contextual(V8Primitive::class, V8ValueSerializer.conform()) - contextual(V8Null::class, V8ValueSerializer.conform()) - } - ) - ) - - private val memoryScope: MemoryManager = MemoryManager(config.runtime) - - override val scope: CoroutineScope by lazy { - CoroutineScope(Dispatchers.Default + SupervisorJob()) - } - - override fun execute(script: String): Any? = v8.blockingLock { - executeScript(script).handleValue(format) - } - - override fun add(name: String, value: V8Value) { - v8.blockingLock { - when (value) { - is V8Primitive -> addPrimitive(name, value) - else -> add(name, value) - } - } - } - - override fun serialize(serializer: SerializationStrategy, value: T): Any? = v8.blockingLock { - format.encodeToRuntimeValue(serializer, value).handleValue(format) - } - - override fun release() { - v8.blockingLock { - scope.cancel() - memoryScope.release() - runtime.release(true) - } - } - - override fun toString() = "J2V8" - - // Delegated Node members - private val backingNode: Node = V8Node(v8, this) - - override val runtime: V8Runtime = this - override val entries: Set> by backingNode::entries - override val keys: Set by backingNode::keys - override val size: Int by backingNode::size - override val values: Collection by backingNode::values - override fun containsKey(key: String): Boolean = backingNode.containsKey(key) - override fun containsValue(value: Any?): Boolean = backingNode.containsValue(value) - override fun get(key: String): Any? = backingNode[key] - override fun isEmpty(): Boolean = backingNode.isEmpty() - override fun getSerializable(key: String, deserializer: DeserializationStrategy): T? = - backingNode.getSerializable(key, deserializer) - override fun deserialize(deserializer: DeserializationStrategy): T = backingNode.deserialize(deserializer) - override fun isReleased(): Boolean = backingNode.isReleased() - override fun isUndefined(): Boolean = backingNode.isUndefined() - override fun nativeReferenceEquals(other: Any?): Boolean = backingNode.nativeReferenceEquals(other) - override fun getString(key: String): String? = backingNode.getString(key) - override fun getInt(key: String): Int? = backingNode.getInt(key) - override fun getDouble(key: String): Double? = backingNode.getDouble(key) - override fun getLong(key: String): Long? = backingNode.getLong(key) - override fun getBoolean(key: String): Boolean? = backingNode.getBoolean(key) - override fun getFunction(key: String): Invokable? = backingNode.getFunction(key) - override fun getList(key: String): List<*>? = backingNode.getList(key) - override fun getObject(key: String): Node? = backingNode.getObject(key) -} - -public object J2V8 : PlayerRuntimeFactory { - override fun create(block: J2V8RuntimeConfig.() -> Unit): Runtime = - V8Runtime(J2V8RuntimeConfig().apply(block)) - - override fun toString(): String = "J2V8" -} - -public data class J2V8RuntimeConfig( - var runtime: V8 = V8.createV8Runtime().unlock(), -) : PlayerRuntimeConfig() - -public class J2V8RuntimeContainer : PlayerRuntimeContainer { - override val factory: PlayerRuntimeFactory<*> = J2V8 - - override fun toString(): String = "J2V8" -} diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/handleValue.kt b/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/handleValue.kt deleted file mode 100644 index 628b600fc..000000000 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/handleValue.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.intuit.player.jvm.j2v8.extensions - -import com.eclipsesource.v8.* -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.j2v8.V8Primitive -import com.intuit.player.jvm.j2v8.bridge.V8Node -import com.intuit.player.jvm.j2v8.v8Array -import kotlinx.serialization.builtins.ArraySerializer - -internal fun Any?.handleValue(format: RuntimeFormat): Any? = when (this) { - is V8Primitive -> value - is V8Value -> transform(format) - else -> this -} - -private fun V8Value.transform(format: RuntimeFormat): Any? = lockIfDefined { - when (this) { - V8.getUndefined() -> null - is V8Primitive -> value - is V8Function -> toInvokable(format, this) - is V8Array -> toList(format) - is V8Object -> toNode(format) - else -> null - } -} - -internal fun V8Array.toList(format: RuntimeFormat): List? = if (isUndefined) null else lockIfDefined { - keys.map(::get).map { it.handleValue(format) } -} - -internal fun V8Object.toNode(format: RuntimeFormat): Node? = if (isUndefined) null else lockIfDefined { - if (contains("id") && contains("type")) - Asset(V8Node(this, format.runtime)) - else V8Node(this, format.runtime) -} - -internal fun V8Function.toInvokable(format: RuntimeFormat, receiver: V8Object): Invokable? = if (isUndefined) null else lockIfDefined { - Invokable { args -> - blockingLock { - try { - call( - receiver, - format.encodeToRuntimeValue( - ArraySerializer(GenericSerializer()), - args as Array - ).v8Array - ).handleValue(format) as R - } catch (e: Throwable) { - e.printStackTrace() - throw e - } - } - } -} diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/invoke.kt b/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/invoke.kt deleted file mode 100644 index b7f076171..000000000 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/invoke.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.intuit.player.jvm.j2v8.extensions - -import com.eclipsesource.v8.V8Array -import com.eclipsesource.v8.V8Function -import com.intuit.player.jvm.j2v8.bridge.serialization.format.J2V8Format - -internal operator fun V8Function.invoke(): Any? = - call(runtime, runtime.blockingLock(::V8Array)) - -internal operator fun V8Function.invoke(format: J2V8Format, vararg args: Any?): Any? = - call(runtime, format.args(*args)) diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/lock.kt b/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/lock.kt deleted file mode 100644 index b8d4041c0..000000000 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/lock.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.intuit.player.jvm.j2v8.extensions - -import com.eclipsesource.v8.V8Value -import com.intuit.player.jvm.j2v8.bridge.runtime.PlayerRuntimeException -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout - -/** - * Wait for lock and execute [block]. After execution, or on exception, the lock will be released - * unless the lock was already acquired within the calling context. To prevent deadlock, this - * lock will only wait for [timeout] milliseconds, defaulting to 5000. - * - * NOTE: Suspend methods are non-blocking by convention. - */ -internal suspend fun Context.lock(timeout: Long = 5000, block: suspend Context.() -> T): T = withTimeout(timeout) { - if (isReleased) throw PlayerRuntimeException(runtime, "Runtime object has been released!") - val alreadyHadLock = runtime.locker.hasLock() - try { - while (!runtime.locker.tryAcquire()) delay(50) - block() - } finally { - if (!alreadyHadLock && runtime.locker.hasLock()) runtime.locker.release() - } -} - -/** - * Blocking method to execute [block]. If the calling context does not have the lock - * then the lock will attempt to be acquired through delegation to [lock]. This - * will continue to block the calling thread. - */ -internal fun Context.blockingLock(block: Context.() -> T): T = when { - isReleased -> throw PlayerRuntimeException(runtime, "Runtime object has been released!") - runtime.locker.hasLock() -> block() - else -> runBlocking { - lock { block() } - } -} - -/** Special [blockingLock] helper to guard against undefined values */ -internal fun Context.lockIfDefined( - block: Context.() -> T -): T? = mapUndefinedToNull()?.let { blockingLock(block) } diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/V8Primitive.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/V8Primitive.kt similarity index 81% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/V8Primitive.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/V8Primitive.kt index 7aaae1eb4..25a9e1fb3 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/V8Primitive.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/V8Primitive.kt @@ -1,9 +1,14 @@ -package com.intuit.player.jvm.j2v8 - -import com.eclipsesource.v8.* -import com.intuit.player.jvm.j2v8.bridge.serialization.format.J2V8Format -import com.intuit.player.jvm.j2v8.bridge.serialization.format.encodeToV8Value -import com.intuit.player.jvm.j2v8.extensions.blockingLock +package com.intuit.playerui.j2v8 + +import com.eclipsesource.v8.V8 +import com.eclipsesource.v8.V8Array +import com.eclipsesource.v8.V8Function +import com.eclipsesource.v8.V8Object +import com.eclipsesource.v8.V8Value +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.j2v8.bridge.serialization.format.J2V8Format +import com.intuit.playerui.j2v8.bridge.serialization.format.encodeToV8Value +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking /** * Primitive wrapper for [V8Value]s. The intent behind this construct is to enable the @@ -53,8 +58,12 @@ internal val Context.v8Function: V8Function get() = this as? ?: throw IllegalArgumentException("Element ${this::class} is not a V8Function") // [get] helpers for wrapping primitive values -internal fun V8Object.getV8Value(key: String): V8Value = blockingLock { get(key) }.let(::V8Value) -internal fun V8Array.getV8Value(index: Int): V8Value = blockingLock { get(index) }.let(::V8Value) +internal fun V8Object.getV8Value(runtime: Runtime, key: String): V8Value = evaluateInJSThreadBlocking(runtime) { + get(key).let(::V8Value) +} +internal fun V8Array.getV8Value(runtime: Runtime, index: Int): V8Value = evaluateInJSThreadBlocking(runtime) { + get(index).let(::V8Value) +} internal fun V8Value(content: Any?): V8Value = when (content) { is V8Value -> content @@ -87,9 +96,9 @@ internal fun Context.V8Array(block: V8Array.() -> Unit = {}) * This _should_ be the main entry point for creating [V8Function]s within this module b/c it takes into account * runtime locking and ensuring that the return value can be appropriately handled by J2V8 */ -internal inline fun V8Function(format: J2V8Format, crossinline block: V8Object.(args: V8Array) -> T): V8Function = format.v8.blockingLock { +internal inline fun V8Function(format: J2V8Format, crossinline block: V8Object.(args: V8Array) -> T): V8Function = format.v8.evaluateInJSThreadBlocking(format.runtime) { V8Function(this) { receiver, args -> - receiver.blockingLock { + receiver.evaluateInJSThreadBlocking(format.runtime) { when (val retVal = format.encodeToV8Value(block(args))) { is V8Primitive -> retVal.value else -> retVal diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/V8Node.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/V8Node.kt similarity index 51% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/V8Node.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/V8Node.kt index ffc2d747f..7d78091dd 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/V8Node.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/V8Node.kt @@ -1,11 +1,23 @@ -package com.intuit.player.jvm.j2v8.bridge - -import com.eclipsesource.v8.* -import com.intuit.player.jvm.core.bridge.* -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat -import com.intuit.player.jvm.j2v8.extensions.* -import com.intuit.player.jvm.j2v8.getV8Value +package com.intuit.playerui.j2v8.bridge + +import com.eclipsesource.v8.V8 +import com.eclipsesource.v8.V8Array +import com.eclipsesource.v8.V8Function +import com.eclipsesource.v8.V8Object +import com.eclipsesource.v8.V8Value +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadIfDefinedBlocking +import com.intuit.playerui.j2v8.extensions.handleValue +import com.intuit.playerui.j2v8.extensions.mapUndefinedToNull +import com.intuit.playerui.j2v8.extensions.toInvokable +import com.intuit.playerui.j2v8.extensions.toList +import com.intuit.playerui.j2v8.extensions.toNode +import com.intuit.playerui.j2v8.getV8Value import kotlinx.serialization.DeserializationStrategy /** Pseudo constructor to create a [Node] from a [V8Object] */ @@ -16,7 +28,7 @@ internal class V8Node(override val v8Object: V8Object, override val runtime: Run override val format: RuntimeFormat get() = runtime.format override val keys: Set by lazy { - v8Object.lockIfDefined { + v8Object.evaluateInJSThreadIfDefinedBlocking(runtime) { keys.filter { v8Object.get(it) != V8.getUndefined() }.toSet() } ?: emptySet() } @@ -40,26 +52,30 @@ internal class V8Node(override val v8Object: V8Object, override val runtime: Run override fun isEmpty(): Boolean = size == 0 // Getter APIs - override operator fun get(key: String): Any? = v8Object.lockIfDefined { + override operator fun get(key: String): Any? = v8Object.evaluateInJSThreadIfDefinedBlocking(runtime) { get(key).handleValue(format) } - override fun getFunction(key: String): Invokable? = v8Object.lockIfDefined { + override fun getInvokable(key: String, deserializationStrategy: DeserializationStrategy): Invokable? = v8Object.evaluateInJSThreadIfDefinedBlocking(runtime) { get(key) as? V8Function - }?.toInvokable(format, v8Object) + }?.toInvokable(format, v8Object, deserializationStrategy) - override fun getList(key: String): List<*>? = v8Object.lockIfDefined { + override fun getFunction(key: String): Invokable? = v8Object.evaluateInJSThreadIfDefinedBlocking(runtime) { + get(key) as? V8Function + }?.toInvokable(format, v8Object, null) + + override fun getList(key: String): List<*>? = v8Object.evaluateInJSThreadIfDefinedBlocking(runtime) { get(key) as? V8Array }?.toList(format) - override fun getObject(key: String): Node? = v8Object.lockIfDefined { + override fun getObject(key: String): Node? = v8Object.evaluateInJSThreadIfDefinedBlocking(runtime) { get(key) as? V8Object }?.toNode(format) - override fun getSerializable(key: String, deserializer: DeserializationStrategy): T? = v8Object.blockingLock { - if (keys.contains(key)) - format.decodeFromRuntimeValue(deserializer, getV8Value(key)) - else null + override fun getSerializable(key: String, deserializer: DeserializationStrategy): T? = v8Object.evaluateInJSThreadBlocking(runtime) { + getV8Value(this@V8Node.runtime, key).mapUndefinedToNull()?.let { + format.decodeFromRuntimeValue(deserializer, it) + } } override fun deserialize(deserializer: DeserializationStrategy): T = format.decodeFromRuntimeValue(deserializer, v8Object) @@ -71,7 +87,7 @@ internal class V8Node(override val v8Object: V8Object, override val runtime: Run override fun nativeReferenceEquals(other: Any?): Boolean = when (other) { is NodeWrapper -> nativeReferenceEquals(other.node) is V8ObjectWrapper -> nativeReferenceEquals(other.v8Object) - is V8Object -> v8Object.blockingLock { + is V8Object -> v8Object.evaluateInJSThreadBlocking(runtime) { v8Object.strictEquals(other) } else -> false @@ -87,9 +103,9 @@ internal class V8Node(override val v8Object: V8Object, override val runtime: Run else -> false } - override fun hashCode(): Int = v8Object.blockingLock { hashCode() } + override fun hashCode(): Int = v8Object.evaluateInJSThreadBlocking(runtime) { hashCode() } - override fun toString(): String = v8Object.lockIfDefined { + override fun toString(): String = v8Object.evaluateInJSThreadIfDefinedBlocking(runtime) { keys.associate { it to get(it) }.toString() } ?: emptyMap().toString() } diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/V8ObjectWrapper.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/V8ObjectWrapper.kt similarity index 89% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/V8ObjectWrapper.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/V8ObjectWrapper.kt index 6641420f5..64f1423c0 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/V8ObjectWrapper.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/V8ObjectWrapper.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.j2v8.bridge +package com.intuit.playerui.j2v8.bridge import com.eclipsesource.v8.V8Object diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/runtime/PlayerRuntimeException.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/runtime/PlayerRuntimeException.kt similarity index 57% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/runtime/PlayerRuntimeException.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/runtime/PlayerRuntimeException.kt index 3206aaa62..cf1039837 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/runtime/PlayerRuntimeException.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/runtime/PlayerRuntimeException.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.j2v8.bridge.runtime +package com.intuit.playerui.j2v8.bridge.runtime import com.eclipsesource.v8.V8 -import com.intuit.player.jvm.core.bridge.PlayerRuntimeException +import com.intuit.playerui.core.bridge.PlayerRuntimeException internal fun PlayerRuntimeException(runtime: V8, message: String) = PlayerRuntimeException(runtime.let(::Runtime), message) diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/runtime/V8Runtime.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/runtime/V8Runtime.kt new file mode 100644 index 000000000..49698a7d0 --- /dev/null +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/runtime/V8Runtime.kt @@ -0,0 +1,221 @@ +package com.intuit.playerui.j2v8.bridge.runtime + +import com.alexii.j2v8debugger.ScriptSourceProvider +import com.eclipsesource.v8.V8 +import com.eclipsesource.v8.V8Array +import com.eclipsesource.v8.V8Object +import com.eclipsesource.v8.V8Value +import com.eclipsesource.v8.utils.MemoryManager +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.PlayerRuntimeException +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeConfig +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeContainer +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeFactory +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.ScriptContext +import com.intuit.playerui.core.bridge.serialization.serializers.playerSerializersModule +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.core.utils.InternalPlayerApi +import com.intuit.playerui.core.utils.await +import com.intuit.playerui.j2v8.V8Null +import com.intuit.playerui.j2v8.V8Primitive +import com.intuit.playerui.j2v8.addPrimitive +import com.intuit.playerui.j2v8.bridge.V8Node +import com.intuit.playerui.j2v8.bridge.serialization.format.J2V8Format +import com.intuit.playerui.j2v8.bridge.serialization.format.J2V8FormatConfiguration +import com.intuit.playerui.j2v8.bridge.serialization.serializers.V8ValueSerializer +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.extensions.handleValue +import com.intuit.playerui.j2v8.extensions.unlock +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExecutorCoroutineDispatcher +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.cancel +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.plus +import java.nio.file.Path +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.io.path.createTempDirectory +import kotlin.io.path.pathString + +@JvmOverloads +public fun Runtime(runtime: V8, config: J2V8RuntimeConfig = J2V8RuntimeConfig(runtime)): Runtime = + V8Runtime(config) + +public fun Runtime(globalAlias: String? = null, tempDir: Path? = null): Runtime = + Runtime(V8.createV8Runtime(globalAlias, tempDir?.pathString).unlock()) + +// TODO: Do a better job of exposing runtime args as Config params to limit the need for these +@JvmOverloads +public fun Runtime(globalAlias: String? = null, tempDirPrefix: String? = null): Runtime = + Runtime(globalAlias, tempDirPrefix?.let(::createTempDirectory)) + +internal class V8Runtime(override val config: J2V8RuntimeConfig) : Runtime, ScriptSourceProvider { + + lateinit var v8: V8; private set + + override val dispatcher: ExecutorCoroutineDispatcher = config.executorService.asCoroutineDispatcher() + + override val format: J2V8Format = J2V8Format( + J2V8FormatConfiguration( + this, + playerSerializersModule + SerializersModule { + contextual(V8Value::class, V8ValueSerializer) + contextual(V8Object::class, V8ValueSerializer.conform()) + contextual(V8Array::class, V8ValueSerializer.conform()) + contextual(V8Primitive::class, V8ValueSerializer.conform()) + contextual(V8Null::class, V8ValueSerializer.conform()) + }, + ), + ) + + private lateinit var memoryScope: MemoryManager; private set + + override val scope: CoroutineScope by lazy { + // explicitly not using the JS specific dispatcher to avoid clogging up that thread + CoroutineScope(Dispatchers.Default + SupervisorJob() + (config.coroutineExceptionHandler ?: EmptyCoroutineContext)) + } + + init { + // TODO: Uplevel suspension init towards creation point instead of runBlocking + runBlocking { + init() + } + } + + // Call to initialize V8 -- needs to be invoked for V8 to be set, will acquire the lock for pre-created V8s on the + // single threaded executor (will fail if another thread has the lock). Otherwise, create a new V8 runtime + suspend fun init() { + withContext(dispatcher) { + v8 = config.runtime?.apply { + locker.acquire() + } ?: if (config.debuggable) { + try { + com.alexii.j2v8debugger.V8Debugger.createDebuggableV8Runtime(config.executorService, "debuggableJ2v8", true).await() + } catch (e: ClassNotFoundException) { + throw PlayerRuntimeException("V8Debugger not found. Ensure 'com.github.AlexTrotsenko:j2v8-debugger:0.2.3' is included on your classpath.", e) + } + } else { + V8.createV8Runtime() + } + memoryScope = MemoryManager(v8) + } + } + + override fun execute(script: String): Any? = v8.evaluateInJSThreadBlocking(runtime) { + executeScript(script).handleValue(format) + } + + override fun load(scriptContext: ScriptContext): Unit = v8.evaluateInJSThreadBlocking(runtime) { + if (config.debuggable) { + scriptIds.add(scriptContext.id) + scriptMapping[scriptContext.id] = scriptContext.script + } + executeScript(scriptContext.script, scriptContext.id.takeIf { config.debuggable }, 0) + } + + override fun add(name: String, value: V8Value) { + v8.evaluateInJSThreadBlocking(runtime) { + when (value) { + is V8Primitive -> addPrimitive(name, value) + else -> add(name, value) + } + } + } + + override fun serialize(serializer: SerializationStrategy, value: T): Any? = v8.evaluateInJSThreadBlocking(runtime) { + format.encodeToRuntimeValue(serializer, value).handleValue(format) + } + + override fun release() { + // cancel work in runtime scope + scope.cancel("releasing runtime") + // swap to dispatcher to release everything + runBlocking(dispatcher) { + memoryScope.release() + v8.release(true) + } + // close dispatcher + dispatcher.close() + } + + @InternalPlayerApi + override var checkBlockingThread: Thread.() -> Unit = {} + + private val scriptMapping = mutableMapOf() + + private val scriptIds: MutableSet = mutableSetOf() + override val allScriptIds: Collection + get() = scriptIds.toSet() + + override fun getSource(scriptId: String): String = scriptMapping[scriptId] ?: throw PlayerException("Script with name $scriptId not available for debugging, was it loaded?") + + override fun toString(): String = "J2V8" + + // Delegated Node members + private val backingNode: Node = V8Node(v8, this) + + override val runtime: V8Runtime = this + override val entries: Set> by backingNode::entries + override val keys: Set by backingNode::keys + override val size: Int by backingNode::size + override val values: Collection by backingNode::values + override fun containsKey(key: String): Boolean = backingNode.containsKey(key) + override fun containsValue(value: Any?): Boolean = backingNode.containsValue(value) + override fun get(key: String): Any? = backingNode[key] + override fun isEmpty(): Boolean = backingNode.isEmpty() + override fun getSerializable(key: String, deserializer: DeserializationStrategy): T? = + backingNode.getSerializable(key, deserializer) + + override fun deserialize(deserializer: DeserializationStrategy): T = backingNode.deserialize(deserializer) + override fun isReleased(): Boolean = backingNode.isReleased() + override fun isUndefined(): Boolean = backingNode.isUndefined() + override fun nativeReferenceEquals(other: Any?): Boolean = backingNode.nativeReferenceEquals(other) + override fun getString(key: String): String? = backingNode.getString(key) + override fun getInt(key: String): Int? = backingNode.getInt(key) + override fun getDouble(key: String): Double? = backingNode.getDouble(key) + override fun getLong(key: String): Long? = backingNode.getLong(key) + override fun getBoolean(key: String): Boolean? = backingNode.getBoolean(key) + override fun getInvokable(key: String, deserializationStrategy: DeserializationStrategy): Invokable? = backingNode.getInvokable(key, deserializationStrategy) + override fun getFunction(key: String): Invokable? = backingNode.getFunction(key) + override fun getList(key: String): List<*>? = backingNode.getList(key) + override fun getObject(key: String): Node? = backingNode.getObject(key) +} + +public object J2V8 : PlayerRuntimeFactory { + override fun create(block: J2V8RuntimeConfig.() -> Unit): Runtime = + V8Runtime(J2V8RuntimeConfig().apply(block)) + + override fun toString(): String = "J2V8" +} + +public data class J2V8RuntimeConfig( + var runtime: V8? = null, + private val explicitExecutorService: ExecutorService? = null, + override var debuggable: Boolean = false, + override var coroutineExceptionHandler: CoroutineExceptionHandler? = null, +) : PlayerRuntimeConfig() { + public val executorService: ExecutorService by lazy { + explicitExecutorService ?: Executors.newSingleThreadExecutor { + Executors.defaultThreadFactory().newThread(it).apply { + name = "js-runtime" + } + } + } +} + +public class J2V8RuntimeContainer : PlayerRuntimeContainer { + override val factory: PlayerRuntimeFactory<*> = J2V8 + + override fun toString(): String = "J2V8" +} diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/V8Decoders.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/V8Decoders.kt similarity index 68% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/V8Decoders.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/V8Decoders.kt index 5cc55e06c..7109ebec6 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/V8Decoders.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/V8Decoders.kt @@ -1,16 +1,28 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.encoding +package com.intuit.playerui.j2v8.bridge.serialization.encoding import com.eclipsesource.v8.V8Array import com.eclipsesource.v8.V8Object import com.eclipsesource.v8.V8Value -import com.intuit.player.jvm.core.bridge.serialization.encoding.* -import com.intuit.player.jvm.core.experimental.RuntimeClassDiscriminator -import com.intuit.player.jvm.j2v8.* -import com.intuit.player.jvm.j2v8.bridge.serialization.format.J2V8DecodingException -import com.intuit.player.jvm.j2v8.bridge.serialization.format.J2V8Format -import com.intuit.player.jvm.j2v8.extensions.blockingLock -import com.intuit.player.jvm.j2v8.extensions.handleValue -import kotlinx.serialization.* +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.serialization.encoding.AbstractRuntimeArrayListDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.AbstractRuntimeObjectClassDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.AbstractRuntimeObjectMapDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.AbstractRuntimeValueDecoder +import com.intuit.playerui.core.bridge.serialization.encoding.NodeDecoder +import com.intuit.playerui.core.experimental.RuntimeClassDiscriminator +import com.intuit.playerui.j2v8.V8Null +import com.intuit.playerui.j2v8.V8Primitive +import com.intuit.playerui.j2v8.bridge.serialization.format.J2V8DecodingException +import com.intuit.playerui.j2v8.bridge.serialization.format.J2V8Format +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.extensions.handleValue +import com.intuit.playerui.j2v8.extensions.toInvokable +import com.intuit.playerui.j2v8.getV8Value +import com.intuit.playerui.j2v8.v8Array +import com.intuit.playerui.j2v8.v8Function +import com.intuit.playerui.j2v8.v8Object +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind @@ -35,6 +47,10 @@ internal sealed class AbstractV8Decoder( PolymorphicKind.SEALED -> V8SealedClassDecoder(format, currentValue.v8Object) else -> error("Runtime format decoders can't decode kinds of (${descriptor.kind}) into structures for $descriptor") } + + override fun decodeFunction(returnTypeSerializer: KSerializer): Invokable { + return currentValue.v8Function.toInvokable(format, currentValue.v8Function, returnTypeSerializer) ?: error("Unable to decode V8 function using return type serializer ${returnTypeSerializer.descriptor}") + } } /** Simple implementation of [AbstractV8Decoder] can be treated as the entry point for [value] decoding */ @@ -42,14 +58,14 @@ internal class V8ValueDecoder(format: J2V8Format, value: V8Value) : AbstractV8De internal class V8ObjectMapDecoder(override val format: J2V8Format, override val value: V8Object) : AbstractRuntimeObjectMapDecoder(), NodeDecoder by V8ValueDecoder(format, value) { - override val keys: List = value.blockingLock { + override val keys: List = value.evaluateInJSThreadBlocking(format.runtime) { keys.toList() } - override fun getElementAtIndex(index: Int): V8Value = value.getV8Value(getKeyAtIndex(index)) + override fun getElementAtIndex(index: Int): V8Value = value.getV8Value(format.runtime, getKeyAtIndex(index)) override fun decodeElement(descriptor: SerialDescriptor, index: Int): V8Value = - value.getV8Value(descriptor.getElementName(index)) + value.getV8Value(format.runtime, descriptor.getElementName(index)) override fun buildDecoderForSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy): V8ValueDecoder = when (index % 2 == 0) { true -> V8ValueDecoder(format, getKeyAtIndex(index).let(::V8Primitive)) @@ -62,11 +78,11 @@ internal class V8ObjectMapDecoder(override val format: J2V8Format, override val internal class V8ArrayListDecoder(override val format: J2V8Format, override val value: V8Array) : AbstractRuntimeArrayListDecoder(), NodeDecoder by V8ValueDecoder(format, value) { - override val keys: List = value.blockingLock { + override val keys: List = value.evaluateInJSThreadBlocking(format.runtime) { keys.map(String::toInt) } - override fun getElementAtIndex(index: Int): V8Value = value.getV8Value(getKeyAtIndex(index)) + override fun getElementAtIndex(index: Int): V8Value = value.getV8Value(format.runtime, getKeyAtIndex(index)) override fun buildDecoderForSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy): V8ValueDecoder = V8ValueDecoder(format, decodeElement(descriptor, index)) @@ -77,14 +93,14 @@ internal class V8ArrayListDecoder(override val format: J2V8Format, override val internal class V8ObjectClassDecoder(override val format: J2V8Format, override val value: V8Object) : AbstractRuntimeObjectClassDecoder(), NodeDecoder by V8ValueDecoder(format, value) { - override val keys: List = value.blockingLock { - keys.toList().filter { !value.getV8Value(it).isUndefined } + override val keys: List = value.evaluateInJSThreadBlocking(format.runtime) { + keys.toList().filter { !value.getV8Value(format.runtime, it).isUndefined } } - override fun getElementAtIndex(index: Int): V8Value = value.getV8Value(getKeyAtIndex(index)) + override fun getElementAtIndex(index: Int): V8Value = value.getV8Value(format.runtime, getKeyAtIndex(index)) override fun decodeElement(descriptor: SerialDescriptor, index: Int): V8Value = - value.getV8Value(descriptor.getElementName(index)) + value.getV8Value(format.runtime, descriptor.getElementName(index)) override fun buildDecoderForSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy): V8ValueDecoder = V8ValueDecoder(format, decodeElement(descriptor, index)) @@ -110,6 +126,6 @@ internal class V8SealedClassDecoder(override val format: J2V8Format, override va } as? RuntimeClassDiscriminator )?.discriminator ?: format.config.discriminator - return value.getV8Value(discriminator).handleValue(format) + return value.getV8Value(format.runtime, discriminator).handleValue(format) } } diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/V8Encoders.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/V8Encoders.kt similarity index 72% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/V8Encoders.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/V8Encoders.kt index 7b99b6b51..b26871fc5 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/V8Encoders.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/V8Encoders.kt @@ -1,24 +1,33 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.encoding - -import com.eclipsesource.v8.* -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.invokeVararg -import com.intuit.player.jvm.core.bridge.serialization.encoding.FunctionEncoder -import com.intuit.player.jvm.core.bridge.serialization.encoding.NodeEncoder -import com.intuit.player.jvm.core.bridge.serialization.json.isJsonElementSerializer -import com.intuit.player.jvm.core.bridge.serialization.json.value -import com.intuit.player.jvm.core.bridge.serialization.serializers.FunctionLikeSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.ThrowableSerializer -import com.intuit.player.jvm.j2v8.* -import com.intuit.player.jvm.j2v8.bridge.V8Node -import com.intuit.player.jvm.j2v8.bridge.V8ObjectWrapper -import com.intuit.player.jvm.j2v8.bridge.serialization.format.J2V8EncodingException -import com.intuit.player.jvm.j2v8.bridge.serialization.format.J2V8Format -import com.intuit.player.jvm.j2v8.extensions.blockingLock -import com.intuit.player.jvm.j2v8.extensions.handleValue +package com.intuit.playerui.j2v8.bridge.serialization.encoding + +import com.eclipsesource.v8.V8 +import com.eclipsesource.v8.V8Array +import com.eclipsesource.v8.V8Function +import com.eclipsesource.v8.V8Object +import com.eclipsesource.v8.V8Value +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.invokeVararg +import com.intuit.playerui.core.bridge.serialization.encoding.FunctionEncoder +import com.intuit.playerui.core.bridge.serialization.encoding.NodeEncoder +import com.intuit.playerui.core.bridge.serialization.json.isJsonElementSerializer +import com.intuit.playerui.core.bridge.serialization.json.value +import com.intuit.playerui.core.bridge.serialization.serializers.FunctionLikeSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.ThrowableSerializer +import com.intuit.playerui.j2v8.V8Function +import com.intuit.playerui.j2v8.V8Null +import com.intuit.playerui.j2v8.V8Primitive +import com.intuit.playerui.j2v8.V8Value +import com.intuit.playerui.j2v8.addPrimitive +import com.intuit.playerui.j2v8.bridge.V8Node +import com.intuit.playerui.j2v8.bridge.V8ObjectWrapper +import com.intuit.playerui.j2v8.bridge.serialization.format.J2V8EncodingException +import com.intuit.playerui.j2v8.bridge.serialization.format.J2V8Format +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.extensions.handleValue +import com.intuit.playerui.j2v8.pushPrimitive import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.SerialDescriptor @@ -55,30 +64,32 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m MAP, LIST, PRIMITIVE, - UNDECIDED + UNDECIDED, } private val currentContent get() = when (mode) { Mode.MAP -> contentMap Mode.LIST -> contentList Mode.PRIMITIVE, - Mode.UNDECIDED -> content + Mode.UNDECIDED, + -> content } private var content: V8Value = V8.getUndefined() get() = when (mode) { Mode.UNDECIDED, - Mode.PRIMITIVE -> field + Mode.PRIMITIVE, + -> field else -> error("cannot get content unless in PRIMITIVE mode") } - protected open val contentList = format.v8.blockingLock(::V8Array) + protected open val contentList = format.v8.evaluateInJSThreadBlocking(format.runtime) { V8Array(this) } get() = when (mode) { Mode.LIST -> field else -> error("cannot get list unless in LIST mode") } - protected open val contentMap = format.v8.blockingLock(::V8Object) + protected open val contentMap = format.v8.evaluateInJSThreadBlocking(format.runtime) { V8Object(this) } get() = when (mode) { Mode.MAP -> field else -> error("cannot get map unless in MAP mode") @@ -94,14 +105,15 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m else -> putContent(tag, content) } Mode.PRIMITIVE, - Mode.UNDECIDED -> { + Mode.UNDECIDED, + -> { this.content = content as? V8Value ?: error("cannot set content (${content?.let { it::class }}) unless wrapped as V8Value") endEncode() } } } - private operator fun V8Object.set(key: String, content: Any?): Unit = blockingLock { + private operator fun V8Object.set(key: String, content: Any?): Unit = evaluateInJSThreadBlocking(format.runtime) { when (content) { null -> addNull(key) Unit -> addUndefined(key) @@ -116,7 +128,7 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m } } - private fun V8Array.add(content: Any?): Unit = blockingLock { + private fun V8Array.add(content: Any?): Unit = evaluateInJSThreadBlocking(format.runtime) { when (content) { null -> pushNull() Unit -> pushUndefined() @@ -136,7 +148,8 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m Mode.LIST -> contentList.add(content) Mode.MAP -> contentMap[tag] = content Mode.UNDECIDED, - Mode.PRIMITIVE -> { + Mode.PRIMITIVE, + -> { this.content = content as? V8Value ?: error("cannot set content (${content?.let { it::class }}) unless wrapped as V8Value") endEncode() } @@ -145,22 +158,26 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m } override fun beginStructure( - descriptor: SerialDescriptor + descriptor: SerialDescriptor, ): CompositeEncoder { val consumer = when (mode) { Mode.LIST, - Mode.MAP -> { node -> putContent(node) } + Mode.MAP, + -> { node -> putContent(node) } Mode.PRIMITIVE, - Mode.UNDECIDED -> consumer + Mode.UNDECIDED, + -> consumer } - return if (descriptor == ThrowableSerializer().descriptor) + return if (descriptor == ThrowableSerializer().descriptor) { V8ExceptionEncoder(format, ::putContent) - else when (descriptor.kind) { - StructureKind.CLASS -> V8ValueEncoder(format, Mode.MAP, consumer) - StructureKind.LIST, is PolymorphicKind -> V8ValueEncoder(format, Mode.LIST, consumer) - StructureKind.MAP -> V8ValueEncoder(format, Mode.MAP, consumer) - else -> V8ValueEncoder(format, consumer) + } else { + when (descriptor.kind) { + StructureKind.CLASS -> V8ValueEncoder(format, Mode.MAP, consumer) + StructureKind.LIST, is PolymorphicKind -> V8ValueEncoder(format, Mode.LIST, consumer) + StructureKind.MAP -> V8ValueEncoder(format, Mode.MAP, consumer) + else -> V8ValueEncoder(format, consumer) + } } } @@ -172,7 +189,7 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m is V8Node -> value.v8Object is V8Value -> value else -> V8Value(value) - } + }, ) override fun encodeNull(): Unit = putContent(V8Null) @@ -208,7 +225,7 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m is JsonArray -> value.toList() is JsonPrimitive -> value.value else -> value - } as T + } as T, ) else -> super.encodeSerializableValue(serializer, value) @@ -222,7 +239,7 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m .toTypedArray() invokable(*encodedArgs) - } + }, ) /** @@ -251,15 +268,17 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m // check if type is nullable and value is null if (( - currValue == null && - // base type or type argument could be marked nullable - (kParam.type.isMarkedNullable || kParam.type.arguments[0].type?.isMarkedNullable == true) - ) || + currValue == null && + // base type or type argument could be marked nullable + (kParam.type.isMarkedNullable || kParam.type.arguments[0].type?.isMarkedNullable == true) + ) || // otherwise check if arg matches type if not null (currValue != null && currValue::class.isSubclassOf(kParam.type.arguments[0].type?.classifier as KClass<*>)) - ) + ) { index++ - else break + } else { + break + } } // only take matching args encodedArgs.slice(start until index).toTypedArray() @@ -272,7 +291,7 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m handleInvocation(kCallable::class, matchedArgs) { kCallable.call(*it) } - } + }, ) /** @@ -292,25 +311,29 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m * implementation of [KCallable.call] so that we * don't even need to use Java reflection. Until then... */ - override fun encodeFunction(function: Function<*>) = if (function is Invokable<*>) encodeFunction(function) else putContent( - V8Function(format) { args -> - val encodedArgs = (0 until args.length()) - .map { args[it].handleValue(format) } - - // Hate that we need to look at an internal class for arity - val arity = (function as kotlin.jvm.internal.FunctionBase<*>).arity - - // trim and pad args to fit arity constraints, - // note that padding will fail if arg types are non-nullable - val matchedArgs = (0 until arity) - .map { encodedArgs.getOrNull(it) } - .toTypedArray() - - handleInvocation(function::class, matchedArgs) { - function.invokeVararg(*it) - } - } - ) + override fun encodeFunction(function: Function<*>) = if (function is Invokable<*>) { + encodeFunction(function) + } else { + putContent( + V8Function(format) { args -> + val encodedArgs = (0 until args.length()) + .map { args[it].handleValue(format) } + + // Hate that we need to look at an internal class for arity + val arity = (function as kotlin.jvm.internal.FunctionBase<*>).arity + + // trim and pad args to fit arity constraints, + // note that padding will fail if arg types are non-nullable + val matchedArgs = (0 until arity) + .map { encodedArgs.getOrNull(it) } + .toTypedArray() + + handleInvocation(function::class, matchedArgs) { + function.invokeVararg(*it) + } + }, + ) + } private fun handleInvocation(reference: KClass<*>, args: Array, block: (Array) -> Any?) = try { block(args) @@ -326,7 +349,7 @@ internal open class V8ValueEncoder(private val format: J2V8Format, private val m internal class V8ExceptionEncoder(format: J2V8Format, consumer: (V8Value) -> Unit) : V8ValueEncoder(format, Mode.MAP, consumer) { override val contentMap by lazy { - format.v8.blockingLock { + format.v8.evaluateInJSThreadBlocking(format.runtime) { executeObjectScript("""(new Error())""") } } diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/Builders.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/Builders.kt similarity index 71% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/Builders.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/Builders.kt index 3f21aba1c..ae15757db 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/Builders.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/Builders.kt @@ -1,14 +1,14 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.format +package com.intuit.playerui.j2v8.bridge.serialization.format import com.eclipsesource.v8.V8Array import com.eclipsesource.v8.V8Object import com.eclipsesource.v8.V8Value -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeArrayBuilder -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeBuilderDsl -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeObjectBuilder -import com.intuit.player.jvm.core.bridge.serialization.format.runtimeObject -import com.intuit.player.jvm.j2v8.v8Array -import com.intuit.player.jvm.j2v8.v8Object +import com.intuit.playerui.core.bridge.serialization.format.RuntimeArrayBuilder +import com.intuit.playerui.core.bridge.serialization.format.RuntimeBuilderDsl +import com.intuit.playerui.core.bridge.serialization.format.RuntimeObjectBuilder +import com.intuit.playerui.core.bridge.serialization.format.runtimeObject +import com.intuit.playerui.j2v8.v8Array +import com.intuit.playerui.j2v8.v8Object import kotlin.contracts.InvocationKind import kotlin.contracts.contract diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/J2V8Format.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/J2V8Format.kt similarity index 57% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/J2V8Format.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/J2V8Format.kt index 8b4015a41..bd0c5ac87 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/J2V8Format.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/J2V8Format.kt @@ -1,24 +1,27 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.format +package com.intuit.playerui.j2v8.bridge.serialization.format import com.eclipsesource.v8.V8 import com.eclipsesource.v8.V8Value -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.serialization.format.AbstractRuntimeFormat -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormatConfiguration -import com.intuit.player.jvm.core.bridge.serialization.format.serializer -import com.intuit.player.jvm.j2v8.V8Value -import com.intuit.player.jvm.j2v8.bridge.runtime.V8Runtime -import com.intuit.player.jvm.j2v8.bridge.serialization.encoding.readV8 -import com.intuit.player.jvm.j2v8.bridge.serialization.encoding.writeV8 -import com.intuit.player.jvm.j2v8.extensions.blockingLock -import kotlinx.serialization.* +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.serialization.format.AbstractRuntimeFormat +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormatConfiguration +import com.intuit.playerui.core.bridge.serialization.format.serializer +import com.intuit.playerui.j2v8.V8Value +import com.intuit.playerui.j2v8.bridge.runtime.V8Runtime +import com.intuit.playerui.j2v8.bridge.serialization.encoding.readV8 +import com.intuit.playerui.j2v8.bridge.serialization.encoding.writeV8 +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.modules.SerializersModule public class J2V8Format( config: J2V8FormatConfiguration, ) : AbstractRuntimeFormat(config) { - public val v8: V8 = (config.runtime as V8Runtime).v8 + public val v8: V8 by lazy { + (config.runtime as V8Runtime).v8 + } override fun encodeToRuntimeValue(serializer: SerializationStrategy, value: T): V8Value = writeV8(value, serializer) @@ -29,7 +32,7 @@ public class J2V8Format( public fun parseToV8Value(string: String): V8Value = parseToRuntimeValue(string) - override fun parseToRuntimeValue(string: String): V8Value = v8.blockingLock { + override fun parseToRuntimeValue(string: String): V8Value = v8.evaluateInJSThreadBlocking(runtime) { executeScript("($string)").let(::V8Value) } } diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/J2V8FormatExceptions.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/J2V8FormatExceptions.kt similarity index 78% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/J2V8FormatExceptions.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/J2V8FormatExceptions.kt index 0ae6ccc31..34cbbf944 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/J2V8FormatExceptions.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/J2V8FormatExceptions.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.format +package com.intuit.playerui.j2v8.bridge.serialization.format -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeSerializationException -import com.intuit.player.jvm.core.utils.InternalPlayerApi +import com.intuit.playerui.core.bridge.serialization.format.RuntimeSerializationException +import com.intuit.playerui.core.utils.InternalPlayerApi /** Generic exception indicating a problem with [J2V8Format] serialization and deserialization */ internal open class J2V8SerializationException @InternalPlayerApi constructor(message: String?, cause: Throwable? = null) : RuntimeSerializationException(message, cause) diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/serializers/V8ValueSerializer.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/serializers/V8ValueSerializer.kt similarity index 78% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/serializers/V8ValueSerializer.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/serializers/V8ValueSerializer.kt index 5ec73e0d0..1d384a26b 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/serializers/V8ValueSerializer.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/bridge/serialization/serializers/V8ValueSerializer.kt @@ -1,8 +1,8 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.serializers +package com.intuit.playerui.j2v8.bridge.serialization.serializers import com.eclipsesource.v8.V8Value -import com.intuit.player.jvm.core.bridge.serialization.encoding.requireNodeEncoder -import com.intuit.player.jvm.j2v8.bridge.serialization.encoding.AbstractV8Decoder +import com.intuit.playerui.core.bridge.serialization.encoding.requireNodeEncoder +import com.intuit.playerui.j2v8.bridge.serialization.encoding.AbstractV8Decoder import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializer import kotlinx.serialization.descriptors.SerialDescriptor diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/args.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Args.kt similarity index 65% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/args.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Args.kt index de2a7fa45..4f25962c3 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/args.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Args.kt @@ -1,10 +1,10 @@ -package com.intuit.player.jvm.j2v8.extensions +package com.intuit.playerui.j2v8.extensions import com.eclipsesource.v8.V8Array -import com.intuit.player.jvm.j2v8.V8Array -import com.intuit.player.jvm.j2v8.bridge.serialization.format.J2V8Format -import com.intuit.player.jvm.j2v8.bridge.serialization.format.encodeToV8Value -import com.intuit.player.jvm.j2v8.v8Array +import com.intuit.playerui.j2v8.V8Array +import com.intuit.playerui.j2v8.bridge.serialization.format.J2V8Format +import com.intuit.playerui.j2v8.bridge.serialization.format.encodeToV8Value +import com.intuit.playerui.j2v8.v8Array /** Convenience helper for building an empty [V8Array] for V8 function calls */ internal fun J2V8Format.emptyArgs(): V8Array = v8.V8Array() diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/HandleValue.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/HandleValue.kt new file mode 100644 index 000000000..8fbc9e0c0 --- /dev/null +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/HandleValue.kt @@ -0,0 +1,77 @@ +package com.intuit.playerui.j2v8.extensions + +import com.eclipsesource.v8.V8 +import com.eclipsesource.v8.V8Array +import com.eclipsesource.v8.V8Function +import com.eclipsesource.v8.V8Object +import com.eclipsesource.v8.V8Value +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.core.bridge.serialization.format.serializer +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.j2v8.V8Primitive +import com.intuit.playerui.j2v8.bridge.V8Node +import com.intuit.playerui.j2v8.v8Array +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.builtins.ArraySerializer + +internal fun Any?.handleValue(format: RuntimeFormat): Any? = when (this) { + is V8Primitive -> value + is V8Value -> transform(format) + else -> this +} + +private fun V8Value.transform(format: RuntimeFormat): Any? = evaluateInJSThreadIfDefinedBlocking(format.runtime) { + when (this) { + V8.getUndefined() -> null + is V8Primitive -> value + is V8Function -> toInvokable(format, this, format.serializer()) + is V8Array -> toList(format) + is V8Object -> toNode(format) + else -> null + } +} + +internal fun V8Array.toList(format: RuntimeFormat): List? = evaluateInJSThreadIfDefinedBlocking(format.runtime) { + keys.map(::get).map { it.handleValue(format) } +} + +internal fun V8Object.toNode(format: RuntimeFormat): Node? = evaluateInJSThreadIfDefinedBlocking(format.runtime) { + if (contains("id") && contains("type")) { + Asset(V8Node(this, format.runtime)) + } else { + V8Node(this, format.runtime) + } +} + +internal fun V8Function.toInvokable(format: RuntimeFormat, receiver: V8Object, deserializationStrategy: DeserializationStrategy?): Invokable? = evaluateInJSThreadIfDefinedBlocking(format.runtime) { + Invokable { args -> + evaluateInJSThreadBlocking(format.runtime) { + try { + when ( + val result = + call( + receiver, + format.encodeToRuntimeValue( + ArraySerializer(GenericSerializer()), + args as Array, + ).v8Array, + ).handleValue(format) + ) { + is Node -> deserializationStrategy?.let { + result.deserialize(deserializationStrategy) + } ?: run { + result as R + } + + else -> result as R + } + } catch (e: Throwable) { + e.printStackTrace() + throw e + } + } + } +} diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Invoke.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Invoke.kt new file mode 100644 index 000000000..b29a250b2 --- /dev/null +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Invoke.kt @@ -0,0 +1,11 @@ +package com.intuit.playerui.j2v8.extensions + +import com.eclipsesource.v8.V8Array +import com.eclipsesource.v8.V8Function +import com.intuit.playerui.j2v8.bridge.serialization.format.J2V8Format + +internal operator fun V8Function.invoke(format: J2V8Format): Any? = + call(runtime, runtime.evaluateInJSThreadBlocking(format.runtime) { V8Array(this) }) + +internal operator fun V8Function.invoke(format: J2V8Format, vararg args: Any?): Any? = + call(runtime, format.args(*args)) diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Lock.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Lock.kt new file mode 100644 index 000000000..1d912e8b1 --- /dev/null +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Lock.kt @@ -0,0 +1,50 @@ +package com.intuit.playerui.j2v8.extensions + +import com.eclipsesource.v8.V8Value +import com.intuit.playerui.core.bridge.PlayerRuntimeException +import com.intuit.playerui.core.bridge.runtime.Runtime +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout + +internal suspend fun Context.evaluateInJSThread( + runtime: Runtime, + timeout: Long = 5000, + block: suspend Context.() -> T, +): T = withTimeout(timeout) { + if (runtime.isReleased()) throw PlayerRuntimeException(runtime, "Runtime object has been released!") + withContext(runtime.dispatcher) { + runtime.scope.ensureActive() + block() + } +} + +internal fun Context.evaluateInJSThreadBlocking( + runtime: Runtime, + timeout: Long = 5000, + block: Context.() -> T, +): T { + if (runtime.isReleased()) throw PlayerRuntimeException(runtime, "Runtime object has been released!") + // if we're already on the dispatcher thread, DON'T BLOCK + return if (this@evaluateInJSThreadBlocking.runtime.locker.hasLock()) { + block() + } else { + runtime.checkBlockingThread(Thread.currentThread()) + runBlocking { + evaluateInJSThread(runtime, timeout, block) + } + } +} + +internal suspend fun Context.evaluateInJSThreadIfDefined( + runtime: Runtime, + timeout: Long = 5000, + block: suspend Context.() -> T, +): T? = mapUndefinedToNull()?.let { evaluateInJSThread(runtime, timeout, block) } + +internal fun Context.evaluateInJSThreadIfDefinedBlocking( + runtime: Runtime, + timeout: Long = 5000, + block: Context.() -> T, +): T? = mapUndefinedToNull()?.let { evaluateInJSThreadBlocking(runtime, timeout, block) } diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/map.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Map.kt similarity index 86% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/map.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Map.kt index 128306f89..f9951d389 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/map.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Map.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.j2v8.extensions +package com.intuit.playerui.j2v8.extensions import com.eclipsesource.v8.V8Value diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/unlock.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Unlock.kt similarity index 73% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/unlock.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Unlock.kt index 8bf468587..75203c031 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/unlock.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Unlock.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.j2v8.extensions +package com.intuit.playerui.j2v8.extensions import com.eclipsesource.v8.V8Value diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/unwrap.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Unwrap.kt similarity index 62% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/unwrap.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Unwrap.kt index 9d7482bd4..13648f37a 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/extensions/unwrap.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/extensions/Unwrap.kt @@ -1,9 +1,9 @@ -package com.intuit.player.jvm.j2v8.extensions +package com.intuit.playerui.j2v8.extensions import com.eclipsesource.v8.V8Object -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.j2v8.bridge.V8Node +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.j2v8.bridge.V8Node /** Extension to recursively unwrap [NodeWrapper]s until we hit a [Node] implementation */ internal fun Node.unwrap(): V8Object? = when (this) { diff --git a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/plugins/V8ScriptPlayerPlugin.kt b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/plugins/V8ScriptPlayerPlugin.kt similarity index 71% rename from jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/plugins/V8ScriptPlayerPlugin.kt rename to jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/plugins/V8ScriptPlayerPlugin.kt index bc2d9ef5d..443c39c72 100644 --- a/jvm/j2v8/src/main/kotlin/com/intuit/player/jvm/j2v8/plugins/V8ScriptPlayerPlugin.kt +++ b/jvm/j2v8/src/main/kotlin/com/intuit/playerui/j2v8/plugins/V8ScriptPlayerPlugin.kt @@ -1,20 +1,20 @@ -package com.intuit.player.jvm.j2v8.plugins +package com.intuit.playerui.j2v8.plugins -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper @Deprecated( "Replaced with more generic JSScriptPluginWrapper", ReplaceWith("JSScriptPluginWrapper"), - DeprecationLevel.HIDDEN + DeprecationLevel.HIDDEN, ) public typealias V8ScriptPlayerPlugin = JSScriptPluginWrapper @Deprecated( "Replaced with more generic V8ScriptPluginWrapper", ReplaceWith("JSScriptPluginWrapper"), - DeprecationLevel.HIDDEN + DeprecationLevel.HIDDEN, ) -public abstract class V8ScriptPluginWrapper(name: String, script: String) : JSScriptPluginWrapper(name, script) { +public abstract class V8ScriptPluginWrapper(name: String, script: String) : JSScriptPluginWrapper(name, script = script) { public constructor(name: String, sourcePath: String, classLoader: ClassLoader = JSScriptPluginWrapper::class.java.classLoader!!) : this(name, classLoader.getResource(sourcePath)!!.readText()) } diff --git a/jvm/j2v8/src/main/resources/META-INF/services/com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeContainer b/jvm/j2v8/src/main/resources/META-INF/services/com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeContainer deleted file mode 100644 index 70e782bc6..000000000 --- a/jvm/j2v8/src/main/resources/META-INF/services/com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeContainer +++ /dev/null @@ -1 +0,0 @@ -com.intuit.player.jvm.j2v8.bridge.runtime.J2V8RuntimeContainer diff --git a/jvm/j2v8/src/main/resources/META-INF/services/com.intuit.playerui.core.bridge.runtime.PlayerRuntimeContainer b/jvm/j2v8/src/main/resources/META-INF/services/com.intuit.playerui.core.bridge.runtime.PlayerRuntimeContainer new file mode 100644 index 000000000..302e150b1 --- /dev/null +++ b/jvm/j2v8/src/main/resources/META-INF/services/com.intuit.playerui.core.bridge.runtime.PlayerRuntimeContainer @@ -0,0 +1 @@ +com.intuit.playerui.j2v8.bridge.runtime.J2V8RuntimeContainer diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/base/AutoAcquireJ2V8Test.kt b/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/base/AutoAcquireJ2V8Test.kt deleted file mode 100644 index d3e7335b9..000000000 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/base/AutoAcquireJ2V8Test.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.intuit.player.jvm.j2v8.base - -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach - -internal abstract class AutoAcquireJ2V8Test : J2V8Test() { - - @BeforeEach - fun acquire() { - v8.locker.acquire() - } - - @AfterEach - fun release() { - v8.locker.release() - } -} diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/extensions/InvokeTest.kt b/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/extensions/InvokeTest.kt deleted file mode 100644 index a1b3959ac..000000000 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/extensions/InvokeTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.intuit.player.jvm.j2v8.extensions - -import com.eclipsesource.v8.V8 -import com.eclipsesource.v8.V8Function -import com.intuit.player.jvm.j2v8.V8Function -import com.intuit.player.jvm.j2v8.base.AutoAcquireJ2V8Test -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - -internal class InvokeTest : AutoAcquireJ2V8Test() { - - @Test - fun `test V8Function vararg invoke`() { - val func = v8.executeObjectScript("""(function(a,b){return a+b})""") as V8Function - assertEquals(4, func.call(v8, format.args(2, 2))) - assertEquals(4, func.invoke(format, 2, 2)) - } - - @Test - fun `test V8Function creation helper`() { - assertEquals(V8.getUndefined(), V8Function(format) { Unit }()) - assertEquals(4, V8Function(format) { 2 + 2 }()) - } -} diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/base/J2V8Test.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/base/J2V8Test.kt similarity index 63% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/base/J2V8Test.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/base/J2V8Test.kt index 1e9612dfb..cc2137516 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/base/J2V8Test.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/base/J2V8Test.kt @@ -1,23 +1,26 @@ -package com.intuit.player.jvm.j2v8.base +package com.intuit.playerui.j2v8.base import com.eclipsesource.v8.V8 import com.eclipsesource.v8.V8Array import com.eclipsesource.v8.V8Object -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.serialization.json.prettyPrint -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.j2v8.bridge.runtime.Runtime -import com.intuit.player.jvm.j2v8.bridge.runtime.V8Runtime -import com.intuit.player.jvm.j2v8.bridge.serialization.format.decodeFromV8Value -import com.intuit.player.jvm.j2v8.bridge.serialization.format.encodeToV8Value -import com.intuit.player.jvm.j2v8.extensions.blockingLock -import com.intuit.player.jvm.j2v8.extensions.unlock -import com.intuit.player.jvm.j2v8.v8Object -import com.intuit.player.jvm.utils.test.PromiseUtils -import com.intuit.player.jvm.utils.test.ThreadUtils +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.serialization.json.prettyPrint +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.j2v8.bridge.runtime.Runtime +import com.intuit.playerui.j2v8.bridge.runtime.V8Runtime +import com.intuit.playerui.j2v8.bridge.serialization.format.decodeFromV8Value +import com.intuit.playerui.j2v8.bridge.serialization.format.encodeToV8Value +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadIfDefinedBlocking +import com.intuit.playerui.j2v8.extensions.unlock +import com.intuit.playerui.j2v8.v8Object +import com.intuit.playerui.utils.test.PromiseUtils +import com.intuit.playerui.utils.test.ThreadUtils import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.buildJsonObject import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach @@ -32,8 +35,8 @@ internal abstract class J2V8Test(val v8: V8 = V8.createV8Runtime().unlock()) : P fun buildV8Object(jsonElement: JsonElement = buildJsonObject {}) = buildV8ObjectFromMap( Json.decodeFromJsonElement( MapSerializer(String.serializer(), GenericSerializer()), - jsonElement - ) + jsonElement, + ), ) fun buildV8ObjectFromMap(map: Map): V8Object = format.encodeToV8Value(map).v8Object @@ -49,7 +52,7 @@ internal abstract class J2V8Test(val v8: V8 = V8.createV8Runtime().unlock()) : P @BeforeEach fun resetGlobalLog() { - v8.blockingLock { + v8.evaluateInJSThreadIfDefinedBlocking(runtime) { val globalLog = V8Array(this) add("globalLog", globalLog) globalLog.close() @@ -57,7 +60,7 @@ internal abstract class J2V8Test(val v8: V8 = V8.createV8Runtime().unlock()) : P } fun flushRuntimeLogs() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val globalLog = getArray("globalLog") globalLog.keys.map { globalLog.get(it) }.forEach { it.prettyPrint() } globalLog.close() @@ -71,8 +74,8 @@ internal abstract class J2V8Test(val v8: V8 = V8.createV8Runtime().unlock()) : P fun V8Object.assertEquivalent(another: Any?) { assertTrue( another is V8Object, - "value to compare is not a V8Object: $another" - ) + ) { "value to compare is not a V8Object: $another" } + (another as V8Object).let { // verify that all missing keys from another are null or undefined (keys.toSet() - another.keys.toSet()).forEach { missingKey -> @@ -88,12 +91,14 @@ internal abstract class J2V8Test(val v8: V8 = V8.createV8Runtime().unlock()) : P if (isUndefined) { assertEquals(this, another) - } else keys.forEach { key -> - val (expected, actual) = get(key) to another.get(key) - if (expected is V8Object && !expected.isUndefined) { - expected.assertEquivalent(actual) - } else { - assertEquals(expected, actual, "comparing key: $key") + } else { + keys.forEach { key -> + val (expected, actual) = get(key) to another.get(key) + if (expected is V8Object && !expected.isUndefined) { + expected.assertEquivalent(actual) + } else { + assertEquals(expected, actual, "comparing key: $key") + } } } } diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/V8NodeTest.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/V8NodeTest.kt similarity index 71% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/V8NodeTest.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/V8NodeTest.kt index 2f2d50f18..3dbf7ae7e 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/V8NodeTest.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/V8NodeTest.kt @@ -1,22 +1,25 @@ -package com.intuit.player.jvm.j2v8.bridge +package com.intuit.playerui.j2v8.bridge -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.getJson -import com.intuit.player.jvm.core.bridge.toJson -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.j2v8.base.J2V8Test -import com.intuit.player.jvm.j2v8.bridge.serialization.format.v8Object -import com.intuit.player.jvm.j2v8.extensions.blockingLock -import com.intuit.player.jvm.j2v8.extensions.handleValue +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.getJson +import com.intuit.playerui.core.bridge.toJson +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.j2v8.bridge.serialization.format.v8Object +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.extensions.handleValue import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.serialization.builtins.serializer import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import kotlin.concurrent.thread @@ -24,23 +27,23 @@ import kotlin.concurrent.thread internal class V8NodeTest : J2V8Test() { @Test - fun get() = v8.blockingLock { + fun get() = v8.evaluateInJSThreadBlocking(runtime) { val node = buildNodeFromMap( "string" to "thisisastring", "int" to 1, "object" to mapOf( - "string" to "anotherstring" + "string" to "anotherstring", ), "list" to listOf( 1, "two", mapOf( - "string" to "onemorestring" + "string" to "onemorestring", ), - null + null, ), "function" to Invokable { "classicstring" }, - "null" to null + "null" to null, ) assertEquals("thisisastring", node["string"]) @@ -58,7 +61,7 @@ internal class V8NodeTest : J2V8Test() { fun getString() { val node = buildNodeFromMap( "string" to "string", - "notastring" to 1 + "notastring" to 1, ) assertEquals("string", node.getString("string")) @@ -67,24 +70,24 @@ internal class V8NodeTest : J2V8Test() { } @Test - fun getFunction() = v8.blockingLock { + fun getFunction() = v8.evaluateInJSThreadBlocking(runtime) { val node = buildNodeFromMap( "function" to Invokable { "classicstring" }, "tuple" to Invokable { (p0, p1) -> listOf(p0, p1) }, - "notafunction" to 1 + "notafunction" to 1, ) - assertEquals("classicstring", node.getFunction("function")?.invoke()) - assertEquals(listOf("1", 2), node.getFunction("tuple")?.invoke("1", 2)) - assertEquals(null, node.getFunction("notafunction")) - assertEquals(null, node.getFunction("notthere")) + assertEquals("classicstring", node.getInvokable("function")?.invoke()) + assertEquals(listOf("1", 2), node.getInvokable("tuple")?.invoke("1", 2)) + assertEquals(null, node.getInvokable("notafunction")) + assertEquals(null, node.getInvokable("notthere")) } @Test fun getList() { val node = buildNodeFromMap( "list" to listOf(1, 2, 3), - "notalist" to 1 + "notalist" to 1, ) assertEquals(listOf(1, 2, 3), node.getList("list")) @@ -96,9 +99,9 @@ internal class V8NodeTest : J2V8Test() { fun getObject() { val node = buildNodeFromMap( "object" to mapOf( - "string" to "thisisastring" + "string" to "thisisastring", ), - "notaobject" to 1234 + "notaobject" to 1234, ) assertEquals("thisisastring", node.getObject("object")?.getString("string")) @@ -112,8 +115,8 @@ internal class V8NodeTest : J2V8Test() { val node = buildNodeFromMap( "asset" to mapOf( "id" to "testId", - "type" to "testType" - ) + "type" to "testType", + ), ) val (id, type) = node.getObject("asset") as Asset @@ -127,14 +130,14 @@ internal class V8NodeTest : J2V8Test() { "assets" to listOf( mapOf( "id" to "testId1", - "type" to "testType" + "type" to "testType", ), mapOf( - "id" to "notAnAsset" + "id" to "notAnAsset", ), - 1 + 1, ), - "notassets" to "justastring" + "notassets" to "justastring", ) val assets = node.getList("assets") as List<*> @@ -156,7 +159,7 @@ internal class V8NodeTest : J2V8Test() { fun getInt() { val node = buildNodeFromMap( "int" to 1, - "notanint" to "asdf" + "notanint" to "asdf", ) assertEquals(1, node.getInt("int")) @@ -167,7 +170,7 @@ internal class V8NodeTest : J2V8Test() { @Test fun getJson() { val node = buildNodeFromMap( - "beacon" to mapOf("key" to "value") + "beacon" to mapOf("key" to "value"), ) assertEquals(JsonNull, node.getJson("notthere")) assertEquals(buildJsonObject { put("key", "value") }, node.getJson("beacon")) @@ -187,17 +190,17 @@ internal class V8NodeTest : J2V8Test() { "beacon", buildJsonObject { put("key", "value") - } + }, ) }, - node.toJson() + node.toJson(), ) } @Test fun getBoolean() { val node = buildNodeFromMap( - "isSelected" to true + "isSelected" to true, ) assertEquals(true, node.getBoolean("isSelected")) assertNull(node.getBoolean("notthere")) @@ -209,51 +212,51 @@ internal class V8NodeTest : J2V8Test() { "string" to "thisisastring", "int" to 1, "object" to mapOf( - "string" to "anotherstring" + "string" to "anotherstring", ), "list" to listOf( 1, "two", mapOf( - "string" to "onemorestring" + "string" to "onemorestring", ), - null + null, ), "function" to Invokable { "classicstring" }, - "null" to null + "null" to null, ) addThreads( thread { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { runBlocking { delay(1000) } assertEquals(1, node.getInt("int")) } }, thread(false) { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { assertEquals("thisisastring", node["string"]) } - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { assertEquals("thisisastring", node.getString("string")) } - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { assertEquals(1, node.getInt("int")) } assertEquals("thisisastring", node.get("string")) }, thread(false) { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { assertEquals("thisisastring", node["string"]) } - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { assertEquals("thisisastring", node.getString("string")) } - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { assertEquals(1, node.getInt("int")) } assertEquals("thisisastring", node.get("string")) - } + }, ) startThreads() verifyThreads() @@ -262,7 +265,7 @@ internal class V8NodeTest : J2V8Test() { @Test fun getSerializablePrimitive() { val node = buildNodeFromMap( - "number" to 9 + "number" to 9, ) assertEquals(9, node.getSerializable("number", Int.serializer())) } @@ -271,8 +274,8 @@ internal class V8NodeTest : J2V8Test() { fun getSerializable() { val node = buildNodeFromMap( "flow" to mapOf( - "id" to "testId" - ) + "id" to "testId", + ), ) assertEquals("testId", node.getSerializable("flow", Flow.serializer())?.id) } diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/V8DecoderTest.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/V8DecoderTest.kt similarity index 84% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/V8DecoderTest.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/V8DecoderTest.kt index 1ea995f29..1a3a7562a 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/V8DecoderTest.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/V8DecoderTest.kt @@ -1,20 +1,22 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization - -import com.eclipsesource.v8.* -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.serialization.json.prettify -import com.intuit.player.jvm.core.bridge.serialization.json.prettyPrint -import com.intuit.player.jvm.j2v8.V8Function -import com.intuit.player.jvm.j2v8.V8Value -import com.intuit.player.jvm.j2v8.base.AutoAcquireJ2V8Test -import com.intuit.player.jvm.j2v8.bridge.serialization.format.decodeFromV8Value -import com.intuit.player.jvm.j2v8.bridge.serialization.format.encodeToV8Value -import com.intuit.player.jvm.j2v8.extensions.args -import com.intuit.player.jvm.j2v8.extensions.blockingLock -import com.intuit.player.jvm.j2v8.extensions.emptyArgs -import com.intuit.player.jvm.j2v8.extensions.invoke -import com.intuit.player.jvm.j2v8.v8Function -import com.intuit.player.jvm.j2v8.v8Object +package com.intuit.playerui.j2v8.bridge.serialization + +import com.eclipsesource.v8.V8 +import com.eclipsesource.v8.V8Object +import com.eclipsesource.v8.V8ScriptExecutionException +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.serialization.json.prettify +import com.intuit.playerui.core.bridge.serialization.json.prettyPrint +import com.intuit.playerui.j2v8.V8Function +import com.intuit.playerui.j2v8.V8Value +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.j2v8.bridge.serialization.format.decodeFromV8Value +import com.intuit.playerui.j2v8.bridge.serialization.format.encodeToV8Value +import com.intuit.playerui.j2v8.extensions.args +import com.intuit.playerui.j2v8.extensions.emptyArgs +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.extensions.invoke +import com.intuit.playerui.j2v8.v8Function +import com.intuit.playerui.j2v8.v8Object import kotlinx.serialization.Serializable import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows @@ -22,13 +24,13 @@ import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test /** Legacy tests for encoding values into J2V8 */ -internal class V8DecoderTest : AutoAcquireJ2V8Test() { +internal class V8DecoderTest : J2V8Test() { private val retVal = "this is my return" @Test fun testPrimitives() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { listOf(42, 1234L, "string", null, Unit to V8.getUndefined()) .map { if (it is Pair<*, *>) it else it to V8Value(it) } .forEach { (actual, expected) -> assertEquals(expected, format.encodeToV8Value(actual)) } @@ -37,7 +39,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testArray() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val list = listOf(1, 2, 3) val v8Array = executeArrayScript("""(${list.prettify()})""") val result = format.encodeToV8Value(list) @@ -47,7 +49,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testNestedArray() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val list = listOf("a", 2, 3, listOf(4, 5, 6)) val nestedV8Array = executeArrayScript("""(${list.prettify()})""") val result = format.encodeToV8Value(list) @@ -57,7 +59,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testObject() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val map = mapOf("a" to "b", "c" to "d") val v8Object = executeObjectScript("""(${map.prettify()})""") val result = format.encodeToV8Value(map) @@ -67,7 +69,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testObjectDynamicKeys() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val map = mapOf(1 to "b", "c" to "d") val v8Object = executeObjectScript("""(${map.prettify()})""") val result = format.encodeToV8Value(map) @@ -77,7 +79,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testNestedObject() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val map = mapOf("a" to "b", "c" to "d") val v8Object = executeObjectScript("""(${map.prettify()})""") val result = format.encodeToV8Value(map) @@ -87,7 +89,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val function = { arg: String -> println(arg); retVal } assertEquals(retVal, function("this is my arg")) @@ -98,7 +100,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testFunctionReturn() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val function = { arg: String -> arg } assertEquals("this is my arg", function("this is my arg")) @@ -109,7 +111,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testTooManyParamsOnFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val function = { arg: String -> println(arg); retVal } assertEquals(retVal, function("this is my arg")) @@ -120,7 +122,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun notEnoughParamsOnFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val function = { arg: String? -> println(arg); retVal } assertEquals(retVal, function("this is my arg")) @@ -131,7 +133,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun wrongParamsOnFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val function = { arg: String -> println(arg); retVal } assertEquals(retVal, function("this is my arg")) @@ -144,7 +146,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testMemberFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val logger = LoggerAsMethod() val function = logger::log assertEquals(retVal, function("this is my arg")) @@ -156,7 +158,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testMemberFunctionReturn() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val logger = LoggerAsMethod() val function = logger::logAndReturn assertEquals("this is my arg", function("this is my arg")) @@ -168,7 +170,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testTooManyParamsOnMemberFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val logger = LoggerAsMethod() val function = logger::log assertEquals(retVal, function("this is my arg")) @@ -180,19 +182,19 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun notEnoughParamsOnMemberFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val logger = LoggerAsMethod() val function = logger::log assertEquals(retVal, function("this is my arg")) val result = format.encodeToV8Value(function).v8Function - assertEquals(retVal, result()) + assertEquals(retVal, result(format)) } } @Test fun wrongParamsOnMemberFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val logger = LoggerAsMethod() val function = logger::log assertEquals(retVal, function("this is my arg")) @@ -215,7 +217,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { } } - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val logger = ComplicatedVarargMethod() val function = logger::log // assertEquals(retVal, function("someArg", argsAsList.toTypedArray(), 42)) @@ -228,12 +230,12 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testComplexStructure() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val complex = mapOf( "string" to "thisisastring", "int" to 1, "object" to mapOf( - "string" to "anotherstring" + "string" to "anotherstring", ), "list" to listOf( 1, @@ -241,14 +243,14 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { listOf( "a", "b", - "c" + "c", ), mapOf( - "string" to "onemorestring" + "string" to "onemorestring", ), - null + null, ), - "null" to null + "null" to null, ) val v8Object = executeObjectScript("""(${complex.prettify()})""") val result = format.encodeToV8Value(complex) @@ -264,7 +266,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testV8ValueDecoding() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val testClass = TestClass1(1, "string") val obj = executeObjectScript("""(${testClass.prettify(TestClass1.serializer())})""") @@ -274,7 +276,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { val mapTestClass = mapOf( "one" to 1, - "string" to "string" + "string" to "string", ) val encodedMapTestClass = format.decodeFromV8Value(obj) @@ -300,7 +302,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testV8ValueDecodingWithFunctionType() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val testClass = TestClass2(1, "string") { println("$it called me!") return@TestClass2 true @@ -311,7 +313,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { V8Function(format) { println("$it called me!") true - } + }, ) val obj = executeObjectScript("""({one: 1, string: "string", method: f})""") val decodedObj = format.encodeToV8Value(testClass).v8Object @@ -351,18 +353,22 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Test fun testV8ValueDecodingWithInvokableType() { - v8.blockingLock { - val testClass = TestClass3(1, "string") { - println("${it.firstOrNull()} called me!") - return@TestClass3 true - } + v8.evaluateInJSThreadBlocking(runtime) { + val testClass = TestClass3( + 1, + "string", + Invokable { + println("${it.firstOrNull()} called me!") + return@Invokable true + }, + ) add( "f", V8Function(format) { println("$it called me!") true - } + }, ) val obj = executeObjectScript("""({one: 1, string: "string", method: f})""") val decodedObj = format.encodeToV8Value(testClass) as V8Object @@ -398,12 +404,12 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { data class TestClass4( val one: Int, val string: String, - val nested: TestClass4? = null + val nested: TestClass4? = null, ) @Test fun testV8ValueDecodingWithNestedDataClass() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val testClass = TestClass4(1, "string", TestClass4(2, "another")) testClass.prettyPrint(TestClass4.serializer()) @@ -418,8 +424,8 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { "string" to "string", "nested" to mapOf( "one" to 2, - "string" to "another" - ) + "string" to "another", + ), ) val encodedMapTestClass = format.decodeFromV8Value>(obj) @@ -440,18 +446,18 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { data class TestClass5( val one: Int, val string: String, - val nested: TestClass6 + val nested: TestClass6, ) @Serializable data class TestClass6( val two: String, - val string: Int + val string: Int, ) @Test fun testV8ValueDecodingWithComplexDataClass() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val testClass = TestClass5(1, "string", TestClass6("another", 2)) testClass.prettyPrint(TestClass5.serializer()) @@ -466,8 +472,8 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { "string" to "string", "nested" to mapOf( "two" to "another", - "string" to 2 - ) + "string" to 2, + ), ) val encodedMapTestClass = format.decodeFromV8Value>(obj) @@ -486,7 +492,7 @@ internal class V8DecoderTest : AutoAcquireJ2V8Test() { @Serializable class LoggerAsValue( - var TAG: String = "Logger As Value" + var TAG: String = "Logger As Value", ) { private val retVal = "this is my return" val log: ((String?) -> String)? = { println(TAG); println(it); retVal } diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/V8EncoderTest.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/V8EncoderTest.kt similarity index 76% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/V8EncoderTest.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/V8EncoderTest.kt index 36d130ca8..ea75f2012 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/V8EncoderTest.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/V8EncoderTest.kt @@ -1,22 +1,22 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization +package com.intuit.playerui.j2v8.bridge.serialization import com.eclipsesource.v8.V8Function -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.serialization.json.prettify -import com.intuit.player.jvm.j2v8.V8Function -import com.intuit.player.jvm.j2v8.base.AutoAcquireJ2V8Test -import com.intuit.player.jvm.j2v8.bridge.serialization.format.decodeFromV8Value -import com.intuit.player.jvm.j2v8.extensions.blockingLock -import com.intuit.player.jvm.j2v8.extensions.invoke +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.serialization.json.prettify +import com.intuit.playerui.j2v8.V8Function +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.j2v8.bridge.serialization.format.decodeFromV8Value +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.extensions.invoke import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test /** Legacy tests for decoding values from J2V8 */ -internal class V8EncoderTest : AutoAcquireJ2V8Test() { +internal class V8EncoderTest : J2V8Test() { @Test fun testArray() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val list = listOf(1, 2, 3) val v8Array = executeArrayScript("""(${list.prettify()})""") val result = format.decodeFromV8Value>(v8Array) @@ -26,7 +26,7 @@ internal class V8EncoderTest : AutoAcquireJ2V8Test() { @Test fun testNestedArray() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val list = listOf("a", 2, 3, listOf(4, 5, 6)) val nestedV8Array = executeArrayScript("""(${list.prettify()})""") val result: List = format.decodeFromV8Value(nestedV8Array) @@ -36,7 +36,7 @@ internal class V8EncoderTest : AutoAcquireJ2V8Test() { @Test fun testObject() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val map = mapOf("a" to "b", "c" to "d") val v8Object = executeObjectScript("""(${map.prettify()})""") val result = format.decodeFromV8Value>(v8Object) @@ -46,7 +46,7 @@ internal class V8EncoderTest : AutoAcquireJ2V8Test() { @Test fun testObjectDynamicKeysAreConvertedToStrings() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val map = mapOf(1 to "b", "c" to "d") val v8Object = executeObjectScript("""(${map.prettify()})""") val result = format.decodeFromV8Value>(v8Object) @@ -56,7 +56,7 @@ internal class V8EncoderTest : AutoAcquireJ2V8Test() { @Test fun testNestedObject() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val map = mapOf("a" to mapOf("b" to "c"), "d" to mapOf("e" to "f")) val v8Object = executeObjectScript("""(${map.prettify()})""") val result = format.decodeFromV8Value>>(v8Object) @@ -66,7 +66,7 @@ internal class V8EncoderTest : AutoAcquireJ2V8Test() { @Test fun testFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val retVal = "this is my return" val v8Function = V8Function(format) { args -> println(args.get(0)); retVal @@ -80,13 +80,13 @@ internal class V8EncoderTest : AutoAcquireJ2V8Test() { @Test fun testContainedFunction() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val retVal = "this is my return" add( "func", V8Function(format) { args -> println(args.get(0)); retVal - } + }, ) val functionContainer = executeObjectScript("""({ log: func })""") val v8Function = functionContainer.getObject("log") as? V8Function @@ -101,12 +101,12 @@ internal class V8EncoderTest : AutoAcquireJ2V8Test() { @Test fun testComplexStructure() { - v8.blockingLock { + v8.evaluateInJSThreadBlocking(runtime) { val complex = mapOf( "string" to "thisisastring", "int" to 1, "object" to mapOf( - "string" to "anotherstring" + "string" to "anotherstring", ), "list" to listOf( 1, @@ -114,14 +114,14 @@ internal class V8EncoderTest : AutoAcquireJ2V8Test() { listOf( "a", "b", - "c" + "c", ), mapOf( - "string" to "onemorestring" + "string" to "onemorestring", ), - null + null, ), - "null" to null + "null" to null, ) val v8Object = executeObjectScript("""(${complex.prettify()})""") val result: Any? = format.decodeFromV8Value(v8Object) diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/DecodingTests.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/DecodingTests.kt similarity index 61% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/DecodingTests.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/DecodingTests.kt index d87b771ab..46c614ea6 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/DecodingTests.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/DecodingTests.kt @@ -1,11 +1,15 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.encoding +package com.intuit.playerui.j2v8.bridge.serialization.encoding import com.eclipsesource.v8.V8 -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.j2v8.* -import com.intuit.player.jvm.j2v8.base.J2V8Test -import com.intuit.player.jvm.j2v8.bridge.serialization.format.decodeFromV8Value -import com.intuit.player.jvm.j2v8.extensions.blockingLock +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.j2v8.V8Array +import com.intuit.playerui.j2v8.V8Function +import com.intuit.playerui.j2v8.V8Null +import com.intuit.playerui.j2v8.V8Object +import com.intuit.playerui.j2v8.V8Primitive +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.j2v8.bridge.serialization.format.decodeFromV8Value +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking import kotlinx.serialization.Serializable import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -13,39 +17,39 @@ import org.junit.jupiter.api.Test internal class PrimitiveDecoding : J2V8Test() { @Test - fun `decode string primitive`() = v8.blockingLock { + fun `decode string primitive`() = v8.evaluateInJSThreadBlocking(runtime) { assertEquals("hello", format.decodeFromV8Value(V8Primitive("hello"))) } @Test - fun `decode boolean primitive`() = v8.blockingLock { + fun `decode boolean primitive`() = v8.evaluateInJSThreadBlocking(runtime) { assertEquals(true, format.decodeFromV8Value(V8Primitive(true))) } @Test - fun `decode int primitive`() = v8.blockingLock { + fun `decode int primitive`() = v8.evaluateInJSThreadBlocking(runtime) { assertEquals(20, format.decodeFromV8Value(V8Primitive(20))) } @Test - fun `decode double primitive`() = v8.blockingLock { + fun `decode double primitive`() = v8.evaluateInJSThreadBlocking(runtime) { assertEquals(2.2, format.decodeFromV8Value(V8Primitive(2.2))) } @Test - fun `decode unit`() = v8.blockingLock { + fun `decode unit`() = v8.evaluateInJSThreadBlocking(runtime) { assertEquals(null, format.decodeFromV8Value(V8.getUndefined())) } @Test - fun `decode null`() = v8.blockingLock { + fun `decode null`() = v8.evaluateInJSThreadBlocking(runtime) { assertEquals(null, format.decodeFromV8Value(V8Null)) } } internal class FunctionDecoding : J2V8Test() { - @Test fun `decode typed lambda`() = v8.blockingLock { + @Test fun `decode typed lambda`() = v8.evaluateInJSThreadBlocking(runtime) { val args = V8Array { push("PLAYER") push(1) @@ -60,11 +64,11 @@ internal class FunctionDecoding : J2V8Test() { assertEquals("PLAYER: 1", function.call(this, args)) assertEquals( "PLAYER: 2", - format.decodeFromV8Value>(function)("PLAYER", 2) + format.decodeFromV8Value>(function)("PLAYER", 2), ) } - @Test fun `decode invokable`() = v8.blockingLock { + @Test fun `decode invokable`() = v8.evaluateInJSThreadBlocking(runtime) { val args = V8Array { push("PLAYER") push(1) @@ -79,14 +83,14 @@ internal class FunctionDecoding : J2V8Test() { assertEquals("PLAYER: 1", function.call(this, args)) assertEquals( "PLAYER: 2", - format.decodeFromV8Value>(function)("PLAYER", 2) + format.decodeFromV8Value>(function)("PLAYER", 2), ) } - @Test fun `decode kcallable`() = v8.blockingLock { + @Test fun `decode kcallable`() = v8.evaluateInJSThreadBlocking(runtime) { @Serializable data class Container( - val method: (String, Int) -> String + val method: (String, Int) -> String, ) val args = V8Array { @@ -106,8 +110,8 @@ internal class FunctionDecoding : J2V8Test() { format.decodeFromV8Value( V8Object { add("method", function) - } - ).method("PLAYER", 2) + }, + ).method("PLAYER", 2), ) } } diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/EncodingTests.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/EncodingTests.kt similarity index 54% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/EncodingTests.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/EncodingTests.kt index d80a04a75..237e47d06 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/EncodingTests.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/EncodingTests.kt @@ -1,62 +1,65 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.encoding +package com.intuit.playerui.j2v8.bridge.serialization.encoding import com.eclipsesource.v8.V8 -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.j2v8.* -import com.intuit.player.jvm.j2v8.base.J2V8Test -import com.intuit.player.jvm.j2v8.bridge.serialization.format.encodeToV8Value -import com.intuit.player.jvm.j2v8.extensions.blockingLock +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.j2v8.V8Array +import com.intuit.playerui.j2v8.V8Null +import com.intuit.playerui.j2v8.V8Primitive +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.j2v8.bridge.serialization.format.encodeToV8Value +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.v8Function import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test internal class PrimitiveEncoding : J2V8Test() { - @Test fun `encode string primitive`() = format.v8.blockingLock { + @Test fun `encode string primitive`() = format.v8.evaluateInJSThreadBlocking(runtime) { assertEquals(V8Primitive("hello"), format.encodeToV8Value("hello")) } - @Test fun `encode boolean primitive`() = format.v8.blockingLock { + @Test fun `encode boolean primitive`() = format.v8.evaluateInJSThreadBlocking(runtime) { assertEquals(V8Primitive(true), format.encodeToV8Value(true)) } - @Test fun `encode int primitive`() = format.v8.blockingLock { + @Test fun `encode int primitive`() = format.v8.evaluateInJSThreadBlocking(runtime) { assertEquals(V8Primitive(20), format.encodeToV8Value(20)) } - @Test fun `encode double primitive`() = format.v8.blockingLock { + @Test fun `encode double primitive`() = format.v8.evaluateInJSThreadBlocking(runtime) { assertEquals(V8Primitive(2.2), format.encodeToV8Value(2.2)) } - @Test fun `encode long primitive`() = format.v8.blockingLock { + @Test fun `encode long primitive`() = format.v8.evaluateInJSThreadBlocking(runtime) { assertEquals(V8Primitive(20.0), format.encodeToV8Value(20L)) } - @Test fun `encode unit`() = format.v8.blockingLock { + @Test fun `encode unit`() = format.v8.evaluateInJSThreadBlocking(runtime) { assertEquals(V8.getUndefined(), format.encodeToV8Value(Unit)) } - @Test fun `encode null`() = format.v8.blockingLock { + @Test fun `encode null`() = format.v8.evaluateInJSThreadBlocking(runtime) { assertEquals(V8Null, format.encodeToV8Value(null)) } } internal class FunctionEncoding : J2V8Test() { - @Test fun `encode typed lambda`() = v8.blockingLock { + @Test fun `encode typed lambda`() = v8.evaluateInJSThreadBlocking(runtime) { val callback = { p0: String, p1: Int -> "$p0: $p1" } assertEquals("PLAYER: 1", callback("PLAYER", 1)) assertEquals("PLAYER: 2", format.encodeToV8Value(callback).v8Function.call(this, V8Array { push("PLAYER"); push(2) })) } - @Test fun `encode invokable`() = v8.blockingLock { + @Test fun `encode invokable`() = v8.evaluateInJSThreadBlocking(runtime) { val callback = Invokable { (p0, p1) -> "$p0: $p1" } assertEquals("PLAYER: 1", callback("PLAYER", 1)) assertEquals("PLAYER: 2", format.encodeToV8Value(callback).v8Function.call(this, V8Array { push("PLAYER"); push(2) })) } - @Test fun `encode kcallable`() = v8.blockingLock { + @Test fun `encode kcallable`() = v8.evaluateInJSThreadBlocking(runtime) { class Container { fun callback(p0: String, p1: Int) = "$p0: $p1" } diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/JsonEncodingTests.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/JsonEncodingTests.kt similarity index 57% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/JsonEncodingTests.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/JsonEncodingTests.kt index e77b4f71e..757025a25 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/encoding/JsonEncodingTests.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/encoding/JsonEncodingTests.kt @@ -1,11 +1,11 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.encoding +package com.intuit.playerui.j2v8.bridge.serialization.encoding -import com.intuit.player.jvm.core.flow.FlowResult -import com.intuit.player.jvm.j2v8.base.AutoAcquireJ2V8Test -import com.intuit.player.jvm.j2v8.bridge.V8Node -import com.intuit.player.jvm.j2v8.bridge.serialization.format.encodeToV8Value -import com.intuit.player.jvm.j2v8.extensions.blockingLock -import com.intuit.player.jvm.j2v8.v8Object +import com.intuit.playerui.core.flow.FlowResult +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.j2v8.bridge.V8Node +import com.intuit.playerui.j2v8.bridge.serialization.format.encodeToV8Value +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.j2v8.v8Object import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement @@ -14,7 +14,7 @@ import kotlinx.serialization.json.put import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -internal class JsonEncodingTests : AutoAcquireJ2V8Test() { +internal class JsonEncodingTests : J2V8Test() { private val expectedJson = buildJsonObject { put("data", buildJsonObject { put("a", "b") }) put( @@ -22,11 +22,11 @@ internal class JsonEncodingTests : AutoAcquireJ2V8Test() { buildJsonObject { put("state_type", "END") put("outcome", "doneWithTopic") - } + }, ) } private val expectedJsonString = expectedJson.toString() - private val expectedV8Object = v8.blockingLock { + private val expectedV8Object = v8.evaluateInJSThreadBlocking(runtime) { executeObjectScript("""($expectedJsonString)""") } private val expectedFlowResult = FlowResult(V8Node(expectedV8Object, runtime)) @@ -45,7 +45,9 @@ internal class JsonEncodingTests : AutoAcquireJ2V8Test() { @Test fun testToV8() { - expectedV8Object.assertEquivalent(format.encodeToV8Value(expectedFlowResult)) + expectedV8Object.evaluateInJSThreadBlocking(runtime) { + expectedV8Object.assertEquivalent(format.encodeToV8Value(expectedFlowResult)) + } } @Test @@ -56,10 +58,12 @@ internal class JsonEncodingTests : AutoAcquireJ2V8Test() { @Test fun testToAndFromV8() { - val v8Object = format.encodeToV8Value(expectedFlowResult).v8Object - expectedV8Object.assertEquivalent(v8Object) + expectedV8Object.evaluateInJSThreadBlocking(runtime) { + val v8Object = format.encodeToV8Value(expectedFlowResult).v8Object + expectedV8Object.assertEquivalent(v8Object) - val flow = format.decodeFromRuntimeValue(FlowResult.serializer(), v8Object) - Assertions.assertEquals(expectedFlowResult, flow) + val flow = format.decodeFromRuntimeValue(FlowResult.serializer(), v8Object) + Assertions.assertEquals(expectedFlowResult, flow) + } } } diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/J2V8FormatTest.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/J2V8FormatTest.kt similarity index 76% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/J2V8FormatTest.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/J2V8FormatTest.kt index 3910d78e5..616d8278c 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/format/J2V8FormatTest.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/format/J2V8FormatTest.kt @@ -1,13 +1,14 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.format +package com.intuit.playerui.j2v8.bridge.serialization.format import com.eclipsesource.v8.V8 import com.eclipsesource.v8.V8Object -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.j2v8.V8Function -import com.intuit.player.jvm.j2v8.base.J2V8Test -import com.intuit.player.jvm.j2v8.extensions.blockingLock +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.j2v8.V8Function +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer @@ -16,7 +17,7 @@ import org.junit.jupiter.api.Test internal class J2V8FormatTest : J2V8Test() { - @Test fun `encode simple map into V8Object with explicit serializers`() = format.v8.blockingLock { + @Test fun `encode simple map into V8Object with explicit serializers`() = format.v8.evaluateInJSThreadBlocking(runtime) { val map = mapOf( "one" to 1, "two" to 2, @@ -29,7 +30,7 @@ internal class J2V8FormatTest : J2V8Test() { assertEquals(V8.getUndefined(), v8Object.get("three")) } - @Test fun `encode simple map into V8Object with implicit serializers`() = format.v8.blockingLock { + @Test fun `encode simple map into V8Object with implicit serializers`() = format.v8.evaluateInJSThreadBlocking(runtime) { val map = mapOf( "one" to 1, "two" to 2, @@ -42,7 +43,7 @@ internal class J2V8FormatTest : J2V8Test() { assertEquals(V8.getUndefined(), v8Object.get("three")) } - @Test fun `encode nested map into V8Object with implicit serializers`() = format.v8.blockingLock { + @Test fun `encode nested map into V8Object with implicit serializers`() = format.v8.evaluateInJSThreadBlocking(runtime) { val map = mapOf( "one" to mapOf("three" to 3), "two" to mapOf("four" to 4), @@ -55,7 +56,7 @@ internal class J2V8FormatTest : J2V8Test() { assertEquals(V8.getUndefined(), v8Object.get("five")) } - @Test fun `encode serializable into V8Object with implicit serializers`() = format.v8.blockingLock { + @Test fun `encode serializable into V8Object with implicit serializers`() = format.v8.evaluateInJSThreadBlocking(runtime) { @Serializable data class Simple( val one: Int = 1, @@ -70,7 +71,7 @@ internal class J2V8FormatTest : J2V8Test() { assertEquals(V8.getUndefined(), v8Object.get("three")) } - @Test fun `decode V8Object into serializable with implicit serializers`() = format.v8.blockingLock { + @Test fun `decode V8Object into serializable with implicit serializers`() = format.v8.evaluateInJSThreadBlocking(runtime) { @Serializable data class Simple( val one: Int = 1, @@ -82,16 +83,16 @@ internal class J2V8FormatTest : J2V8Test() { V8Object(this).apply { add("one", 3) add("two", 4) - } + }, ) assertEquals(3, simple.one) assertEquals(4, simple.two) } - @Test fun `decode into Node backed serializable`() = format.v8.blockingLock { + @Test fun `decode into Node backed serializable`() = format.v8.evaluateInJSThreadBlocking(runtime) { data class Simple(override val node: Node) : NodeWrapper { - fun increment(value: Int) = node.getFunction("increment")!!(value) + fun increment(value: Int) = node.getInvokable("increment")!!(value) } val simple = format.decodeFromRuntimeValue( @@ -101,15 +102,15 @@ internal class J2V8FormatTest : J2V8Test() { "increment", V8Function(format) { args -> args.getInteger(0) + 1 - } + }, ) - } + }, ) assertEquals(1, simple.increment(0)) } - @Test fun `decode function into data class`() = format.v8.blockingLock { + @Test fun `decode function into data class`() = format.v8.evaluateInJSThreadBlocking(runtime) { @Serializable data class Data( val one: Int = 1, @@ -124,9 +125,9 @@ internal class J2V8FormatTest : J2V8Test() { "increment", V8Function(format) { args -> args.getInteger(0) + 1 - } + }, ) - } + }, ) assertEquals(3, simple.one) diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/serializers/ThrowableSerializerTest.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/serializers/ThrowableSerializerTest.kt similarity index 77% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/serializers/ThrowableSerializerTest.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/serializers/ThrowableSerializerTest.kt index d89804550..2f87b3c3c 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/bridge/serialization/serializers/ThrowableSerializerTest.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/bridge/serialization/serializers/ThrowableSerializerTest.kt @@ -1,12 +1,13 @@ -package com.intuit.player.jvm.j2v8.bridge.serialization.serializers +package com.intuit.playerui.j2v8.bridge.serialization.serializers import com.eclipsesource.v8.V8Object -import com.intuit.player.jvm.core.bridge.serialization.serializers.ThrowableSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.ThrowableSerializer.SerializableStackTraceElement -import com.intuit.player.jvm.core.player.PlayerException -import com.intuit.player.jvm.j2v8.base.J2V8Test -import com.intuit.player.jvm.j2v8.bridge.serialization.format.decodeFromV8Value -import com.intuit.player.jvm.j2v8.extensions.blockingLock +import com.intuit.playerui.core.bridge.serialization.serializers.ThrowableSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.ThrowableSerializer.SerializableStackTraceElement +import com.intuit.playerui.core.player.PlayerException +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.j2v8.bridge.serialization.format.decodeFromV8Value +import com.intuit.playerui.j2v8.extensions.evaluateInJSThreadBlocking +import com.intuit.playerui.utils.normalizeStackTraceElements import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -18,7 +19,7 @@ internal class ThrowableSerializerTest : J2V8Test() { @Test fun `JS Error is deserialized as PlayerException (using regex)`() { - val error = format.v8.blockingLock { + val error = format.v8.evaluateInJSThreadBlocking(runtime) { executeObjectScript("""(new Error("hello"))""") } val exception = format.decodeFromRuntimeValue(ThrowableSerializer(), error) @@ -27,10 +28,10 @@ internal class ThrowableSerializerTest : J2V8Test() { exception as PlayerException assertEquals("Error: hello", exception.message) assertEquals( - """com.intuit.player.jvm.core.bridge.JSErrorException: Error: hello + """com.intuit.playerui.core.bridge.JSErrorException: Error: hello at .(:1) """, - exception.stackTraceToString() + exception.stackTraceToString(), ) } @@ -45,7 +46,7 @@ internal class ThrowableSerializerTest : J2V8Test() { className, methodName, fileName, - lineNumber + lineNumber, ) val exception = PlayerException("world") @@ -55,7 +56,7 @@ internal class ThrowableSerializerTest : J2V8Test() { assertTrue(error is V8Object) error as V8Object - error.blockingLock { + error.evaluateInJSThreadBlocking(runtime) { assertEquals("world", error.get("message")) assertEquals(exception.stackTraceToString(), error.getString("stack")) @@ -64,7 +65,7 @@ internal class ThrowableSerializerTest : J2V8Test() { format.encodeToRuntimeValue( SerializableStackTraceElement.serializer(), serializableStackTraceElement, - ).jsEquals(error.getArray("stackTrace").getObject(0)) + ).jsEquals(error.getArray("stackTrace").getObject(0)), ) } } @@ -84,7 +85,7 @@ internal class ThrowableSerializerTest : J2V8Test() { assertTrue(exception is PlayerException) exception as PlayerException assertEquals("hello world", exception.message) - assertEquals(listOf(stackTraceElement), exception.stackTrace.toList()) + assertEquals(arrayOf(stackTraceElement).normalizeStackTraceElements(), exception.stackTrace.normalizeStackTraceElements()) exception.printStackTrace() } @@ -99,14 +100,14 @@ internal class ThrowableSerializerTest : J2V8Test() { className, methodName, fileName, - lineNumber + lineNumber, ) val exception = PlayerException( "hello", PlayerException("world").apply { stackTrace = arrayOf(stackTraceElement) - } + }, ).apply { stackTrace = arrayOf(stackTraceElement) } @@ -116,7 +117,7 @@ internal class ThrowableSerializerTest : J2V8Test() { assertTrue(error is V8Object) error as V8Object - error.blockingLock { + error.evaluateInJSThreadBlocking(runtime) { assertEquals("hello", error.get("message")) assertEquals(exception.stackTraceToString(), error.getString("stack")) @@ -125,12 +126,12 @@ internal class ThrowableSerializerTest : J2V8Test() { format.encodeToRuntimeValue( SerializableStackTraceElement.serializer(), serializableStackTraceElement, - ).jsEquals(error.getArray("stackTrace").getObject(0)) + ).jsEquals(error.getArray("stackTrace").getObject(0)), ) val cause = format.decodeFromV8Value(error.getObject("cause")) assertEquals("world", cause.message) - assertEquals(exception.cause!!.stackTrace.toList(), cause.stackTrace.toList()) + assertEquals(exception.cause!!.stackTrace.normalizeStackTraceElements(), cause.stackTrace.normalizeStackTraceElements()) } } } diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/extensions/InvokeTest.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/extensions/InvokeTest.kt new file mode 100644 index 000000000..201578a4c --- /dev/null +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/extensions/InvokeTest.kt @@ -0,0 +1,28 @@ +package com.intuit.playerui.j2v8.extensions + +import com.eclipsesource.v8.V8 +import com.eclipsesource.v8.V8Function +import com.intuit.playerui.j2v8.V8Function +import com.intuit.playerui.j2v8.base.J2V8Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +internal class InvokeTest : J2V8Test() { + + @Test + fun `test V8Function vararg invoke`() { + v8.evaluateInJSThreadBlocking(runtime) { + val func = v8.executeObjectScript("""(function(a,b){return a+b})""") as V8Function + assertEquals(4, func.call(v8, format.args(2, 2))) + assertEquals(4, func.invoke(format, 2, 2)) + } + } + + @Test + fun `test V8Function creation helper`() { + v8.evaluateInJSThreadBlocking(runtime) { + assertEquals(V8.getUndefined(), V8Function(format) { Unit }(this@InvokeTest.format)) + assertEquals(4, V8Function(format) { 2 + 2 }(this@InvokeTest.format)) + } + } +} diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/extensions/UnlockTest.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/extensions/UnlockTest.kt similarity index 57% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/extensions/UnlockTest.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/extensions/UnlockTest.kt index 0c41ee593..d950a938c 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/extensions/UnlockTest.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/extensions/UnlockTest.kt @@ -1,6 +1,8 @@ -package com.intuit.player.jvm.j2v8.extensions +package com.intuit.playerui.j2v8.extensions -import com.intuit.player.jvm.j2v8.base.J2V8Test +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.utils.test.runBlockingTest +import kotlinx.coroutines.withContext import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertThrows @@ -16,14 +18,18 @@ internal class UnlockTest : J2V8Test() { } @Test - fun `unlock when no thread has the lock`() { - assertNull(v8.locker.thread) - v8.unlock() + fun `unlock when no thread has the lock`() = runBlockingTest { + withContext(runtime.dispatcher) { + v8.unlock() + } assertNull(v8.locker.thread) } @Test - fun `unlock when current thread has the lock`() { + fun `unlock when current thread has the lock`() = runBlockingTest { + withContext(runtime.dispatcher) { + v8.unlock() + } v8.locker.acquire() assertNotNull(v8.locker.thread) v8.unlock() diff --git a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/promise/V8PromiseTest.kt b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/promise/V8PromiseTest.kt similarity index 65% rename from jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/promise/V8PromiseTest.kt rename to jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/promise/V8PromiseTest.kt index 3cf1442dd..fa89dc8cf 100644 --- a/jvm/j2v8/src/test/kotlin/com/intuit/player/jvm/j2v8/promise/V8PromiseTest.kt +++ b/jvm/j2v8/src/test/kotlin/com/intuit/playerui/j2v8/promise/V8PromiseTest.kt @@ -1,13 +1,13 @@ -package com.intuit.player.jvm.j2v8.promise +package com.intuit.playerui.j2v8.promise -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.Promise -import com.intuit.player.jvm.j2v8.base.AutoAcquireJ2V8Test -import com.intuit.player.jvm.utils.test.PromiseUtils +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.Promise +import com.intuit.playerui.j2v8.base.J2V8Test +import com.intuit.playerui.utils.test.PromiseUtils import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -internal class V8PromiseTest : AutoAcquireJ2V8Test(), PromiseUtils { +internal class V8PromiseTest : J2V8Test(), PromiseUtils { override val thenChain = mutableListOf() override val catchChain = mutableListOf() @@ -21,7 +21,7 @@ internal class V8PromiseTest : AutoAcquireJ2V8Test(), PromiseUtils { const promise = new Promise(function(resolve, reject) { asdf.asdf.asdf.asdf }); return [promise, resolver]; })(); - """.trimIndent() + """.trimIndent(), ) as List<*> Promise(promise as Node).thenRecord.catchRecord @@ -33,13 +33,13 @@ internal class V8PromiseTest : AutoAcquireJ2V8Test(), PromiseUtils { val exception = catchChain[0] as Throwable Assertions.assertEquals( - """com.intuit.player.jvm.core.bridge.JSErrorException: ReferenceError: asdf is not defined + """com.intuit.playerui.core.bridge.JSErrorException: ReferenceError: asdf is not defined at .(:3) at .new Promise(Native Method) at .(:3) at .(:5) """, - exception.stackTraceToString() + exception.stackTraceToString(), ) } } diff --git a/jvm/perf/BUILD b/jvm/perf/BUILD index fb7f56015..4caf13e3c 100644 --- a/jvm/perf/BUILD +++ b/jvm/perf/BUILD @@ -4,8 +4,8 @@ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") kt_player_module( name = "perf_tests", main_deps = ["//jvm/testutils"], - main_srcs = ["src/main/kotlin/com/intuit/player/jvm/perf/junit/JSEngineTest.kt"], - test_package = "com.intuit.player.jvm.perf", + main_srcs = ["src/main/kotlin/com/intuit/playerui/perf/junit/JSEngineTest.kt"], + test_package = "com.intuit.playerui.perf", ) java_plugin( @@ -16,7 +16,7 @@ java_plugin( kt_jvm_library( name = "perf_lib", - srcs = ["src/main/kotlin/com/intuit/player/jvm/perf/jmh/PlayerPerf.kt"], + srcs = ["src/main/kotlin/com/intuit/playerui/perf/jmh/PlayerPerf.kt"], kotlinc_opts = "//jvm:test_options", plugins = [":jmh_annotation_processor"], resources = glob(["src/main/resources/*.json"]), diff --git a/jvm/perf/src/main/kotlin/com/intuit/player/jvm/perf/jmh/PlayerPerf.kt b/jvm/perf/src/main/kotlin/com/intuit/playerui/perf/jmh/PlayerPerf.kt similarity index 86% rename from jvm/perf/src/main/kotlin/com/intuit/player/jvm/perf/jmh/PlayerPerf.kt rename to jvm/perf/src/main/kotlin/com/intuit/playerui/perf/jmh/PlayerPerf.kt index 91501a951..379774ad3 100644 --- a/jvm/perf/src/main/kotlin/com/intuit/player/jvm/perf/jmh/PlayerPerf.kt +++ b/jvm/perf/src/main/kotlin/com/intuit/playerui/perf/jmh/PlayerPerf.kt @@ -1,19 +1,27 @@ -package com.intuit.player.jvm.perf.jmh - -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.Promise -import com.intuit.player.jvm.core.bridge.runtime.PlayerRuntimeFactory -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.runtime.runtimeContainers -import com.intuit.player.jvm.core.data.DataController -import com.intuit.player.jvm.core.data.set -import com.intuit.player.jvm.core.player.HeadlessPlayer +package com.intuit.playerui.perf.jmh + +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.Promise +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeFactory +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.runtimeContainers +import com.intuit.playerui.core.data.DataController +import com.intuit.playerui.core.data.set +import com.intuit.playerui.core.player.HeadlessPlayer import kotlinx.coroutines.runBlocking import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable -import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.BenchmarkMode +import org.openjdk.jmh.annotations.CompilerControl +import org.openjdk.jmh.annotations.Mode +import org.openjdk.jmh.annotations.OutputTimeUnit +import org.openjdk.jmh.annotations.Param +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State import org.openjdk.jmh.infra.Blackhole import java.util.concurrent.TimeUnit import kotlin.coroutines.resume @@ -105,7 +113,7 @@ open class RuntimePlayerCreation : RuntimePerformance() { @CompilerControl(CompilerControl.Mode.DONT_INLINE) @Benchmark fun createHeadlessPlayer(consumer: Blackhole) { - consumer.consume(HeadlessPlayer(runtime = jsRuntime)) + consumer.consume(HeadlessPlayer(explicitRuntime = jsRuntime)) } @CompilerControl(CompilerControl.Mode.DONT_INLINE) @@ -149,7 +157,7 @@ open class HeadlessPlayerFlowPerformance : ContentPerformance() { @CompilerControl(CompilerControl.Mode.DONT_INLINE) @Benchmark fun startFlow(consumer: Blackhole) { - val player = HeadlessPlayer(runtime = jsRuntime) + val player = HeadlessPlayer(explicitRuntime = jsRuntime) consumer.consume(runBlocking { suspendCoroutine { player.hooks.viewController.tap("perf") { vc -> @@ -167,7 +175,7 @@ open class HeadlessPlayerFlowPerformance : ContentPerformance() { @CompilerControl(CompilerControl.Mode.DONT_INLINE) @Benchmark fun startAndUpdateFlow(consumer: Blackhole) { - val player = HeadlessPlayer(runtime = jsRuntime) + val player = HeadlessPlayer(explicitRuntime = jsRuntime) consumer.consume(runBlocking { suspendCoroutine { var dataController: DataController? = null diff --git a/jvm/perf/src/main/kotlin/com/intuit/player/jvm/perf/junit/JSEngineTest.kt b/jvm/perf/src/main/kotlin/com/intuit/playerui/perf/junit/JSEngineTest.kt similarity index 92% rename from jvm/perf/src/main/kotlin/com/intuit/player/jvm/perf/junit/JSEngineTest.kt rename to jvm/perf/src/main/kotlin/com/intuit/playerui/perf/junit/JSEngineTest.kt index ca74694fc..2a24aad45 100644 --- a/jvm/perf/src/main/kotlin/com/intuit/player/jvm/perf/junit/JSEngineTest.kt +++ b/jvm/perf/src/main/kotlin/com/intuit/playerui/perf/junit/JSEngineTest.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.perf.junit +package com.intuit.playerui.perf.junit -import com.intuit.player.jvm.utils.test.RuntimeTest +import com.intuit.playerui.utils.test.RuntimeTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestInfo import java.util.logging.Logger diff --git a/jvm/perf/src/test/kotlin/com/intuit/player/jvm/perf/junit/RuntimePerfTest.kt b/jvm/perf/src/test/kotlin/com/intuit/playerui/perf/junit/RuntimePerfTest.kt similarity index 73% rename from jvm/perf/src/test/kotlin/com/intuit/player/jvm/perf/junit/RuntimePerfTest.kt rename to jvm/perf/src/test/kotlin/com/intuit/playerui/perf/junit/RuntimePerfTest.kt index ad252ac5a..7aae7b506 100644 --- a/jvm/perf/src/test/kotlin/com/intuit/player/jvm/perf/junit/RuntimePerfTest.kt +++ b/jvm/perf/src/test/kotlin/com/intuit/playerui/perf/junit/RuntimePerfTest.kt @@ -1,11 +1,14 @@ -package com.intuit.player.jvm.perf.junit +package com.intuit.playerui.perf.junit -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.runtime.add -import com.intuit.player.jvm.core.player.JSPlayerConfig -import com.intuit.player.jvm.core.plugins.JSPluginWrapper -import com.intuit.player.jvm.core.plugins.Plugin -import org.junit.jupiter.api.Assertions.* +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.add +import com.intuit.playerui.core.player.JSPlayerConfig +import com.intuit.playerui.core.plugins.JSPluginWrapper +import com.intuit.playerui.core.plugins.Plugin +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.TestTemplate internal class RuntimePerfTest : JSEngineTest() { @@ -30,7 +33,7 @@ internal class RuntimePerfTest : JSEngineTest() { val person = runtime.getObject("person") var resultString = "" captureTime { - resultString = runtime.getFunction("getAge")?.invoke(person) ?: "" + resultString = runtime.getInvokable("getAge")?.invoke(person) ?: "" } assert(resultString.isNotEmpty()) assertEquals("Joe is 25 years old", resultString) diff --git a/jvm/pom.tpl b/jvm/pom.tpl new file mode 100644 index 000000000..555726cf3 --- /dev/null +++ b/jvm/pom.tpl @@ -0,0 +1,42 @@ + + + 4.0.0 + + Player - {artifactId} + A cross-platform semantic rendering engine + https://player-ui.github.io + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + + + brocollie08 + Tony Lin + sentony93@gmail.com + + + sugarmanz + Jeremiah Zucker + zucker.jeremiah@gmail.com + + + + https://github.com/player-ui/player-ui.git + https://github.com/player-ui/player-ui.git + v{version} + https://github.com/player-ui/player-ui.git + + + {groupId} + {artifactId} + {version} + {type} + + +{dependencies} + + diff --git a/jvm/testutils/BUILD b/jvm/testutils/BUILD index 54d2b7368..f72a1d117 100644 --- a/jvm/testutils/BUILD +++ b/jvm/testutils/BUILD @@ -5,6 +5,5 @@ kt_player_module( name = "testutils", main_deps = main_deps, main_exports = main_exports, - main_resource_strip_prefix = "plugins/reference-assets/mocks", main_resources = main_resources, ) diff --git a/jvm/testutils/deps.bzl b/jvm/testutils/deps.bzl index d793d524f..35be018fb 100644 --- a/jvm/testutils/deps.bzl +++ b/jvm/testutils/deps.bzl @@ -11,11 +11,10 @@ main_exports = [ "//jvm/j2v8:j2v8-all", "//jvm/graaljs", "//plugins/common-types/jvm:common-types", - "//plugins/reference-assets/jvm:reference-assets", + "//plugins/mocks:jar", ] main_deps = main_exports main_resources = [ - "//plugins/reference-assets/mocks", ] diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/Assertions.kt b/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/Assertions.kt deleted file mode 100644 index 941c82542..000000000 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/Assertions.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.intuit.player.jvm.utils.test - -import com.intuit.player.jvm.core.bridge.runtime.serialize -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat - -public val equals: RuntimeFormat<*>.(first: Any?, second: Any?) -> Boolean = { first, second -> - runtime.serialize(first) == this.runtime.serialize(second) -} diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/testutils/Node.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/testutils/Node.kt similarity index 74% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/testutils/Node.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/testutils/Node.kt index 4ef834afa..ad5925961 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/testutils/Node.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/testutils/Node.kt @@ -1,14 +1,13 @@ -package com.intuit.player.jvm.testutils +package com.intuit.playerui.testutils -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import kotlinx.serialization.ContextualSerializer +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.builtins.nullable import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json import kotlin.reflect.jvm.reflect /** @@ -17,9 +16,9 @@ import kotlin.reflect.jvm.reflect * not follow the requirements for being an asset. Thus, this * should strictly only be used for testing purposes. */ -public class Node(private val map: Map) : com.intuit.player.jvm.core.bridge.Node, Map by map { +public class Node(private val map: Map) : com.intuit.playerui.core.bridge.Node, Map by map { - override fun getFunction(key: String): Invokable? = get(key)?.let { + override fun getInvokable(key: String, deserializationStrategy: DeserializationStrategy): Invokable? = get(key)?.let { when (it) { is Function<*> -> it else -> null @@ -31,13 +30,13 @@ public class Node(private val map: Map) : com.intuit.player.jvm.co } override fun getSerializable(key: String, deserializer: DeserializationStrategy): T = get(key)?.let { value -> - Json.decodeFromJsonElement(deserializer, Json.encodeToJsonElement(ContextualSerializer(Any::class).nullable, value)) + Json.decodeFromJsonElement(deserializer, Json.encodeToJsonElement(GenericSerializer(), value)) }!! override fun deserialize(deserializer: DeserializationStrategy): T = Json.decodeFromJsonElement( deserializer, - Json.encodeToJsonElement(MapSerializer(String.serializer(), ContextualSerializer(Any::class).nullable), map) + Json.encodeToJsonElement(MapSerializer(String.serializer(), GenericSerializer()), map), ) override fun isReleased(): Boolean = false diff --git a/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/Assertions.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/Assertions.kt new file mode 100644 index 000000000..5190ffc3c --- /dev/null +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/Assertions.kt @@ -0,0 +1,8 @@ +package com.intuit.playerui.utils.test + +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat + +public val equals: RuntimeFormat<*>.(first: Any?, second: Any?) -> Boolean = { first, second -> + runtime.serialize(first) == this.runtime.serialize(second) +} diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/PlayerTest.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/PlayerTest.kt similarity index 70% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/PlayerTest.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/PlayerTest.kt index 1f9a05ad4..f53a74c3e 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/PlayerTest.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/PlayerTest.kt @@ -1,11 +1,11 @@ -package com.intuit.player.jvm.utils.test +package com.intuit.playerui.utils.test -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.player.HeadlessPlayer -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.plugins.LoggerPlugin -import com.intuit.player.jvm.core.plugins.Pluggable -import com.intuit.player.jvm.core.plugins.Plugin +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.player.HeadlessPlayer +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.plugins.LoggerPlugin +import com.intuit.playerui.core.plugins.Pluggable +import com.intuit.playerui.core.plugins.Plugin import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestTemplate @@ -28,7 +28,7 @@ public abstract class PlayerTest : RuntimeTest(), Pluggable, LoggerPlugin by Tes /** Helper method for setting a [player] with configurable [plugins] and [runtime] */ public fun setupPlayer(plugins: List = this.plugins + this, runtime: Runtime<*> = this.runtime) { - player = HeadlessPlayer(plugins, runtime) + player = HeadlessPlayer(plugins, runtime, config = runtime.config) } } diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/PlayerTestException.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/PlayerTestException.kt similarity index 65% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/PlayerTestException.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/PlayerTestException.kt index cdd89b041..39568ee8e 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/PlayerTestException.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/PlayerTestException.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.utils.test +package com.intuit.playerui.utils.test -import com.intuit.player.jvm.core.player.PlayerException +import com.intuit.playerui.core.player.PlayerException /** Exception denoting an issue during [PlayerTest] setup or teardown */ public class PlayerTestException(message: String, cause: Throwable? = null) : PlayerException(message, cause) diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/PromiseUtils.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/PromiseUtils.kt similarity index 89% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/PromiseUtils.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/PromiseUtils.kt index 7c160ccdc..9802ec921 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/PromiseUtils.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/PromiseUtils.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.utils.test +package com.intuit.playerui.utils.test -import com.intuit.player.jvm.core.bridge.Promise -import com.intuit.player.jvm.core.bridge.then +import com.intuit.playerui.core.bridge.Promise +import com.intuit.playerui.core.bridge.then import kotlinx.serialization.DeserializationStrategy import org.junit.jupiter.api.Assertions @@ -12,7 +12,7 @@ public interface PromiseUtils { public fun assertThen(vararg expected: Any?): Unit = assertChain( thenChain, - *expected + *expected, ) public fun assertCatch(vararg expected: Any?): Unit = assertChain( catchChain.map { @@ -21,7 +21,7 @@ public interface PromiseUtils { else -> it } }, - *expected + *expected, ) public fun assertChain(chain: List, vararg expected: Any?): Unit = Assertions.assertEquals(expected.asList(), chain) diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/runBlockingTest.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/RunBlockingTest.kt similarity index 92% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/runBlockingTest.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/RunBlockingTest.kt index be116d993..a17e9509a 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/runBlockingTest.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/RunBlockingTest.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.utils.test +package com.intuit.playerui.utils.test import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/RuntimePluginTest.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/RuntimePluginTest.kt similarity index 86% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/RuntimePluginTest.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/RuntimePluginTest.kt index 5e3e9c942..5ad9dd81a 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/RuntimePluginTest.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/RuntimePluginTest.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.utils.test +package com.intuit.playerui.utils.test -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.plugins.RuntimePlugin +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.plugins.RuntimePlugin import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestTemplate diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/RuntimeTest.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/RuntimeTest.kt similarity index 70% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/RuntimeTest.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/RuntimeTest.kt index 92240cb4c..96de9aa0c 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/RuntimeTest.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/RuntimeTest.kt @@ -1,10 +1,18 @@ -package com.intuit.player.jvm.utils.test +package com.intuit.playerui.utils.test -import com.intuit.player.jvm.core.bridge.runtime.* -import com.intuit.player.jvm.core.bridge.serialization.format.RuntimeFormat +import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeContainer +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.runtimeContainers +import com.intuit.playerui.core.bridge.runtime.runtimeFactory +import com.intuit.playerui.core.bridge.serialization.format.RuntimeFormat import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestTemplate -import org.junit.jupiter.api.extension.* +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extension +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.TestTemplateInvocationContext +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider /** * Simple base test class for testing each [Runtime] implementation on the classpath. Tests should be diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/TestLogger.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/TestLogger.kt similarity index 83% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/TestLogger.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/TestLogger.kt index bd9281d02..f5636ec72 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/TestLogger.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/TestLogger.kt @@ -1,6 +1,6 @@ -package com.intuit.player.jvm.utils.test +package com.intuit.playerui.utils.test -import com.intuit.player.jvm.core.plugins.LoggerPlugin +import com.intuit.playerui.core.plugins.LoggerPlugin public object TestLogger : LoggerPlugin { override fun trace(vararg args: Any?): Unit = print("TRACE", *args) diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/testMocks.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/TestMocks.kt similarity index 72% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/testMocks.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/TestMocks.kt index 14b4ce02e..d32b1502e 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/testMocks.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/TestMocks.kt @@ -1,8 +1,8 @@ -package com.intuit.player.jvm.utils.test +package com.intuit.playerui.utils.test -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.utils.mocks.ClassLoaderMock -import com.intuit.player.jvm.utils.mocks.ClassLoaderMocksReader +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.utils.mocks.ClassLoaderMock +import com.intuit.playerui.utils.mocks.ClassLoaderMocksReader import kotlinx.serialization.json.Json public val ClassLoaderMocksReader.simpleMock: ClassLoaderMock diff --git a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/ThreadUtils.kt b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/ThreadUtils.kt similarity index 93% rename from jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/ThreadUtils.kt rename to jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/ThreadUtils.kt index 01ddc2908..e89ec3b18 100644 --- a/jvm/testutils/src/main/kotlin/com/intuit/player/jvm/utils/test/ThreadUtils.kt +++ b/jvm/testutils/src/main/kotlin/com/intuit/playerui/utils/test/ThreadUtils.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.utils.test +package com.intuit.playerui.utils.test public interface ThreadUtils { diff --git a/jvm/utils/BUILD b/jvm/utils/BUILD index ad3c74383..797bfe8a7 100644 --- a/jvm/utils/BUILD +++ b/jvm/utils/BUILD @@ -5,5 +5,5 @@ kt_player_module( name = "utils", main_deps = main_deps, main_resources = ["//core/make-flow:MakeFlow_Bundles"], - test_package = "com.intuit.player.jvm.utils", + test_package = "com.intuit.playerui.utils", ) diff --git a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/json.kt b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/Json.kt similarity index 80% rename from jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/json.kt rename to jvm/utils/src/main/kotlin/com/intuit/playerui/utils/Json.kt index 49f2eafc2..19ee5cf5e 100644 --- a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/json.kt +++ b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/Json.kt @@ -1,6 +1,12 @@ -package com.intuit.player.jvm.utils +package com.intuit.playerui.utils -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject internal val JsonElement.safeJsonObject: JsonObject? get() = try { jsonObject } catch (e: IllegalArgumentException) { null } internal val JsonElement.safeJsonArray: JsonArray? get() = try { jsonArray } catch (e: IllegalArgumentException) { null } diff --git a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/makeFlow.kt b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/MakeFlow.kt similarity index 71% rename from jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/makeFlow.kt rename to jvm/utils/src/main/kotlin/com/intuit/playerui/utils/MakeFlow.kt index e6d6b5e87..93da92198 100644 --- a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/makeFlow.kt +++ b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/MakeFlow.kt @@ -1,10 +1,11 @@ -package com.intuit.player.jvm.utils - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.runtime.runtimeFactory -import com.intuit.player.jvm.core.bridge.toJson -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper +package com.intuit.playerui.utils + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.runtimeFactory +import com.intuit.playerui.core.bridge.toJson +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement @@ -18,7 +19,7 @@ public open class MakeFlowModule internal constructor() : JSScriptPluginWrapper( public fun makeFlow(flow: Node): JsonElement { ensureInitialized() - return instance.getFunction("makeFlow").let(::requireNotNull)(flow).toJson() + return instance.getInvokable("makeFlow").let(::requireNotNull)(flow).toJson() } public fun makeFlow(flow: String): JsonElement { diff --git a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/player.kt b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/Player.kt similarity index 73% rename from jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/player.kt rename to jvm/utils/src/main/kotlin/com/intuit/playerui/utils/Player.kt index e2dc43d67..4e5b371bf 100644 --- a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/player.kt +++ b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/Player.kt @@ -1,10 +1,10 @@ -package com.intuit.player.jvm.utils +package com.intuit.playerui.utils -import com.intuit.player.jvm.core.bridge.Completable -import com.intuit.player.jvm.core.experimental.ExperimentalPlayerApi -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.player.state.CompletedState +import com.intuit.playerui.core.bridge.Completable +import com.intuit.playerui.core.experimental.ExperimentalPlayerApi +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.player.state.CompletedState import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement diff --git a/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/StackTraceElements.kt b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/StackTraceElements.kt new file mode 100644 index 000000000..fb5c07227 --- /dev/null +++ b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/StackTraceElements.kt @@ -0,0 +1,15 @@ +package com.intuit.playerui.utils + +import com.intuit.playerui.core.bridge.serialization.serializers.ThrowableSerializer.SerializableStackTraceElement +import com.intuit.playerui.core.utils.InternalPlayerApi + +/** Make a collection of [StackTraceElement]s subject to element equality */ +@InternalPlayerApi +public fun Array.normalizeStackTraceElements(): List = map { stackTraceElement -> + SerializableStackTraceElement( + stackTraceElement.className, + stackTraceElement.methodName, + stackTraceElement.fileName, + stackTraceElement.lineNumber, + ) +} diff --git a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/mocks/ClassLoaderMock.kt b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/mocks/ClassLoaderMock.kt similarity index 90% rename from jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/mocks/ClassLoaderMock.kt rename to jvm/utils/src/main/kotlin/com/intuit/playerui/utils/mocks/ClassLoaderMock.kt index 67a828e65..b2b848b1e 100644 --- a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/mocks/ClassLoaderMock.kt +++ b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/mocks/ClassLoaderMock.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.utils.mocks +package com.intuit.playerui.utils.mocks import kotlinx.serialization.Serializable @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable public data class ClassLoaderMock( override val group: String, override val name: String, - override val path: String + override val path: String, ) : Mock { /** Helper to provide default [ClassLoader] overload of [read] */ diff --git a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/mocks/ClassLoaderMocksReader.kt b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/mocks/ClassLoaderMocksReader.kt similarity index 76% rename from jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/mocks/ClassLoaderMocksReader.kt rename to jvm/utils/src/main/kotlin/com/intuit/playerui/utils/mocks/ClassLoaderMocksReader.kt index a56dc2049..f1d9eb4b9 100644 --- a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/mocks/ClassLoaderMocksReader.kt +++ b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/mocks/ClassLoaderMocksReader.kt @@ -1,10 +1,13 @@ -package com.intuit.player.jvm.utils.mocks +package com.intuit.playerui.utils.mocks import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json /** Utility class to catalogue the published mocks bundled in a player plugin mocks package */ -public class ClassLoaderMocksReader(private val classLoader: ClassLoader = ClassLoader.getSystemClassLoader()) { +public class ClassLoaderMocksReader( + private val classLoader: ClassLoader = ClassLoader.getSystemClassLoader(), + private val manifestPath: String = "mocks/manifest.json", +) { /** * Manifest is a JSON blob published by the player plugin mocks package declaring @@ -12,7 +15,7 @@ public class ClassLoaderMocksReader(private val classLoader: ClassLoader = Class * will read the manifest on demand and default to an empty list if not found. */ public val manifest: String by lazy { - classLoader.getResource("manifest.json")?.readText() ?: "[]" + classLoader.getResource(manifestPath)?.readText() ?: "[]" } /** diff --git a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/mocks/Mock.kt b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/mocks/Mock.kt similarity index 87% rename from jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/mocks/Mock.kt rename to jvm/utils/src/main/kotlin/com/intuit/playerui/utils/mocks/Mock.kt index c2018ebbe..28cd0ed4e 100644 --- a/jvm/utils/src/main/kotlin/com/intuit/player/jvm/utils/mocks/Mock.kt +++ b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/mocks/Mock.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.utils.mocks +package com.intuit.playerui.utils.mocks -import com.intuit.player.jvm.utils.makeFlow -import com.intuit.player.jvm.utils.stringify +import com.intuit.playerui.utils.makeFlow +import com.intuit.playerui.utils.stringify /** * Utility interface to standardize how to read "mocks". Simply put, a mock diff --git a/jvm/utils/src/test/kotlin/com/intuit/player/jvm/utils/MakeFlowTests.kt b/jvm/utils/src/test/kotlin/com/intuit/playerui/utils/MakeFlowTests.kt similarity index 91% rename from jvm/utils/src/test/kotlin/com/intuit/player/jvm/utils/MakeFlowTests.kt rename to jvm/utils/src/test/kotlin/com/intuit/playerui/utils/MakeFlowTests.kt index 07f106a3c..05d6f1a56 100644 --- a/jvm/utils/src/test/kotlin/com/intuit/player/jvm/utils/MakeFlowTests.kt +++ b/jvm/utils/src/test/kotlin/com/intuit/playerui/utils/MakeFlowTests.kt @@ -1,4 +1,4 @@ -package com.intuit.player.jvm.utils +package com.intuit.playerui.utils import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject @@ -15,7 +15,7 @@ class MakeFlowTests { buildJsonObject { put("id", "some-id") put("type", "some-type") - } + }, ) assertTrue(flow is JsonObject) @@ -27,7 +27,7 @@ class MakeFlowTests { "data", "navigation", ), - keys + keys, ) } } diff --git a/package.json b/package.json index 94265b0b9..9ad842f84 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "@emotion/styled": "^11", "@mdx-js/loader": "^1.6.22", "@monaco-editor/react": "^4.6.0", - "@player-tools/cli": "0.5.0--canary.62.1176", - "@player-tools/dsl": "0.5.0--canary.62.1176", + "@player-tools/cli": "0.5.2", + "@player-tools/dsl": "0.5.2", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", diff --git a/patches/BUILD b/patches/BUILD new file mode 100644 index 000000000..6f665d778 --- /dev/null +++ b/patches/BUILD @@ -0,0 +1,3 @@ +exports_files([ + "rules_jvm_external.default_public_visibility.patch", +]) diff --git a/patches/rules_jvm_external.default_public_visibility.patch b/patches/rules_jvm_external.default_public_visibility.patch new file mode 100644 index 000000000..e82ecd881 --- /dev/null +++ b/patches/rules_jvm_external.default_public_visibility.patch @@ -0,0 +1,13 @@ +diff --git coursier.bzl coursier.bzl +index a7c5f30..57977ae 100644 +--- coursier.bzl ++++ coursier.bzl +@@ -28,7 +28,7 @@ load("//private/rules:v1_lock_file.bzl", "v1_lock_file") + load("//private/rules:v2_lock_file.bzl", "v2_lock_file") + + _BUILD = """ +-# package(default_visibility = [{visibilities}]) # https://github.com/bazelbuild/bazel/issues/13681 ++package(default_visibility = [{visibilities}]) # https://github.com/bazelbuild/bazel/issues/13681 + + load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + load("@rules_jvm_external//private/rules:jvm_import.bzl", "jvm_import") diff --git a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift index acdab6af9..6b09eada6 100644 --- a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift +++ b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift @@ -78,7 +78,7 @@ open class BaseBeaconPlugin: JSBasePlugin { name: fileName, ext: "js", bundle: Bundle(for: BaseBeaconPlugin.self), - pathComponent: "BaseBeaconPlugin.bundle" + pathComponent: "PlayerUI_BaseBeaconPlugin.bundle" ) #endif } diff --git a/plugins/beacon/jvm/BUILD b/plugins/beacon/jvm/BUILD index 219c4eec1..f4552af80 100644 --- a/plugins/beacon/jvm/BUILD +++ b/plugins/beacon/jvm/BUILD @@ -6,5 +6,5 @@ # main_deps = main_deps, # main_exports = main_exports, # main_resources = main_resources, -# test_package = "com.intuit.player.plugins.beacon", +# test_package = "com.intuit.playerui.plugins.beacon", # ) diff --git a/plugins/beacon/jvm/README.md b/plugins/beacon/jvm/README.md index 7247ad452..5b6c7d6ab 100644 --- a/plugins/beacon/jvm/README.md +++ b/plugins/beacon/jvm/README.md @@ -2,4 +2,4 @@ ## Gradle Dependency -`implementation("com.intuit.player.jvm.plugins:beacon:$VERSION")` +`implementation("com.intuit.playerui.plugins:beacon:$VERSION")` diff --git a/plugins/beacon/jvm/src/main/kotlin/com/intuit/player/plugins/beacon/BeaconPlugin.kt b/plugins/beacon/jvm/src/main/kotlin/com/intuit/playerui/plugins/beacon/BeaconPlugin.kt similarity index 73% rename from plugins/beacon/jvm/src/main/kotlin/com/intuit/player/plugins/beacon/BeaconPlugin.kt rename to plugins/beacon/jvm/src/main/kotlin/com/intuit/playerui/plugins/beacon/BeaconPlugin.kt index c08895584..664121c5b 100644 --- a/plugins/beacon/jvm/src/main/kotlin/com/intuit/player/plugins/beacon/BeaconPlugin.kt +++ b/plugins/beacon/jvm/src/main/kotlin/com/intuit/playerui/plugins/beacon/BeaconPlugin.kt @@ -1,15 +1,17 @@ -package com.intuit.player.plugins.beacon +package com.intuit.playerui.plugins.beacon -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.runtime.add -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.plugins.JSPluginWrapper -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper -import com.intuit.player.jvm.core.plugins.Pluggable -import com.intuit.player.jvm.core.plugins.findPlugin -import com.intuit.player.plugins.settimeout.SetTimeoutPlugin +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.ScriptContext +import com.intuit.playerui.core.bridge.runtime.add +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.plugins.JSPluginWrapper +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper +import com.intuit.playerui.core.plugins.Pluggable +import com.intuit.playerui.core.plugins.findPlugin +import com.intuit.playerui.plugins.settimeout.SetTimeoutPlugin import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -41,7 +43,7 @@ public class BeaconPlugin(override val plugins: List) : JSScrip } } - runtime.execute(script) + runtime.load(ScriptContext(if (runtime.config.debuggable) debugScript else script, bundledSourcePath)) runtime.add("beaconOptions", config) instance = runtime.buildInstance("(new $name.$name(beaconOptions))") } @@ -53,15 +55,16 @@ public class BeaconPlugin(override val plugins: List) : JSScrip handlers.add(handler) } + // TODO: Convert to suspend method to ensure view scope is captured in a non-blocking way /** Fire a beacon event */ public fun beacon(action: String, element: String, asset: Asset, data: Any? = null) { - instance.getFunction("beacon")!!.invoke( + instance.getInvokable("beacon")!!.invoke( mapOf( "action" to action, "element" to element, "asset" to asset, "data" to data, - ) + ), ) } @@ -73,7 +76,7 @@ public class BeaconPlugin(override val plugins: List) : JSScrip @Serializable internal data class JSBeaconPluginConfig( val plugins: List, - val callback: (@Contextual Any?) -> Unit + val callback: (@Contextual Any?) -> Unit, ) } diff --git a/plugins/beacon/jvm/src/test/kotlin/com/intuit/player/plugins/beacon/BeaconPluginTest.kt b/plugins/beacon/jvm/src/test/kotlin/com/intuit/playerui/plugins/beacon/BeaconPluginTest.kt similarity index 87% rename from plugins/beacon/jvm/src/test/kotlin/com/intuit/player/plugins/beacon/BeaconPluginTest.kt rename to plugins/beacon/jvm/src/test/kotlin/com/intuit/playerui/plugins/beacon/BeaconPluginTest.kt index d5addcfed..ddbdb4e3c 100644 --- a/plugins/beacon/jvm/src/test/kotlin/com/intuit/player/plugins/beacon/BeaconPluginTest.kt +++ b/plugins/beacon/jvm/src/test/kotlin/com/intuit/playerui/plugins/beacon/BeaconPluginTest.kt @@ -1,17 +1,21 @@ -package com.intuit.player.plugins.beacon - -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.runtime.serialize -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper -import com.intuit.player.jvm.utils.test.RuntimePluginTest -import com.intuit.player.jvm.utils.test.runBlockingTest +package com.intuit.playerui.plugins.beacon + +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.runtime.serialize +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper +import com.intuit.playerui.utils.test.RuntimePluginTest +import com.intuit.playerui.utils.test.runBlockingTest import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import org.amshove.kluent.`should be instance of` -import org.amshove.kluent.`should not be null` import org.amshove.kluent.`should not be` +import org.amshove.kluent.`should not be null` import org.amshove.kluent.shouldBe import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.TestTemplate diff --git a/plugins/beacon/mocks/beacon/beacon-action.json b/plugins/beacon/mocks/beacon/beacon-action.json new file mode 100644 index 000000000..e69eb4830 --- /dev/null +++ b/plugins/beacon/mocks/beacon/beacon-action.json @@ -0,0 +1,43 @@ +{ + "id": "generated-flow", + "views": [ + { + "id": "action", + "type": "action", + "exp": [ + "{{count}} = {{count}} + 1", + "beacon('action', 'some-data')" + ], + "metaData": { + "beacon": "Click count: {{count}}" + }, + "label": { + "asset": { + "id": "action-label-text", + "type": "text", + "value": "Clicked {{count}} times" + } + } + } + ], + "data": { + "count": 0 + }, + "navigation": { + "BEGIN": "FLOW_1", + "FLOW_1": { + "startState": "VIEW_1", + "VIEW_1": { + "state_type": "VIEW", + "ref": "action", + "transitions": { + "*": "END_Done" + } + }, + "END_Done": { + "state_type": "END", + "outcome": "done" + } + } + } +} \ No newline at end of file diff --git a/plugins/build.bzl b/plugins/build.bzl index 5bcffff8b..5bef458fd 100644 --- a/plugins/build.bzl +++ b/plugins/build.bzl @@ -1,4 +1,4 @@ -# load("//jvm:build.bzl", _kt_player_module = "kt_player_module") +# load("//jvm:build.bzl", "DEFAULT_GROUP", _kt_player_module = "kt_player_module") # load("//jvm/dependencies:common.bzl", "test_deps") # load("@bazel_skylib//lib:dicts.bzl", "dicts") @@ -11,14 +11,6 @@ # # Project level config # include_common_deps = True, -# # Distribution config - -# # (optional) TODO: Maybe hardcode these -# project_name = None, -# project_description = None, -# project_url = None, -# scm_url = None, - # # Package level config # module_name = None, # main_srcs = None, @@ -40,13 +32,7 @@ # _kt_player_module( # name = name, # include_common_deps = include_common_deps, -# group = "com.intuit.player.plugins", - -# # (optional) TODO: Maybe hardcode these -# project_name = project_name, -# project_description = project_description, -# project_url = project_url, -# scm_url = scm_url, +# group = DEFAULT_GROUP + ".plugins", # # Package level config # module_name = module_name, @@ -83,7 +69,7 @@ # project_description = None, # project_url = None, # scm_url = None): -# package = "com.intuit.player.plugins.%s" % package_scope +# package = "com.intuit.playerui.plugins.%s" % package_scope # generate_plugin_wrapper( # name = "%s-gen" % name, # package = package, diff --git a/plugins/check-path/ios/Sources/CheckPathPlugin.swift b/plugins/check-path/ios/Sources/CheckPathPlugin.swift index cd359025d..626f529d1 100644 --- a/plugins/check-path/ios/Sources/CheckPathPlugin.swift +++ b/plugins/check-path/ios/Sources/CheckPathPlugin.swift @@ -64,7 +64,7 @@ open class BaseCheckPathPlugin: JSBasePlugin { #if SWIFT_PACKAGE ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module) #else - ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: BaseCheckPathPlugin.self), pathComponent: "CheckPathPlugin.bundle") + ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: BaseCheckPathPlugin.self), pathComponent: "PlayerUI_CheckPathPlugin.bundle") #endif } } diff --git a/plugins/check-path/jvm/BUILD b/plugins/check-path/jvm/BUILD index d421aea67..c084de1d5 100644 --- a/plugins/check-path/jvm/BUILD +++ b/plugins/check-path/jvm/BUILD @@ -6,5 +6,5 @@ # main_deps = main_deps, # main_exports = main_exports, # main_resources = main_resources, -# test_package = "com.intuit.player.plugins.checkpath", +# test_package = "com.intuit.playerui.plugins.checkpath", # ) diff --git a/plugins/check-path/jvm/src/main/kotlin/CheckPathPlugin.kt b/plugins/check-path/jvm/src/main/kotlin/com/intuit/playerui/plugins/checkpath/CheckPathPlugin.kt similarity index 62% rename from plugins/check-path/jvm/src/main/kotlin/CheckPathPlugin.kt rename to plugins/check-path/jvm/src/main/kotlin/com/intuit/playerui/plugins/checkpath/CheckPathPlugin.kt index 911cf0ca4..443f87a2d 100644 --- a/plugins/check-path/jvm/src/main/kotlin/CheckPathPlugin.kt +++ b/plugins/check-path/jvm/src/main/kotlin/com/intuit/playerui/plugins/checkpath/CheckPathPlugin.kt @@ -1,15 +1,16 @@ -package com.intuit.player.plugins.checkpath +package com.intuit.playerui.plugins.checkpath -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper -import com.intuit.player.jvm.core.plugins.findPlugin +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper +import com.intuit.playerui.core.plugins.findPlugin /** Reduced wrapper of the core check-path plugin for relative asset querying */ public class CheckPathPlugin : JSScriptPluginWrapper(pluginName, sourcePath = bundledSourcePath) { /** Get the [Asset] represented by the [id] */ - public fun getAsset(id: String): Asset? = instance.getFunction("getAsset")?.invoke(id) as? Asset + public fun getAsset(id: String): Asset? = instance.getInvokable("getAsset")?.invoke(id) as? Asset private companion object { private const val bundledSourcePath = "plugins/check-path/core/dist/check-path-plugin.prod.js" diff --git a/plugins/check-path/jvm/src/test/kotlin/CheckPathPluginTest.kt b/plugins/check-path/jvm/src/test/kotlin/com/intuit/playerui/plugins/checkpath/CheckPathPluginTest.kt similarity index 77% rename from plugins/check-path/jvm/src/test/kotlin/CheckPathPluginTest.kt rename to plugins/check-path/jvm/src/test/kotlin/com/intuit/playerui/plugins/checkpath/CheckPathPluginTest.kt index e6b681c83..300fbf570 100644 --- a/plugins/check-path/jvm/src/test/kotlin/CheckPathPluginTest.kt +++ b/plugins/check-path/jvm/src/test/kotlin/com/intuit/playerui/plugins/checkpath/CheckPathPluginTest.kt @@ -1,7 +1,7 @@ -package com.intuit.player.plugins.checkpath +package com.intuit.playerui.plugins.checkpath -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.utils.test.simpleFlowString +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.simpleFlowString import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestTemplate diff --git a/plugins/common-expressions/core/src/expressions/__tests__/expressions.test.ts b/plugins/common-expressions/core/src/expressions/__tests__/expressions.test.ts index c3fc2db8e..42985817a 100644 --- a/plugins/common-expressions/core/src/expressions/__tests__/expressions.test.ts +++ b/plugins/common-expressions/core/src/expressions/__tests__/expressions.test.ts @@ -181,12 +181,12 @@ describe("expr functions", () => { pet: "dog", }, { - name: "Margie", + name: "Frodo", pet: "cat", }, ], ], - ["names", ["Adam", "Tyler", "Andrew", "Kendall"]], + ["names", ["Adam", "Spencer", "Ketan", "Harris"]], ]); }); @@ -195,7 +195,7 @@ describe("expr functions", () => { context, "people", "name", - "Margie", + "Frodo", "pet", undefined, ); @@ -204,7 +204,7 @@ describe("expr functions", () => { context, "people", "name", - "Margie", + "Frodo", ); expect(property).toBe("cat"); @@ -218,7 +218,7 @@ describe("expr functions", () => { pet: "dog", }, { - name: "Margie", + name: "Frodo", pet: "cat", }, ]; @@ -227,12 +227,12 @@ describe("expr functions", () => { context, arr, "name", - "Margie", + "Frodo", "pet", undefined, ); - const propertyIndex = findPropertyIndex(context, arr, "name", "Margie"); + const propertyIndex = findPropertyIndex(context, arr, "name", "Frodo"); expect(property).toBe("cat"); expect(propertyIndex).toBe(1); @@ -243,7 +243,7 @@ describe("expr functions", () => { context, "people", "name", - "Tyler", + "Spencer", ); expect(propertyIndex).toBe(-1); @@ -254,7 +254,7 @@ describe("expr functions", () => { context, undefined as any, "name", - "Tyler", + "Spencer", ); expect(propertyIndex).toBe(-1); @@ -279,7 +279,7 @@ describe("expr functions", () => { context, "people", "name", - "Tyler", + "Spencer", "pet", "rabbit", ); @@ -305,12 +305,12 @@ describe("expr functions", () => { pet: "dog", }, { - name: "Margie", + name: "Frodo", pet: "cat", }, ], ], - ["names", ["Adam", "Tyler", "Andrew", "Kendall"]], + ["names", ["Adam", "Spencer", "Ketan", "Harris"]], ]); }); diff --git a/plugins/common-expressions/core/src/expressions/index.ts b/plugins/common-expressions/core/src/expressions/index.ts index f65ed2eb0..8a47f548e 100644 --- a/plugins/common-expressions/core/src/expressions/index.ts +++ b/plugins/common-expressions/core/src/expressions/index.ts @@ -55,7 +55,10 @@ export const concat = withoutContext((...args: Array) => { if (args.every((v) => Array.isArray(v))) { const arrayArgs = args as Array>; - return arrayArgs.reduce((merged, next) => [...merged, ...next]); + return arrayArgs.reduce((merged, next) => { + merged.push(...next); + return merged; + }); } return args.reduce((merged: any, next) => merged + (next ?? ""), ""); diff --git a/plugins/common-expressions/ios/Sources/CommonExpressionsPlugin.swift b/plugins/common-expressions/ios/Sources/CommonExpressionsPlugin.swift index 9229cdb1a..15f0405b2 100644 --- a/plugins/common-expressions/ios/Sources/CommonExpressionsPlugin.swift +++ b/plugins/common-expressions/ios/Sources/CommonExpressionsPlugin.swift @@ -21,7 +21,7 @@ public class CommonExpressionsPlugin: JSBasePlugin, NativePlugin { #if SWIFT_PACKAGE ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module) #else - ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: CommonExpressionsPlugin.self), pathComponent: "CommonExpressionsPlugin.bundle") + ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: CommonExpressionsPlugin.self), pathComponent: "PlayerUI_CommonExpressionsPlugin.bundle") #endif } } diff --git a/plugins/common-types/core/src/data-types/refs.ts b/plugins/common-types/core/src/data-types/refs.ts deleted file mode 100644 index 374d68628..000000000 --- a/plugins/common-types/core/src/data-types/refs.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Language } from "@player-ui/player"; - -export const BooleanTypeRef: Language.DataTypeRef = { - type: "BooleanType", -}; - -export const IntegerTypeRef: Language.DataTypeRef = { - type: "IntegerType", -}; - -export const IntegerPosTypeRef: Language.DataTypeRef = { - type: "IntegerPosType", -}; - -export const IntegerNNTypeRef: Language.DataTypeRef = { - type: "IntegerNNType", -}; - -export const StringTypeRef: Language.DataTypeRef = { - type: "StringType", -}; - -export const CollectionTypeRef: Language.DataTypeRef = { - type: "CollectionType", -}; - -export const DateTypeRef: Language.DataTypeRef = { - type: "DateType", -}; - -export const PhoneTypeRef: Language.DataTypeRef = { - type: "PhoneType", -}; diff --git a/plugins/common-types/core/src/data-types/types.ts b/plugins/common-types/core/src/data-types/types.ts index 1d6e0fc0e..f2aa9468c 100644 --- a/plugins/common-types/core/src/data-types/types.ts +++ b/plugins/common-types/core/src/data-types/types.ts @@ -1,17 +1,7 @@ import type { Schema } from "@player-ui/player"; -import { - BooleanTypeRef, - CollectionTypeRef, - DateTypeRef, - IntegerPosTypeRef, - IntegerTypeRef, - PhoneTypeRef, - StringTypeRef, - IntegerNNTypeRef, -} from "./refs"; export const BooleanType: Schema.DataType = { - ...BooleanTypeRef, + type: "BooleanType", default: false, validation: [ { @@ -23,7 +13,7 @@ export const BooleanType: Schema.DataType = { }; export const IntegerType: Schema.DataType = { - ...IntegerTypeRef, + type: "IntegerType", validation: [ { type: "integer", @@ -35,7 +25,7 @@ export const IntegerType: Schema.DataType = { }; export const IntegerPosType: Schema.DataType = { - ...IntegerPosTypeRef, + type: "IntegerPosType", validation: [ { type: "integer", @@ -51,7 +41,7 @@ export const IntegerPosType: Schema.DataType = { }; export const IntegerNNType: Schema.DataType = { - ...IntegerNNTypeRef, + type: "IntegerNNType", validation: [ { type: "integer", @@ -67,7 +57,7 @@ export const IntegerNNType: Schema.DataType = { }; export const StringType: Schema.DataType = { - ...StringTypeRef, + type: "StringType", default: "", validation: [ { @@ -80,7 +70,7 @@ export const StringType: Schema.DataType = { }; export const CollectionType: Schema.DataType> = { - ...CollectionTypeRef, + type: "CollectionType", validation: [ { type: "collection", @@ -89,7 +79,7 @@ export const CollectionType: Schema.DataType> = { }; export const DateType: Schema.DataType = { - ...DateTypeRef, + type: "DateType", validation: [ { type: "string", @@ -101,7 +91,7 @@ export const DateType: Schema.DataType = { }; export const PhoneType: Schema.DataType = { - ...PhoneTypeRef, + type: "PhoneType", validation: [ { type: "phone", diff --git a/plugins/common-types/core/src/formats/__tests__/formats.test.ts b/plugins/common-types/core/src/formats/__tests__/formats.test.ts index b5c62b15f..384a257d4 100644 --- a/plugins/common-types/core/src/formats/__tests__/formats.test.ts +++ b/plugins/common-types/core/src/formats/__tests__/formats.test.ts @@ -69,6 +69,9 @@ describe("commaNumber", () => { expect(commaNumber.format?.(1000.999, { precision: 2 })).toBe("1,000.99"); expect(commaNumber.format?.(1000, { precision: 2 })).toBe("1,000.00"); expect(commaNumber.format?.(1000.1, { precision: 2 })).toBe("1,000.10"); + expect(commaNumber.format?.(123456789, { precision: 0 })).toBe( + "123,456,789", + ); }); it("handles out of bounds", () => { diff --git a/plugins/common-types/core/src/formats/index.ts b/plugins/common-types/core/src/formats/index.ts index a5127fe3a..fa19b2624 100644 --- a/plugins/common-types/core/src/formats/index.ts +++ b/plugins/common-types/core/src/formats/index.ts @@ -115,7 +115,6 @@ export const commaNumber: FormatType< // Beautify preDecDigits = preDecDigits.replace(/\B(?=(\d{3})+(?!\d))/g, ","); - if (preDecDigits === "" && firstDecimal === 0) { preDecDigits = "0"; } @@ -127,7 +126,10 @@ export const commaNumber: FormatType< retVal = `-${retVal}`; } - if (firstDecimal >= 0 || options?.precision !== undefined) { + if ( + (firstDecimal >= 0 || options?.precision !== undefined) && + postDecDigits !== "" + ) { retVal += `.${postDecDigits}`; } diff --git a/plugins/common-types/core/src/formats/utils.ts b/plugins/common-types/core/src/formats/utils.ts index 3782f2384..0177d3c35 100644 --- a/plugins/common-types/core/src/formats/utils.ts +++ b/plugins/common-types/core/src/formats/utils.ts @@ -103,13 +103,12 @@ export const formatAsEnum = ( return validCompletions; } - return [ - ...validCompletions, - { - count: overlap, - target: validValue, - }, - ]; + validCompletions.push({ + count: overlap, + target: validValue, + }); + + return validCompletions; }, []) .sort((e) => e.count); diff --git a/plugins/common-types/core/src/index.ts b/plugins/common-types/core/src/index.ts index 49cb4cc37..a63938009 100644 --- a/plugins/common-types/core/src/index.ts +++ b/plugins/common-types/core/src/index.ts @@ -3,7 +3,6 @@ import { TypesProviderPlugin } from "@player-ui/types-provider-plugin"; import * as validators from "./validators"; import * as dataTypes from "./data-types/types"; -import * as dataRefs from "./data-types/refs"; import * as formats from "./formats"; import type { BooleanType, @@ -16,7 +15,8 @@ import type { PhoneType, } from "./data-types/types"; -export { validators, dataTypes, dataRefs, formats }; +export { validators, dataTypes, formats }; + export * from "./formats/utils"; /** diff --git a/plugins/common-types/ios/Sources/CommonTypesPlugin.swift b/plugins/common-types/ios/Sources/CommonTypesPlugin.swift index 335e2143d..cbff80abc 100644 --- a/plugins/common-types/ios/Sources/CommonTypesPlugin.swift +++ b/plugins/common-types/ios/Sources/CommonTypesPlugin.swift @@ -19,7 +19,7 @@ public class CommonTypesPlugin: JSBasePlugin, NativePlugin { #if SWIFT_PACKAGE ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module) #else - ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: CommonTypesPlugin.self), pathComponent: "CommonTypesPlugin.bundle") + ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: CommonTypesPlugin.self), pathComponent: "PlayerUI_CommonTypesPlugin.bundle") #endif } } diff --git a/plugins/computed-properties/ios/Sources/ComputedPropertiesPlugin.swift b/plugins/computed-properties/ios/Sources/ComputedPropertiesPlugin.swift index d7eb8a228..9bdfc1a48 100644 --- a/plugins/computed-properties/ios/Sources/ComputedPropertiesPlugin.swift +++ b/plugins/computed-properties/ios/Sources/ComputedPropertiesPlugin.swift @@ -23,7 +23,7 @@ public class ComputedPropertiesPlugin: JSBasePlugin, NativePlugin { name: fileName, ext: "js", bundle: Bundle(for: ComputedPropertiesPlugin.self), - pathComponent: "ComputedPropertiesPlugin.bundle" + pathComponent: "PlayerUI_ComputedPropertiesPlugin.bundle" ) #endif } diff --git a/plugins/coroutines/jvm/BUILD b/plugins/coroutines/jvm/BUILD index 39c5e8ea3..c84b1a3cf 100644 --- a/plugins/coroutines/jvm/BUILD +++ b/plugins/coroutines/jvm/BUILD @@ -5,5 +5,5 @@ # name = "coroutines", # main_deps = main_deps, # main_exports = main_exports, -# test_package = "com.intuit.player.plugins.coroutines", +# test_package = "com.intuit.playerui.plugins.coroutines", # ) diff --git a/plugins/coroutines/jvm/README.md b/plugins/coroutines/jvm/README.md index 2a726a5f1..044ffc87d 100644 --- a/plugins/coroutines/jvm/README.md +++ b/plugins/coroutines/jvm/README.md @@ -2,4 +2,4 @@ ## Gradle Dependency -`implementation("com.intuit.player.jvm.plugins:coroutines:$VERSION")` +`implementation("com.intuit.playerui.plugins:coroutines:$VERSION")` diff --git a/plugins/coroutines/jvm/src/main/kotlin/com/intuit/player/plugins/coroutines/FlowScopePlugin.kt b/plugins/coroutines/jvm/src/main/kotlin/com/intuit/playerui/plugins/coroutines/FlowScopePlugin.kt similarity index 68% rename from plugins/coroutines/jvm/src/main/kotlin/com/intuit/player/plugins/coroutines/FlowScopePlugin.kt rename to plugins/coroutines/jvm/src/main/kotlin/com/intuit/playerui/plugins/coroutines/FlowScopePlugin.kt index c23c6f671..f1985db82 100644 --- a/plugins/coroutines/jvm/src/main/kotlin/com/intuit/player/plugins/coroutines/FlowScopePlugin.kt +++ b/plugins/coroutines/jvm/src/main/kotlin/com/intuit/playerui/plugins/coroutines/FlowScopePlugin.kt @@ -1,12 +1,17 @@ -package com.intuit.player.plugins.coroutines - -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.player.state.InProgressState -import com.intuit.player.jvm.core.plugins.PlayerPlugin -import com.intuit.player.jvm.core.plugins.findPlugin -import kotlinx.coroutines.* +package com.intuit.playerui.plugins.coroutines + +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.player.subScope +import com.intuit.playerui.core.plugins.PlayerPlugin +import com.intuit.playerui.core.plugins.findPlugin +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.job import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** Simple [PlayerPlugin] that provides a [CoroutineScope] reflective of the current [Flow] */ public class FlowScopePlugin : PlayerPlugin { @@ -20,14 +25,14 @@ public class FlowScopePlugin : PlayerPlugin { flowScope?.cancel("player changed state: $state") if (state is InProgressState) { // only create a new scope for new flows, determined on InProgressState updates - flowScope = CoroutineScope(Dispatchers.Default + FlowContext(state.flow) + SupervisorJob()) + flowScope = player.subScope(FlowContext(state.flow)) } } } /** Build a child scope of the current [flowScope] to ensure that these scopes will be structured according to the current [Flow] */ - public fun subScope(coroutineContext: CoroutineContext = Dispatchers.Default): CoroutineScope? = flowScope?.let { - CoroutineScope(it.coroutineContext[Job].let(::SupervisorJob) + coroutineContext) + public fun subScope(coroutineContext: CoroutineContext = EmptyCoroutineContext): CoroutineScope? = flowScope?.let { + CoroutineScope(it.coroutineContext + SupervisorJob(it.coroutineContext.job) + coroutineContext) } /** [CoroutineContext.Element] that contains the [Flow] bound to the current [CoroutineContext] */ @@ -52,5 +57,5 @@ public val Player.flowScopePlugin: FlowScopePlugin? get() = findPlugin() public val Player.flowScope: CoroutineScope? get() = flowScopePlugin?.flowScope /** Convenience method for building a subscope of the [flowScope] */ -public fun Player.subScope(coroutineContext: CoroutineContext = Dispatchers.Default): CoroutineScope? = +public fun Player.subScope(coroutineContext: CoroutineContext = EmptyCoroutineContext): CoroutineScope? = flowScopePlugin?.subScope(coroutineContext) diff --git a/plugins/coroutines/jvm/src/main/kotlin/com/intuit/player/plugins/coroutines/UpdatesPlugin.kt b/plugins/coroutines/jvm/src/main/kotlin/com/intuit/playerui/plugins/coroutines/UpdatesPlugin.kt similarity index 71% rename from plugins/coroutines/jvm/src/main/kotlin/com/intuit/player/plugins/coroutines/UpdatesPlugin.kt rename to plugins/coroutines/jvm/src/main/kotlin/com/intuit/playerui/plugins/coroutines/UpdatesPlugin.kt index cd1015611..791333966 100644 --- a/plugins/coroutines/jvm/src/main/kotlin/com/intuit/player/plugins/coroutines/UpdatesPlugin.kt +++ b/plugins/coroutines/jvm/src/main/kotlin/com/intuit/playerui/plugins/coroutines/UpdatesPlugin.kt @@ -1,19 +1,19 @@ -package com.intuit.player.plugins.coroutines - -import com.intuit.player.jvm.core.asset.Asset -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.player.state.ReleasedState -import com.intuit.player.jvm.core.plugins.PlayerPlugin -import com.intuit.player.jvm.core.plugins.findPlugin -import kotlinx.coroutines.* +package com.intuit.playerui.plugins.coroutines + +import com.intuit.playerui.core.asset.Asset +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.plugins.PlayerPlugin +import com.intuit.playerui.core.plugins.findPlugin +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout /** [PlayerPlugin] that converts view updates to a [ReceiveChannel] and provides functionality for waiting for an update */ public class UpdatesPlugin : PlayerPlugin { - private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default) - private val _updates: Channel = Channel() public val updates: ReceiveChannel by ::_updates @@ -40,16 +40,12 @@ public class UpdatesPlugin : PlayerPlugin { player.hooks.viewController.tap { vc -> vc?.hooks?.view?.tap { v -> v?.hooks?.onUpdate?.tap { asset -> - scope.launch { + player.scope.launch { _updates.send(asset) } } } } - - player.hooks.state.tap { state -> - if (state is ReleasedState) scope.cancel() - } } } diff --git a/plugins/coroutines/jvm/src/test/kotlin/com/intuit/player/plugins/coroutines/FlowScopePluginTest.kt b/plugins/coroutines/jvm/src/test/kotlin/com/intuit/playerui/plugins/coroutines/FlowScopePluginTest.kt similarity index 59% rename from plugins/coroutines/jvm/src/test/kotlin/com/intuit/player/plugins/coroutines/FlowScopePluginTest.kt rename to plugins/coroutines/jvm/src/test/kotlin/com/intuit/playerui/plugins/coroutines/FlowScopePluginTest.kt index 8ce344d04..12a399b07 100644 --- a/plugins/coroutines/jvm/src/test/kotlin/com/intuit/player/plugins/coroutines/FlowScopePluginTest.kt +++ b/plugins/coroutines/jvm/src/test/kotlin/com/intuit/playerui/plugins/coroutines/FlowScopePluginTest.kt @@ -1,17 +1,25 @@ -package com.intuit.player.plugins.coroutines +package com.intuit.playerui.plugins.coroutines -import com.intuit.player.jvm.core.flow.Flow -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.player.state.CompletedState -import com.intuit.player.jvm.core.player.state.InProgressState -import com.intuit.player.jvm.core.player.state.PlayerFlowState +import com.intuit.playerui.core.flow.Flow +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.player.state.PlayerFlowState +import com.intuit.playerui.core.player.state.ReleasedState import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.invoke import io.mockk.junit5.MockKExtension import io.mockk.slot +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel import kotlinx.coroutines.isActive -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -19,9 +27,14 @@ import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(MockKExtension::class) internal class FlowScopePluginTest { + private val scope = CoroutineScope(Dispatchers.Default) + @MockK lateinit var player: Player + @MockK lateinit var inProgressState: InProgressState + @MockK lateinit var completedState: CompletedState + @MockK lateinit var flow: Flow var stateTap = slot<(PlayerFlowState?) -> Unit>() @@ -31,6 +44,7 @@ internal class FlowScopePluginTest { @BeforeEach fun setup() { every { player.hooks.state.tap(any(), capture(stateTap)) } returns "some-id" every { inProgressState.flow } returns flow + every { player.scope } returns scope flowScopePlugin = FlowScopePlugin().apply { apply(player) @@ -39,6 +53,7 @@ internal class FlowScopePluginTest { private fun transitionToInProgress() = stateTap.invoke(inProgressState) private fun transitionToEnd() = stateTap.invoke(completedState) + private fun transitionToReleased() = stateTap.invoke(ReleasedState) @Test fun testNewScope() { assertNull(flowScopePlugin.flowScope) @@ -47,7 +62,7 @@ internal class FlowScopePluginTest { assertTrue(flowScopePlugin.flowScope!!.isActive) } - @Test fun testScopeCancelled() { + @Test fun testScopeCancelledOnEnd() { assertNull(flowScopePlugin.flowScope) transitionToInProgress() assertNotNull(flowScopePlugin.flowScope) @@ -56,6 +71,15 @@ internal class FlowScopePluginTest { assertFalse(flowScopePlugin.flowScope!!.isActive) } + @Test fun testScopeCancelledOnReleased() { + assertNull(flowScopePlugin.flowScope) + transitionToInProgress() + assertNotNull(flowScopePlugin.flowScope) + assertTrue(flowScopePlugin.flowScope!!.isActive) + transitionToReleased() + assertFalse(flowScopePlugin.flowScope!!.isActive) + } + @Test fun testSubScopeCancelled() { assertNull(flowScopePlugin.flowScope) transitionToInProgress() @@ -79,6 +103,15 @@ internal class FlowScopePluginTest { assertEquals(flow, flowScopePlugin.flowScope!!.flow) val subScope = flowScopePlugin.subScope() assertNotNull(subScope) - assertNull(subScope!!.flow) + assertEquals(flow, subScope!!.flow) + } + + @Test fun testRuntimeScopeCancelsFlowScope() { + assertNull(flowScopePlugin.flowScope) + transitionToInProgress() + assertNotNull(flowScopePlugin.flowScope) + assertTrue(flowScopePlugin.flowScope!!.isActive) + scope.cancel() + assertFalse(flowScopePlugin.flowScope!!.isActive) } } diff --git a/plugins/coroutines/jvm/src/test/kotlin/com/intuit/player/plugins/coroutines/UpdatesPluginTest.kt b/plugins/coroutines/jvm/src/test/kotlin/com/intuit/playerui/plugins/coroutines/UpdatesPluginTest.kt similarity index 70% rename from plugins/coroutines/jvm/src/test/kotlin/com/intuit/player/plugins/coroutines/UpdatesPluginTest.kt rename to plugins/coroutines/jvm/src/test/kotlin/com/intuit/playerui/plugins/coroutines/UpdatesPluginTest.kt index a6fa35598..d7e38936a 100644 --- a/plugins/coroutines/jvm/src/test/kotlin/com/intuit/player/plugins/coroutines/UpdatesPluginTest.kt +++ b/plugins/coroutines/jvm/src/test/kotlin/com/intuit/playerui/plugins/coroutines/UpdatesPluginTest.kt @@ -1,8 +1,8 @@ -package com.intuit.player.plugins.coroutines +package com.intuit.playerui.plugins.coroutines -import com.intuit.player.jvm.core.plugins.Plugin -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.utils.test.simpleFlowString +import com.intuit.playerui.core.plugins.Plugin +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.simpleFlowString import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.TestTemplate import kotlin.concurrent.thread diff --git a/plugins/data-change-listener/core/src/index.test.ts b/plugins/data-change-listener/core/src/index.test.ts index b97cfcfd0..1fd5c1a1a 100644 --- a/plugins/data-change-listener/core/src/index.test.ts +++ b/plugins/data-change-listener/core/src/index.test.ts @@ -343,3 +343,91 @@ describe("Data-Change-Listener with Validations", () => { expect(getCurrentView()?.initialView?.listeners).toBeUndefined(); }); }); + +describe("Data-Change-Listener that are chained", () => { + let player: Player; + let dataController: DataController; + let testExpression: Mock; + + const flow: Flow = { + id: "test-flow", + data: { + name: { + first: "", + second: "", + third: "", + }, + }, + views: [ + { + id: "view-1", + type: "info", + fields: { + asset: { + id: "input", + type: "input", + binding: "name.first", + }, + }, + listeners: { + "dataChange.name.first": ["{{name.second}} = 'update 1'"], + "dataChange.name.second": ["{{name.third}} = 'update 2'"], + }, + }, + ], + navigation: { + BEGIN: "FLOW_1", + FLOW_1: { + startState: "VIEW_1", + VIEW_1: { + state_type: "VIEW", + ref: "view-1", + transitions: { + "*": "END", + }, + }, + }, + }, + }; + + beforeEach(() => { + player = new Player({ + plugins: [ + new DataChangeListenerPlugin(), + new AssetTransformPlugin( + new Registry([[{ type: "input" }, transform]]), + ), + ], + }); + + testExpression = vitest.fn(); + + player.hooks.expressionEvaluator.tap("test", (ev) => { + ev.addExpressionFunction("test", (context, ...args) => { + testExpression(...args); + }); + }); + + player.hooks.dataController.tap("test", (dc) => { + dataController = dc; + }); + + player.start(flow); + }); + + function getState() { + return player.getState() as InProgressState; + } + + function getInputAsset() { + return getState().controllers.view.currentView?.lastUpdate?.fields.asset; + } + + it("chained listeners that set data trigger each other", async () => { + getInputAsset().set("something"); + await vitest.waitFor(() => { + expect(dataController.get("name.second")).toStrictEqual("update 1"); + expect(dataController.get("name.third")).toStrictEqual("update 2"); + }); + }); +}); diff --git a/plugins/data-change-listener/core/src/index.ts b/plugins/data-change-listener/core/src/index.ts index b71a14cd4..f31151691 100644 --- a/plugins/data-change-listener/core/src/index.ts +++ b/plugins/data-change-listener/core/src/index.ts @@ -8,6 +8,7 @@ import type { ExpressionEvaluator, BindingInstance, BindingParser, + ValidationController, } from "@player-ui/player"; import { isExpressionNode } from "@player-ui/player"; @@ -166,22 +167,20 @@ function extractDataChangeListeners( ); if (listenerKey.match(WILDCARD_REGEX)) { - return [ - ...allListeners, + allListeners.push( createWildcardHandler(listenerRawBinding, listenerExp, bindingParser), - ]; + ); + return allListeners; } const parsedOriginalBinding = bindingParser.parse(listenerRawBinding); - return [ - ...allListeners, - (context, binding) => { - if (parsedOriginalBinding.contains(binding)) { - context.expressionEvaluator.evaluate(listenerExp); - } - }, - ]; + allListeners.push((context, binding) => { + if (parsedOriginalBinding.contains(binding)) { + context.expressionEvaluator.evaluate(listenerExp); + } + }); + return allListeners; }, [], ); @@ -196,6 +195,7 @@ export class DataChangeListenerPlugin implements PlayerPlugin { apply(player: Player) { let expressionEvaluator: ExpressionEvaluator; let dataChangeListeners: Array = []; + let validationController: ValidationController; player.hooks.expressionEvaluator.tap( this.name, @@ -236,8 +236,9 @@ export class DataChangeListenerPlugin implements PlayerPlugin { const { silent = false } = options || {}; if (silent) return; const validUpdates = updates.filter((update) => { - const committedVal = options?.context?.model.get(update.binding); - return committedVal === update.newValue; + return !validationController + .getValidationForBinding(update.binding) + ?.getAll().length; }); onFieldUpdateHandler(validUpdates.map((t) => t.binding)); }), @@ -279,6 +280,10 @@ export class DataChangeListenerPlugin implements PlayerPlugin { }, ); + player.hooks.validationController.tap(this.name, (vc) => { + validationController = vc; + }); + player.hooks.flowController.tap(this.name, (flowController) => { flowController.hooks.flow.tap(this.name, (flow) => { flow.hooks.transition.tap(this.name, (from, to) => { diff --git a/plugins/expression/ios/Sources/ExpressionPlugin.swift b/plugins/expression/ios/Sources/ExpressionPlugin.swift index 6045d1448..e9aa1d872 100644 --- a/plugins/expression/ios/Sources/ExpressionPlugin.swift +++ b/plugins/expression/ios/Sources/ExpressionPlugin.swift @@ -32,7 +32,7 @@ public class ExpressionPlugin: JSBasePlugin, NativePlugin { #if SWIFT_PACKAGE ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module) #else - ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: ExpressionPlugin.self), pathComponent: "ExpressionPlugin.bundle") + ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: ExpressionPlugin.self), pathComponent: "PlayerUI_ExpressionPlugin.bundle") #endif } diff --git a/plugins/expression/jvm/BUILD b/plugins/expression/jvm/BUILD index 2399bd51d..c013f1272 100644 --- a/plugins/expression/jvm/BUILD +++ b/plugins/expression/jvm/BUILD @@ -6,5 +6,5 @@ # main_deps = main_deps, # main_exports = main_exports, # main_resources = main_resources, -# test_package = "com.intuit.player.plugins.expression", +# test_package = "com.intuit.playerui.plugins.expression", # ) diff --git a/plugins/expression/jvm/README.md b/plugins/expression/jvm/README.md index 9860c3006..ac34cf006 100644 --- a/plugins/expression/jvm/README.md +++ b/plugins/expression/jvm/README.md @@ -2,4 +2,4 @@ ## Gradle Dependency -`implementation("com.intuit.player.jvm.plugins:expression:$VERSION")` +`implementation("com.intuit.playerui.plugins:expression:$VERSION")` diff --git a/plugins/expression/jvm/src/main/kotlin/com/intuit/player/plugins/expression/ExpressionPlugin.kt b/plugins/expression/jvm/src/main/kotlin/com/intuit/playerui/plugins/expression/ExpressionPlugin.kt similarity index 66% rename from plugins/expression/jvm/src/main/kotlin/com/intuit/player/plugins/expression/ExpressionPlugin.kt rename to plugins/expression/jvm/src/main/kotlin/com/intuit/playerui/plugins/expression/ExpressionPlugin.kt index 7fb4810d0..c44d8bd9c 100644 --- a/plugins/expression/jvm/src/main/kotlin/com/intuit/player/plugins/expression/ExpressionPlugin.kt +++ b/plugins/expression/jvm/src/main/kotlin/com/intuit/playerui/plugins/expression/ExpressionPlugin.kt @@ -1,10 +1,12 @@ -package com.intuit.player.plugins.expression +package com.intuit.playerui.plugins.expression -import com.intuit.player.jvm.core.bridge.Invokable -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.runtime.add -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper +import com.intuit.playerui.core.bridge.Invokable +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.ScriptContext +import com.intuit.playerui.core.bridge.runtime.add +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper // TODO: Turn into functional interface public typealias ExpressionHandler = (List) -> Any? @@ -16,26 +18,26 @@ public typealias ExpressionHandler = (List) -> Any? * Any subsequent expressions registered with the same name will override previous handlers. */ public class ExpressionPlugin( - public val map: Map + public val map: Map, ) : JSScriptPluginWrapper(pluginName, sourcePath = bundledSourcePath) { public constructor(vararg expressions: Pair) : this(expressions.toMap()) override fun apply(runtime: Runtime<*>) { - runtime.execute(script) + runtime.load(ScriptContext(if (runtime.config.debuggable) debugScript else script, bundledSourcePath)) runtime.add( "expressionHandlers", map.entries.fold(runtime.execute("new Map()") as Node) { acc, entry -> acc.apply { val (name, handler) = entry - getFunction("set")!!.invoke( + getInvokable("set")!!.invoke( name, Invokable { args -> handler.invoke(args.drop(1)) - } + }, ) } - } + }, ) instance = runtime.buildInstance("(new $name(expressionHandlers))") } diff --git a/plugins/expression/jvm/src/test/kotlin/com/intuit/player/plugins/expression/ExpressionPluginTest.kt b/plugins/expression/jvm/src/test/kotlin/com/intuit/playerui/plugins/expression/ExpressionPluginTest.kt similarity index 71% rename from plugins/expression/jvm/src/test/kotlin/com/intuit/player/plugins/expression/ExpressionPluginTest.kt rename to plugins/expression/jvm/src/test/kotlin/com/intuit/playerui/plugins/expression/ExpressionPluginTest.kt index 77bcc92a3..e2e526745 100644 --- a/plugins/expression/jvm/src/test/kotlin/com/intuit/player/plugins/expression/ExpressionPluginTest.kt +++ b/plugins/expression/jvm/src/test/kotlin/com/intuit/playerui/plugins/expression/ExpressionPluginTest.kt @@ -1,13 +1,16 @@ -package com.intuit.player.plugins.expression - -import com.intuit.player.jvm.core.expressions.ExpressionEvaluator -import com.intuit.player.jvm.core.expressions.evaluate -import com.intuit.player.jvm.core.plugins.Plugin -import com.intuit.player.jvm.core.plugins.findPlugin -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.utils.test.setupPlayer -import com.intuit.player.jvm.utils.test.simpleFlowString -import org.amshove.kluent.* +package com.intuit.playerui.plugins.expression + +import com.intuit.playerui.core.expressions.ExpressionEvaluator +import com.intuit.playerui.core.expressions.evaluate +import com.intuit.playerui.core.plugins.Plugin +import com.intuit.playerui.core.plugins.findPlugin +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.setupPlayer +import com.intuit.playerui.utils.test.simpleFlowString +import org.amshove.kluent.`should be empty` +import org.amshove.kluent.`should be equal to` +import org.amshove.kluent.`should be instance of` +import org.amshove.kluent.`should not be null` import org.junit.jupiter.api.TestTemplate internal class ExpressionPluginTest : PlayerTest() { @@ -21,8 +24,8 @@ internal class ExpressionPluginTest : PlayerTest() { else -> "bye" } }, - "MyExpression2" to { null } - ) + "MyExpression2" to { null }, + ), ) private val expressionPlugin get() = player.findPlugin()!! diff --git a/plugins/external-action/ios/Sources/ExternalActionPlugin.swift b/plugins/external-action/ios/Sources/ExternalActionPlugin.swift index 39a8315b5..97c4900da 100644 --- a/plugins/external-action/ios/Sources/ExternalActionPlugin.swift +++ b/plugins/external-action/ios/Sources/ExternalActionPlugin.swift @@ -70,7 +70,7 @@ public class ExternalActionPlugin: JSBasePlugin, NativePlugin { #if SWIFT_PACKAGE ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module) #else - ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: ExternalActionPlugin.self), pathComponent: "ExternalActionPlugin.bundle") + ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: ExternalActionPlugin.self), pathComponent: "PlayerUI_ExternalActionPlugin.bundle") #endif } diff --git a/plugins/external-action/jvm/BUILD b/plugins/external-action/jvm/BUILD index 09c1ce5be..3e2d48974 100644 --- a/plugins/external-action/jvm/BUILD +++ b/plugins/external-action/jvm/BUILD @@ -7,5 +7,5 @@ # main_exports = main_exports, # main_resources = main_resources, # # TODO: Reformat package -# test_package = "com.intuit.player.plugins.externalAction", +# test_package = "com.intuit.playerui.plugins.externalAction", # ) diff --git a/plugins/external-action/jvm/README.md b/plugins/external-action/jvm/README.md index 70c541096..0a9dd55f5 100644 --- a/plugins/external-action/jvm/README.md +++ b/plugins/external-action/jvm/README.md @@ -2,4 +2,4 @@ ## Gradle Dependency -`implementation("com.intuit.player.jvm.plugins:external-action:$VERSION")` +`implementation("com.intuit.playerui.plugins:external-action:$VERSION")` diff --git a/plugins/external-action/jvm/src/main/kotlin/com/intuit/player/plugins/externalAction/ExternalActionPlugin.kt b/plugins/external-action/jvm/src/main/kotlin/com/intuit/playerui/plugins/externalAction/ExternalActionPlugin.kt similarity index 68% rename from plugins/external-action/jvm/src/main/kotlin/com/intuit/player/plugins/externalAction/ExternalActionPlugin.kt rename to plugins/external-action/jvm/src/main/kotlin/com/intuit/playerui/plugins/externalAction/ExternalActionPlugin.kt index 033fd3b6d..2f2a27cff 100644 --- a/plugins/external-action/jvm/src/main/kotlin/com/intuit/player/plugins/externalAction/ExternalActionPlugin.kt +++ b/plugins/external-action/jvm/src/main/kotlin/com/intuit/playerui/plugins/externalAction/ExternalActionPlugin.kt @@ -1,17 +1,18 @@ -package com.intuit.player.plugins.externalAction +package com.intuit.playerui.plugins.externalAction -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.Promise -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.runtime.add -import com.intuit.player.jvm.core.flow.state.NavigationFlowExternalState -import com.intuit.player.jvm.core.flow.state.NavigationFlowState -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.player.state.ControllerState -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper -import com.intuit.player.jvm.core.plugins.PlayerPlugin -import com.intuit.player.jvm.core.plugins.findPlugin -import com.intuit.player.plugins.settimeout.SetTimeoutPlugin +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.Promise +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.ScriptContext +import com.intuit.playerui.core.bridge.runtime.add +import com.intuit.playerui.core.flow.state.NavigationFlowExternalState +import com.intuit.playerui.core.flow.state.NavigationFlowState +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.player.state.ControllerState +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper +import com.intuit.playerui.core.plugins.PlayerPlugin +import com.intuit.playerui.core.plugins.findPlugin +import com.intuit.playerui.plugins.settimeout.SetTimeoutPlugin /** Function definition of an external action handler */ public fun interface ExternalActionHandler { @@ -20,7 +21,7 @@ public fun interface ExternalActionHandler { /** Core plugin wrapper providing external action support for the JVM Player */ public class ExternalActionPlugin( - private var handler: ExternalActionHandler? = null + private var handler: ExternalActionHandler? = null, ) : JSScriptPluginWrapper(pluginName, sourcePath = bundledSourcePath), PlayerPlugin { private lateinit var player: Player @@ -31,11 +32,11 @@ public class ExternalActionPlugin( override fun apply(runtime: Runtime<*>) { SetTimeoutPlugin().apply(runtime) - runtime.execute(script) + runtime.load(ScriptContext(if (runtime.config.debuggable) debugScript else script, bundledSourcePath)) runtime.add("externalActionHandler") externalActionHandler@{ state: Node, options: Node -> val state = state.deserialize(NavigationFlowState.serializer()) as? NavigationFlowExternalState ?: return@externalActionHandler null - val options = options.deserialize(ControllerState.serializer())!! + val options = options.deserialize(ControllerState.serializer()) return@externalActionHandler runtime.Promise { resolve, _ -> handler?.onExternalState(state, options, resolve) diff --git a/plugins/external-action/jvm/src/test/kotlin/com/intuit/player/plugins/externalAction/ExternalActionPluginTest.kt b/plugins/external-action/jvm/src/test/kotlin/com/intuit/playerui/plugins/externalAction/ExternalActionPluginTest.kt similarity index 89% rename from plugins/external-action/jvm/src/test/kotlin/com/intuit/player/plugins/externalAction/ExternalActionPluginTest.kt rename to plugins/external-action/jvm/src/test/kotlin/com/intuit/playerui/plugins/externalAction/ExternalActionPluginTest.kt index a34be5dea..f7616c938 100644 --- a/plugins/external-action/jvm/src/test/kotlin/com/intuit/player/plugins/externalAction/ExternalActionPluginTest.kt +++ b/plugins/external-action/jvm/src/test/kotlin/com/intuit/playerui/plugins/externalAction/ExternalActionPluginTest.kt @@ -1,13 +1,11 @@ -package com.intuit.player.plugins.externalAction +package com.intuit.playerui.plugins.externalAction -import com.intuit.player.jvm.core.expressions.evaluate -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.utils.test.runBlockingTest -import kotlinx.coroutines.GlobalScope +import com.intuit.playerui.core.expressions.evaluate +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.runBlockingTest import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.TestTemplate import org.junit.jupiter.api.assertThrows @@ -78,14 +76,14 @@ internal class ExternalActionPluginTest : PlayerTest() { runBlocking { player.start(jsonFlow).await() } - }.message + }.message, ) } @TestTemplate fun testExternalStateHandlingWithDelay() = runBlockingTest { player.onExternalAction { _, _, transition -> - GlobalScope.launch { + launch { delay(2000) transition("Next") } diff --git a/plugins/external-action/mocks/BUILD b/plugins/external-action/mocks/BUILD new file mode 100644 index 000000000..864dee67b --- /dev/null +++ b/plugins/external-action/mocks/BUILD @@ -0,0 +1,29 @@ +#load("//:index.bzl", "generate_manifest","javascript_pipeline") +# +#filegroup( +# name = "mocks", +# srcs = glob(["**/*.json"]), +# visibility = ["//visibility:public"], +#) +# +#generate_manifest( +# name = "manifest", +# mocks = [":mocks"], +# visibility = ["//visibility:public"], +#) +#javascript_pipeline( +# name = "@player-ui/external-action-plugin-mocks", +# entry = "index.ts", +# other_srcs = [ +# "index.ts", +# ":manifest", +# ], +# out_dir = "", +#) +# +#java_library( +# name = "jar", +# resources = [":mocks"], +# resource_strip_prefix = "plugins/external-action/mocks", +# visibility = ["//visibility:public"], +#) diff --git a/plugins/external-action/mocks/external-action/external-action.json b/plugins/external-action/mocks/external-action/external-action.json new file mode 100644 index 000000000..348556413 --- /dev/null +++ b/plugins/external-action/mocks/external-action/external-action.json @@ -0,0 +1,28 @@ +{ + "id": "test-flow", + "data": { + "transitionValue": "Next" + }, + "navigation": { + "BEGIN": "FLOW_1", + "FLOW_1": { + "startState": "EXT_1", + "EXT_1": { + "state_type": "EXTERNAL", + "ref": "test-1", + "transitions": { + "Next": "END_FWD", + "Prev": "END_BCK" + } + }, + "END_FWD": { + "state_type": "END", + "outcome": "FWD" + }, + "END_BCK": { + "state_type": "END", + "outcome": "BCK" + } + } + } +} \ No newline at end of file diff --git a/plugins/external-action/mocks/index.ts b/plugins/external-action/mocks/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/external-action/swiftui/Sources/ExternalActionViewModifierPlugin.swift b/plugins/external-action/swiftui/Sources/ExternalActionViewModifierPlugin.swift index 682d1f4b2..eec075494 100644 --- a/plugins/external-action/swiftui/Sources/ExternalActionViewModifierPlugin.swift +++ b/plugins/external-action/swiftui/Sources/ExternalActionViewModifierPlugin.swift @@ -107,7 +107,7 @@ open class ExternalActionViewModifierPlugin Unit = {}, ) : PlayerLoggingConfig() diff --git a/plugins/java-logger/jvm/src/main/resources/META-INF/services/com.intuit.player.jvm.core.plugins.logging.PlayerLoggingContainer b/plugins/java-logger/jvm/src/main/resources/META-INF/services/com.intuit.player.jvm.core.plugins.logging.PlayerLoggingContainer deleted file mode 100644 index 73418c098..000000000 --- a/plugins/java-logger/jvm/src/main/resources/META-INF/services/com.intuit.player.jvm.core.plugins.logging.PlayerLoggingContainer +++ /dev/null @@ -1 +0,0 @@ -com.intuit.player.jvm.plugins.logging.jul.JavaLoggingContainer \ No newline at end of file diff --git a/plugins/java-logger/jvm/src/main/resources/META-INF/services/com.intuit.playerui.core.plugins.logging.PlayerLoggingContainer b/plugins/java-logger/jvm/src/main/resources/META-INF/services/com.intuit.playerui.core.plugins.logging.PlayerLoggingContainer new file mode 100644 index 000000000..e51d90382 --- /dev/null +++ b/plugins/java-logger/jvm/src/main/resources/META-INF/services/com.intuit.playerui.core.plugins.logging.PlayerLoggingContainer @@ -0,0 +1 @@ +com.intuit.playerui.plugins.logging.jul.JavaLoggingContainer \ No newline at end of file diff --git a/plugins/java-logger/jvm/src/test/kotlin/com/intuit/player/jvm/plugins/logging/jul/JavaLoggerPluginTest.kt b/plugins/java-logger/jvm/src/test/kotlin/com/intuit/playerui/plugins/logging/jul/JavaLoggerPluginTest.kt similarity index 77% rename from plugins/java-logger/jvm/src/test/kotlin/com/intuit/player/jvm/plugins/logging/jul/JavaLoggerPluginTest.kt rename to plugins/java-logger/jvm/src/test/kotlin/com/intuit/playerui/plugins/logging/jul/JavaLoggerPluginTest.kt index f65a1a6cb..3036cdd17 100644 --- a/plugins/java-logger/jvm/src/test/kotlin/com/intuit/player/jvm/plugins/logging/jul/JavaLoggerPluginTest.kt +++ b/plugins/java-logger/jvm/src/test/kotlin/com/intuit/playerui/plugins/logging/jul/JavaLoggerPluginTest.kt @@ -1,7 +1,7 @@ -package com.intuit.player.jvm.plugins.logging.jul +package com.intuit.playerui.plugins.logging.jul -import com.intuit.player.jvm.core.plugins.logging.loggers -import com.intuit.player.jvm.utils.test.RuntimeTest +import com.intuit.playerui.core.plugins.logging.loggers +import com.intuit.playerui.utils.test.RuntimeTest import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @@ -18,7 +18,7 @@ internal class JavaLoggerPluginTest : RuntimeTest() { "MyLogger", JavaLoggerFactory.create { name = "MyLogger" - }.logger.name + }.logger.name, ) } diff --git a/plugins/metrics/jvm/BUILD b/plugins/metrics/jvm/BUILD index fb588f3ea..bdfbb8af1 100644 --- a/plugins/metrics/jvm/BUILD +++ b/plugins/metrics/jvm/BUILD @@ -6,5 +6,5 @@ # main_deps = main_deps, # main_exports = main_exports, # main_resources = main_resources, -# test_package = "com.intuit.player.jvm.plugins.metrics", +# test_package = "com.intuit.playerui.plugins.metrics", # ) diff --git a/plugins/metrics/jvm/README.md b/plugins/metrics/jvm/README.md index 45a8bdc1a..1a6cae820 100644 --- a/plugins/metrics/jvm/README.md +++ b/plugins/metrics/jvm/README.md @@ -2,4 +2,4 @@ ## Gradle Dependency -`implementation("com.intuit.player.jvm.plugins:metrics:$VERSION")` +`implementation("com.intuit.playerui.plugins:metrics:$VERSION")` diff --git a/plugins/metrics/jvm/src/main/kotlin/com/intuit/player/jvm/plugins/metrics/metrics.kt b/plugins/metrics/jvm/src/main/kotlin/com/intuit/playerui/plugins/metrics/Metrics.kt similarity index 59% rename from plugins/metrics/jvm/src/main/kotlin/com/intuit/player/jvm/plugins/metrics/metrics.kt rename to plugins/metrics/jvm/src/main/kotlin/com/intuit/playerui/plugins/metrics/Metrics.kt index fe0135ed1..a7df9e48b 100644 --- a/plugins/metrics/jvm/src/main/kotlin/com/intuit/player/jvm/plugins/metrics/metrics.kt +++ b/plugins/metrics/jvm/src/main/kotlin/com/intuit/playerui/plugins/metrics/Metrics.kt @@ -1,12 +1,15 @@ -package com.intuit.player.jvm.plugins.metrics - -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.NodeWrapper -import com.intuit.player.jvm.core.bridge.serialization.serializers.NodeWrapperSerializer -import com.intuit.player.jvm.core.bridge.serialization.serializers.PolymorphicNodeWrapperSerializer -import com.intuit.player.jvm.core.player.PlayerException +package com.intuit.playerui.plugins.metrics + +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.bridge.serialization.serializers.PolymorphicNodeWrapperSerializer +import com.intuit.playerui.core.player.PlayerException import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer internal class TimingSerializer : PolymorphicNodeWrapperSerializer() { override fun selectDeserializer(node: Node): KSerializer { @@ -21,7 +24,7 @@ internal class TimingSerializer : PolymorphicNodeWrapperSerializer() { @Serializable(with = TimingSerializer::class) public sealed class Timing(override val node: Node) : NodeWrapper { /** Time this duration started (ms) */ - public val startTime: Double? get() = node.getDouble("startTime") + public val startTime: Double? by NodeSerializableField(Double.serializer().nullable) } @Serializable(with = CompletedTiming.Serializer::class) @@ -29,10 +32,10 @@ public class CompletedTiming internal constructor(override val node: Node) : Tim public val completed: Boolean = true /** The time in (ms) that the process ended */ - public val endTime: Double? get() = node.getDouble("endTime") + public val endTime: Double? by NodeSerializableField(Double.serializer().nullable) /** The elapsed time of this event (ms) */ - public val duration: Int? get() = node.getInt("duration") + public val duration: Int? by NodeSerializableField(Int.serializer().nullable) internal object Serializer : NodeWrapperSerializer(::CompletedTiming) } @@ -46,35 +49,37 @@ public class IncompleteTiming internal constructor(override val node: Node) : Ti public sealed class NodeMetrics(override val node: Node) : Timing(node) { /** The type of the flow-state */ - public val stateType: String get() = node.getString("stateType") ?: "" + public val stateType: String by NodeSerializableField(String.serializer()) /** The name of the flow-state */ - public val stateName: String get() = node.getString("stateName") ?: "" + public val stateName: String by NodeSerializableField(String.serializer()) } @Serializable(with = RenderMetrics.Serializer::class) public class RenderMetrics(override val node: Node) : NodeMetrics(node) { /** Timing representing the initial render */ - public val render: Timing = node.getObject("render")?.deserialize(TimingSerializer())!! + public val render: Timing by NodeSerializableField(Timing.serializer()) internal object Serializer : NodeWrapperSerializer(::RenderMetrics) } +@Serializable(with = MetricsFlow.Serializer::class) public class MetricsFlow(override val node: Node) : NodeWrapper { /** The id of the flow these metrics are for */ - public val id: String? get() = node.getString("id") + public val id: String? by NodeSerializableField(String.serializer().nullable) /** request time */ - public val requestTime: Int? get() = node.getInt("requestTime") + public val requestTime: Int? by NodeSerializableField(Int.serializer().nullable) /** A timing measuring until the first interactive render */ - public val interactive: Timing get() = node.getObject("interactive")?.deserialize(TimingSerializer())!! + public val interactive: Timing by NodeSerializableField(Timing.serializer()) + + internal object Serializer : NodeWrapperSerializer(::MetricsFlow) } @Serializable(with = PlayerFlowMetrics.Serializer::class) public class PlayerFlowMetrics(override val node: Node) : Timing(node) { /** All metrics about a running flow */ - public val flow: MetricsFlow get() = MetricsFlow(node.getObject("flow")!!) - + public val flow: MetricsFlow by NodeSerializableField(MetricsFlow.serializer()) internal object Serializer : NodeWrapperSerializer(::PlayerFlowMetrics) } diff --git a/plugins/metrics/jvm/src/main/kotlin/com/intuit/player/jvm/plugins/metrics/MetricsPlugin.kt b/plugins/metrics/jvm/src/main/kotlin/com/intuit/playerui/plugins/metrics/MetricsPlugin.kt similarity index 69% rename from plugins/metrics/jvm/src/main/kotlin/com/intuit/player/jvm/plugins/metrics/MetricsPlugin.kt rename to plugins/metrics/jvm/src/main/kotlin/com/intuit/playerui/plugins/metrics/MetricsPlugin.kt index b55a8a326..a6aff0ea4 100644 --- a/plugins/metrics/jvm/src/main/kotlin/com/intuit/player/jvm/plugins/metrics/MetricsPlugin.kt +++ b/plugins/metrics/jvm/src/main/kotlin/com/intuit/playerui/plugins/metrics/MetricsPlugin.kt @@ -1,21 +1,23 @@ -package com.intuit.player.jvm.plugins.metrics +package com.intuit.playerui.plugins.metrics -import com.intuit.player.jvm.core.bridge.Node -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.runtime.add -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper -import com.intuit.player.jvm.core.plugins.PlayerPlugin -import com.intuit.player.jvm.core.plugins.findPlugin +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.ScriptContext +import com.intuit.playerui.core.bridge.runtime.add +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper +import com.intuit.playerui.core.plugins.PlayerPlugin +import com.intuit.playerui.core.plugins.findPlugin public typealias RenderEndHandler = (Timing?, RenderMetrics?, PlayerFlowMetrics?) -> Unit public class MetricsPlugin( - private val handler: RenderEndHandler + private val handler: RenderEndHandler, ) : JSScriptPluginWrapper(pluginName, sourcePath = bundledSourcePath) { override fun apply(runtime: Runtime<*>) { - runtime.execute(script) + runtime.load(ScriptContext(if (runtime.config.debuggable) debugScript else script, bundledSourcePath)) runtime.add( "handlers", mapOf( @@ -23,16 +25,16 @@ public class MetricsPlugin( handler.invoke( timing.deserialize(Timing.serializer()), renderMetrics.deserialize(RenderMetrics.serializer()), - flowMetrics.deserialize(PlayerFlowMetrics.serializer()) + flowMetrics.deserialize(PlayerFlowMetrics.serializer()), ) }, - "trackRenderTime" to true - ) + "trackRenderTime" to true, + ), ) instance = runtime.buildInstance("(new $name(handlers))") } - public fun renderEnd(): Unit = instance.getFunction("renderEnd")!!() + public fun renderEnd(): Unit = instance.getInvokable("renderEnd")!!() private companion object { private const val bundledSourcePath = "plugins/metrics/core/dist/metrics-plugin.prod.js" @@ -47,7 +49,7 @@ public typealias RequestTimeClosure = () -> Int /** Wrapper around RequestTimeWebPlugin, which needs to apply to MetricsPlugin */ internal class RequestTimeWebPlugin( - private val getRequestTime: RequestTimeClosure + private val getRequestTime: RequestTimeClosure, ) : JSScriptPluginWrapper(pluginName, sourcePath = bundledSourcePath) { override fun apply(runtime: Runtime<*>) { @@ -55,14 +57,14 @@ internal class RequestTimeWebPlugin( runtime.execute(script) } runtime.add( - "callback" + "callback", ) getRequestTime@{ getRequestTime.invoke() } instance = runtime.buildInstance("(new $name(callback))") } public fun apply(metricsPlugin: MetricsPlugin) { apply(metricsPlugin.instance.runtime) - instance.getFunction("apply")?.invoke(metricsPlugin.instance) + instance.getInvokable("apply")?.invoke(metricsPlugin.instance) } private companion object { @@ -73,7 +75,7 @@ internal class RequestTimeWebPlugin( /** A plugin to supply request time to MetricsPlugin */ public class RequestTimePlugin( - private val getRequestTime: RequestTimeClosure + private val getRequestTime: RequestTimeClosure, ) : PlayerPlugin { private val requestTimeWebPlugin = RequestTimeWebPlugin(getRequestTime) override fun apply(player: Player) { diff --git a/plugins/metrics/jvm/src/test/kotlin/com/intuit/player/jvm/plugins/metrics/MetricsPluginTest.kt b/plugins/metrics/jvm/src/test/kotlin/com/intuit/playerui/plugins/metrics/MetricsPluginTest.kt similarity index 85% rename from plugins/metrics/jvm/src/test/kotlin/com/intuit/player/jvm/plugins/metrics/MetricsPluginTest.kt rename to plugins/metrics/jvm/src/test/kotlin/com/intuit/playerui/plugins/metrics/MetricsPluginTest.kt index f7fcb5590..a483e43cf 100644 --- a/plugins/metrics/jvm/src/test/kotlin/com/intuit/player/jvm/plugins/metrics/MetricsPluginTest.kt +++ b/plugins/metrics/jvm/src/test/kotlin/com/intuit/playerui/plugins/metrics/MetricsPluginTest.kt @@ -1,10 +1,10 @@ -package com.intuit.player.jvm.plugins.metrics +package com.intuit.playerui.plugins.metrics -import com.intuit.player.jvm.core.player.state.inProgressState -import com.intuit.player.jvm.core.plugins.Plugin -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.utils.test.runBlockingTest -import com.intuit.player.jvm.utils.test.simpleFlowString +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.core.plugins.Plugin +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.runBlockingTest +import com.intuit.playerui.utils.test.simpleFlowString import io.mockk.Called import io.mockk.junit5.MockKExtension import io.mockk.mockkObject @@ -60,7 +60,7 @@ internal class RequestTimePluginTest : PlayerTest() { mockkObject(renderEndHandler) return listOf( MetricsPlugin(renderEndHandler), - RequestTimePlugin(getRequestTime) + RequestTimePlugin(getRequestTime), ) } diff --git a/plugins/metrics/swiftui/Sources/MetricsPlugin.swift b/plugins/metrics/swiftui/Sources/MetricsPlugin.swift index 59cb7c26b..8d512568f 100644 --- a/plugins/metrics/swiftui/Sources/MetricsPlugin.swift +++ b/plugins/metrics/swiftui/Sources/MetricsPlugin.swift @@ -55,7 +55,7 @@ class RequestTimeWebPlugin: JSBasePlugin { name: fileName, ext: "js", bundle: Bundle(for: MetricsPlugin.self), - pathComponent: "MetricsPlugin.bundle" + pathComponent: "PlayerUI_MetricsPlugin.bundle" ) #endif } @@ -118,7 +118,7 @@ public class MetricsPlugin: JSBasePlugin, NativePlugin, WithSymbol { name: fileName, ext: "js", bundle: Bundle(for: MetricsPlugin.self), - pathComponent: "MetricsPlugin.bundle" + pathComponent: "PlayerUI_MetricsPlugin.bundle" ) #endif } diff --git a/plugins/mocks/BUILD b/plugins/mocks/BUILD new file mode 100644 index 000000000..ec98d83ca --- /dev/null +++ b/plugins/mocks/BUILD @@ -0,0 +1,16 @@ +#load("//:index.bzl", "generate_manifest") +#load("//:index.bzl", "javascript_pipeline") +# +#generate_manifest( +# name = "mocks", +# mocks = ["//plugins/reference-assets/mocks","//plugins/pubsub/mocks", "//plugins/external-action/mocks"], +# visibility = ["//visibility:public"], +#) +# +#java_library( +# name = "jar", +# resources = [":mocks"], +# resource_strip_prefix = "plugins/", +# visibility = ["//visibility:public"], +#) +# diff --git a/plugins/pending-transaction/jvm/BUILD b/plugins/pending-transaction/jvm/BUILD index 9ede80297..9f8e18e2b 100644 --- a/plugins/pending-transaction/jvm/BUILD +++ b/plugins/pending-transaction/jvm/BUILD @@ -5,5 +5,5 @@ # name = "pending-transaction", # main_deps = main_deps, # main_exports = main_exports, -# test_package = "com.intuit.player.plugins.transactions", +# test_package = "com.intuit.playerui.plugins.transactions", # ) diff --git a/plugins/pending-transaction/jvm/src/main/kotlin/com/intuit/player/plugins/transactions/PendingTransactionPlugin.kt b/plugins/pending-transaction/jvm/src/main/kotlin/com/intuit/playerui/plugins/transactions/PendingTransactionPlugin.kt similarity index 88% rename from plugins/pending-transaction/jvm/src/main/kotlin/com/intuit/player/plugins/transactions/PendingTransactionPlugin.kt rename to plugins/pending-transaction/jvm/src/main/kotlin/com/intuit/playerui/plugins/transactions/PendingTransactionPlugin.kt index 92f53fd3e..c52e82327 100644 --- a/plugins/pending-transaction/jvm/src/main/kotlin/com/intuit/player/plugins/transactions/PendingTransactionPlugin.kt +++ b/plugins/pending-transaction/jvm/src/main/kotlin/com/intuit/playerui/plugins/transactions/PendingTransactionPlugin.kt @@ -1,10 +1,10 @@ -package com.intuit.player.plugins.transactions +package com.intuit.playerui.plugins.transactions import com.intuit.hooks.HookContext import com.intuit.hooks.SyncHook -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.plugins.Plugin -import com.intuit.player.jvm.core.plugins.findPlugin +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.plugins.Plugin +import com.intuit.playerui.core.plugins.findPlugin public fun interface PendingTransaction { public fun commit() diff --git a/plugins/plugin_wrapper_template.kt.tpl b/plugins/plugin_wrapper_template.kt.tpl index 8bc484adf..37d696211 100644 --- a/plugins/plugin_wrapper_template.kt.tpl +++ b/plugins/plugin_wrapper_template.kt.tpl @@ -1,5 +1,5 @@ package @{{package}} -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper public class @{{plugin_name}} : JSScriptPluginWrapper("@{{plugin_constructor}}", sourcePath = "@{{plugin_source_path}}") diff --git a/plugins/plugin_wrapper_test_template.kt.tpl b/plugins/plugin_wrapper_test_template.kt.tpl index bd42e143a..eab2dd0eb 100644 --- a/plugins/plugin_wrapper_test_template.kt.tpl +++ b/plugins/plugin_wrapper_test_template.kt.tpl @@ -1,7 +1,7 @@ package @{{package}} -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.core.plugins.findPlugin +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.core.plugins.findPlugin import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.TestTemplate diff --git a/plugins/pubsub/ios/Sources/PubSubPlugin.swift b/plugins/pubsub/ios/Sources/PubSubPlugin.swift index 1bbe68dea..26d821452 100644 --- a/plugins/pubsub/ios/Sources/PubSubPlugin.swift +++ b/plugins/pubsub/ios/Sources/PubSubPlugin.swift @@ -72,7 +72,7 @@ public class PubSubPlugin: JSBasePlugin, NativePlugin { #if SWIFT_PACKAGE ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module) #else - ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: PubSubPlugin.self), pathComponent: "PubSubPlugin.bundle") + ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle(for: PubSubPlugin.self), pathComponent: "PlayerUI_PubSubPlugin.bundle") #endif } diff --git a/plugins/pubsub/jvm/BUILD b/plugins/pubsub/jvm/BUILD index 0a52ce125..d8f11f36b 100644 --- a/plugins/pubsub/jvm/BUILD +++ b/plugins/pubsub/jvm/BUILD @@ -6,5 +6,5 @@ # main_deps = main_deps, # main_exports = main_exports, # main_resources = main_resources, -# test_package = "com.intuit.player.plugins.pubsub", +# test_package = "com.intuit.playerui.plugins.pubsub", # ) diff --git a/plugins/pubsub/jvm/README.md b/plugins/pubsub/jvm/README.md index 61da754b0..28da7f18e 100644 --- a/plugins/pubsub/jvm/README.md +++ b/plugins/pubsub/jvm/README.md @@ -2,4 +2,4 @@ ## Gradle Dependency -`implementation("com.intuit.player.jvm.plugins:pubsub:$VERSION")` +`implementation("com.intuit.playerui.plugins:pubsub:$VERSION")` diff --git a/plugins/pubsub/jvm/src/main/kotlin/com/intuit/player/plugins/pubsub/PubSubPlugin.kt b/plugins/pubsub/jvm/src/main/kotlin/com/intuit/playerui/plugins/pubsub/PubSubPlugin.kt similarity index 73% rename from plugins/pubsub/jvm/src/main/kotlin/com/intuit/player/plugins/pubsub/PubSubPlugin.kt rename to plugins/pubsub/jvm/src/main/kotlin/com/intuit/playerui/plugins/pubsub/PubSubPlugin.kt index 56cd11530..cd362bb70 100644 --- a/plugins/pubsub/jvm/src/main/kotlin/com/intuit/player/plugins/pubsub/PubSubPlugin.kt +++ b/plugins/pubsub/jvm/src/main/kotlin/com/intuit/playerui/plugins/pubsub/PubSubPlugin.kt @@ -1,10 +1,12 @@ -package com.intuit.player.plugins.pubsub +package com.intuit.playerui.plugins.pubsub -import com.intuit.player.jvm.core.bridge.runtime.Runtime -import com.intuit.player.jvm.core.bridge.runtime.add -import com.intuit.player.jvm.core.player.Player -import com.intuit.player.jvm.core.plugins.JSScriptPluginWrapper -import com.intuit.player.jvm.core.plugins.findPlugin +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.runtime.Runtime +import com.intuit.playerui.core.bridge.runtime.ScriptContext +import com.intuit.playerui.core.bridge.runtime.add +import com.intuit.playerui.core.player.Player +import com.intuit.playerui.core.plugins.JSScriptPluginWrapper +import com.intuit.playerui.core.plugins.findPlugin import kotlinx.serialization.Serializable /** Core plugin wrapper providing pub-sub support to the JVM player */ @@ -12,7 +14,7 @@ public class PubSubPlugin(public val config: Config? = null) : JSScriptPluginWra override fun apply(runtime: Runtime<*>) { config?.let { - runtime.execute(script) + runtime.load(ScriptContext(if (runtime.config.debuggable) debugScript else script, bundledSourcePath)) runtime.add("pubsubConfig", config) instance = runtime.buildInstance("(new $name(pubsubConfig))") } ?: super.apply(runtime) @@ -25,14 +27,14 @@ public class PubSubPlugin(public val config: Config? = null) : JSScriptPluginWra * @return subscription token used to [unsubscribe] */ public fun subscribe(eventName: String, block: (String, Any?) -> Unit): String = instance - .getFunction("subscribe")!!(eventName, block) + .getInvokable("subscribe")!!(eventName, block) /** * Cancel subscription registered with [token] * @param token subscription token obtained from [subscribe] call */ public fun unsubscribe(token: String) { - instance.getFunction("unsubscribe")!!(token) + instance.getInvokable("unsubscribe")!!(token) } /** @@ -41,12 +43,12 @@ public class PubSubPlugin(public val config: Config? = null) : JSScriptPluginWra * @param eventData Arbitrary data associated with the event */ public fun publish(eventName: String, eventData: Any) { - instance.getFunction("publish")!!(eventName, eventData) + instance.getInvokable("publish")!!(eventName, eventData) } @Serializable public data class Config( - public val expressionName: String + public val expressionName: String, ) private companion object { diff --git a/plugins/pubsub/jvm/src/test/kotlin/com/intuit/player/plugins/pubsub/PubSubPluginTest.kt b/plugins/pubsub/jvm/src/test/kotlin/com/intuit/playerui/plugins/pubsub/PubSubPluginTest.kt similarity index 89% rename from plugins/pubsub/jvm/src/test/kotlin/com/intuit/player/plugins/pubsub/PubSubPluginTest.kt rename to plugins/pubsub/jvm/src/test/kotlin/com/intuit/playerui/plugins/pubsub/PubSubPluginTest.kt index 628930fb6..0081d9a9b 100644 --- a/plugins/pubsub/jvm/src/test/kotlin/com/intuit/player/plugins/pubsub/PubSubPluginTest.kt +++ b/plugins/pubsub/jvm/src/test/kotlin/com/intuit/playerui/plugins/pubsub/PubSubPluginTest.kt @@ -1,11 +1,11 @@ -package com.intuit.player.plugins.pubsub - -import com.intuit.player.jvm.core.bridge.serialization.serializers.GenericSerializer -import com.intuit.player.jvm.core.expressions.evaluate -import com.intuit.player.jvm.core.player.state.inProgressState -import com.intuit.player.jvm.utils.test.PlayerTest -import com.intuit.player.jvm.utils.test.setupPlayer -import com.intuit.player.jvm.utils.test.simpleFlowString +package com.intuit.playerui.plugins.pubsub + +import com.intuit.playerui.core.bridge.serialization.serializers.GenericSerializer +import com.intuit.playerui.core.expressions.evaluate +import com.intuit.playerui.core.player.state.inProgressState +import com.intuit.playerui.utils.test.PlayerTest +import com.intuit.playerui.utils.test.setupPlayer +import com.intuit.playerui.utils.test.simpleFlowString import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -76,7 +76,7 @@ internal class PubSubPluginTest : PlayerTest() { @TestTemplate fun pubsubWithMap() { val (expectedName, expectedData) = "eventName" to mapOf( - "some" to "data" + "some" to "data", ) var name: String? = null var data: Any? = null @@ -90,14 +90,14 @@ internal class PubSubPluginTest : PlayerTest() { @TestTemplate fun pubsubWithV8Object() { val (expectedName, expectedData) = "eventName" to mapOf( - "some" to "data" + "some" to "data", ) var name: String? = null var data: Any? = null plugin.subscribe(expectedName) { n, d -> name = n; data = d; println("EVENT: $n: ${Json.encodeToString(GenericSerializer(), d)}") } plugin.publish( expectedName, - expectedData + expectedData, ) name `should be equal to` expectedName @@ -107,7 +107,7 @@ internal class PubSubPluginTest : PlayerTest() { @TestTemplate fun pubsubWithDefaultName() { val (expectedName, expectedData) = "eventName" to mapOf( - "some" to "data" + "some" to "data", ) var name: String? = null var data: Any? = null @@ -123,7 +123,7 @@ internal class PubSubPluginTest : PlayerTest() { fun pubsubWithCustomName() { setupPlayer(PubSubPlugin(PubSubPlugin.Config("publishEvent"))) val (expectedName, expectedData) = "eventName" to mapOf( - "some" to "data" + "some" to "data", ) var name: String? = null var data: Any? = null diff --git a/plugins/pubsub/mocks/BUILD b/plugins/pubsub/mocks/BUILD new file mode 100644 index 000000000..4ee63092f --- /dev/null +++ b/plugins/pubsub/mocks/BUILD @@ -0,0 +1,36 @@ +#load("//:index.bzl", "generate_manifest","javascript_pipeline") +# +# +# +# +#filegroup( +# name = "mocks", +# srcs = glob(["**/*.json"]), +# visibility = ["//visibility:public"], +#) +# +#generate_manifest( +# name = "manifest", +# mocks = [":mocks"], +# visibility = ["//visibility:public"], +#) +# +#javascript_pipeline( +# name = "@player-ui/pubsub-plugin-mocks", +# entry = "index.ts", +# dependencies = [ +# "@npm//@player-tools/dsl", +# ], +# other_srcs = [ +# "index.ts", +# ":manifest", +# ] + glob(["**/*.tsx"]), +# out_dir = "", +#) +# +#java_library( +# name = "jar", +# resources = [":mocks"], +# resource_strip_prefix = "plugins/pubsub/mocks/", +# visibility = ["//visibility:public"], +#) diff --git a/plugins/pubsub/mocks/index.ts b/plugins/pubsub/mocks/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/pubsub/mocks/pubsub/pub-sub-basic.json b/plugins/pubsub/mocks/pubsub/pub-sub-basic.json new file mode 100644 index 000000000..c87a91691 --- /dev/null +++ b/plugins/pubsub/mocks/pubsub/pub-sub-basic.json @@ -0,0 +1,37 @@ +{ + "id": "generated-flow", + "views": [ + { + "id": "action", + "type": "action", + "exp": "@[ publish('some-event', 'event published message') ]@", + "label": { + "asset": { + "id": "action-label", + "type": "text", + "value": "Clicked to publish event" + } + } + } + ], + "data": { + "count": 0 + }, + "navigation": { + "BEGIN": "FLOW_1", + "FLOW_1": { + "startState": "VIEW_1", + "VIEW_1": { + "state_type": "VIEW", + "ref": "action", + "transitions": { + "*": "END_Done" + } + }, + "END_Done": { + "state_type": "END", + "outcome": "done" + } + } + } +} \ No newline at end of file diff --git a/plugins/reference-assets/android/BUILD b/plugins/reference-assets/android/BUILD index 881d7c911..b6257b721 100644 --- a/plugins/reference-assets/android/BUILD +++ b/plugins/reference-assets/android/BUILD @@ -5,12 +5,12 @@ # kt_android_library( # name = "assets", # srcs = glob(["src/main/java/**/*.kt"]), -# custom_package = "com.intuit.player.android.reference.assets", +# custom_package = "com.intuit.playerui.android.reference.assets", # manifest = ":src/main/AndroidManifest.xml", # resource_files = glob(["src/main/res/**"]), -# deps = main_deps, -# exports = main_exports, # visibility = ["//visibility:public"], +# exports = main_exports, +# deps = main_deps, # ) # lint( diff --git a/plugins/reference-assets/android/build.bzl b/plugins/reference-assets/android/build.bzl index 6ca1825fd..cbb2caa08 100644 --- a/plugins/reference-assets/android/build.bzl +++ b/plugins/reference-assets/android/build.bzl @@ -1,28 +1,27 @@ # load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_local_test") # load("@rules_player//kotlin:lint.bzl", "lint") -# def kt_asset_test( -# name, -# test_class, -# srcs = [], -# deps = [] -# ): -# kt_android_local_test( -# name = name, -# srcs = srcs, -# custom_package = "com.intuit.player.android.reference.assets", -# test_class = test_class, -# deps = deps + [ -# "//plugins/reference-assets/android/src/androidTest/java/com/intuit/player/android/reference/assets/test", -# "//jvm/j2v8:j2v8-all", -# ], -# resources = [ -# "//plugins/reference-assets/mocks", -# ], -# manifest_values = { -# "minSdkVersion": "14", -# }, -# ) +def kt_asset_test( + name, + test_class, + srcs = [], + deps = []): + kt_android_local_test( + name = name, + srcs = srcs, + custom_package = "com.intuit.playerui.android.reference.assets", + test_class = test_class, + deps = deps + [ + "//plugins/mocks:jar", + "//plugins/reference-assets/android/src/androidTest/java/com/intuit/playerui/android/reference/assets/test", + "//jvm/j2v8:j2v8-all", + ], + resources = [ + ], + manifest_values = { + "minSdkVersion": "14", + }, + ) # lint( # name = name, diff --git a/plugins/reference-assets/android/deps.bzl b/plugins/reference-assets/android/deps.bzl index 57a819c4b..8886b7467 100644 --- a/plugins/reference-assets/android/deps.bzl +++ b/plugins/reference-assets/android/deps.bzl @@ -1,6 +1,6 @@ load("//jvm/dependencies:versions.bzl", "versions") load("@rules_player//maven:parse_coordinates.bzl", "parse_coordinates") -load("//plugins/reference-assets/android/src/androidTest/java/com/intuit/player/android/reference/assets/test:deps.bzl", maven_test = "maven") +load("//plugins/reference-assets/android/src/androidTest/java/com/intuit/playerui/android/reference/assets/test:deps.bzl", maven_test = "maven") maven_main = [] diff --git a/plugins/reference-assets/android/src/androidTest/java/com/intuit/player/android/reference/assets/test/AssetTest.kt b/plugins/reference-assets/android/src/androidTest/java/com/intuit/player/android/reference/assets/test/AssetTest.kt deleted file mode 100644 index dd3d2d913..000000000 --- a/plugins/reference-assets/android/src/androidTest/java/com/intuit/player/android/reference/assets/test/AssetTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.intuit.player.android.reference.assets.test - -import android.content.Context -import android.os.Build.VERSION_CODES.P -import android.view.View -import androidx.test.core.app.ApplicationProvider -import androidx.test.runner.AndroidJUnit4 -import com.intuit.player.android.AndroidPlayer -import com.intuit.player.android.asset.RenderableAsset -import com.intuit.player.android.reference.assets.ReferenceAssetsPlugin -import com.intuit.player.jvm.core.player.state.PlayerFlowState -import com.intuit.player.jvm.core.plugins.Plugin -import com.intuit.player.jvm.utils.makeFlow -import com.intuit.player.jvm.utils.mocks.ClassLoaderMock -import com.intuit.player.jvm.utils.mocks.ClassLoaderMocksReader -import com.intuit.player.jvm.utils.mocks.Mock -import com.intuit.player.jvm.utils.mocks.getFlow -import com.intuit.player.jvm.utils.start -import com.intuit.player.plugins.transactions.PendingTransactionPlugin -import com.intuit.player.plugins.types.CommonTypesPlugin -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import org.junit.Before -import org.junit.Rule -import org.junit.rules.TestName -import org.junit.runner.RunWith -import org.robolectric.annotation.Config - -@RunWith(AndroidJUnit4::class) -@Config(sdk = [P]) -abstract class AssetTest(val group: String? = null) { - - @get:Rule - val name = TestName() - - open val plugins: List by lazy { listOf(ReferenceAssetsPlugin(), CommonTypesPlugin(), PendingTransactionPlugin()) } - - val context: Context get() = ApplicationProvider.getApplicationContext() - - val player by lazy { - AndroidPlayer(plugins) - } - - var currentAssetTree: RenderableAsset? = null - private set(value) { - field = value - currentView = field?.render(context) - } - - var currentView: View? = null - private set - - protected val currentState: PlayerFlowState get() = player.state - - protected val mocks get() = ClassLoaderMocksReader(context.classLoader).mocks.filter { - group == null || group == it.group - } - - @Before - fun beforeEach() { - player.onUpdate { asset, _ -> currentAssetTree = asset } - } - - fun launchMock() = launchMock(name.methodName) - - fun launchMock(name: String) = launchMock( - mocks.find { it.name == name || it.name == "$group-$name" } - ?: throw IllegalArgumentException("$name not found in mocks: ${mocks.map { "${it.group}/${it.name}" }}") - ) - - fun launchMock(mock: Mock<*>) = launchJson( - when (mock) { - is ClassLoaderMock -> mock.getFlow(context.classLoader) - else -> throw IllegalArgumentException("mock of type ${mock::class.java.simpleName} not supported") - } - ) - - fun launchJson(json: JsonElement) = launchJson(Json.encodeToString(json)) - - fun launchJson(json: String) = player.start(makeFlow(json)).onComplete { - it.exceptionOrNull()?.printStackTrace() - } -} diff --git a/plugins/reference-assets/android/src/androidTest/java/com/intuit/player/android/reference/assets/action/ActionTest.kt b/plugins/reference-assets/android/src/androidTest/java/com/intuit/playerui/android/reference/assets/action/ActionTest.kt similarity index 64% rename from plugins/reference-assets/android/src/androidTest/java/com/intuit/player/android/reference/assets/action/ActionTest.kt rename to plugins/reference-assets/android/src/androidTest/java/com/intuit/playerui/android/reference/assets/action/ActionTest.kt index 29bfc6ecf..5ce5ae3f5 100644 --- a/plugins/reference-assets/android/src/androidTest/java/com/intuit/player/android/reference/assets/action/ActionTest.kt +++ b/plugins/reference-assets/android/src/androidTest/java/com/intuit/playerui/android/reference/assets/action/ActionTest.kt @@ -1,26 +1,26 @@ -package com.intuit.player.android.reference.assets.action +package com.intuit.playerui.android.reference.assets.action import android.widget.Button import android.widget.LinearLayout import androidx.core.view.get -import com.intuit.player.android.reference.assets.R -import com.intuit.player.android.reference.assets.test.AssetTest -import com.intuit.player.android.reference.assets.test.shouldBeAsset -import com.intuit.player.android.reference.assets.test.shouldBePlayerState -import com.intuit.player.android.reference.assets.test.shouldBeView -import com.intuit.player.android.reference.assets.text.Text -import com.intuit.player.jvm.core.player.state.CompletedState -import com.intuit.player.jvm.core.player.state.ErrorState -import com.intuit.player.jvm.core.player.state.InProgressState -import com.intuit.player.jvm.core.player.state.dataModel +import com.intuit.playerui.android.reference.assets.R +import com.intuit.playerui.android.reference.assets.test.AssetTest +import com.intuit.playerui.android.reference.assets.test.shouldBeAsset +import com.intuit.playerui.android.reference.assets.test.shouldBePlayerState +import com.intuit.playerui.android.reference.assets.test.shouldBeView +import com.intuit.playerui.android.reference.assets.text.Text +import com.intuit.playerui.core.player.state.CompletedState +import com.intuit.playerui.core.player.state.ErrorState +import com.intuit.playerui.core.player.state.InProgressState +import com.intuit.playerui.core.player.state.dataModel import org.junit.Assert.assertEquals import org.junit.Test -class ActionTest : AssetTest("action") { +class ActionTest : AssetTest("reference-assets") { @Test fun actionExpression() { - launchMock("basic") + launchMock("action-basic") currentAssetTree.shouldBeAsset { data.label.shouldBeAsset { @@ -32,6 +32,7 @@ class ActionTest : AssetTest("action") { repeat(10) { assertEquals("Count: $it", text.toString()) performClick() + blockUntilRendered() } } @@ -42,7 +43,7 @@ class ActionTest : AssetTest("action") { @Test fun transitionToEndSuccess() { - launchMock("transition-to-end") + launchMock("action-transition-to-end") val collectionValues = currentView?.findViewById(R.id.collection_values) ?: throw AssertionError("current view is null") assertEquals(2, collectionValues.childCount) @@ -50,6 +51,7 @@ class ActionTest : AssetTest("action") { collectionValues[0].shouldBeView diff --git a/tools/asset-testing-library/core/src/index.ts b/tools/asset-testing-library/core/src/index.ts index c438b13c3..a3be5c6f2 100644 --- a/tools/asset-testing-library/core/src/index.ts +++ b/tools/asset-testing-library/core/src/index.ts @@ -1,5 +1,5 @@ -import type { TransformFunction, Asset, Flow, View } from '@player-ui/player'; -import { Player } from '@player-ui/player'; +import type { TransformFunction, Asset, Flow, View } from "@player-ui/player"; +import { Player } from "@player-ui/player"; import { AssetTransformPlugin } from '@player-ui/asset-transform-plugin'; import { makeFlow } from '@player-ui/make-flow'; diff --git a/tools/build_ios_bundles.sh b/tools/build_ios_bundles.sh index c9107d462..3b2346d8f 100755 --- a/tools/build_ios_bundles.sh +++ b/tools/build_ios_bundles.sh @@ -1,5 +1,11 @@ #!/bin/bash git_root=$(git rev-parse --show-toplevel) +# Build all bundle targets that we rely on "bazel" build `bazel query 'attr(name, "^.*_Bundles$", //...)' --output label 2>/dev/null` +# explicitly build make-flow +"bazel" build //core/make-flow/... +# run yarn so make-flow is importable in node runtime +yarn +# Run pod install to generate xcworkspace cd "$git_root/xcode" && bundle exec pod install \ No newline at end of file