diff --git a/.eslintrc.json b/.eslintrc.json index 3eab9c6ec0..e93d64d2dc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,12 +1,12 @@ { "root": true, "ignorePatterns": ["**/*"], - "plugins": ["@nrwl/nx"], + "plugins": ["@nx"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { - "@nrwl/nx/enforce-module-boundaries": [ + "@nx/enforce-module-boundaries": [ "error", { "enforceBuildableLibDependency": true, @@ -23,7 +23,7 @@ }, { "files": ["*.ts", "*.tsx"], - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "plugin:@nrwl/nx/typescript"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "plugin:@nx/typescript"], "rules": { "no-empty": "off", "no-useless-escape": "off", @@ -38,19 +38,29 @@ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-this-alias": "warn", "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-inferrable-types": "off" + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-extra-semi": "error", + "no-extra-semi": "off" } }, { "files": ["*.js", "*.jsx"], - "extends": ["plugin:@nrwl/nx/javascript"], - "rules": {} + "extends": ["plugin:@nx/javascript"], + "rules": { + "@typescript-eslint/no-extra-semi": "error", + "no-extra-semi": "off" + } }, { "files": ["references.d.ts"], "rules": { "@typescript-eslint/triple-slash-reference": "off" } + }, + { + "files": "*.json", + "parser": "jsonc-eslint-parser", + "rules": {} } ] } diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..00c0cf4037 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,226 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + + - package-ecosystem: npm + directory: /apps/automated + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/pages + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/ui/lifecycle + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/ui/root-view/mymodule + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/xml-declaration/mymodulewithxml + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/xml-declaration + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/toolbox + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/ui + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: / + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/core/css-value + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/core/css + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/core/js-libs/easysax + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/core + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/devtools + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/types-android + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/types-ios + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/types-minimal + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/types + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/ui-mobile-base + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/webpack5 + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/winter-tc + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /tools/workspace-plugin + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] \ No newline at end of file diff --git a/.github/workflows/apps_automated.yml b/.github/workflows/apps_automated.yml deleted file mode 100644 index 1fce002040..0000000000 --- a/.github/workflows/apps_automated.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: 'apps/automated' - -on: - push: - branches: - - main - - 'ci/**' - pull_request: - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - test: - runs-on: macos-12 - - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-node@v3 - with: - node-version: 18 - - - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '11' - - - name: Install Python - uses: actions/setup-python@v4 - with: - python-version: '3' - - - name: Install NativeScript - run: | - python3 -m pip install --upgrade pip six - npm i -g nativescript --ignore-scripts --legacy-peer-deps - ns usage-reporting disable - ns error-reporting disable - ns doctor - - - name: Setup - run: npm run setup - - - name: Unit Tests - run: npx nx run-many --target=test --configuration=ci --projects=core - - - name: Create Emulator - uses: rigor789/action-create-emulator@main - - - name: Test (Android) - run: node tools/scripts/run-automated.js android - - - name: Start iOS Simulator - if: ${{ always() && !cancelled() }} # run iOS tests even if Android tests failed - uses: futureware-tech/simulator-action@v2 - with: - model: 'iPhone 13 Pro Max' - os_version: '>=15.0' - - - name: Test (iOS) - if: ${{ always() && !cancelled() }} # run iOS tests even if Android tests failed - run: node tools/scripts/run-automated.js ios diff --git a/.github/workflows/apps_automated_android.yml b/.github/workflows/apps_automated_android.yml new file mode 100644 index 0000000000..ff4a3eb088 --- /dev/null +++ b/.github/workflows/apps_automated_android.yml @@ -0,0 +1,72 @@ +name: 'apps/automated/android' +permissions: + contents: read + pull-requests: write + +on: + push: + branches: + - main + - 'ci/**' + pull_request: + workflow_dispatch: + +env: + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-android: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + + + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: 23.5.0 + + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1 + with: + main-branch-name: 'main' + + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'temurin' + java-version: '21' + + - name: Install Python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: '3' + + - name: Install NativeScript + run: | + python3 -m pip install --upgrade pip six + npm i -g nativescript --ignore-scripts + ns usage-reporting disable + ns error-reporting disable + + - name: Setup + run: npm run setup + + - name: Unit Tests + run: npx nx run-many --target=test --configuration=ci --projects=core + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Run tests on Android Emulator + uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0 + with: + api-level: 35 + arch: x86_64 + script: npx nx test apps-automated -c=android diff --git a/.github/workflows/apps_automated_ios.yml b/.github/workflows/apps_automated_ios.yml new file mode 100644 index 0000000000..7dfb51f331 --- /dev/null +++ b/.github/workflows/apps_automated_ios.yml @@ -0,0 +1,60 @@ +name: 'apps/automated/ios' +permissions: + contents: read + pull-requests: write + +on: + push: + branches: + - main + - 'ci/**' + pull_request: + workflow_dispatch: + +env: + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-ios: + # runs-on: macos-latest + runs-on: warp-macos-15-arm64-6x + + steps: + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + + # - name: ActionDebugger By Warpbuild + # uses: Warpbuilds/action-debugger@v1.3 + + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: 23.5.0 + + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1 + with: + main-branch-name: 'main' + + - name: Install NativeScript + run: | + npm i -g nativescript --ignore-scripts + ns usage-reporting disable + ns error-reporting disable + # ns doctor + + - name: Setup + run: npm run setup + + - name: Unit Tests + run: npx nx run-many --target=test --configuration=ci --projects=core + + - name: Start iOS Simulator + uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5 # v5 + with: + model: 'iPhone 16 Pro' + + - name: Run tests on iOS Simulator + run: npx nx test apps-automated -c=ios diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000000..6b3ebd28cc --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,22 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - name: 'Dependency Review' + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 \ No newline at end of file diff --git a/.github/workflows/npm_release_core.yml b/.github/workflows/npm_release_core.yml deleted file mode 100644 index 67e6359f82..0000000000 --- a/.github/workflows/npm_release_core.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: '@nativescript/core -> npm' - -on: - push: - branches: [ 'main' ] - paths: - - 'packages/core/**' - workflow_dispatch: - -env: - NPM_TAG: 'next' - -jobs: - release: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Setup - run: npm run setup - - - name: Generate Version - working-directory: packages/core - run: | - echo NPM_VERSION=$(node -e "console.log(require('./package.json').version);")-$NPM_TAG-$(date +"%m-%d-%Y")-$GITHUB_RUN_ID >> $GITHUB_ENV - - - name: Bump Version - working-directory: packages/core - run: npm version $NPM_VERSION - - # TODO: build ui-mobile-base first - - name: Build @nativescript/core - run: npx nx run core:build - - - name: Publish @nativescript/core - working-directory: dist/packages - env: - NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} - run: | - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ../../.npmrc - echo "Publishing @nativescript/core@$NPM_VERSION to NPM with tag $NPM_TAG..." - npm publish nativescript-core-$NPM_VERSION.tgz --tag $NPM_TAG diff --git a/.github/workflows/npm_release_tns_core.yml b/.github/workflows/npm_release_tns_core.yml index 5b3849e1b6..c4f405ee63 100644 --- a/.github/workflows/npm_release_tns_core.yml +++ b/.github/workflows/npm_release_tns_core.yml @@ -1,4 +1,7 @@ name: 'tns-core-modules -> npm' +permissions: + contents: read + pull-requests: write on: push: @@ -15,10 +18,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 + with: + egress-policy: audit + + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup - run: npm install --legacy-peer-deps + run: npm install - name: Generate Version run: | diff --git a/.github/workflows/npm_release_types.yml b/.github/workflows/npm_release_types.yml index 440df76ad5..4aa5c151e5 100644 --- a/.github/workflows/npm_release_types.yml +++ b/.github/workflows/npm_release_types.yml @@ -1,5 +1,8 @@ # TODO: modify to build android & ios types first and then merge into types name: '@nativescript/types -> npm' +permissions: + contents: read + pull-requests: write on: push: @@ -15,10 +18,15 @@ jobs: runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 + with: + egress-policy: audit + - name: Todo run: | echo "TODO: implement action" -# - uses: actions/checkout@v2 +# - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0 # # - name: Setup # run: npm install diff --git a/.github/workflows/npm_release_webpack.yml b/.github/workflows/npm_release_webpack.yml index 700aed5222..8d78e4e624 100644 --- a/.github/workflows/npm_release_webpack.yml +++ b/.github/workflows/npm_release_webpack.yml @@ -1,4 +1,7 @@ name: '@nativescript/webpack -> npm' +permissions: + contents: read + pull-requests: write on: push: @@ -14,10 +17,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 + with: + egress-policy: audit + + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup - run: npm install --legacy-peer-deps + run: npm install - name: Generate Version working-directory: packages/webpack @@ -32,10 +40,10 @@ jobs: run: npx nx run webpack:build - name: Publish @nativescript/webpack - working-directory: dist/packages + working-directory: dist/packages/webpack5 env: NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} run: | echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ../../.npmrc echo "Publishing @nativescript/webpack@$NPM_VERSION to NPM with tag $NPM_TAG..." - npm publish nativescript-webpack.tgz --tag $NPM_TAG --dry-run + npm publish --tag $NPM_TAG --dry-run diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml new file mode 100644 index 0000000000..a4a2873931 --- /dev/null +++ b/.github/workflows/ossf-scorecard.yml @@ -0,0 +1,78 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '23 13 * * 3' + push: + branches: [ "main" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. + if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore + # file_mode: git + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 + with: + sarif_file: results.sarif diff --git a/.github/workflows/secure_nx_release.yml b/.github/workflows/secure_nx_release.yml new file mode 100644 index 0000000000..63bf00c3e9 --- /dev/null +++ b/.github/workflows/secure_nx_release.yml @@ -0,0 +1,424 @@ +name: Release Workflow + +on: + push: + branches: + - main + tags: + # Matches the Nx releaseTag pattern in nx.json: "{version}-{projectName}" + - '*-*' + workflow_dispatch: + inputs: + dist-tag: + description: "npm dist-tag to use (e.g. latest | next | canary)" + required: false + type: string + default: next + dry-run: + description: "Run release steps without making changes (no git push, no publish)" + required: false + type: boolean + default: false + release-group: + description: "Optional Nx project pattern to scope the release (empty = default behavior)" + required: false + type: string + default: "" + +concurrency: + # Avoid overlapping publishes on the same ref/branch + group: nx-release-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write # needed to push version commits and tags + id-token: write # required for npm provenance / trusted publishing (OIDC) + +jobs: + release: + name: Version and Publish (gated by environment) + if: ${{ github.actor != 'github-actions[bot]' }} + runs-on: ubuntu-latest + environment: + name: ${{ (github.event_name == 'workflow_dispatch' && inputs.dry-run) && 'npm-publish-dry-run' || 'npm-publish' }} + + env: + # Optional: provide Nx Cloud token if used in this repo + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + NEXT_PRERELEASE_PROJECT_ALLOWLIST: core,webpack5 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@fe104658747b27e96e4f7e80cd0a94068e53901d # v2.16.1 + with: + egress-policy: audit + + - name: Checkout repository (full history for tagging) + uses: actions/checkout@v6.0.0 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + + - name: Update npm (required for OIDC trusted publishing) + run: | + npm install -g npm@^11.5.1 + npm --version + + - name: Install dependencies + run: npm ci + + - name: Repo setup + run: npm run setup + + - name: Resolve release context + id: ctx + shell: bash + run: | + set -euo pipefail + + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + dist_tag="${{ inputs['dist-tag'] }}" + scope="${{ inputs['release-group'] }}" + dry_run="${{ inputs['dry-run'] }}" + mode="dispatch" + elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then + dist_tag="" + scope="" + dry_run="false" + mode="tag" + else + dist_tag="next" + scope="" + dry_run="false" + mode="main" + fi + + echo "mode=${mode}" >> "$GITHUB_OUTPUT" + echo "dist_tag=${dist_tag}" >> "$GITHUB_OUTPUT" + echo "scope=${scope}" >> "$GITHUB_OUTPUT" + echo "dry_run=${dry_run}" >> "$GITHUB_OUTPUT" + + - name: Determine affected release projects (main) + id: affected + if: ${{ steps.ctx.outputs.mode == 'main' }} + shell: bash + run: | + set -euo pipefail + + base='${{ github.event.before }}' + head='${{ github.sha }}' + + # Handle edge cases where base commit doesn't exist (first push, force-push, etc.) + # Use HEAD~1 as fallback, or just compare against HEAD if no parent exists + if [[ "$base" == "0000000000000000000000000000000000000000" ]] || ! git cat-file -e "$base" 2>/dev/null; then + echo "Base commit not available, falling back to HEAD~1" + base="HEAD~1" + # If HEAD~1 doesn't exist (first commit), use empty tree + if ! git cat-file -e "$base" 2>/dev/null; then + base="$(git hash-object -t tree /dev/null)" + fi + fi + + # Only consider main-branch prerelease libraries allowed for automatic next publishes. + affected_json=$(npx nx show projects --affected --base "$base" --head "$head" --type lib --projects "$NEXT_PRERELEASE_PROJECT_ALLOWLIST" --json) + affected_list=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(a.join(","));});') + affected_count=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(String(a.length));});') + + echo "projects=${affected_list}" >> "$GITHUB_OUTPUT" + echo "count=${affected_count}" >> "$GITHUB_OUTPUT" + + - name: Determine tag release project and dist-tag (tags) + id: taginfo + if: ${{ steps.ctx.outputs.mode == 'tag' }} + shell: bash + run: | + set -euo pipefail + + tag_name="${GITHUB_REF_NAME}" + + # Find the project by matching the tag suffix against known releaseable packages. + projects=$(npx nx show projects --projects "packages/*" --type lib --exclude "ui-mobile-base,types-minimal,winter-tc,types,types-ios,types-android" --sep ' ') + + best_match="" + best_len=0 + for p in $projects; do + suffix="-${p}" + if [[ "$tag_name" == *"$suffix" ]]; then + if (( ${#p} > best_len )); then + best_match="$p" + best_len=${#p} + fi + fi + done + + if [[ -z "$best_match" ]]; then + echo "Could not determine project from tag '$tag_name'. Expected '{version}-{projectName}'." >&2 + exit 1 + fi + + version_part="${tag_name%-$best_match}" + if [[ "$version_part" == *-* ]]; then + dist_tag="next" + else + dist_tag="latest" + fi + + echo "project=${best_match}" >> "$GITHUB_OUTPUT" + echo "version=${version_part}" >> "$GITHUB_OUTPUT" + echo "dist_tag=${dist_tag}" >> "$GITHUB_OUTPUT" + + - name: Configure git user for automated commits + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # VERSION: updates versions and creates git tags following nx.json releaseTag.pattern. + - name: nx release version (main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' }} + shell: bash + run: | + set -euo pipefail + npx nx release version prerelease \ + --preid next \ + --projects "${{ steps.affected.outputs.projects }}" \ + --git-commit \ + --git-push \ + --verbose + + - name: nx release version (main, no-op) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count == '0' }} + run: echo "No affected release projects on main; skipping version + publish." + + - name: nx release version (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }} + shell: bash + run: | + set -euo pipefail + + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg=(--projects "$scope") + else + projects_arg=() + fi + + npx nx release version prerelease \ + --preid "${{ steps.ctx.outputs.dist_tag }}" \ + "${projects_arg[@]}" \ + --git-commit \ + --git-push \ + --verbose + + - name: nx release version (dispatch, dry-run) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run }} + shell: bash + run: | + set -euo pipefail + + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg=(--projects "$scope") + else + projects_arg=() + fi + + npx nx release version prerelease \ + --preid "${{ steps.ctx.outputs.dist_tag }}" \ + "${projects_arg[@]}" \ + --verbose \ + --dry-run + + # BUILD: Ensure projects are built before publishing + - name: Build affected projects (main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' }} + run: npx nx run-many -t build --projects "${{ steps.affected.outputs.projects }}" --verbose + + - name: Build projects (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' }} + shell: bash + run: | + set -euo pipefail + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + npx nx run-many -t build --projects "$scope" --verbose + else + npx nx run-many -t build --all --verbose + fi + + # PUBLISH: OIDC trusted publishing (default). Avoid any lingering token auth. + - name: nx release publish (OIDC, main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + npx nx release publish \ + --projects "${{ steps.affected.outputs.projects }}" \ + --tag "${{ steps.ctx.outputs.dist_tag }}" \ + --access public \ + --verbose + + - name: nx release publish (OIDC, dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && steps.ctx.outputs.dry_run != 'true' && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + + npx nx release publish \ + $projects_arg \ + --tag "${{ steps.ctx.outputs.dist_tag }}" \ + --access public \ + --verbose + + - name: nx release publish (OIDC, dispatch dry-run) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + + npx nx release publish \ + $projects_arg \ + --tag "${{ steps.ctx.outputs.dist_tag }}" \ + --access public \ + --verbose \ + --dry-run + + # PUBLISH: token fallback (only when explicitly enabled via repo/environment variable USE_NPM_TOKEN=true). + - name: nx release publish (token, main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + npx nx release publish --projects "${{ steps.affected.outputs.projects }}" --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose + + - name: nx release publish (token, dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && steps.ctx.outputs.dry_run != 'true' && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + set -euo pipefail + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + npx nx release publish $projects_arg --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose + + - name: nx release publish (token, dispatch dry-run) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + set -euo pipefail + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + npx nx release publish $projects_arg --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose --dry-run + + # Tag-triggered publishing: publish the single package referenced by the tag. + - name: Build project before publish (tag) + if: ${{ steps.ctx.outputs.mode == 'tag' }} + run: npx nx build "${{ steps.taginfo.outputs.project }}" --verbose + + - name: nx release publish (tag) + if: ${{ steps.ctx.outputs.mode == 'tag' && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + npx nx release publish \ + --projects "${{ steps.taginfo.outputs.project }}" \ + --tag "${{ steps.taginfo.outputs.dist_tag }}" \ + --access public \ + --verbose + + - name: nx release publish (tag, token) + if: ${{ steps.ctx.outputs.mode == 'tag' && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + npx nx release publish --projects "${{ steps.taginfo.outputs.project }}" --tag "${{ steps.taginfo.outputs.dist_tag }}" --access public --verbose + + - name: Summary + if: always() + run: | + mode="${{ steps.ctx.outputs.mode }}" + + echo "Nx Release completed." + echo "- mode: ${mode}" + echo "- dist-tag: ${{ steps.ctx.outputs.mode == 'tag' && steps.taginfo.outputs.dist_tag || steps.ctx.outputs.dist_tag }}" + echo "- scope: '${{ steps.ctx.outputs.scope }}'" + echo "- next-prerelease-allowlist: ${NEXT_PRERELEASE_PROJECT_ALLOWLIST}" + if [[ "$mode" == "main" ]]; then + echo "- projects: ${{ steps.affected.outputs.projects }}" + elif [[ "$mode" == "dispatch" ]]; then + if [[ -n "${{ steps.ctx.outputs.scope }}" ]]; then + echo "- projects: ${{ steps.ctx.outputs.scope }}" + else + echo "- projects: all configured release projects" + fi + else + echo "- project: ${{ steps.taginfo.outputs.project }}" + fi + echo "- dry-run: ${{ steps.ctx.outputs.dry_run }}" + echo "- use-token: ${{ vars.USE_NPM_TOKEN == 'true' }}" diff --git a/.gitignore b/.gitignore index d9476c44e2..05192106d7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,10 @@ /dist /tmp /out-tsc +.ns-vite-build # dependencies **/node_modules -**/package-lock.json **/yarn.lock **/pnpm-lock.yaml .npmrc @@ -41,6 +41,7 @@ apps/**/*/*.js apps/**/*/*.map apps/**/*/platforms apps/**/*/webpack.*.js +apps/**/package-lock.json *.tgz .npmrc **/**/*.log @@ -55,3 +56,10 @@ Thumbs.db # types ios-typings-prj + +.nx/cache +.nx/workspace-data +vite.config.*.timestamp* +vitest.config.*.timestamp* +.cursor/rules/nx-rules.mdc +.github/instructions/nx.instructions.md diff --git a/.husky/pre-commit b/.husky/pre-commit index a3cebf1ec5..abf1181ca3 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,7 +1,4 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" -export NVM_DIR="$HOME/.nvm" -[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" - -npx lint-staged +npx lint-staged --allow-empty diff --git a/.prettierignore b/.prettierignore index 7cc93b890e..ad9f2caf8a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,6 +10,7 @@ e2e js-libs platforms tests +apps/**/*.xml packages/types-android packages/types-ios packages/types-minimal @@ -24,3 +25,6 @@ package.json *.scss *.sh !packages/webpack/templates/*.js + +/.nx/cache +/.nx/workspace-data \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 5acd7ae98f..1358635a74 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,10 @@ -// Place your settings in this file to overwrite default and user settings. { - "search.exclude": { - "**/node_modules": true, - "**/bower_components": true, - "**/platforms": true, - "**/*.js": true, - "**/*.js.map": true - } -} \ No newline at end of file + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "**/platforms": true, + "**/*.js": true, + "**/*.js.map": true + }, + "eslint.validate": ["json"] +} diff --git a/README.md b/README.md index 1025e582fb..02b3bc6655 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,55 @@ -

- - NativeScript - -

+[![NativeScript](./tools/graphics/cover.png)](https://nativescript.org)

- [![Automated Tests Passing](https://github.com/NativeScript/NativeScript/actions/workflows/apps_automated.yml/badge.svg)](https://github.com/NativeScript/NativeScript/actions/workflows/apps_automated.yml) + [![Automated Android Tests Passing](https://github.com/NativeScript/NativeScript/actions/workflows/apps_automated_android.yml/badge.svg)](https://github.com/NativeScript/NativeScript/actions/workflows/apps_automated_android.yml) + [![Automated iOS Tests Passing](https://github.com/NativeScript/NativeScript/actions/workflows/apps_automated_ios.yml/badge.svg)](https://github.com/NativeScript/NativeScript/actions/workflows/apps_automated_ios.yml) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/NativeScript/NativeScript/blob/main/LICENSE) [![NPM Version](https://badge.fury.io/js/%40nativescript%2Fcore.svg)](https://www.npmjs.com/@nativescript/core) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://nativescript.org/discord) + [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FNativeScript%2FNativeScript.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FNativeScript%2FNativeScript?ref=badge_shield) + [![website](https://img.shields.io/badge/website-nativescript.org-purple.svg)](https://nativescript.org) + [![https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg](https://good-labs.github.io/greater-good-affirmation/assets/images/badge.svg)](https://good-labs.github.io/greater-good-affirmation) + [![support](https://img.shields.io/badge/sponsor-Open%20Collective-blue.svg)](https://opencollective.com/NativeScript)

-[NativeScript](http://www.nativescript.org) empowers you to access native APIs from JavaScript directly. Currently iOS and Android runtimes are provided for rich mobile development across a variety of diverse use cases. +[NativeScript](http://www.nativescript.org) empowers you to access native APIs from JavaScript directly. Currently iOS, Android, and visionOS runtimes are provided for rich mobile development across a variety of diverse use cases. + + +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FNativeScript%2FNativeScript.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FNativeScript%2FNativeScript?ref=badge_large) + +## Quick Start + +To get started with NativeScript, follow these steps: + +1. **Install the NativeScript CLI globally:** + ```bash + npm install -g nativescript + ``` + +2. **Create a new project:** + ```bash + ns create my-app + ``` + +3. **Navigate into your project directory:** + ```bash + cd my-app + ``` + +4. **Run your app on an emulator or device:** + ```bash + ns run android + ``` + or + ```bash + ns run ios + ``` ## Contribute -1. [Setup your local development environment](https://docs.nativescript.org/environment-setup.html) +1. [Setup your local development environment](https://docs.nativescript.org/setup/) 2. Clone to contribute: @@ -36,32 +68,34 @@ We love you and your pull requests 🤗. Please follow our [contributing guide]( ## @nativescript/* -* [@nativescript/core](https://github.com/NativeScript/NativeScript/tree/main/packages/core) - * Core iOS/Android for NativeScript. -* [@nativescript/types](https://www.npmjs.com/package/@nativescript/types) - * Types for both iOS/Android below wrapped up as a convenience. *Most commonly used.* -* [@nativescript/types-ios](https://github.com/NativeScript/NativeScript/tree/main/packages/types-ios) - * Types for iOS. -* [@nativescript/types-android](https://github.com/NativeScript/NativeScript/tree/main/packages/types-android) - * Types for Android. -* [@nativescript/types-minimal](https://github.com/NativeScript/NativeScript/tree/main/packages/types-minimal) - * A very minimal set of types for only the latest Android and iOS sdks. Most commonly used to optimize Web-based IDE's which auto load all type declarations from node_modules. -* [@nativescript/ui-mobile-base](https://github.com/NativeScript/NativeScript/tree/main/packages/ui-mobile-base) - * UI mobile base native classes used by core. -* [@nativescript/webpack](https://github.com/NativeScript/NativeScript/tree/main/packages/webpack5) - * Webpack build utilities and configs used by NativeScript apps. +- [@nativescript/core](https://github.com/NativeScript/NativeScript/tree/main/packages/core) + Singular primitives offering an easy-to-use API surface for diverse iOS/visionOS/Android APIs implemented with NativeScript. +- [@nativescript/types](https://github.com/NativeScript/NativeScript/tree/main/packages/types) + Types for both iOS/Android below wrapped up as a convenience. *Most commonly used.* +- [@nativescript/types-ios](https://github.com/NativeScript/NativeScript/tree/main/packages/types-ios) + Types for iOS. +- [@nativescript/types-android](https://github.com/NativeScript/NativeScript/tree/main/packages/types-android) + Types for Android. +- [@nativescript/types-minimal](https://github.com/NativeScript/NativeScript/tree/main/packages/types-minimal) + A very minimal set of types for only the latest Android and iOS sdks. Most commonly used to optimize Web-based IDE's which auto load all type declarations from node_modules. +- [@nativescript/ui-mobile-base](https://github.com/NativeScript/NativeScript/tree/main/packages/ui-mobile-base) + UI mobile base native classes used by core. +- [@nativescript/webpack](https://github.com/NativeScript/NativeScript/tree/main/packages/webpack5) + Webpack build utilities and configs used by NativeScript apps. ## Quick Links - [NativeScript Home](https://nativescript.org) -- [NativeScript Tutorials](https://docs.nativescript.org/tutorial/) +- [NativeScript Tutorials](https://docs.nativescript.org/tutorials/) - [NativeScript documentation](https://docs.nativescript.org/) - JavaScript starter: https://nativescript.new/javascript - TypeScript starter: https://nativescript.new/typescript - Angular starter: https://nativescript.new/angular -- Vue starter: https://nativescript.new/vue -- Svelte starter: https://nativescript.new/svelte - React starter: https://nativescript.new/react +- Solid starter: https://nativescript.new/solid +- Svelte starter: https://nativescript.new/svelte +- Vue starter: https://nativescript.new/vue +- Vue 3 starter: https://nativescript.new/vue3 - [NativeScript on Twitter](http://twitter.com/NativeScript) - [NativeScript on Discord](https://nativescript.org/discord) - [NativeScript on Stack Overflow](http://stackoverflow.com/questions/tagged/nativescript) @@ -70,23 +104,29 @@ We love you and your pull requests 🤗. Please follow our [contributing guide]( Outside the source centralized in this repo, NativeScript consists of a few other source repos. Here are the major ones: -- **[iOS Runtime](https://github.com/NativeScript/ns-v8ios-runtime)** - - This repo contains the NativeScript iOS Runtime — the code that hosts NativeScript iOS apps, and allows JavaScript code to be executed on iOS devices. The iOS runtime is written in a mix of C++, Objective-C, and more. -- **[Android Runtime](https://github.com/NativeScript/android-runtime)** - - This repo contains the NativeScript Android Runtime — the code that hosts NativeScript Android apps, and allows JavaScript code to be executed on Android devices. The Android runtime is written in a mix of C++ and Java. -- **[CLI](//github.com/NativeScript/nativescript-cli)** - - This repo contains the NativeScript command-line interface, which lets you create, build, and run apps using NativeScript. The CLI is written in TypeScript. -- **[Docs](//github.com/NativeScript/docs-new)** - - This repo contains NativeScript documentation, which is available at . The docs are written in Markdown. -- **[Plugins](https://github.com/NativeScript/plugins)** - - This repo contains a [plugin workspace](https://docs.nativescript.org/plugins/plugin-workspace-guide.html) with several often useful plugins. -- **[Firebase](https://github.com/NativeScript/firebase)** - - Modular Firebase 🔥 implementation for iOS & Android. -- **[ML Kit](https://github.com/NativeScript/mlkit)** - - Google's [ML Kit SDKs for iOS and Android](https://developers.google.com/ml-kit). -- **[Payments](https://github.com/NativeScript/payments)** - - In-App Purchase, Subscriptions, Google Pay and Apple Pay. -- **[Artwork](https://github.com/NativeScript/artwork)** - - Want to use our logo or colors? This repo contains ready to use media material. +- [iOS and visionOS Runtime](https://github.com/NativeScript/ios) + Empowers JavaScript code to be executed on iOS and visionOS devices written in a mix of C++, Objective-C, and Swift. +- [Android Runtime](https://github.com/NativeScript/android) + Empowers JavaScript code to be executed on Android devices written in a mix of C++, Java and Kotlin. +- [CLI](https://github.com/NativeScript/nativescript-cli) + Command-line interface empowering you to create, build, and run apps using NativeScript. +- [Docs](https://github.com/NativeScript/docs) + Documentation available at written in Markdown. +- [Plugins](https://github.com/NativeScript/plugins) + Various TSC managed plugins. Also a good reference is the [plugin marketplace](https://market.nativescript.org/) with several additional plugins. +- [Firebase](https://github.com/NativeScript/firebase) + Modular Firebase 🔥 implementation for supported platforms. +- [ML Kit](https://github.com/NativeScript/mlkit) + Google's [ML Kit SDKs for supported platforms](https://developers.google.com/ml-kit). +- [Payments](https://github.com/NativeScript/payments) + In-App Purchase, Subscriptions, Google Pay and Apple Pay. +- [Artwork](https://github.com/NativeScript/artwork) + Want to use our logo or colors? Feel free to use any of our ready-to-use media material. + +## Copyright notice + +Copyright [OpenJS Foundation](https://openjsf.org) and `NativeScript` contributors. All rights reserved. The [OpenJS Foundation](https://openjsf.org) has registered trademarks and uses trademarks. For a list of trademarks of the [OpenJS Foundation](https://openjsf.org), please see our [Trademark Policy](https://trademark-policy.openjsf.org/) and [Trademark List](https://trademark-list.openjsf.org/). Trademarks and logos not indicated on the [list of OpenJS Foundation trademarks](https://trademark-list.openjsf.org) are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them. + +[The OpenJS Foundation](https://openjsf.org/) | [Terms of Use](https://terms-of-use.openjsf.org/) | [Privacy Policy](https://privacy-policy.openjsf.org/) | [OpenJS Foundation Bylaws](https://bylaws.openjsf.org/) | [Trademark Policy](https://trademark-policy.openjsf.org/) | [Trademark List](https://trademark-list.openjsf.org/) | [Cookie Policy](https://www.linuxfoundation.org/cookies/)

Made with ❤️

diff --git a/apps/automated/.gitignore b/apps/automated/.gitignore index 512c68e15a..e9dc6c6f24 100644 --- a/apps/automated/.gitignore +++ b/apps/automated/.gitignore @@ -1 +1,2 @@ !webpack.config.js +hooks \ No newline at end of file diff --git a/apps/automated/.npmrc b/apps/automated/.npmrc deleted file mode 100644 index 3b59692060..0000000000 --- a/apps/automated/.npmrc +++ /dev/null @@ -1,6 +0,0 @@ -legacy-peer-deps=true - -# npm 9+ this defaults to `true` meaning `file:` dependencies are packed and then installed -# but since we want to link the source files in this case, we disable this behavior and -# opt to link the dependency as-is instead. (eg. @nativescript/core) -install-links=false diff --git a/apps/automated/nativescript.config.ts b/apps/automated/nativescript.config.ts index 043f59c668..7dfd261e33 100644 --- a/apps/automated/nativescript.config.ts +++ b/apps/automated/nativescript.config.ts @@ -9,5 +9,8 @@ export default { }, cli: { packageManager: 'npm', + additionalPathsToClean: ['.ns-vite-build'], }, + // bundler: 'vite', + // bundlerConfigPath: 'vite.config.ts', } as NativeScriptConfig; diff --git a/apps/automated/package.json b/apps/automated/package.json index 9ed1314a99..56964cd456 100644 --- a/apps/automated/package.json +++ b/apps/automated/package.json @@ -11,11 +11,13 @@ "nativescript-theme-core": "file:../../node_modules/nativescript-theme-core" }, "devDependencies": { - "@nativescript/android": "~8.5.0", - "@nativescript/ios": "~8.5.0", - "@nativescript/webpack": "file:../../dist/packages/nativescript-webpack.tgz", + "@nativescript/android": "~9.0.0", + "@nativescript/ios": "~9.0.0", + "@nativescript/visionos": "~9.0.0", + "@nativescript/vite": "file:../../dist/packages/vite", + "@nativescript/webpack": "file:../../dist/packages/webpack5", "circular-dependency-plugin": "^5.2.2", - "typescript": "~4.9.5" + "typescript": "~5.8.0" }, "gitHead": "c06800e52ee1a184ea2dffd12a6702aaa43be4e3", "readme": "NativeScript Application" diff --git a/apps/automated/project.json b/apps/automated/project.json index d61bc888b7..6aae5b2a9a 100644 --- a/apps/automated/project.json +++ b/apps/automated/project.json @@ -11,43 +11,67 @@ "targets": { "build": { "executor": "@nativescript/nx:build", + "inputs": ["default", "^production"], "options": { "noHmr": true, "production": true, "uglify": true, "release": true, "forDevice": true - } + }, + "configurations": {}, + "dependsOn": ["^build"] }, - "ios": { - "executor": "@nativescript/nx:build", + "debug": { + "executor": "@nativescript/nx:debug", "inputs": ["default", "^production"], - "outputs": [], "options": { "noHmr": true, - "platform": "ios" - } + "debug": false, + "uglify": false, + "release": false, + "forDevice": false, + "prepare": false + }, + "dependsOn": ["^build"] }, - "android": { - "executor": "@nativescript/nx:build", + "prepare": { + "executor": "@nativescript/nx:prepare", "inputs": ["default", "^production"], - "outputs": [], "options": { "noHmr": true, - "platform": "android" - } + "production": true, + "uglify": true, + "release": true, + "forDevice": true, + "prepare": true + }, + "configurations": {}, + "dependsOn": ["^build"] + }, + "test": { + "executor": "nx:run-commands", + "defaultConfiguration": "ios", + "configurations": { + "ios": { + "commands": ["node tools/scripts/run-automated.js ios"] + }, + "android": { + "commands": ["node tools/scripts/run-automated.js android"] + } + }, + "dependsOn": ["^build"] }, "clean": { - "executor": "@nativescript/nx:build", - "options": { - "clean": true - } + "executor": "@nativescript/nx:clean", + "options": {} }, "lint": { - "executor": "@nrwl/linter:eslint", + "executor": "@nx/eslint:lint", "options": { "lintFilePatterns": ["apps/automated/**/*.ts", "apps/automated/src/**/*.html"] } } - } + }, + "implicitDependencies": ["webpack5", "vite"] } diff --git a/apps/automated/src/app-root.ts b/apps/automated/src/app-root.ts new file mode 100644 index 0000000000..2c052a575f --- /dev/null +++ b/apps/automated/src/app-root.ts @@ -0,0 +1,31 @@ +import { Application, Frame, Page } from '@nativescript/core'; + +export function onLoaded(args) { + try { + console.log('[automated] app-root onLoaded'); + const rootPage = args.object as Page; + // Create a Frame and navigate to main-page to ensure code-behind is bound + const frame = new Frame(); + try { + frame.navigate('main-page'); + } catch (e) { + try { + console.error('[automated] app-root onLoaded: navigate to main-page failed', e); + } catch {} + } + // Replace the temporary Page root with the Frame containing the page + try { + if ((Application as any).resetRootView) { + (Application as any).resetRootView({ create: () => frame }); + } + } catch (e) { + try { + console.error('[automated] app-root onLoaded: resetRootView failed', e); + } catch {} + } + } catch (e) { + try { + console.error('[automated] app-root onLoaded failed', e); + } catch {} + } +} diff --git a/apps/automated/src/app-root.xml b/apps/automated/src/app-root.xml index 54e70d9760..3d2c920bc8 100644 --- a/apps/automated/src/app-root.xml +++ b/apps/automated/src/app-root.xml @@ -1,2 +1,3 @@ - - + + diff --git a/apps/automated/src/application/application-tests-common.ts b/apps/automated/src/application/application-tests-common.ts index 440ecb35e0..b1802a2d5d 100644 --- a/apps/automated/src/application/application-tests-common.ts +++ b/apps/automated/src/application/application-tests-common.ts @@ -11,12 +11,15 @@ if (isAndroid) { export function testInitialized() { if (Device.os === platformNames.android) { TKUnit.assert(Application.android, 'Application module not properly intialized'); - } else if (Device.os === platformNames.ios) { + } else if (__APPLE__) { TKUnit.assert(Application.ios, 'Application module not properly intialized'); } } export function testDisplayedEvent() { + if (__VISIONOS__) { + return; + } // global.isDisplayedEventFired flag is set in app.ts application.displayedEvent handler TKUnit.assert(global.isDisplayedEventFired, 'application.displayedEvent not fired'); } diff --git a/apps/automated/src/application/application-tests.android.ts b/apps/automated/src/application/application-tests.android.ts index d927fb1cc4..3371a7cd0b 100644 --- a/apps/automated/src/application/application-tests.android.ts +++ b/apps/automated/src/application/application-tests.android.ts @@ -1,5 +1,5 @@ /* tslint:disable:no-unused-variable */ -import { Application } from '@nativescript/core'; +import { Application, Utils } from '@nativescript/core'; import * as TKUnit from '../tk-unit'; export * from './application-tests-common'; @@ -43,15 +43,16 @@ export function testAndroidApplicationInitialized() { TKUnit.assert( // @ts-expect-error Application.android.foregroundActivity.isNativeScriptActivity, - 'Android foregroundActivity.isNativeScriptActivity is false.' + 'Android foregroundActivity.isNativeScriptActivity is false.', ); TKUnit.assert(Application.android.startActivity, 'Android startActivity not initialized.'); TKUnit.assert(Application.android.nativeApp, 'Android nativeApp not initialized.'); - TKUnit.assert(Application.android.orientation, 'Android orientation not initialized.'); - TKUnit.assert(Application.android.packageName, 'Android packageName not initialized.'); - TKUnit.assert(Application.android.systemAppearance, 'Android system appearance not initialized.'); + TKUnit.assert(Application.android.orientation(), 'Android orientation not initialized.'); + TKUnit.assert(Utils.android.getPackageName(), 'Android packageName not initialized.'); + TKUnit.assert(Application.android.systemAppearance(), 'Android system appearance not initialized.'); + TKUnit.assert(Application.android.layoutDirection(), 'Android layout direction not initialized.'); } export function testSystemAppearance() { - TKUnit.assert(Application.android.systemAppearance, 'System appearance not initialized.'); + TKUnit.assert(Application.android.systemAppearance(), 'System appearance not initialized.'); } diff --git a/apps/automated/src/application/application-tests.ios.ts b/apps/automated/src/application/application-tests.ios.ts index add87fc103..b60c32f234 100644 --- a/apps/automated/src/application/application-tests.ios.ts +++ b/apps/automated/src/application/application-tests.ios.ts @@ -44,22 +44,23 @@ export function testIOSApplicationInitialized() { TKUnit.assert(Application.ios, 'iOS application not initialized.'); TKUnit.assert(Application.ios.delegate, 'iOS delegate not initialized.'); TKUnit.assert(Application.ios.nativeApp, 'iOS nativeApp not initialized.'); - TKUnit.assert(Application.ios.orientation, 'iOS orientation not initialized.'); + TKUnit.assert(Application.ios.orientation(), 'iOS orientation not initialized.'); - if (Utils.ios.MajorVersion <= 11) { - TKUnit.assertNull(Application.ios.systemAppearance, 'iOS system appearance should be `null` on iOS <= 11.'); + if (!__VISIONOS__ && Utils.SDK_VERSION <= 11) { + TKUnit.assertNull(Application.ios.systemAppearance(), 'iOS system appearance should be `null` on iOS <= 11.'); } else { - TKUnit.assert(Application.ios.systemAppearance, 'iOS system appearance not initialized.'); + TKUnit.assert(Application.ios.systemAppearance(), 'iOS system appearance not initialized.'); } + TKUnit.assert(Application.ios.layoutDirection(), 'iOS layout direction not initialized.'); TKUnit.assert(Application.ios.window, 'iOS window not initialized.'); TKUnit.assert(Application.ios.rootController, 'iOS root controller not initialized.'); } export function testSystemAppearance() { - if (Utils.ios.MajorVersion <= 11) { - TKUnit.assertNull(Application.ios.systemAppearance, 'System appearance should be `null` on iOS <= 11.'); + if (!__VISIONOS__ && Utils.SDK_VERSION <= 11) { + TKUnit.assertNull(Application.ios.systemAppearance(), 'System appearance should be `null` on iOS <= 11.'); } else { - TKUnit.assert(Application.ios.systemAppearance, 'System appearance not initialized.'); + TKUnit.assert(Application.ios.systemAppearance(), 'System appearance not initialized.'); } } diff --git a/apps/automated/src/xml-parser-tests/xml-with-namespaces.xml b/apps/automated/src/assets/xml-with-namespaces.xml similarity index 100% rename from apps/automated/src/xml-parser-tests/xml-with-namespaces.xml rename to apps/automated/src/assets/xml-with-namespaces.xml diff --git a/apps/automated/src/xml-parser-tests/xml.expected b/apps/automated/src/assets/xml.expected similarity index 96% rename from apps/automated/src/xml-parser-tests/xml.expected rename to apps/automated/src/assets/xml.expected index 5cdb47fc44..45a1441970 100644 --- a/apps/automated/src/xml-parser-tests/xml.expected +++ b/apps/automated/src/assets/xml.expected @@ -1 +1 @@ -{"eventType":"StartElement","position":{"line":2,"column":1},"elementName":"DocumentElement","attributes":{"param":"value"}}{"eventType":"StartElement","position":{"line":3,"column":3},"elementName":"First.Element","attributes":{"some.attr":"some.value"}}{"eventType":"Text","position":{"line":3,"column":41},"data":"\n ¶ Some Text ®\n "}{"eventType":"EndElement","position":{"line":5,"column":3},"elementName":"First.Element"}{"eventType":"StartElement","position":{"line":7,"column":3},"elementName":"SecondElement","attributes":{"param2":"something"}}{"eventType":"Text","position":{"line":7,"column":37},"data":"\n Pre-Text "}{"eventType":"StartElement","position":{"line":8,"column":14},"elementName":"Inline"}{"eventType":"Text","position":{"line":8,"column":22},"data":"Inlined text"}{"eventType":"EndElement","position":{"line":8,"column":34},"elementName":"Inline"}{"eventType":"Text","position":{"line":8,"column":43},"data":" Post-text.\n "}{"eventType":"EndElement","position":{"line":9,"column":3},"elementName":"SecondElement"}{"eventType":"StartElement","position":{"line":10,"column":3},"elementName":"entities"}{"eventType":"Text","position":{"line":10,"column":13},"data":"Xml tags begin with \"<\" and end with \">\" Ampersand is & and apostrophe is '"}{"eventType":"EndElement","position":{"line":10,"column":123},"elementName":"entities"}{"eventType":"StartElement","position":{"line":11,"column":3},"elementName":"script"}{"eventType":"CDATA","position":{"line":12,"column":5},"data":"\nfunction sum(a,b)\n{\n return a+b;\n}\n"}{"eventType":"EndElement","position":{"line":18,"column":3},"elementName":"script"}{"eventType":"Comment","position":{"line":19,"column":3},"data":"\n Hello,\n I am a multi-line XML comment.\n"}{"eventType":"EndElement","position":{"line":23,"column":1},"elementName":"DocumentElement"} +{"eventType":"StartElement","position":{"line":2,"column":1},"elementName":"DocumentElement","attributes":{"param":"value"}}{"eventType":"StartElement","position":{"line":3,"column":3},"elementName":"First.Element","attributes":{"some.attr":"some.value"}}{"eventType":"Text","position":{"line":3,"column":41},"data":"\n ¶ Some Text ®\n "}{"eventType":"EndElement","position":{"line":5,"column":3},"elementName":"First.Element"}{"eventType":"StartElement","position":{"line":7,"column":3},"elementName":"SecondElement","attributes":{"param2":"something"}}{"eventType":"Text","position":{"line":7,"column":37},"data":"\n Pre-Text "}{"eventType":"StartElement","position":{"line":8,"column":14},"elementName":"Inline"}{"eventType":"Text","position":{"line":8,"column":22},"data":"Inlined text"}{"eventType":"EndElement","position":{"line":8,"column":34},"elementName":"Inline"}{"eventType":"Text","position":{"line":8,"column":43},"data":" Post-text.\n "}{"eventType":"EndElement","position":{"line":9,"column":3},"elementName":"SecondElement"}{"eventType":"StartElement","position":{"line":10,"column":3},"elementName":"entities"}{"eventType":"Text","position":{"line":10,"column":13},"data":"Xml tags begin with \"<\" and end with \">\" Ampersand is & and apostrophe is '"}{"eventType":"EndElement","position":{"line":10,"column":123},"elementName":"entities"}{"eventType":"StartElement","position":{"line":11,"column":3},"elementName":"script"}{"eventType":"CDATA","position":{"line":12,"column":5},"data":"\nfunction sum(a,b)\n{\n return a+b;\n}\n"}{"eventType":"EndElement","position":{"line":18,"column":3},"elementName":"script"}{"eventType":"Comment","position":{"line":19,"column":3},"data":"\n Hello,\n I am a multi-line XML comment.\n"}{"eventType":"EndElement","position":{"line":23,"column":1},"elementName":"DocumentElement"} \ No newline at end of file diff --git a/apps/automated/src/xml-parser-tests/xml.xml b/apps/automated/src/assets/xml.xml similarity index 96% rename from apps/automated/src/xml-parser-tests/xml.xml rename to apps/automated/src/assets/xml.xml index 93a55b0186..3b9a16b9a5 100644 --- a/apps/automated/src/xml-parser-tests/xml.xml +++ b/apps/automated/src/assets/xml.xml @@ -20,4 +20,4 @@ function sum(a,b) Hello, I am a multi-line XML comment. --> - + \ No newline at end of file diff --git a/apps/automated/src/color/color-tests-common.ts b/apps/automated/src/color/color-tests-common.ts index 18d94bac96..13182f417f 100644 --- a/apps/automated/src/color/color-tests-common.ts +++ b/apps/automated/src/color/color-tests-common.ts @@ -17,6 +17,19 @@ export var test_Hex_Color = function () { TKUnit.assertEqual(color.argb, 0xffff0000, 'Color.argb not properly parsed'); }; +export var test_Hex_rgba_Color = function () { + // >> color-hex-rgba + // Creates the red color + var color = new Color('#FF0000FF'); + // << color-hex-rgba + TKUnit.assertEqual(color.a, 255, 'Color.a not properly parsed'); + TKUnit.assertEqual(color.r, 255, 'Color.r not properly parsed'); + TKUnit.assertEqual(color.g, 0, 'Color.g not properly parsed'); + TKUnit.assertEqual(color.b, 0, 'Color.b not properly parsed'); + TKUnit.assertEqual(color.hex, '#FF0000', 'Color.hex not properly parsed'); + TKUnit.assertEqual(color.argb, 0xffff0000, 'Color.argb not properly parsed'); +}; + export var test_ShortHex_Color = function () { // >> color-hex-short // Creates the color #FF8800 @@ -30,6 +43,19 @@ export var test_ShortHex_Color = function () { TKUnit.assertEqual(color.argb, 0xffff8800, 'Color.argb not properly parsed'); }; +export var test_ShortHex_rgba_Color = function () { + // >> color-hex-short-rgba + // Creates the color #FF8800 + var color = new Color('#F80F'); + // << color-hex-short-rgba + TKUnit.assertEqual(color.a, 255, 'Color.a not properly parsed'); + TKUnit.assertEqual(color.r, 255, 'Color.r not properly parsed'); + TKUnit.assertEqual(color.g, 136, 'Color.g not properly parsed'); // 0x88 == 136 + TKUnit.assertEqual(color.b, 0, 'Color.b not properly parsed'); + TKUnit.assertEqual(color.hex, '#FF8800', 'Color.hex not properly parsed'); + TKUnit.assertEqual(color.argb, 0xffff8800, 'Color.argb not properly parsed'); +}; + export var test_Argb_Color = function () { // >> color-rgb // Creates the color with 100 alpha, 255 red, 100 green, 100 blue @@ -112,7 +138,10 @@ export var test_Color_isValid = function () { var color = new Color('#FF0000'); TKUnit.assertEqual(Color.isValid(color), true, 'Failed to validate color instance'); - TKUnit.assertEqual(Color.isValid('#FF0000'), true, 'Failed to validate hex color'); + TKUnit.assertEqual(Color.isValid('#FFF'), true, 'Failed to validate 3-digit hex color'); + TKUnit.assertEqual(Color.isValid('#FFF0'), true, 'Failed to validate 4-digit hex color'); + TKUnit.assertEqual(Color.isValid('#FF0000'), true, 'Failed to validate 6-digit hex color'); + TKUnit.assertEqual(Color.isValid('#FF000000'), true, 'Failed to validate 8-digit hex color'); TKUnit.assertEqual(Color.isValid('rgb(255, 100, 100)'), true, 'Failed to validate rgb color'); TKUnit.assertEqual(Color.isValid('hsl(50, 50%, 50%)'), true, 'Failed to validate hsl color'); TKUnit.assertEqual(Color.isValid(null) || Color.isValid(undefined), false, 'Failed to invalidate nullish value'); diff --git a/apps/automated/src/color/color-tests.ios.ts b/apps/automated/src/color/color-tests.ios.ts index 228776da32..0acf4f767d 100644 --- a/apps/automated/src/color/color-tests.ios.ts +++ b/apps/automated/src/color/color-tests.ios.ts @@ -6,7 +6,7 @@ import * as TKUnit from '../tk-unit'; export * from './color-tests-common'; -export function testFromIosColorWhite () { +export function testFromIosColorWhite() { // >> color-ios-white // Creates the white color const color = Color.fromIosColor(UIColor.whiteColor); @@ -17,4 +17,4 @@ export function testFromIosColorWhite () { TKUnit.assertEqual(color.b, 255, 'Color.b not properly parsed'); TKUnit.assertEqual(color.hex, '#FFFFFF', 'Color.hex not properly parsed'); TKUnit.assertEqual(color.argb, 0xffffffff, 'Color.argb not properly parsed'); -}; +} diff --git a/apps/automated/src/data/observable-tests.ts b/apps/automated/src/data/observable-tests.ts index 5bfa1cefb7..32efd1f956 100644 --- a/apps/automated/src/data/observable-tests.ts +++ b/apps/automated/src/data/observable-tests.ts @@ -163,7 +163,7 @@ export var test_Observable_addEventListener_MultipleEvents = function () { obj.addEventListener(events, callback); obj.set('testName', 1); obj.test(); - TKUnit.assert(receivedCount === 2, 'Callbacks not raised properly.'); + TKUnit.assert(receivedCount === 0, "Expected no event handlers to fire upon the 'propertyChange' event when listening for event name 'propertyChange,tested', as we have dropped support for listening to plural event names."); }; export var test_Observable_addEventListener_MultipleEvents_ShouldTrim = function () { @@ -176,13 +176,14 @@ export var test_Observable_addEventListener_MultipleEvents_ShouldTrim = function var events = Observable.propertyChangeEvent + ' , ' + TESTED_NAME; obj.addEventListener(events, callback); - TKUnit.assert(obj.hasListeners(Observable.propertyChangeEvent), 'Observable.addEventListener for multiple events should trim each event name.'); - TKUnit.assert(obj.hasListeners(TESTED_NAME), 'Observable.addEventListener for multiple events should trim each event name.'); + TKUnit.assert(obj.hasListeners(events), "Expected a listener to be present for event name 'propertyChange , tested', as we have dropped support for splitting plural event names."); + TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), "Expected no listeners to be present for event name 'propertyChange', as we have dropped support for splitting plural event names."); + TKUnit.assert(!obj.hasListeners(TESTED_NAME), "Expected no listeners to be present for event name 'tested', as we have dropped support for splitting plural event names."); obj.set('testName', 1); obj.test(); - TKUnit.assert(receivedCount === 2, 'Callbacks not raised properly.'); + TKUnit.assert(receivedCount === 0, "Expected no event handlers to fire upon the 'propertyChange' event when listening for event name 'propertyChange , tested', as we have dropped support for listening to plural event names (and trimming whitespace in event names)."); }; export var test_Observable_addEventListener_MultipleCallbacks = function () { @@ -223,7 +224,7 @@ export var test_Observable_addEventListener_MultipleCallbacks_MultipleEvents = f obj.set('testName', 1); obj.test(); - TKUnit.assert(receivedCount === 4, 'The propertyChanged notification should be raised twice.'); + TKUnit.assert(receivedCount === 0, "Expected no event handlers to fire upon the 'propertyChange' event when listening for event name 'propertyChange , tested' with two different callbacks, as we have dropped support for listening to plural event names (and trimming whitespace in event names)."); }; export var test_Observable_removeEventListener_SingleEvent_SingleCallback = function () { @@ -273,7 +274,65 @@ export var test_Observable_removeEventListener_SingleEvent_MultipleCallbacks = f TKUnit.assert(receivedCount === 3, 'Observable.removeEventListener not working properly with multiple listeners.'); }; -export var test_Observable_removeEventListener_MutlipleEvents_SingleCallback = function () { +export var test_Observable_identity = function () { + const obj = new Observable(); + + let receivedCount = 0; + const callback = () => receivedCount++; + const eventName = Observable.propertyChangeEvent; + + // The identity of an event listener is determined by the tuple of + // [eventType, callback, thisArg], and influences addition and removal. + + // If you try to add the same callback for a given event name twice, without + // distinguishing by its thisArg, the second addition will no-op. + obj.addEventListener(eventName, callback); + obj.addEventListener(eventName, callback); + obj.set('testName', 1); + TKUnit.assert(receivedCount === 1, 'Expected Observable to fire exactly once upon a property change, having passed the same callback into addEventListener() twice'); + obj.removeEventListener(eventName, callback); + TKUnit.assert(!obj.hasListeners(eventName), 'Expected removeEventListener(eventName, callback) to remove all matching callbacks regardless of thisArg'); + receivedCount = 0; + + // All truthy thisArgs are distinct, so we have three distinct identities here + // and they should all get added. + obj.addEventListener(eventName, callback); + obj.addEventListener(eventName, callback, 1); + obj.addEventListener(eventName, callback, 2); + obj.set('testName', 2); + TKUnit.assert(receivedCount === 3, 'Expected Observable to fire exactly three times upon a property change, having passed the same callback into addEventListener() three times, with the latter two distinguished by each having a different truthy thisArg'); + obj.removeEventListener(eventName, callback); + TKUnit.assert(!obj.hasListeners(eventName), 'Expected removeEventListener(eventName, callback) to remove all matching callbacks regardless of thisArg'); + receivedCount = 0; + + // If you specify thisArg when removing an event listener, it should remove + // just the event listener with the corresponding thisArg. + obj.addEventListener(eventName, callback, 1); + obj.addEventListener(eventName, callback, 2); + obj.set('testName', 3); + TKUnit.assert(receivedCount === 2, 'Expected Observable to fire exactly three times upon a property change, having passed the same callback into addEventListener() three times, with the latter two distinguished by each having a different truthy thisArg'); + obj.removeEventListener(eventName, callback, 2); + TKUnit.assert(obj.hasListeners(eventName), 'Expected removeEventListener(eventName, callback, thisArg) to remove just the event listener that matched the callback and thisArg'); + obj.removeEventListener(eventName, callback, 1); + TKUnit.assert(!obj.hasListeners(eventName), 'Expected removeEventListener(eventName, callback, thisArg) to remove the remaining event listener that matched the callback and thisArg'); + receivedCount = 0; + + // All falsy thisArgs are treated alike, so these all have the same identity + // and only the first should get added. + obj.addEventListener(eventName, callback); + obj.addEventListener(eventName, callback, 0); + obj.addEventListener(eventName, callback, false); + obj.addEventListener(eventName, callback, null); + obj.addEventListener(eventName, callback, undefined); + obj.addEventListener(eventName, callback, ''); + obj.set('testName', 4); + TKUnit.assert(receivedCount === 1, 'Expected Observable to fire exactly once upon a property change, having passed the same callback into addEventListener() multiple times, each time with a different falsy (and therefore indistinct) thisArg'); + obj.removeEventListener(eventName, callback); + TKUnit.assert(!obj.hasListeners(eventName), 'Expected removeEventListener(eventName, callback) to remove all matching callbacks regardless of thisArg'); + receivedCount = 0; +}; + +export var test_Observable_removeEventListener_MultipleEvents_SingleCallback = function () { var obj = new TestObservable(); var receivedCount = 0; @@ -283,19 +342,22 @@ export var test_Observable_removeEventListener_MutlipleEvents_SingleCallback = f var events = Observable.propertyChangeEvent + ' , ' + TESTED_NAME; obj.addEventListener(events, callback); + TKUnit.assert(obj.hasListeners(events), "Expected a listener to be present for event name 'propertyChange , tested', as we have dropped support for splitting plural event names."); + TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), "Expected no listeners to be present for event name 'propertyChange', as we have dropped support for splitting plural event names."); + TKUnit.assert(!obj.hasListeners(TESTED_NAME), "Expected no listeners to be present for event name 'tested', as we have dropped support for splitting plural event names."); + TKUnit.assert(receivedCount === 0, "Expected no event handlers to fire upon the 'propertyChange' event when listening for event name 'propertyChange , tested', as we have dropped support for listening to plural event names (and trimming whitespace in event names)."); obj.set('testName', 1); obj.test(); obj.removeEventListener(events, callback); - TKUnit.assert(!obj.hasListeners(Observable.propertyChangeEvent), 'Expected result for hasObservers is false'); - TKUnit.assert(!obj.hasListeners(TESTED_NAME), 'Expected result for hasObservers is false.'); + TKUnit.assert(!obj.hasListeners(events), "Expected the listener for event name 'propertyChange , tested' to have been removed, as we have dropped support for splitting plural event names."); obj.set('testName', 2); obj.test(); - TKUnit.assert(receivedCount === 2, 'Expected receive count is 2'); + TKUnit.assert(receivedCount === 0, "Expected no event handlers to fire upon the 'propertyChange' event when listening for event name 'propertyChange , tested', as we have dropped support for listening to plural event names (and trimming whitespace in event names)."); }; export var test_Observable_removeEventListener_SingleEvent_NoCallbackSpecified = function () { diff --git a/apps/automated/src/debugger/dom-node-tests.ts b/apps/automated/src/debugger/dom-node-tests.ts index 52b8b8ad88..80d76e21c4 100644 --- a/apps/automated/src/debugger/dom-node-tests.ts +++ b/apps/automated/src/debugger/dom-node-tests.ts @@ -1,15 +1,9 @@ import { assert, assertEqual } from '../tk-unit'; -import { DOMNode } from '@nativescript/core/debugger/dom-node'; +import { DOMNode } from '@nativescript/core/debugger/dom-types'; import { attachDOMInspectorCommandCallbacks, attachCSSInspectorCommandCallbacks, attachDOMInspectorEventCallbacks } from '@nativescript/core/debugger/devtools-elements'; import { InspectorCommands, InspectorEvents } from '@nativescript/core/debugger/devtools-elements'; -import { unsetValue } from '@nativescript/core/ui/core/properties'; -import { Button } from '@nativescript/core/ui/button'; -import { Slider } from '@nativescript/core/ui/slider'; -import { Label } from '@nativescript/core/ui/label'; +import { unsetValue, Button, Slider, Label, TextView, StackLayout, isAndroid } from '@nativescript/core'; import { textProperty } from '@nativescript/core/ui/text-base'; -import { TextView } from '@nativescript/core/ui/text-view'; -import { StackLayout } from '@nativescript/core/ui/layouts/stack-layout'; -import { isAndroid } from '@nativescript/core/platform'; let originalInspectorGlobal: InspectorCommands & InspectorEvents; diff --git a/apps/automated/src/file-system/file-system-tests.ts b/apps/automated/src/file-system/file-system-tests.ts index 3a2ba03c84..b78254ae01 100644 --- a/apps/automated/src/file-system/file-system-tests.ts +++ b/apps/automated/src/file-system/file-system-tests.ts @@ -61,7 +61,7 @@ export var testFileFromPath = function () { function (error) { TKUnit.assert(false, 'Failed to read/write text'); //console.dir(error); - } + }, ); // << (hide) }, @@ -71,7 +71,7 @@ export var testFileFromPath = function () { TKUnit.assert(false, 'Failed to read/write text'); //console.dir(error); // << (hide) - } + }, ); // << file-system-create }; @@ -106,7 +106,7 @@ export var testFileWrite = function () { function (error) { TKUnit.assert(false, 'Failed to read/write text'); //console.dir(error); - } + }, ); // << (hide) }, @@ -116,7 +116,7 @@ export var testFileWrite = function () { TKUnit.assert(false, 'Failed to read/write text'); //console.dir(error); // << (hide) - } + }, ); // << file-system-write-string }; @@ -172,7 +172,7 @@ export var testFileRead = function () { TKUnit.assert(false, 'Failed to read/write text'); //console.dir(error); // << (hide) - } + }, ); }, function (error) { @@ -181,20 +181,20 @@ export var testFileRead = function () { TKUnit.assert(false, 'Failed to read/write text'); //console.dir(error); // << (hide) - } + }, ); // << file-system-example-text }; -export var testFileReadWriteBinary = function () { +export function testFileReadWriteBinary() { // >> file-system-read-binary - var fileName = 'logo.png'; - var error; + const fileName = 'logo.png'; + let error; + const appFolder = fs.knownFolders.currentApp().path; + const sourceFile = fs.File.fromPath(appFolder + '/assets/' + fileName); + const destinationFile = fs.knownFolders.documents().getFile(fileName); - var sourceFile = fs.File.fromPath(__dirname + '/assets/' + fileName); - var destinationFile = fs.knownFolders.documents().getFile(fileName); - - var source = sourceFile.readSync((e) => { + const source = sourceFile.readSync((e) => { error = e; }); @@ -203,11 +203,11 @@ export var testFileReadWriteBinary = function () { }); // >> (hide) - var destination = destinationFile.readSync((e) => { + const destination = destinationFile.readSync((e) => { error = e; }); TKUnit.assertNull(error); - if (Device.os === platformNames.ios) { + if (__APPLE__) { TKUnit.assertTrue(source.isEqualToData(destination)); } else { TKUnit.assertEqual(new java.io.File(sourceFile.path).length(), new java.io.File(destinationFile.path).length()); @@ -216,14 +216,14 @@ export var testFileReadWriteBinary = function () { destinationFile.removeSync(); // << (hide) // << file-system-read-binary -}; +} -export var testFileReadWriteBinaryAsync = function () { +export function testFileReadWriteBinaryAsync() { // >> file-system-read-binary-async - var fileName = 'logo.png'; - - var sourceFile = fs.File.fromPath(__dirname + '/assets/' + fileName); - var destinationFile = fs.knownFolders.documents().getFile(fileName); + const fileName = 'logo.png'; + const appFolder = fs.knownFolders.currentApp().path; + const sourceFile = fs.File.fromPath(appFolder + '/assets/' + fileName); + const destinationFile = fs.knownFolders.documents().getFile(fileName); // Read the file sourceFile.read().then( @@ -235,7 +235,7 @@ export var testFileReadWriteBinaryAsync = function () { // Succeded in writing the file destinationFile.read().then( function (destination) { - if (Device.os === platformNames.ios) { + if (__APPLE__) { TKUnit.assertTrue(source.isEqualToData(destination)); } else { TKUnit.assertEqual(new java.io.File(sourceFile.path).length(), new java.io.File(destinationFile.path).length()); @@ -245,13 +245,13 @@ export var testFileReadWriteBinaryAsync = function () { }, function (error) { TKUnit.assert(false, 'Failed to read destination binary async'); - } + }, ); }, function (error) { // Failed to write the file. TKUnit.assert(false, 'Failed to write binary async'); - } + }, ); // << (hide) }, @@ -260,10 +260,10 @@ export var testFileReadWriteBinaryAsync = function () { // >> (hide) TKUnit.assert(false, 'Failed to read binary async'); // << (hide) - } + }, ); // << file-system-read-binary-async -}; +} export var testGetKnownFolders = function () { // >> file-system-known-folders @@ -306,14 +306,16 @@ function _testIOSSpecificKnownFolder(knownFolderName: string) { } export var testIOSSpecificKnownFolders = function () { - _testIOSSpecificKnownFolder('library'); - _testIOSSpecificKnownFolder('developer'); - _testIOSSpecificKnownFolder('desktop'); - _testIOSSpecificKnownFolder('downloads'); - _testIOSSpecificKnownFolder('movies'); - _testIOSSpecificKnownFolder('music'); - _testIOSSpecificKnownFolder('pictures'); - _testIOSSpecificKnownFolder('sharedPublic'); + if (__IOS__) { + _testIOSSpecificKnownFolder('library'); + _testIOSSpecificKnownFolder('developer'); + _testIOSSpecificKnownFolder('desktop'); + _testIOSSpecificKnownFolder('downloads'); + _testIOSSpecificKnownFolder('movies'); + _testIOSSpecificKnownFolder('music'); + _testIOSSpecificKnownFolder('pictures'); + _testIOSSpecificKnownFolder('sharedPublic'); + } }; export var testGetEntities = function () { @@ -356,7 +358,7 @@ export var testGetEntities = function () { function (error) { // Failed to obtain folder's contents. // globalConsole.error(error.message); - } + }, ); // << file-system-folders-content }; @@ -423,11 +425,11 @@ export var testFileNameExtension = function () { var file = documents.getFile('Test.txt'); // Getting the file name "Test.txt". var fileName = file.name; - // Getting the file extension ".txt". + // Getting the file extension "txt". var fileExtension = file.extension; // >> (hide) TKUnit.assert(fileName === 'Test.txt', 'Wrong file name.'); - TKUnit.assert(fileExtension === '.txt', 'Wrong extension.'); + TKUnit.assert(fileExtension === 'txt', 'Wrong extension.'); file.remove(); // << (hide) // << file-system-extension @@ -491,7 +493,7 @@ export var testFileRename = function () { // >> (hide) TKUnit.assert(false, 'Failed to rename file'); // << (hide) - } + }, ); // << file-system-renaming }; @@ -515,7 +517,7 @@ export var testFolderRename = function () { // >> (hide) TKUnit.assert(false, 'Folder.rename API not working.'); // << (hide) - } + }, ); // << file-system-renaming-folder }; @@ -536,7 +538,7 @@ export var testFileRemove = function () { // >> (hide) TKUnit.assert(false, 'File.remove API not working.'); // << (hide) - } + }, ); // << file-system-remove-file }; @@ -558,7 +560,7 @@ export var testFolderRemove = function () { // >> (hide) TKUnit.assert(false, 'File.remove API not working.'); // << (hide) - } + }, ); // << file-system-remove-folder }; @@ -585,7 +587,7 @@ export var testFolderClear = function () { // >> (hide) TKUnit.assert(false, error.message); // << (hide) - } + }, ); // >> (hide) folder.getEntities().then(function (entities) { @@ -607,7 +609,7 @@ export var testKnownFolderRename = function () { }, function (error) { TKUnit.assert(true); - } + }, ); } }; @@ -623,7 +625,7 @@ export function testKnownFolderRemove(done) { }, function (error) { done(null); - } + }, ); } @@ -631,7 +633,7 @@ export function test_FSEntity_Properties() { var documents = fs.knownFolders.documents(); var file = documents.getFile('Test_File.txt'); - TKUnit.assert(file.extension === '.txt', 'FileEntity.extension not working.'); + TKUnit.assert(file.extension === 'txt', 'FileEntity.extension not working.'); TKUnit.assert(file.isLocked === false, 'FileEntity.isLocked not working.'); TKUnit.assert(file.lastModified instanceof Date, 'FileEntity.lastModified not working.'); TKUnit.assert(file.size === 0, 'FileEntity.size not working.'); @@ -744,7 +746,7 @@ export function testAndroidCreate() { export function test_FileAppend(done) { const content = 'Hello World'; - const hello_world = global.isIOS ? NSString.stringWithString(content).dataUsingEncoding(NSUTF8StringEncoding) : new java.lang.String(content).getBytes('UTF-8'); + const hello_world = __APPLE__ ? NSString.stringWithString(content).dataUsingEncoding(NSUTF8StringEncoding) : new java.lang.String(content).getBytes('UTF-8'); const file = fs.File.fromPath(fs.path.join(fs.knownFolders.temp().path, `${Date.now()}-app.txt`)); file .appendText('Hello') diff --git a/apps/automated/src/globals/globals-tests.ts b/apps/automated/src/globals/globals-tests.ts index 65c10e14d4..9bd0244651 100644 --- a/apps/automated/src/globals/globals-tests.ts +++ b/apps/automated/src/globals/globals-tests.ts @@ -23,8 +23,8 @@ export function test_global_registerModule() { TKUnit.assert(typeof global.registerModule === 'function', 'global.registerModule not a function'); } -export function test_global_registerWebpackModules() { - TKUnit.assert(typeof global.registerWebpackModules === 'function', 'global.registerWebpackModules not a function'); +export function test_global_registerBundlerModules() { + TKUnit.assert(typeof global.registerBundlerModules === 'function', 'global.registerBundlerModules not a function'); } export function test_global_loadModule() { diff --git a/apps/automated/src/http/http-tests.ts b/apps/automated/src/http/http-tests.ts index 5901bdb880..5272f8759a 100644 --- a/apps/automated/src/http/http-tests.ts +++ b/apps/automated/src/http/http-tests.ts @@ -2,7 +2,7 @@ import { ImageSource } from '@nativescript/core'; import * as TKUnit from '../tk-unit'; import * as http from '@nativescript/core/http'; import * as fs from '@nativescript/core/file-system'; -import { addHeader } from '@nativescript/core/http/http-request'; +import { requestInternal, addHeader, BaseHttpContent } from '@nativescript/core/http/http-request-internal'; export var test_getString_isDefined = function () { TKUnit.assert(typeof http.getString !== 'undefined', 'Method http.getString() should be defined!'); @@ -17,7 +17,7 @@ export var test_getString = function (done: (err: Error, res?: string) => void) function (e) { //// Argument (e) is Error! done(e); - } + }, ); }; @@ -67,7 +67,7 @@ export var test_getJSON = function (done) { //// Argument (e) is Error! //console.log(e); done(e); - } + }, ); }; @@ -115,7 +115,7 @@ export var test_getJSONP = function (done) { }, function (e) { done(e); - } + }, ); }; @@ -156,7 +156,7 @@ export var test_gzip_request_explicit = function (done) { }, function (e) { done(e); - } + }, ); }; @@ -180,7 +180,7 @@ export var test_gzip_request_implicit = function (done) { }, function (e) { done(e); - } + }, ); }; @@ -205,7 +205,7 @@ export var test_getImage = function (done) { (err) => { // Argument (e) is Error! done(err); - } + }, ); }; @@ -258,7 +258,7 @@ export var test_getFile = function (done) { function (e) { //// Argument (e) is Error! done(e); - } + }, ); }; @@ -280,7 +280,7 @@ export var test_getContentAsFile = function (done) { function (e) { //// Argument (e) is Error! done(e); - } + }, ); }; @@ -329,6 +329,89 @@ export var test_request_requestShouldTimeout = function (done) { }); }; +export var test_requestInternal_responseStatusCodeShouldBeDefined = function (done) { + requestInternal({ url: 'https://http-echo.nativescript.org/get', method: 'GET' }).then( + function (response) { + //// Argument (response) is HttpResponse! + var statusCode = response.statusCode; + try { + TKUnit.assert(typeof statusCode !== 'undefined', 'response.statusCode should be defined!'); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + }, + ); +}; + +export var test_requestInternal_responseContentShouldExposeNativeContentFunctions = function (done) { + requestInternal({ url: 'https://http-echo.nativescript.org/get', method: 'GET' }).then( + function (response) { + try { + TKUnit.assert(typeof response.content.toNativeImage === 'function' && typeof response.content.toNativeString === 'function', `response.content should expose native content functions!`); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + }, + ); +}; + +export var test_requestInternal_responseContentShouldExposeHandlerFunctions = function (done) { + const responseHandler = { + toDummy1: () => 'dummy1', + toDummy2: () => 'dummy2', + }; + + requestInternal({ url: 'https://http-echo.nativescript.org/get', method: 'GET' }, responseHandler).then( + function (response) { + try { + TKUnit.assert(typeof response.content.toDummy1 === 'function' && typeof response.content.toDummy2 === 'function', `response.content should expose content handler functions!`); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + }, + ); +}; + +export var test_requestInternal_responseHandlerShouldBeAvailable = function (done) { + const suffix = '-nsformatted'; + const responseHandler = { + toFormattedString: function (this: BaseHttpContent) { + return this.toNativeString() + suffix; + }, + }; + + requestInternal({ url: 'https://http-echo.nativescript.org/get', method: 'GET' }, responseHandler).then( + function (response) { + const value = response.content.toFormattedString(); + try { + TKUnit.assert(typeof value === 'string' && value.endsWith(suffix), `response.content.toFormattedString should return the response string appended with ${suffix} at the end!`); + done(null); + } catch (err) { + done(err); + } + }, + function (e) { + //// Argument (e) is Error! + done(e); + }, + ); +}; + export var test_request_responseStatusCodeShouldBeDefined = function (done) { var result: http.HttpResponse; @@ -347,7 +430,7 @@ export var test_request_responseStatusCodeShouldBeDefined = function (done) { function (e) { //// Argument (e) is Error! done(e); - } + }, ); }; @@ -363,7 +446,7 @@ export var test_headRequest_responseStatusCodeShouldBeDefined = function (done) }, function (e) { done(e); - } + }, ); }; @@ -387,7 +470,7 @@ export var test_request_responseHeadersShouldBeDefined = function (done) { function (e) { //// Argument (e) is Error! done(e); - } + }, ); }; @@ -409,7 +492,7 @@ export var test_request_responseContentShouldBeDefined = function (done) { function (e) { //// Argument (e) is Error! done(e); - } + }, ); }; @@ -428,7 +511,7 @@ export var test_request_responseContentToStringShouldReturnString = function (do }, function (e) { done(e); - } + }, ); }; @@ -447,7 +530,7 @@ export var test_request_responseContentToJSONShouldReturnJSON = function (done) }, function (e) { done(e); - } + }, ); }; @@ -468,7 +551,7 @@ export var test_request_responseContentToImageShouldReturnCorrectImage = functio }, function (e) { done(e); - } + }, ); }; @@ -487,7 +570,7 @@ export var test_request_responseContentToFileFromUrlShouldReturnCorrectFile = fu }, function (e) { done(e); - } + }, ); }; export var test_request_responseContentToFileFromUrlShouldReturnCorrectFileAndCreateDirPathIfNecesary = function (done) { @@ -507,7 +590,7 @@ export var test_request_responseContentToFileFromUrlShouldReturnCorrectFileAndCr }, function (e) { done(e); - } + }, ); }; @@ -526,7 +609,7 @@ export var test_request_responseContentToFileFromContentShouldReturnCorrectFile }, function (e) { done(e); - } + }, ); }; @@ -551,7 +634,7 @@ export var test_request_headersSentAndReceivedProperly = function (done) { }, function (e) { done(e); - } + }, ); }; @@ -597,7 +680,7 @@ export var test_request_contentSentAndReceivedProperly = function (done) { }, function (e) { done(e); - } + }, ); }; @@ -627,7 +710,7 @@ export var test_request_FormDataContentSentAndReceivedProperly = function (done) }, function (e) { done(e); - } + }, ); }; @@ -655,7 +738,7 @@ export var test_request_NonStringHeadersSentAndReceivedProperly = function (done }, function (e) { done(e); - } + }, ); }; @@ -684,12 +767,12 @@ export var test_request_jsonAsContentSentAndReceivedProperly = function (done) { function (e) { done(e); // console.log("Error occurred " + e); - } + }, ); }; export var test_getString_WorksProperlyInWorker = function (done) { - const worker = new Worker('./http-string-worker'); + const worker = new Worker(new URL('./http-string-worker', import.meta.url)); console.log('Worker Created'); worker.onmessage = function (msg) { console.log('Message received'); diff --git a/apps/automated/src/main-page.ts b/apps/automated/src/main-page.ts index 5a62b4412d..4b15c8855f 100644 --- a/apps/automated/src/main-page.ts +++ b/apps/automated/src/main-page.ts @@ -1,7 +1,5 @@ import { Trace, Page } from '@nativescript/core'; -import * as tests from './test-runner'; - let executeTests = true; Trace.enable(); @@ -18,10 +16,18 @@ Trace.addCategories(Trace.categories.Test + ',' + Trace.categories.Error); // )); function runTests() { - setTimeout(() => tests.runAll(''), 10); + setTimeout(async () => { + try { + const tests = await import('./test-runner'); + tests.runAll(''); + } catch (e) { + console.error('[automated] failed to load test-runner', e); + } + }, 10); } export function onNavigatedTo(args) { + console.log('onNavigatedTo'); args.object.off(Page.loadedEvent, onNavigatedTo); if (executeTests) { executeTests = false; diff --git a/apps/automated/src/main.ts b/apps/automated/src/main.ts index 05cbb7bf85..5271a64c9e 100644 --- a/apps/automated/src/main.ts +++ b/apps/automated/src/main.ts @@ -12,6 +12,13 @@ if (Application.ios) { Application.ios.addNotificationObserver(UIApplicationDidFinishLaunchingNotification, (notification: NSNotification) => { console.log('UIApplicationDidFinishLaunchingNotification:', notification); }); + + // Make sure we can add multiple handlers for the same event. + for (let i = 0; i < 10; i++) { + Application.ios.addDelegateHandler('applicationDidBecomeActive', (application: UIApplication) => { + console.log('applicationDidBecomeActive', i, application); + }); + } } // Common events for both Android and iOS. @@ -80,14 +87,14 @@ Application.on(Application.lowMemoryEvent, function (args: ApplicationEventData) // Error events. Application.on(Application.uncaughtErrorEvent, function (args: UnhandledErrorEventData) { console.log('NativeScriptError:', args.error); - console.log((args.error).nativeException ?? (args.error).nativeError); - console.log((args.error).stackTrace ?? (args.error).stack); + console.log(args.error.nativeException ?? (args.error as any).nativeError); + console.log(args.error.stackTrace ?? args.error.stack); }); Application.on(Application.discardedErrorEvent, function (args: DiscardedErrorEventData) { console.log('[Discarded] NativeScriptError:', args.error); - console.log((args.error).nativeException ?? (args.error).nativeError); - console.log((args.error).stackTrace ?? (args.error).stack); + console.log(args.error.nativeException ?? (args.error as any).nativeError); + console.log(args.error.stackTrace ?? args.error.stack); }); // Android activity events. @@ -142,5 +149,4 @@ if (typeof NSDate !== 'undefined') { } console.log(`TIME TO LOAD APP: ${time} ms`); - Application.run({ moduleName: 'app-root' }); diff --git a/apps/automated/src/navigation/custom-transition.ios.ts b/apps/automated/src/navigation/custom-transition.ios.ts index 16a0138899..644b9c4743 100644 --- a/apps/automated/src/navigation/custom-transition.ios.ts +++ b/apps/automated/src/navigation/custom-transition.ios.ts @@ -1,5 +1,4 @@ import { PageTransition, SharedTransition, SharedTransitionAnimationType, SharedTransitionHelper, Transition, Utils } from '@nativescript/core'; -import { CORE_ANIMATION_DEFAULTS } from '@nativescript/core/utils'; export class CustomTransition extends Transition { constructor(duration: number, curve: any) { @@ -32,7 +31,7 @@ export class CustomTransition extends Transition { }, (finished) => { transitionContext.completeTransition(finished); - } + }, ); } } @@ -90,7 +89,7 @@ class PageTransitionController extends NSObject implements UIViewControllerAnima } } } - return CORE_ANIMATION_DEFAULTS.duration; + return Utils.CORE_ANIMATION_DEFAULTS.duration; } animateTransition(transitionContext: UIViewControllerContextTransitioning): void { diff --git a/apps/automated/src/navigation/transition-tests.ts b/apps/automated/src/navigation/transition-tests.ts index e25d482475..9101a57e13 100644 --- a/apps/automated/src/navigation/transition-tests.ts +++ b/apps/automated/src/navigation/transition-tests.ts @@ -32,7 +32,7 @@ export function test_Transitions() { }); var transitions; - if (Device.os === platformNames.ios) { + if (__APPLE__) { transitions = ['curl']; } else { const _sdkVersion = parseInt(Device.sdkVersion); diff --git a/apps/automated/src/pages/package.json b/apps/automated/src/pages/package.json index 7117029859..b95dd49cd5 100644 --- a/apps/automated/src/pages/package.json +++ b/apps/automated/src/pages/package.json @@ -1,4 +1,4 @@ { - "name": "testsapp", - "main": "app.js" + "name": "testsapp", + "main": "app.js" } diff --git a/apps/automated/src/pages/page5.ts b/apps/automated/src/pages/page5.ts index 349e272a9a..347a1ae1fd 100644 --- a/apps/automated/src/pages/page5.ts +++ b/apps/automated/src/pages/page5.ts @@ -7,9 +7,7 @@ function printDeviceInfoAndroid() { console.log('android.os.Build.VERSION.SDK_INT = ' + android.os.Build.VERSION.SDK_INT); //android.os.Build.VERSION.SDK_INT = 19 console.log('android.os.Build.VERSION.CODENAME = ' + android.os.Build.VERSION.CODENAME); //android.os.Build.VERSION.CODENAME = REL console.log('android.os.Build.VERSION.RELEASE = ' + android.os.Build.VERSION.RELEASE); //android.os.Build.VERSION.RELEASE = 4.4.4 - var metrics: android.util.DisplayMetrics = Application.android.context - .getResources() - .getDisplayMetrics(); + var metrics: android.util.DisplayMetrics = Application.android.context.getResources().getDisplayMetrics(); console.log('metrics.density = ' + metrics.density); //metrics.density = 3 console.log('metrics.scaledDensity = ' + metrics.scaledDensity); //metrics.scaledDensity = 3 console.log('metrics.densityDpi = ' + metrics.densityDpi); //metrics.densityDpi = 480 @@ -22,12 +20,7 @@ function printDeviceInfoAndroid() { console.log('config.screenWidthDp = ' + config.screenWidthDp); console.log('config.screenHeightDp = ' + config.screenHeightDp); console.log('config.smallestScreenWidthDp = ' + config.smallestScreenWidthDp); - console.log( - 'config.orientation = ' + - (config.orientation === android.content.res.Configuration.ORIENTATION_PORTRAIT - ? 'portrait' - : 'ladscape') - ); + console.log('config.orientation = ' + (config.orientation === android.content.res.Configuration.ORIENTATION_PORTRAIT ? 'portrait' : 'ladscape')); } function printDeviceInfoIOS() { @@ -41,12 +34,7 @@ function printDeviceInfoIOS() { console.log('device.batteryLevel = ' + device.batteryLevel); //device.batteryLevel = -1 var screen = UIScreen.mainScreen; console.log('screen = ' + screen); - console.log( - 'screen.nativeBounds = ' + - screen.nativeBounds.size.width + - ', ' + - screen.nativeBounds.size.height - ); //screen.nativeBounds = 640, 1136 + console.log('screen.nativeBounds = ' + screen.nativeBounds.size.width + ', ' + screen.nativeBounds.size.height); //screen.nativeBounds = 640, 1136 console.log('screen.scale = ' + screen.scale); //screen.scale = 2 console.log('screen.nativeScale = ' + screen.nativeScale); //screen.nativeScale = 2 } @@ -58,19 +46,11 @@ function printTNSInfo() { console.log('platform.Device.sdkVersion = ' + platform.Device.sdkVersion); console.log('platform.Device.deviceType = ' + platform.Device.deviceType); - console.log( - 'platform.Screen.mainScreen.widthDIPs = ' + platform.Screen.mainScreen.widthDIPs - ); - console.log( - 'platform.Screen.mainScreen.heightDIPs = ' + platform.Screen.mainScreen.heightDIPs - ); + console.log('platform.Screen.mainScreen.widthDIPs = ' + platform.Screen.mainScreen.widthDIPs); + console.log('platform.Screen.mainScreen.heightDIPs = ' + platform.Screen.mainScreen.heightDIPs); console.log('platform.Screen.mainScreen.scale = ' + platform.Screen.mainScreen.scale); - console.log( - 'platform.Screen.mainScreen.widthPixels = ' + platform.Screen.mainScreen.widthPixels - ); - console.log( - 'platform.Screen.mainScreen.heightPixels = ' + platform.Screen.mainScreen.heightPixels - ); + console.log('platform.Screen.mainScreen.widthPixels = ' + platform.Screen.mainScreen.widthPixels); + console.log('platform.Screen.mainScreen.heightPixels = ' + platform.Screen.mainScreen.heightPixels); } function print() { diff --git a/apps/automated/src/pages/property-bindings.ts b/apps/automated/src/pages/property-bindings.ts index 12a9ec9fcd..cb7c3f8bca 100644 --- a/apps/automated/src/pages/property-bindings.ts +++ b/apps/automated/src/pages/property-bindings.ts @@ -8,7 +8,7 @@ import { Trace } from '@nativescript/core'; import * as gridModule from '@nativescript/core/ui/layouts/grid-layout'; import * as sliders from '@nativescript/core/ui/slider'; import * as switches from '@nativescript/core/ui/switch'; -import * as bindable from '@nativescript/core/ui/core/bindable'; +import type { BindingOptions } from '@nativescript/core/ui/core/bindable/bindable-types'; Trace.enable(); //Trace.setCategories(Trace.categories.Style + " ," + Trace.categories.Binding); @@ -64,7 +64,7 @@ export function createPage() { slider.value = desc.value; stack.addChild(slider); - var options: bindable.BindingOptions = { + var options: BindingOptions = { sourceProperty: 'value', targetProperty: desc.name, }; @@ -82,7 +82,7 @@ export function createPage() { sw.horizontalAlignment = 'left'; stack.addChild(sw); - var options: bindable.BindingOptions = { + var options: BindingOptions = { sourceProperty: 'checked', targetProperty: desc.name, }; @@ -99,7 +99,7 @@ export function createPage() { txt.text = desc.value; stack.addChild(txt); - var options: bindable.BindingOptions = { + var options: BindingOptions = { sourceProperty: 'text', targetProperty: desc.name, }; diff --git a/apps/automated/src/platform/platform-tests.ts b/apps/automated/src/platform/platform-tests.ts index e308ccb928..8a2aa4c0bd 100644 --- a/apps/automated/src/platform/platform-tests.ts +++ b/apps/automated/src/platform/platform-tests.ts @@ -5,6 +5,8 @@ export function test_platform() { let expectedPlatform; if (isAndroid) { expectedPlatform = 'Android'; + } else if (__VISIONOS__) { + expectedPlatform = 'visionOS'; } else { expectedPlatform = 'iOS'; } diff --git a/apps/automated/src/test-runner.ts b/apps/automated/src/test-runner.ts index 3e97b56735..fd4ffbca88 100644 --- a/apps/automated/src/test-runner.ts +++ b/apps/automated/src/test-runner.ts @@ -2,7 +2,7 @@ import * as TKUnit from './tk-unit'; import './ui-test'; -import { isIOS, isAndroid, Application, Device, platformNames, Trace, Button, Frame, StackLayout, Page, TextView, Utils } from '@nativescript/core'; +import { isAndroid, Device, platformNames, Trace, Button, Frame, StackLayout, Page, TextView, Utils, Color } from '@nativescript/core'; Frame.defaultAnimatedNavigation = false; export function isRunningOnEmulator(): boolean { @@ -16,7 +16,7 @@ export function isRunningOnEmulator(): boolean { android.os.Build.PRODUCT.toLocaleLowerCase().indexOf('sdk') > -1 || android.os.Build.PRODUCT.toLocaleLowerCase().indexOf('emulator') > -1 ); // VS Emulator - } else if (Device.os === platformNames.ios) { + } else if (__APPLE__) { return __dirname.search('Simulator') > -1; } } @@ -147,7 +147,7 @@ import * as scrollViewSafeAreaTests from './ui/scroll-view/scroll-view-safe-area import * as repeaterSafeAreaTests from './ui/repeater/repeater-safe-area-tests'; import * as webViewSafeAreaTests from './ui/web-view/web-view-safe-area-tests'; -if (isIOS && Utils.ios.MajorVersion > 10) { +if (__APPLE__ && Utils.SDK_VERSION > 10) { allTests['SAFEAREALAYOUT'] = safeAreaLayoutTests; allTests['SAFEAREA-LISTVIEW'] = safeAreaListViewtTests; allTests['SAFEAREA-SCROLL-VIEW'] = scrollViewSafeAreaTests; @@ -176,6 +176,9 @@ allTests['STYLE'] = styleTests; import * as visualStateTests from './ui/styling/visual-state-tests'; allTests['VISUAL-STATE'] = visualStateTests; +import * as cssKeywordsTests from './ui/styling/css-keywords-tests'; +allTests['CSS-KEYWORDS'] = cssKeywordsTests; + import * as valueSourceTests from './ui/styling/value-source-tests'; allTests['VALUE-SOURCE'] = valueSourceTests; @@ -218,8 +221,8 @@ allTests['PROGRESS'] = progressTests; import * as placeholderTests from './ui/placeholder/placeholder-tests'; allTests['PLACEHOLDER'] = placeholderTests; -// import * as pageTests from './ui/page/page-tests'; -// allTests['PAGE'] = pageTests; +import * as pageTests from './ui/page/page-tests'; +allTests['PAGE'] = pageTests; import * as listViewTests from './ui/list-view/list-view-tests'; allTests['LISTVIEW'] = listViewTests; @@ -245,6 +248,7 @@ allTests['DATE-PICKER'] = datePickerTests; import * as timePickerTests from './ui/time-picker/time-picker-tests'; allTests['TIME-PICKER'] = timePickerTests; +// TODO: followup on 3 assertions here - // import * as webViewTests from './ui/web-view/web-view-tests'; // allTests['WEB-VIEW'] = webViewTests; @@ -347,6 +351,8 @@ function printRunTestStats() { let finalMessage = `\n` + `=== ALL TESTS COMPLETE ===\n` + `${allTests.length - failedTestCount} OK, ${failedTestCount} failed\n` + `DURATION: ${totalTime} ms\n` + `=== END OF TESTS ===\n`; + Trace.setCategories(Trace.categories.Test); + Trace.enable(); TKUnit.write(finalMessage, Trace.messageType.info); failedTestInfo.forEach((message, i, arr) => { @@ -397,12 +403,23 @@ function showReportPage(finalMessage: string) { messageContainer.text = finalMessage; stack.addChild(messageContainer); + if (__VISIONOS__) { + // just helps make the results screen more clear on Vision Pro + btn.style.fontSize = 22; + stack.style.padding = 20; + stack.style.marginTop = 20; + messageContainer.style.fontSize = 22; + messageContainer.style.color = new Color('#fff'); + } + Frame.topmost().navigate({ create: () => { const page = new Page(); page.content = stack; messageContainer.focus(); - page.style.fontSize = 11; + if (!__VISIONOS__) { + page.style.fontSize = 11; + } if (isAndroid) { page.on('navigatedTo', () => { messageContainer.focus(); @@ -473,7 +490,7 @@ export function runAll(testSelector?: string) { new TestInfo(() => { running = true; startTime = TKUnit.time(); - }) + }), ); for (const name in allTests) { if (singleModuleName && singleModuleName !== name.toLowerCase()) { @@ -482,7 +499,11 @@ export function runAll(testSelector?: string) { const testModule = allTests[name]; - const test = testModule.createTestCase ? testModule.createTestCase() : testModule; + // In ESM environments (like Vite), module namespace objects are not extensible. + // Some tests expect to set arbitrary properties like `name` on the test instance. + // If a module doesn't provide `createTestCase()`, wrap its exports in a plain + // mutable object to safely attach metadata without mutating the namespace object. + const test = testModule.createTestCase ? testModule.createTestCase() : ({ ...testModule } as any); test.name = name; testsQueue.push(new TestInfo(startLog, test)); @@ -519,7 +540,7 @@ export function runAll(testSelector?: string) { new TestInfo(function () { testsQueue = []; running = false; - }) + }), ); TKUnit.runTests(testsQueue, 0); diff --git a/apps/automated/src/tk-unit.ts b/apps/automated/src/tk-unit.ts index c875f60f4c..3c79c6dffc 100644 --- a/apps/automated/src/tk-unit.ts +++ b/apps/automated/src/tk-unit.ts @@ -11,8 +11,7 @@ */ -import { isIOS, Trace, Device } from '@nativescript/core'; -import * as types from '@nativescript/core/utils/types'; +import { Trace, Device, Utils } from '@nativescript/core'; const sdkVersion = parseInt(Device.sdkVersion); @@ -172,10 +171,10 @@ export function assertFalse(test: boolean, message?: string) { export function assertNotEqual(actual: any, expected: any, message?: string) { let equals = false; - if (types.isUndefined(actual) && types.isUndefined(expected)) { + if (Utils.isUndefined(actual) && Utils.isUndefined(expected)) { equals = true; - } else if (!types.isNullOrUndefined(actual) && !types.isNullOrUndefined(expected)) { - if (types.isFunction(actual.equals)) { + } else if (!Utils.isNullOrUndefined(actual) && !Utils.isNullOrUndefined(expected)) { + if (Utils.isFunction(actual.equals)) { // Use the equals method if (actual.equals(expected)) { equals = true; @@ -191,7 +190,7 @@ export function assertNotEqual(actual: any, expected: any, message?: string) { } export function assertEqual(actual: T, expected: T, message: string = '') { - if (!types.isNullOrUndefined(actual) && !types.isNullOrUndefined(expected) && types.getClass(actual) === types.getClass(expected) && types.isFunction((actual).equals)) { + if (!Utils.isNullOrUndefined(actual) && !Utils.isNullOrUndefined(expected) && Utils.getClass(actual) === Utils.getClass(expected) && Utils.isFunction((actual).equals)) { // Use the equals method if (!(actual).equals(expected)) { throw new Error(`${message} Actual: <${actual}>(${typeof actual}). Expected: <${expected}>(${typeof expected})`); @@ -352,7 +351,7 @@ export function waitUntilReady(isReady: () => boolean, timeoutSec: number = 5, s return; } - if (isIOS) { + if (__APPLE__) { const timeoutMs = timeoutSec * 1000; let totalWaitTime = 0; while (true) { diff --git a/apps/automated/src/ui-helper.ts b/apps/automated/src/ui-helper.ts index 487ba37cf0..3e1879dbec 100644 --- a/apps/automated/src/ui-helper.ts +++ b/apps/automated/src/ui-helper.ts @@ -1,4 +1,4 @@ -import { Color, Button, FormattedString, Span, ActionBar, Builder, isIOS, unsetValue, View, ViewBase, Frame, NavigationEntry, LayoutBase, StackLayout, Page, TabView, TabViewItem, Utils } from '@nativescript/core'; +import { Color, Button, FormattedString, Span, ActionBar, Builder, unsetValue, View, ViewBase, Frame, NavigationEntry, LayoutBase, StackLayout, Page, TabView, TabViewItem, Utils } from '@nativescript/core'; import * as TKUnit from './tk-unit'; @@ -255,7 +255,7 @@ export function assertTabSelectedTabTextColor(testView: ViewBase, hexColor: stri } export function forceGC() { - if (isIOS) { + if (__APPLE__) { /* tslint:disable:no-unused-expression */ // Could cause GC on the next call. new ArrayBuffer(4 * 1024 * 1024); @@ -295,7 +295,7 @@ export function _generateFormattedString(): FormattedString { export function nativeView_recycling_test(createNew: () => View, createLayout?: () => LayoutBase, nativeGetters?: Map any>, customSetters?: Map) { return; - // if (isIOS) { + // if (__APPLE__) { // // recycling not implemented yet. // return; // } diff --git a/apps/automated/src/ui/action-bar/action-bar-tests-common.ts b/apps/automated/src/ui/action-bar/action-bar-tests-common.ts index b0abe4c7ed..708414ff1f 100644 --- a/apps/automated/src/ui/action-bar/action-bar-tests-common.ts +++ b/apps/automated/src/ui/action-bar/action-bar-tests-common.ts @@ -1,16 +1,6 @@ import * as TKUnit from '../../tk-unit'; import * as helper from '../../ui-helper'; -import { Builder } from '@nativescript/core/ui/builder'; -import { Label } from '@nativescript/core/ui/label'; -import { Button } from '@nativescript/core/ui/button'; -import { Page } from '@nativescript/core/ui/page'; -import { View, isIOS } from '@nativescript/core'; -import { fromObject } from '@nativescript/core/data/observable'; -import { Frame } from '@nativescript/core/ui/frame'; - -// >> actionbar-common-require -import * as actionBarModule from '@nativescript/core/ui/action-bar'; -// << actionbar-common-require +import { View, fromObject, Frame, Page, Button, Label, Builder, ActionItem, ActionBar } from '@nativescript/core'; export function test_actionItem_inherit_bindingContext() { let page: Page; @@ -20,7 +10,7 @@ export function test_actionItem_inherit_bindingContext() { const pageFactory = function (): Page { page = new Page(); page.bindingContext = context; - const actionItem = new actionBarModule.ActionItem(); + const actionItem = new ActionItem(); actionItem.bind({ sourceProperty: 'text', @@ -164,7 +154,7 @@ export function test_Setting_ActionItems_doesnt_thrown() { const pageFactory = function (): Page { page = new Page(); - const actionItem = new actionBarModule.ActionItem(); + const actionItem = new ActionItem(); actionItem.text = 'Item'; page.actionBar.actionItems.addItem(actionItem); @@ -286,12 +276,12 @@ export function test_ActionBarVisibility_Never_ShouldNotShowDeclaredActionBar() - ` + `, ); helper.navigate(() => page); let actionBarHidden = false; - if (isIOS) { + if (__APPLE__) { actionBarHidden = page.actionBar.nativeView.hidden; } else { actionBarHidden = !page.actionBar.nativeView.isShown(); @@ -314,12 +304,12 @@ export function test_ActionBarVisibility_Always_ShouldShownHiddenActionBar() { - ` + `, ); helper.navigate(() => page); let actionBarHidden = false; - if (isIOS) { + if (__APPLE__) { actionBarHidden = page.actionBar.nativeView.hidden; } else { actionBarHidden = !page.actionBar.nativeView.isShown(); @@ -342,12 +332,12 @@ export function test_ActionBarVisibility_Auto_ShouldRespectPageActionBarHiddenPr - ` + `, ); helper.navigate(() => page); let actionBarHidden = false; - if (isIOS) { + if (__APPLE__) { actionBarHidden = page.actionBar.nativeView.hidden; } else { actionBarHidden = !page.actionBar.nativeView.isShown(); @@ -391,5 +381,5 @@ export function createPageAndNavigate() { } export function test_recycling() { - helper.nativeView_recycling_test(() => new actionBarModule.ActionBar()); + helper.nativeView_recycling_test(() => new ActionBar()); } diff --git a/apps/automated/src/ui/activity-indicator/activity-indicator-tests.ts b/apps/automated/src/ui/activity-indicator/activity-indicator-tests.ts index 9c029e0a76..b06a470ec8 100644 --- a/apps/automated/src/ui/activity-indicator/activity-indicator-tests.ts +++ b/apps/automated/src/ui/activity-indicator/activity-indicator-tests.ts @@ -49,8 +49,8 @@ export function test_set_TNS_value_updates_native_value() { } // Uncomment this when find way to check android Drawable color set by setColorFilter() method. -if (platform.Device.os === platform.platformNames.ios) { - exports.test_set_color = function () { +export function test_set_color() { + if (__APPLE__) { var ai = new activityIndicatorModule.ActivityIndicator(); ai.color = new color.Color('red'); @@ -59,7 +59,7 @@ if (platform.Device.os === platform.platformNames.ios) { } helper.buildUIAndRunTest(ai, testAction); - }; + } } // This method is only for the code snippet @@ -79,7 +79,7 @@ function binding_busy_to_image() { sourceProperty: 'isLoading', targetProperty: 'busy', }, - image + image, ); // << activity-indicator-loading } diff --git a/apps/automated/src/ui/animation/animation-tests.ts b/apps/automated/src/ui/animation/animation-tests.ts index ad75839ce4..75bbbd6f0d 100644 --- a/apps/automated/src/ui/animation/animation-tests.ts +++ b/apps/automated/src/ui/animation/animation-tests.ts @@ -1,11 +1,6 @@ import * as TKUnit from '../../tk-unit'; import * as helper from '../../ui-helper'; -import * as viewModule from '@nativescript/core/ui/core/view'; -import { Label } from '@nativescript/core/ui/label'; -import { StackLayout } from '@nativescript/core/ui/layouts/stack-layout'; -import * as colorModule from '@nativescript/core/color'; -import { CoreTypes, PercentLength } from '@nativescript/core'; -import { AnimationPromise, AnimationDefinition, Animation } from '@nativescript/core/ui/animation'; +import { AnimationPromise, AnimationDefinition, Animation, CoreTypes, PercentLength, Label, StackLayout, View, Color } from '@nativescript/core'; // >> animation-require // import * as animation from '@nativescript/core/ui/animation'; @@ -38,7 +33,7 @@ export function test_AnimatingProperties(done) { label .animate({ opacity: 0.75, - backgroundColor: new colorModule.Color('Red'), + backgroundColor: new Color('Red'), translate: { x: 100, y: 100 }, scale: { x: 2, y: 2 }, rotate: 180, @@ -79,7 +74,7 @@ export function test_PlayRejectsWhenAlreadyPlayingAnimation(done) { if (e === 'Animation is already playing.') { done(); } - } + }, ); } @@ -269,7 +264,7 @@ export function test_AnimateOpacity(done) { export function test_AnimateOpacity_ShouldThrow_IfNotNumber() { var label = new Label(); - helper.buildUIAndRunTest(label, (views: Array) => { + helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ opacity: '0.75' }); }, 'Setting opacity to a non number should throw.'); @@ -278,7 +273,7 @@ export function test_AnimateOpacity_ShouldThrow_IfNotNumber() { export function test_AnimateDelay_ShouldThrow_IfNotNumber() { var label = new Label(); - helper.buildUIAndRunTest(label, (views: Array) => { + helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ delay: '1' }); }, 'Setting delay to a non number should throw.'); @@ -287,7 +282,7 @@ export function test_AnimateDelay_ShouldThrow_IfNotNumber() { export function test_AnimateDuration_ShouldThrow_IfNotNumber() { var label = new Label(); - helper.buildUIAndRunTest(label, (views: Array) => { + helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ duration: '1' }); }, 'Setting duration to a non number should throw.'); @@ -296,7 +291,7 @@ export function test_AnimateDuration_ShouldThrow_IfNotNumber() { export function test_AnimateIterations_ShouldThrow_IfNotNumber() { var label = new Label(); - helper.buildUIAndRunTest(label, (views: Array) => { + helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ iterations: '1' }); }, 'Setting iterations to a non number should throw.'); @@ -305,7 +300,7 @@ export function test_AnimateIterations_ShouldThrow_IfNotNumber() { export function test_AnimateRotate_ShouldThrow_IfNotNumber() { var label = new Label(); - helper.buildUIAndRunTest(label, (views: Array) => { + helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ rotate: '1' }); }, 'Setting rotate to a non number should throw.'); @@ -314,7 +309,7 @@ export function test_AnimateRotate_ShouldThrow_IfNotNumber() { export function test_AnimateScale_ShouldThrow_IfNotPair() { var label = new Label(); - helper.buildUIAndRunTest(label, (views: Array) => { + helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ scale: '1' }); }, 'Setting scale to a non Pair should throw.'); @@ -323,7 +318,7 @@ export function test_AnimateScale_ShouldThrow_IfNotPair() { export function test_AnimateTranslate_ShouldThrow_IfNotPair() { var label = new Label(); - helper.buildUIAndRunTest(label, (views: Array) => { + helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ translate: '1' }); }, 'Setting translate to a non Pair should throw.'); @@ -332,7 +327,7 @@ export function test_AnimateTranslate_ShouldThrow_IfNotPair() { export function test_AnimateBackgroundColor_ShouldThrow_IfNotValidColorStringOrColor() { var label = new Label(); - helper.buildUIAndRunTest(label, (views: Array) => { + helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ backgroundColor: 'test' }); }, 'Setting backgroundColor to a not valid color string or Color should throw.'); @@ -341,12 +336,12 @@ export function test_AnimateBackgroundColor_ShouldThrow_IfNotValidColorStringOrC export function test_AnimateBackgroundColor(done) { let label = prepareTest(); - var red = new colorModule.Color('Red'); + var red = new Color('Red'); label .animate({ backgroundColor: red, duration: 5 }) .then(() => { - TKUnit.assert((label.backgroundColor).equals(red)); + TKUnit.assert((label.backgroundColor).equals(red)); done(); }) .catch((e) => { @@ -357,12 +352,12 @@ export function test_AnimateBackgroundColor(done) { export function test_AnimateBackgroundColor_FromString(done) { let label = prepareTest(); var expected = 'Red'; - var clr = new colorModule.Color(expected); + var clr = new Color(expected); label .animate({ backgroundColor: expected, duration: 5 }) .then(() => { - TKUnit.assert((label.backgroundColor).equals(clr)); + TKUnit.assert((label.backgroundColor).equals(clr)); done(); }) .catch((e) => { @@ -505,7 +500,7 @@ export function test_AnimateExtent_Should_AcceptNumberValuesAsDip(done) { export function test_AnimateExtent_Should_ThrowIfCannotParsePercentLength() { const label = new Label(); - helper.buildUIAndRunTest(label, (views: Array) => { + helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ width: '-frog%' }); }, 'Invalid percent string should throw'); @@ -610,7 +605,7 @@ export function test_PlayPromiseIsResolvedWhenAnimationFinishes(done) { function onRejected(e) { TKUnit.assert(false, 'Animation play promise should be resolved, not rejected.'); done(e); - } + }, ); } @@ -627,14 +622,14 @@ export function test_PlayPromiseIsRejectedWhenAnimationIsCancelled(done) { function onRejected(e) { TKUnit.assert(animation.isPlaying === false, 'Animation.isPlaying should be false when animation play promise is rejected.'); done(); - } + }, ); animation.cancel(); } -function assertIOSNativeTransformIsCorrect(view: viewModule.View) { - if (global.isIOS) { +function assertIOSNativeTransformIsCorrect(view: View) { + if (__APPLE__) { var errorMessage = (require('@nativescript/core/ui/animation'))._getTransformMismatchErrorMessage(view); if (errorMessage) { TKUnit.assert(false, errorMessage); diff --git a/apps/automated/src/ui/animation/css-animation-tests.ts b/apps/automated/src/ui/animation/css-animation-tests.ts index edb41b1149..4c96efde63 100644 --- a/apps/automated/src/ui/animation/css-animation-tests.ts +++ b/apps/automated/src/ui/animation/css-animation-tests.ts @@ -1,7 +1,7 @@ import * as TKUnit from '../../tk-unit'; import { StyleScope } from '@nativescript/core/ui/styling/style-scope'; import * as keyframeAnimation from '@nativescript/core/ui/animation/keyframe-animation'; -import { CoreTypes } from '@nativescript/core'; +import { Application, CoreTypes, Screen } from '@nativescript/core'; import * as helper from '../../ui-helper'; import * as stackModule from '@nativescript/core/ui/layouts/stack-layout'; import * as labelModule from '@nativescript/core/ui/label'; @@ -18,7 +18,6 @@ function createAnimationFromCSS(css: string, name: string): keyframeAnimation.Ke const selector = findSelectorInScope(scope, name); if (selector) { const animation = scope.getAnimations(selector.ruleset)[0]; - return animation; } } @@ -40,7 +39,7 @@ export function test_ReadAnimationProperties() { animation-direction: reverse; animation-fill-mode: forwards; }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.name, 'first'); TKUnit.assertEqual(animation.duration, 4000); @@ -56,7 +55,7 @@ export function test_ReadTheAnimationProperty() { `.test { animation: second 0.2s ease-out 1s 2; }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.name, 'second'); TKUnit.assertEqual(animation.duration, 200); @@ -70,42 +69,42 @@ export function test_ReadAnimationCurve() { `.test { animation-timing-function: ease-in; }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.curve, CoreTypes.AnimationCurve.easeIn); animation = createAnimationFromCSS( `.test { animation-timing-function: ease-out; }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.curve, CoreTypes.AnimationCurve.easeOut); animation = createAnimationFromCSS( `.test { animation-timing-function: linear; }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.curve, CoreTypes.AnimationCurve.linear); animation = createAnimationFromCSS( `.test { animation-timing-function: ease-in-out; }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.curve, CoreTypes.AnimationCurve.easeInOut); animation = createAnimationFromCSS( `.test { animation-timing-function: spring; }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.curve, CoreTypes.AnimationCurve.spring); animation = createAnimationFromCSS( `.test { animation-timing-function: cubic-bezier(0.1, 1.0, 0.5, 0.5); }`, - 'test' + 'test', ); let curve = animation.curve; TKUnit.assert(curve.x1 === 0.1 && curve.y1 === 1.0 && curve.x2 === 0.5 && curve.y2 === 0.5); @@ -116,14 +115,14 @@ export function test_ReadIterations() { `.test { animation-iteration-count: 5; }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.iterations, 5); animation = createAnimationFromCSS( `.test { animation-iteration-count: infinite; }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.iterations, Number.POSITIVE_INFINITY); } @@ -133,21 +132,21 @@ export function test_ReadFillMode() { `.test { animation-iteration-count: 5; }`, - 'test' + 'test', ); TKUnit.assertFalse(animation.isForwards); animation = createAnimationFromCSS( `.test { animation-fill-mode: forwards; }`, - 'test' + 'test', ); TKUnit.assertTrue(animation.isForwards); animation = createAnimationFromCSS( `.test { animation-fill-mode: backwards; }`, - 'test' + 'test', ); TKUnit.assertFalse(animation.isForwards); } @@ -157,21 +156,21 @@ export function test_ReadDirection() { `.test { animation-iteration-count: 5; }`, - 'test' + 'test', ); TKUnit.assertFalse(animation.isReverse); animation = createAnimationFromCSS( `.test { animation-direction: reverse; }`, - 'test' + 'test', ); TKUnit.assertTrue(animation.isReverse); animation = createAnimationFromCSS( `.test { animation-direction: normal; }`, - 'test' + 'test', ); TKUnit.assertFalse(animation.isReverse); } @@ -183,7 +182,7 @@ export function test_ReadKeyframe() { from { background-color: red; } to { background-color: blue; } }`, - 'test' + 'test', ); TKUnit.assert(animation !== undefined, 'CSS selector was not created!'); TKUnit.assertEqual(animation.name, 'test', 'Wrong animation name!'); @@ -200,7 +199,7 @@ export function test_ReadTransformAllSet() { @keyframes test { to { transform: rotate(10) scaleX(5) translate(100, 200); } }`, - 'test' + 'test', ); const { rotate, scale, translate } = getTransformsValues(animation.keyframes[0].declarations); @@ -219,7 +218,7 @@ export function test_ReadTransformNone() { @keyframes test { to { transform: none; } }`, - 'test' + 'test', ); const { rotate, scale, translate } = getTransformsValues(animation.keyframes[0].declarations); @@ -238,7 +237,7 @@ export function test_ReadScale() { @keyframes test { to { transform: scale(-5, 12.3pt); } }`, - 'test' + 'test', ); const { scale } = getTransforms(animation.keyframes[0].declarations); @@ -253,7 +252,7 @@ export function test_ReadScaleSingle() { @keyframes test { to { transform: scale(2); } }`, - 'test' + 'test', ); const { scale } = getTransforms(animation.keyframes[0].declarations); @@ -268,7 +267,7 @@ export function test_ReadScaleXY() { @keyframes test { to { transform: scaleX(5) scaleY(10); } }`, - 'test' + 'test', ); const { scale } = getTransforms(animation.keyframes[0].declarations); @@ -283,7 +282,7 @@ export function test_ReadScaleX() { @keyframes test { to { transform: scaleX(12.5); } }`, - 'test' + 'test', ); const { scale } = getTransforms(animation.keyframes[0].declarations); @@ -299,7 +298,7 @@ export function test_ReadScaleY() { @keyframes test { to { transform: scaleY(10); } }`, - 'test' + 'test', ); const { scale } = getTransforms(animation.keyframes[0].declarations); @@ -315,7 +314,7 @@ export function test_ReadScale3d() { @keyframes test { to { transform: scale3d(10, 20, 30); } }`, - 'test' + 'test', ); const { scale } = getTransforms(animation.keyframes[0].declarations); @@ -330,7 +329,7 @@ export function test_ReadTranslate() { @keyframes test { to { transform: translate(100, 20); } }`, - 'test' + 'test', ); const { translate } = getTransforms(animation.keyframes[0].declarations); @@ -345,7 +344,7 @@ export function test_ReadTranslateSingle() { @keyframes test { to { transform: translate(30); } }`, - 'test' + 'test', ); const { translate } = getTransforms(animation.keyframes[0].declarations); @@ -360,7 +359,7 @@ export function test_ReadTranslateXY() { @keyframes test { to { transform: translateX(5) translateY(10); } }`, - 'test' + 'test', ); const { translate } = getTransforms(animation.keyframes[0].declarations); @@ -375,7 +374,7 @@ export function test_ReadTranslateX() { @keyframes test { to { transform: translateX(12.5); } }`, - 'test' + 'test', ); const { translate } = getTransforms(animation.keyframes[0].declarations); @@ -391,7 +390,7 @@ export function test_ReadTranslateY() { @keyframes test { to { transform: translateY(10); } }`, - 'test' + 'test', ); const { translate } = getTransforms(animation.keyframes[0].declarations); @@ -407,7 +406,7 @@ export function test_ReadTranslate3d() { @keyframes test { to { transform: translate3d(10, 20, 30); } }`, - 'test' + 'test', ); const { translate } = getTransforms(animation.keyframes[0].declarations); @@ -422,7 +421,7 @@ export function test_ReadRotate() { @keyframes test { to { transform: rotate(5); } }`, - 'test' + 'test', ); const { rotate } = getTransforms(animation.keyframes[0].declarations); @@ -436,7 +435,7 @@ export function test_ReadRotateDeg() { @keyframes test { to { transform: rotate(45deg); } }`, - 'test' + 'test', ); const { rotate } = getTransforms(animation.keyframes[0].declarations); @@ -450,7 +449,7 @@ export function test_ReadRotateRad() { @keyframes test { to { transform: rotate(0.7853981634rad); } }`, - 'test' + 'test', ); const { rotate } = getTransforms(animation.keyframes[0].declarations); @@ -467,7 +466,7 @@ export function test_ReadAnimationWithUnsortedKeyframes() { 40%, 80% { opacity: 0.3; } to { opacity: 1; } }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.keyframes.length, 6); TKUnit.assertEqual(animation.keyframes[0].declarations[0].value, 0); @@ -502,7 +501,7 @@ export function test_LoadTwoAnimationsWithTheSameName() { from { opacity: 0; } to { opacity: 0.5; } /* this should override the previous one */ }`, - 'a' + 'a', ); TKUnit.assertEqual(animation.keyframes.length, 2); TKUnit.assertEqual(animation.keyframes[1].declarations[0].value, 0.5); @@ -520,7 +519,7 @@ export function test_LoadTwoAnimationsWithTheSameName() { from { opacity: 0; } to { opacity: 1; } }`, - 'a' + 'a', ); TKUnit.assertEqual(animation2.keyframes.length, 2); @@ -542,6 +541,68 @@ export function test_LoadAnimationProgrammatically() { }); } +export function test_LoadMatchingMediaQueryKeyframeAnimation() { + const animation = createAnimationFromCSS( + `.a { animation-name: mq1; } + @media only screen and (max-width: ${Screen.mainScreen.widthDIPs}) { + @keyframes mq1 { + from { opacity: 0; } + to { opacity: 1; } + } + }`, + 'a', + ); + TKUnit.assertEqual(animation.keyframes.length, 2); + TKUnit.assertEqual(animation.keyframes[1].declarations[0].value, 1); +} + +export function test_IgnoreNonMatchingMediaQueryKeyframe() { + const animation = createAnimationFromCSS( + `.a { animation-name: mq1; } + @media only screen and (max-width: ${Screen.mainScreen.widthDIPs - 1}) { + @keyframes mq1 { + from { opacity: 0; } + to { opacity: 1; } + } + }`, + 'a', + ); + TKUnit.assertEqual(animation.keyframes, null); +} + +export function test_LoadMatchingNestedMediaQueryKeyframeAnimation() { + const animation = createAnimationFromCSS( + `.a { animation-name: mq1; } + @media only screen and (orientation: ${Application.orientation()}) { + @media only screen and (max-width: ${Screen.mainScreen.widthDIPs}) { + @keyframes mq1 { + from { opacity: 0; } + to { opacity: 1; } + } + } + }`, + 'a', + ); + TKUnit.assertEqual(animation.keyframes.length, 2); + TKUnit.assertEqual(animation.keyframes[1].declarations[0].value, 1); +} + +export function test_IgnoreNonMatchingNestedMediaQueryKeyframe() { + const animation = createAnimationFromCSS( + `.a { animation-name: mq1; } + @media only screen and (orientation: ${Application.orientation()}) { + @media only screen and (max-width: ${Screen.mainScreen.widthDIPs - 1}) { + @keyframes mq1 { + from { opacity: 0; } + to { opacity: 1; } + } + } + }`, + 'a', + ); + TKUnit.assertEqual(animation.keyframes, null); +} + export function test_ExecuteCSSAnimation() { let mainPage = helper.getCurrentPage(); mainPage.css = null; @@ -615,7 +676,7 @@ export function test_AnimationCurveInKeyframes() { 50% { background-color: green; } to { background-color: black; } }`, - 'test' + 'test', ); TKUnit.assertEqual(animation.keyframes[0].curve, CoreTypes.AnimationCurve.linear); diff --git a/apps/automated/src/ui/button/button-tests-native.android.ts b/apps/automated/src/ui/button/button-tests-native.android.ts index c5c03d74c8..ac63dff76a 100644 --- a/apps/automated/src/ui/button/button-tests-native.android.ts +++ b/apps/automated/src/ui/button/button-tests-native.android.ts @@ -1,4 +1,5 @@ import { Color, Button, Utils, CoreTypes } from '@nativescript/core'; +import { AndroidHelper } from '@nativescript/core/ui/core/view'; export function getNativeText(button: Button): string { return button.android.getText(); @@ -19,15 +20,7 @@ export function getNativeColor(button: Button): Color { } export function getNativeBackgroundColor(button: Button): Color { - let bg = button.android.getBackground(); - if (bg instanceof org.nativescript.widgets.BorderDrawable) { - return new Color(bg.getBackgroundColor()); - } else if (bg instanceof android.graphics.drawable.ColorDrawable) { - console.log(bg); - return new Color(bg.getColor()); - } else { - return new Color(bg.backgroundColor); - } + return AndroidHelper.getDrawableColor(button.android.getBackground()); } export function getNativeTextAlignment(button: Button): string { diff --git a/apps/automated/src/ui/button/button-tests.ts b/apps/automated/src/ui/button/button-tests.ts index cc3dfd7036..07f9a1b112 100644 --- a/apps/automated/src/ui/button/button-tests.ts +++ b/apps/automated/src/ui/button/button-tests.ts @@ -1,12 +1,7 @@ import * as TKUnit from '../../tk-unit'; import * as helper from '../../ui-helper'; -import { View, EventData, Button, Observable, Color, Page, FormattedString } from '@nativescript/core'; +import { View, EventData, Button, Observable, Color, Page, FormattedString, BindingOptions, Span } from '@nativescript/core'; import * as buttonTestsNative from './button-tests-native'; -import * as spanModule from '@nativescript/core/text/span'; - -// >> button-require-others -import { BindingOptions } from '@nativescript/core/ui/core/bindable'; -// << button-require-others export function test_recycling() { helper.nativeView_recycling_test(() => new Button()); @@ -274,7 +269,7 @@ export var test_StateHighlighted_also_fires_pressedState = function () { helper.waitUntilLayoutReady(view); - view._goToVisualState('highlighted'); + view._addVisualState('highlighted'); var actualResult = buttonTestsNative.getNativeBackgroundColor(view); TKUnit.assert(actualResult.hex === expectedNormalizedColor, 'Actual: ' + actualResult.hex + '; Expected: ' + expectedNormalizedColor); @@ -291,7 +286,7 @@ export var test_StateHighlighted_also_fires_activeState = function () { helper.waitUntilLayoutReady(view); - view._goToVisualState('highlighted'); + view._addVisualState('highlighted'); var actualResult = buttonTestsNative.getNativeBackgroundColor(view); TKUnit.assert(actualResult.hex === expectedNormalizedColor, 'Actual: ' + actualResult.hex + '; Expected: ' + expectedNormalizedColor); @@ -326,13 +321,13 @@ export var testNativeTextAlignmentFromLocal = function () { }; export var test_WhenFormattedTextPropertyChanges_TextIsUpdated_Button = function () { - var firstSpan = new spanModule.Span(); + var firstSpan = new Span(); firstSpan.fontSize = 10; firstSpan.text = 'First'; - var secondSpan = new spanModule.Span(); + var secondSpan = new Span(); secondSpan.fontSize = 15; secondSpan.text = 'Second'; - var thirdSpan = new spanModule.Span(); + var thirdSpan = new Span(); thirdSpan.fontSize = 20; thirdSpan.text = 'Third'; var formattedString1 = new FormattedString(); @@ -392,7 +387,7 @@ export function test_setting_formattedText_With_UnknownFont_DoesNotCrash() { helper.buildUIAndRunTest(btn, function (views) { TKUnit.waitUntilReady(() => btn.isLayoutValid); - let span = new spanModule.Span(); + let span = new Span(); span.text = 'Login'; let formattedString = new FormattedString(); formattedString.spans.push(span); diff --git a/apps/automated/src/ui/core/bindable/bindable-tests.ts b/apps/automated/src/ui/core/bindable/bindable-tests.ts index 8007ee0ff3..825db61c91 100644 --- a/apps/automated/src/ui/core/bindable/bindable-tests.ts +++ b/apps/automated/src/ui/core/bindable/bindable-tests.ts @@ -1,11 +1,7 @@ -import { Observable, fromObject, fromObjectRecursive } from '@nativescript/core/data/observable'; -import { ViewBase } from '@nativescript/core/ui/core/view-base'; -import { BindingOptions } from '@nativescript/core/ui/core/bindable'; +import { Application, View, Button, Page, StackLayout, Label, TextField, Trace, BindingOptions, ViewBase, Observable, fromObject, fromObjectRecursive, Utils } from '@nativescript/core'; import * as TKUnit from '../../../tk-unit'; -import * as types from '@nativescript/core/utils/types'; import * as helper from '../../../ui-helper'; import * as bindingBuilder from '@nativescript/core/ui/builder/binding-builder'; -import { Application, View, Button, Page, StackLayout, Label, TextField, Trace } from '@nativescript/core'; declare var WeakRef: any; // // For information and examples how to use bindings please refer to special [**Data binding**](../../../../bindings.md) topic. @@ -17,8 +13,8 @@ declare var WeakRef: any; export function test_Bindable_Members() { const obj = new Label(); - TKUnit.assert(types.isDefined(obj.bind), 'Bindable.bind not defined'); - TKUnit.assert(types.isDefined(obj.unbind), 'Bindable.unbind not defined'); + TKUnit.assert(Utils.isDefined(obj.bind), 'Bindable.bind not defined'); + TKUnit.assert(Utils.isDefined(obj.unbind), 'Bindable.unbind not defined'); } export function test_Binding_to_bindingContext_of_View() { @@ -502,7 +498,7 @@ export function test_bindingToNestedPropertyWithValueSyntax() { sourceProperty: '$value.testProperty', targetProperty: 'targetPropertyName', }, - bindingSource + bindingSource, ); TKUnit.assertEqual(testElement.get('targetPropertyName'), 'testValue'); @@ -706,7 +702,7 @@ export function test_UpdatingNestedPropertyViaBinding() { targetProperty: 'text', twoWay: true, }, - viewModel + viewModel, ); const testElement2 = new Label(); @@ -717,7 +713,7 @@ export function test_UpdatingNestedPropertyViaBinding() { targetProperty: 'text', twoWay: true, }, - viewModel + viewModel, ); TKUnit.assertEqual(testElement.get('text'), expectedValue1); @@ -803,7 +799,7 @@ export function test_NestedPropertiesBinding() { targetProperty: 'targetProperty', twoWay: true, }, - viewModel + viewModel, ); TKUnit.assertEqual(target1.get('targetProperty'), expectedValue); @@ -835,7 +831,7 @@ export function test_WrongNestedPropertiesBinding() { targetProperty: 'targetProperty', twoWay: true, }, - viewModel + viewModel, ); TKUnit.assertNotEqual(errorMessage, undefined); @@ -856,7 +852,7 @@ export function test_NestedPropertiesBindingTwoTargets() { targetProperty: 'targetProperty', twoWay: true, }, - viewModel + viewModel, ); const target2 = new Label(); @@ -866,7 +862,7 @@ export function test_NestedPropertiesBindingTwoTargets() { targetProperty: 'targetProp', twoWay: true, }, - viewModel + viewModel, ); TKUnit.assertEqual(target1.get('targetProperty'), expectedText); @@ -897,7 +893,7 @@ export function test_NestedPropertiesBindingTwoTargetsAndSecondChange() { targetProperty: 'targetProperty', twoWay: true, }, - viewModel + viewModel, ); const target2 = new Label(); @@ -907,7 +903,7 @@ export function test_NestedPropertiesBindingTwoTargetsAndSecondChange() { targetProperty: 'targetProp', twoWay: true, }, - viewModel + viewModel, ); TKUnit.assertEqual(target1.get('targetProperty'), expectedText); @@ -948,7 +944,7 @@ export function test_NestedPropertiesBindingTwoTargetsAndRegularChange() { targetProperty: 'targetProperty', twoWay: true, }, - viewModel + viewModel, ); const target2 = new Label(); @@ -958,7 +954,7 @@ export function test_NestedPropertiesBindingTwoTargetsAndRegularChange() { targetProperty: 'targetProp', twoWay: true, }, - viewModel + viewModel, ); TKUnit.assertEqual(target1.get('targetProperty'), expectedText); @@ -998,7 +994,7 @@ export function test_NestedPropertiesBindingTwoTargetsAndReplacingSomeNestedObje targetProperty: 'targetProperty', twoWay: true, }, - viewModel + viewModel, ); const target2 = new Label(); @@ -1008,7 +1004,7 @@ export function test_NestedPropertiesBindingTwoTargetsAndReplacingSomeNestedObje targetProperty: 'targetProp', twoWay: true, }, - viewModel + viewModel, ); TKUnit.assertEqual(target1.get('targetProperty'), expectedText); @@ -1050,7 +1046,7 @@ export function test_NullSourcePropertyShouldNotCrash() { targetProperty: 'targetProp', expression: 'convFunc(field)', }, - model + model, ); TKUnit.assertEqual(target.get('targetProp'), convFunc(expectedValue)); @@ -1123,7 +1119,7 @@ export function test_SupportFunctionsInExpressions() { targetProperty: 'text', expression: "isVisible() ? 'visible' : 'collapsed'", }, - model + model, ); model.set('anyColor', 'blue'); @@ -1151,7 +1147,7 @@ export function test_$ValueSupportWithinExpression() { targetProperty: 'text', expression: "$value.anyColor === 'red' ? 'red' : 'blue'", }, - model + model, ); model.set('anyColor', 'blue'); @@ -1230,7 +1226,7 @@ export function test_BindingToPropertiesWithSameNames() { targetProperty: 'targetProperty', twoWay: true, }, - model + model, ); const target2 = new Label(); @@ -1240,7 +1236,7 @@ export function test_BindingToPropertiesWithSameNames() { targetProperty: 'targetProp', twoWay: true, }, - model + model, ); model.item.set('seconds', model.item.seconds + 1); @@ -1273,7 +1269,7 @@ export function test_BindingToPropertiesWithSameNamesSecondCase() { targetProperty: 'targetProperty', twoWay: true, }, - model + model, ); const target2 = new Label(); @@ -1283,7 +1279,7 @@ export function test_BindingToPropertiesWithSameNamesSecondCase() { targetProperty: 'targetProp', twoWay: true, }, - model + model, ); model.item.set('seconds', model.item.seconds + 1); @@ -1372,7 +1368,7 @@ export function test_Observable_from_nested_json_binds_correctly() { sourceProperty: 'firstObject.secondObject.dummyProperty', targetProperty: 'text', }, - model + model, ); model.get('firstObject').get('secondObject').set('dummyProperty', expectedValue); @@ -1396,7 +1392,7 @@ export function test_Observable_from_nested_json_binds_correctly_when_upper_obje sourceProperty: 'firstObject.secondObject.dummyProperty', targetProperty: 'text', }, - model + model, ); model.get('firstObject').set('secondObject', fromObject({ dummyProperty: expectedValue })); diff --git a/apps/automated/src/ui/frame/frame-tests.android.ts b/apps/automated/src/ui/frame/frame-tests.android.ts index 27a049a890..0a18547250 100644 --- a/apps/automated/src/ui/frame/frame-tests.android.ts +++ b/apps/automated/src/ui/frame/frame-tests.android.ts @@ -1,10 +1,7 @@ -import { Frame } from '@nativescript/core/ui/frame'; import * as TKUnit from '../../tk-unit'; -import { unsetValue } from '@nativescript/core/ui/core/view'; -import { PercentLength } from '@nativescript/core/ui/styling/style-properties'; +import { PercentLength, unsetValue, Frame } from '@nativescript/core'; export * from './frame-tests-common'; - export function test_percent_width_and_height_set_to_page_support() { let topFrame = Frame.topmost(); let currentPage = topFrame.currentPage; diff --git a/apps/automated/src/ui/gestures/gestures-tests.ts b/apps/automated/src/ui/gestures/gestures-tests.ts index 8ab92d7a76..8c82f3d77f 100644 --- a/apps/automated/src/ui/gestures/gestures-tests.ts +++ b/apps/automated/src/ui/gestures/gestures-tests.ts @@ -1,14 +1,5 @@ /* tslint:disable:no-unused-variable */ -import { GestureEventData, Label, GestureTypes, PanGestureEventData, PinchGestureEventData, SwipeGestureEventData, RotationGestureEventData } from '@nativescript/core'; - -export var test_DummyTestForSnippetOnly0 = function () { - // >> gestures-double-tap - var label = new Label(); - var observer = label.on(GestureTypes.doubleTap, function (args: GestureEventData) { - console.log('Double Tap'); - }); - // << gestures-double-tap -}; +import { GestureEventData, Label, PanGestureEventData, PinchGestureEventData, SwipeGestureEventData, RotationGestureEventData } from '@nativescript/core'; export var test_DummyTestForSnippetOnly01 = function () { // >> gestures-double-tap-alt @@ -19,15 +10,6 @@ export var test_DummyTestForSnippetOnly01 = function () { // << gestures-double-tap-alt }; -export var test_DummyTestForSnippetOnly1 = function () { - // >> gestures-long-press - var label = new Label(); - var observer = label.on(GestureTypes.longPress, function (args: GestureEventData) { - console.log('Long Press'); - }); - // << gestures-long-press -}; - export var test_DummyTestForSnippetOnly11 = function () { // >> gestures-long-press-alt var label = new Label(); @@ -37,15 +19,6 @@ export var test_DummyTestForSnippetOnly11 = function () { // << gestures-long-press-alt }; -export var test_DummyTestForSnippetOnly2 = function () { - // >> gestures-pan - var label = new Label(); - var observer = label.on(GestureTypes.pan, function (args: PanGestureEventData) { - console.log('Pan deltaX:' + args.deltaX + '; deltaY:' + args.deltaY + ';'); - }); - // << gestures-pan -}; - export var test_DummyTestForSnippetOnly22 = function () { // >> gestures-pan-alt var label = new Label(); @@ -55,15 +28,6 @@ export var test_DummyTestForSnippetOnly22 = function () { // << gestures-pan-alt }; -export var test_DummyTestForSnippetOnly3 = function () { - // >> gestures-pan-pinch - var label = new Label(); - var observer = label.on(GestureTypes.pinch, function (args: PinchGestureEventData) { - console.log('Pinch scale: ' + args.scale); - }); - // << gestures-pan-pinch -}; - export var test_DummyTestForSnippetOnly33 = function () { // >> gestures-pan-pinch-alt var label = new Label(); @@ -73,15 +37,6 @@ export var test_DummyTestForSnippetOnly33 = function () { // << gestures-pan-pinch-alt }; -export var test_DummyTestForSnippetOnly4 = function () { - // >> gestures-rotation - var label = new Label(); - var observer = label.on(GestureTypes.rotation, function (args: RotationGestureEventData) { - console.log('Rotation: ' + args.rotation); - }); - // << gestures-rotation -}; - export var test_DummyTestForSnippetOnly44 = function () { // >> gestures-rotation-alt var label = new Label(); @@ -91,15 +46,6 @@ export var test_DummyTestForSnippetOnly44 = function () { // << gestures-rotation-alt }; -export var test_DummyTestForSnippetOnly5 = function () { - // >> gestures-swipe - var label = new Label(); - var observer = label.on(GestureTypes.swipe, function (args: SwipeGestureEventData) { - console.log('Swipe direction: ' + args.direction); - }); - // << gestures-swipe -}; - export var test_DummyTestForSnippetOnly55 = function () { // >> gestures-swipe-alt var label = new Label(); @@ -109,15 +55,6 @@ export var test_DummyTestForSnippetOnly55 = function () { // << gestures-swipe-alt }; -export var test_DummyTestForSnippetOnly6 = function () { - // >> gestures-tap - var label = new Label(); - var observer = label.on(GestureTypes.tap, function (args: GestureEventData) { - console.log('Tap'); - }); - // << gestures-tap -}; - export var test_DummyTestForSnippetOnly66 = function () { // >> gestures-tap-alt var label = new Label(); @@ -127,25 +64,6 @@ export var test_DummyTestForSnippetOnly66 = function () { // << gestures-tap-alt }; -export var test_DummyTestForSnippetOnly7 = function () { - // >> gestures-stop-observe - var label = new Label(); - var observer = label.on(GestureTypes.tap, function (args: GestureEventData) { - console.log('Tap'); - }); - observer.disconnect(); - // << gestures-stop-observe -}; - -export var test_DummyTestForSnippetOnly8 = function () { - // >> gestures-multiple - var label = new Label(); - var observer = label.on(GestureTypes.tap | GestureTypes.doubleTap | GestureTypes.longPress, function (args: GestureEventData) { - console.log('Event: ' + args.eventName); - }); - // << gestures-multiple -}; - export var test_DummyTestForSnippetOnly88 = function () { // >> gestures-string var label = new Label(); diff --git a/apps/automated/src/ui/image/image-tests.ts b/apps/automated/src/ui/image/image-tests.ts index 0ea34140c8..20e129e235 100644 --- a/apps/automated/src/ui/image/image-tests.ts +++ b/apps/automated/src/ui/image/image-tests.ts @@ -15,7 +15,7 @@ import { ImageSource } from '@nativescript/core/image-source'; import * as ViewModule from '@nativescript/core/ui/core/view'; import * as helper from '../../ui-helper'; import * as color from '@nativescript/core/color'; -import * as backgroundModule from '@nativescript/core/ui/styling/background'; +import * as appHelpers from '@nativescript/core/application/helpers-common'; import { Application } from '@nativescript/core'; const imagePath = '~/assets/logo.png'; @@ -23,8 +23,8 @@ export function test_recycling() { helper.nativeView_recycling_test(() => new Image()); } -if (global.isAndroid) { - (backgroundModule).initImageCache(Application.android.startActivity, (backgroundModule).CacheMode.memory); // use memory cache only. +if (__ANDROID__) { + appHelpers.initImageCache(Application.android.startActivity, appHelpers.CacheMode.memory); // use memory cache only. } export const test_Image_Members = function () { @@ -63,7 +63,7 @@ function runImageTestSync(image: ImageModule.Image, src: string) { image.src = src; - let imageSourceAvailable = global.isIOS ? !!image.imageSource : true; + let imageSourceAvailable = __APPLE__ ? !!image.imageSource : true; TKUnit.assertFalse(image.isLoading, 'Image.isLoading should be false.'); TKUnit.assertTrue(imageSourceAvailable, 'imageSource should be set.'); } @@ -76,7 +76,7 @@ function runImageTestAsync(image: ImageModule.Image, src: string, done: (e: any) image.off(IMAGE_LOADED_EVENT, handler); try { - let imageSourceAvailable = global.isIOS ? !!image.imageSource : true; + let imageSourceAvailable = __APPLE__ ? !!image.imageSource : true; TKUnit.assertFalse(image.isLoading, 'Image.isLoading should be false.'); TKUnit.assertTrue(imageSourceAvailable, 'imageSource should be set.'); done(null); @@ -253,7 +253,7 @@ export const test_SettingStretch_none = function () { }; function ios(func: T): T { - return global.isIOS ? func : undefined; + return __APPLE__ ? func : undefined; } export const test_SettingImageSourceWhenSizedToParentDoesNotRequestLayout = ios(() => { @@ -267,7 +267,11 @@ export const test_SettingImageSourceWhenSizedToParentDoesNotRequestLayout = ios( let mainPage = helper.getCurrentPage(); mainPage.content = host; - TKUnit.waitUntilReady(() => host.isLoaded); + + const nativeHostView = host.nativeViewProtected as UIView; + + // Check if native view layer is still marked as dirty before proceeding + TKUnit.waitUntilReady(() => host.isLoaded && nativeHostView?.layer && !nativeHostView.layer.needsLayout()); let called = false; image.requestLayout = () => (called = true); @@ -285,7 +289,11 @@ export const test_SettingImageSourceWhenFixedWidthAndHeightDoesNotRequestLayout let mainPage = helper.getCurrentPage(); mainPage.content = host; - TKUnit.waitUntilReady(() => host.isLoaded); + + const nativeHostView = host.nativeViewProtected as UIView; + + // Check if native view layer is still marked as dirty before proceeding + TKUnit.waitUntilReady(() => host.isLoaded && nativeHostView?.layer && !nativeHostView.layer.needsLayout()); let called = false; image.requestLayout = () => (called = true); diff --git a/apps/automated/src/ui/label/label-tests-native.android.ts b/apps/automated/src/ui/label/label-tests-native.android.ts index 426bddf5d3..8437beab29 100644 --- a/apps/automated/src/ui/label/label-tests-native.android.ts +++ b/apps/automated/src/ui/label/label-tests-native.android.ts @@ -1,32 +1,32 @@ import * as labelModule from '@nativescript/core/ui/label'; -import { CoreTypes } from '@nativescript/core'; -import * as colorModule from '@nativescript/core/color'; +import { Color, CoreTypes } from '@nativescript/core'; +import { AndroidHelper } from '@nativescript/core/ui/core/view'; + +const UNEXPECTED_VALUE = 'unexpected value'; export function getNativeTextAlignment(label: labelModule.Label): string { - let gravity = label.android.getGravity(); + let hGravity = label.android.getGravity() & android.view.Gravity.HORIZONTAL_GRAVITY_MASK; + const alignment = label.android.getTextAlignment(); + + if (hGravity === android.view.Gravity.START && alignment === android.view.View.TEXT_ALIGNMENT_VIEW_START) { + return 'initial'; + } - if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.LEFT) { + if (hGravity === android.view.Gravity.LEFT && alignment === android.view.View.TEXT_ALIGNMENT_GRAVITY) { return CoreTypes.TextAlignment.left; } - if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.CENTER_HORIZONTAL) { + if (hGravity === android.view.Gravity.CENTER_HORIZONTAL && alignment === android.view.View.TEXT_ALIGNMENT_CENTER) { return CoreTypes.TextAlignment.center; } - if ((gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK) === android.view.Gravity.RIGHT) { + if (hGravity === android.view.Gravity.RIGHT && alignment === android.view.View.TEXT_ALIGNMENT_GRAVITY) { return CoreTypes.TextAlignment.right; } - return 'unexpected value'; + return UNEXPECTED_VALUE; } -export function getNativeBackgroundColor(label: labelModule.Label): colorModule.Color { - let bg = label.android.getBackground(); - if (bg instanceof org.nativescript.widgets.BorderDrawable) { - return new colorModule.Color(bg.getBackgroundColor()); - } else if (bg instanceof android.graphics.drawable.ColorDrawable) { - return new colorModule.Color(bg.getColor()); - } else { - return new colorModule.Color(bg.backgroundColor); - } +export function getNativeBackgroundColor(label: labelModule.Label): Color { + return AndroidHelper.getDrawableColor(label.android.getBackground()); } diff --git a/apps/automated/src/ui/label/label-tests-native.d.ts b/apps/automated/src/ui/label/label-tests-native.d.ts index dd7dea90ae..77037e0cf4 100644 --- a/apps/automated/src/ui/label/label-tests-native.d.ts +++ b/apps/automated/src/ui/label/label-tests-native.d.ts @@ -3,5 +3,4 @@ import * as labelModule from '@nativescript/core/ui/label'; import * as colorModule from '@nativescript/core/color'; export declare function getNativeTextAlignment(label: labelModule.Label): string; - export declare function getNativeBackgroundColor(label: labelModule.Label): colorModule.Color; diff --git a/apps/automated/src/ui/label/label-tests.ts b/apps/automated/src/ui/label/label-tests.ts index fd1be0c5bd..9696aca3fe 100644 --- a/apps/automated/src/ui/label/label-tests.ts +++ b/apps/automated/src/ui/label/label-tests.ts @@ -1,49 +1,32 @@ import * as TKUnit from '../../tk-unit'; import * as testModule from '../../ui-test'; - -//>> label-require -import * as LabelModule from '@nativescript/core/ui/label'; -// << label-require - -import * as types from '@nativescript/core/utils/types'; -import * as colorModule from '@nativescript/core/color'; -import * as utils from '@nativescript/core/utils'; -import * as observableModule from '@nativescript/core/data/observable'; -import * as bindable from '@nativescript/core/ui/core/bindable'; -import { CoreTypes, Span, FormattedString } from '@nativescript/core'; +import { Label, GridLayout, LayoutBase, StackLayout, BindingOptions, CoreTypes, Span, FormattedString, Utils, Color, Observable, path } from '@nativescript/core'; import * as labelTestsNative from './label-tests-native'; -import * as fs from '@nativescript/core/file-system'; - -import { StackLayout } from '@nativescript/core/ui/layouts/stack-layout'; -import { GridLayout } from '@nativescript/core/ui/layouts/grid-layout'; -import { isIOS, isAndroid } from '@nativescript/core/platform'; -import { Label } from '@nativescript/core/ui/label'; -import { LayoutBase } from '@nativescript/core/ui/layouts/layout-base'; import * as helper from '../../ui-helper'; const testDir = 'ui/label'; -export class LabelTest extends testModule.UITest { - public create(): LabelModule.Label { - const label = new LabelModule.Label(); +export class LabelTest extends testModule.UITest