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_android.yml b/.github/workflows/apps_automated_android.yml index 23aaa687f5..d8483b4448 100644 --- a/.github/workflows/apps_automated_android.yml +++ b/.github/workflows/apps_automated_android.yml @@ -1,4 +1,7 @@ name: 'apps/automated/android' +permissions: + contents: read + pull-requests: write on: push: @@ -8,6 +11,9 @@ on: 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 @@ -17,26 +23,32 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - - uses: actions/setup-node@v4 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 20.10.0 + node-version: 23.5.0 - - uses: actions/setup-java@v4 + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@826660b82addbef3abff5fa871492ebad618c9e1 # v4.3.3 + with: + main-branch-name: 'main' + + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: 'temurin' - java-version: '17' + java-version: '21' - name: Install Python - uses: actions/setup-python@v5 + 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 --legacy-peer-deps + npm i -g nativescript --ignore-scripts ns usage-reporting disable ns error-reporting disable @@ -53,8 +65,8 @@ jobs: sudo udevadm trigger --name-match=kvm - name: Run tests on Android Emulator - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b # v2.35.0 with: - api-level: 34 + api-level: 35 arch: x86_64 - script: node tools/scripts/run-automated.js android + script: npx nx test apps-automated -c=android diff --git a/.github/workflows/apps_automated_ios.yml b/.github/workflows/apps_automated_ios.yml index 5b3650a53c..7fcce34749 100644 --- a/.github/workflows/apps_automated_ios.yml +++ b/.github/workflows/apps_automated_ios.yml @@ -1,4 +1,7 @@ name: 'apps/automated/ios' +permissions: + contents: read + pull-requests: write on: push: @@ -8,33 +11,39 @@ on: 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: macos-latest + runs-on: warp-macos-15-arm64-6x steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - - uses: actions/setup-node@v4 + # - name: ActionDebugger By Warpbuild + # uses: Warpbuilds/action-debugger@v1.3 + + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 20.10.0 + node-version: 23.5.0 - - name: Install Python - uses: actions/setup-python@v4 + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@826660b82addbef3abff5fa871492ebad618c9e1 # v4.3.3 with: - python-version: '3' + main-branch-name: 'main' - name: Install NativeScript run: | - python3 -m pip install --upgrade pip six - npm i -g nativescript --ignore-scripts --legacy-peer-deps + npm i -g nativescript --ignore-scripts ns usage-reporting disable ns error-reporting disable - ns doctor + # ns doctor - name: Setup run: npm run setup @@ -43,7 +52,9 @@ jobs: run: npx nx run-many --target=test --configuration=ci --projects=core - name: Start iOS Simulator - uses: futureware-tech/simulator-action@v3 + uses: futureware-tech/simulator-action@dab10d813144ef59b48d401cd95da151222ef8cd # v4 + with: + model: 'iPhone 16 Pro' - name: Run tests on iOS Simulator - run: node tools/scripts/run-automated.js ios + 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..d69dfb2474 --- /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@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3 \ 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..b0c6ab3bbe 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@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 + 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..d99b1ad1cf 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@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 + 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..3df7b98875 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@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 + 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..643e3ff14b --- /dev/null +++ b/.github/workflows/secure_nx_release.yml @@ -0,0 +1,411 @@ +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 }} + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0 + 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 libs under packages/* and exclude items configured as non-releaseable. + affected_json=$(npx nx show projects --affected --base "$base" --head "$head" --type lib --projects "packages/*" --exclude "ui-mobile-base,types-minimal,winter-tc,types,types-ios,types-android" --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' }} + shell: bash + 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' }} + shell: bash + 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: | + echo "Nx Release completed." + echo "- mode: ${{ steps.ctx.outputs.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 "- dry-run: ${{ steps.ctx.outputs.dry_run }}" + echo "- use-token: ${{ vars.USE_NPM_TOKEN == 'true' }}" diff --git a/.gitignore b/.gitignore index 40b510aa45..05192106d7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /dist /tmp /out-tsc +.ns-vite-build # dependencies **/node_modules @@ -57,4 +58,8 @@ Thumbs.db ios-typings-prj .nx/cache -.nx/workspace-data \ No newline at end of file +.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/README.md b/README.md index 87bfd5a6b1..02b3bc6655 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,34 @@ [![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/setup/) @@ -67,6 +95,7 @@ We love you and your pull requests 🤗. Please follow our [contributing guide]( - 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) 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 41de6a023d..56964cd456 100644 --- a/apps/automated/package.json +++ b/apps/automated/package.json @@ -11,12 +11,13 @@ "nativescript-theme-core": "file:../../node_modules/nativescript-theme-core" }, "devDependencies": { - "@nativescript/android": "~8.8.0", - "@nativescript/ios": "~8.8.0", - "@nativescript/visionos": "~8.7.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": "~5.4.0" + "typescript": "~5.8.0" }, "gitHead": "c06800e52ee1a184ea2dffd12a6702aaa43be4e3", "readme": "NativeScript Application" diff --git a/apps/automated/project.json b/apps/automated/project.json index 8244cf69af..6aae5b2a9a 100644 --- a/apps/automated/project.json +++ b/apps/automated/project.json @@ -11,53 +11,67 @@ "targets": { "build": { "executor": "@nativescript/nx:build", + "inputs": ["default", "^production"], "options": { "noHmr": true, "production": true, "uglify": true, "release": true, "forDevice": true - } - }, - "ios": { - "executor": "@nativescript/nx:build", - "inputs": ["default", "^production"], - "outputs": [], - "options": { - "noHmr": true, - "platform": "ios" - } + }, + "configurations": {}, + "dependsOn": ["^build"] }, - "vision": { - "executor": "@nativescript/nx:build", + "debug": { + "executor": "@nativescript/nx:debug", "inputs": ["default", "^production"], - "outputs": [], "options": { "noHmr": true, "debug": false, - "platform": "vision" - } + "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.android.ts b/apps/automated/src/application/application-tests.android.ts index c5695ea7e3..3371a7cd0b 100644 --- a/apps/automated/src/application/application-tests.android.ts +++ b/apps/automated/src/application/application-tests.android.ts @@ -43,13 +43,14 @@ 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(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() { diff --git a/apps/automated/src/application/application-tests.ios.ts b/apps/automated/src/application/application-tests.ios.ts index 19695328da..b60c32f234 100644 --- a/apps/automated/src/application/application-tests.ios.ts +++ b/apps/automated/src/application/application-tests.ios.ts @@ -52,6 +52,7 @@ export function testIOSApplicationInitialized() { 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.'); } 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/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 559d3126cc..b78254ae01 100644 --- a/apps/automated/src/file-system/file-system-tests.ts +++ b/apps/automated/src/file-system/file-system-tests.ts @@ -186,15 +186,15 @@ export var testFileRead = function () { // << 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,7 +203,7 @@ export var testFileReadWriteBinary = function () { }); // >> (hide) - var destination = destinationFile.readSync((e) => { + const destination = destinationFile.readSync((e) => { error = e; }); TKUnit.assertNull(error); @@ -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( @@ -263,7 +263,7 @@ export var testFileReadWriteBinaryAsync = function () { }, ); // << file-system-read-binary-async -}; +} export var testGetKnownFolders = function () { // >> file-system-known-folders @@ -425,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 @@ -633,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.'); @@ -746,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 818500f9d3..5271a64c9e 100644 --- a/apps/automated/src/main.ts +++ b/apps/automated/src/main.ts @@ -87,13 +87,13 @@ 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.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.nativeException ?? (args.error as any).nativeError); console.log(args.error.stackTrace ?? args.error.stack); }); @@ -149,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/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/test-runner.ts b/apps/automated/src/test-runner.ts index 6990aca1a1..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, Color } 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 { @@ -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; @@ -348,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) => { @@ -494,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)); 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 2b9c32a3a4..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 (__APPLE__) { - 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 (__APPLE__) { } helper.buildUIAndRunTest(ai, testAction); - }; + } } // This method is only for the code snippet 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/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 f4df1d9160..0a18547250 100644 --- a/apps/automated/src/ui/frame/frame-tests.android.ts +++ b/apps/automated/src/ui/frame/frame-tests.android.ts @@ -1,7 +1,5 @@ -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() { 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 c91ea1afd1..8437beab29 100644 --- a/apps/automated/src/ui/label/label-tests-native.android.ts +++ b/apps/automated/src/ui/label/label-tests-native.android.ts @@ -2,22 +2,29 @@ import * as labelModule from '@nativescript/core/ui/label'; 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): Color { 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 72648865fa..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, isApple } 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