diff --git a/.agent/skills/feat/SKILL.md b/.agent/skills/feat/SKILL.md new file mode 100644 index 0000000000..47c628a41d --- /dev/null +++ b/.agent/skills/feat/SKILL.md @@ -0,0 +1,45 @@ +--- +name: feat +description: Build a feature or enhancement end-to-end (GitHub issue or free-text description → plan → implement → test → PR). Use anytime you need to add functionality to a package in this workspace. +argument-hint: [issue-number-or-description] +--- + +# Feature Workflow + +Conventions this workflow depends on (read the ones your change touches before writing code): + +- [CorePlatformModules.md](./references/CorePlatformModules.md) — required before touching `packages/core` (platform-split files, handwritten `.d.ts`). +- [CodeComments.md](./references/CodeComments.md) — comment rules for all TypeScript edits. +- [WritingUnitTests.md](./references/WritingUnitTests.md) — test expectations and environment constraints. +- [CONTRIBUTING.md](./references/CONTRIBUTING.md) — commit message format. + +## Phase 1: Understand + +- If given a GitHub issue number, fetch it: `gh issue view --json title,body,labels,comments`. Otherwise treat the input as a free-text description; if empty, ask for one. +- Summarize the user-facing goal, acceptance criteria, and edge cases before touching code. + +## Phase 2: Ground in the codebase + +- Find an existing module or feature similar to what you are building and use it as the pattern to follow — reference it by path in your plan. +- Map the touch surface: which package, which modules, and for `packages/core` whether the change lands in `-common.ts`, both platform files, or all three — plus the neighboring `.d.ts`. + +## Phase 3: Plan + +Present a short plan before implementing: files to create/edit with a one-liner each, the test strategy, and any public API changes (these require `.d.ts` updates). Prefer the simplest solution that reuses existing patterns. + +## Phase 4: Implement + +- Follow the referenced conventions. Keep `.ios.ts` / `.android.ts` in parity; never leave one side diverged. +- Public API changes update the neighboring `.d.ts` in the same change, with JSDoc in the `.d.ts`. + +## Phase 5: Test + +- Add or extend colocated `*.spec.ts` specs for shared-logic changes. +- Run the focused tests first (`npx nx run core:test -t 'Name'`), then the full package target (`npx nx run core:test`) — it must be green. +- Behavior that needs the real native runtime is covered in `apps/automated`, not unit tests; note it for the PR's manual test scenarios instead. + +## Phase 6: Finish + +- Format: `npx nx format:write`. +- Commit with the conventional format, e.g. `feat(core): `. +- Open the PR with `gh`, following [the PR template](../../../.github/PULL_REQUEST_TEMPLATE.md): reference the issue and include tests for the change. diff --git a/.agent/skills/feat/references/CONTRIBUTING.md b/.agent/skills/feat/references/CONTRIBUTING.md new file mode 120000 index 0000000000..0e3ea98d32 --- /dev/null +++ b/.agent/skills/feat/references/CONTRIBUTING.md @@ -0,0 +1 @@ +../../../../tools/notes/CONTRIBUTING.md \ No newline at end of file diff --git a/.agent/skills/feat/references/CodeComments.md b/.agent/skills/feat/references/CodeComments.md new file mode 120000 index 0000000000..09fd9fc39a --- /dev/null +++ b/.agent/skills/feat/references/CodeComments.md @@ -0,0 +1 @@ +../../../../tools/notes/CodeComments.md \ No newline at end of file diff --git a/.agent/skills/feat/references/CorePlatformModules.md b/.agent/skills/feat/references/CorePlatformModules.md new file mode 120000 index 0000000000..ae7886b24a --- /dev/null +++ b/.agent/skills/feat/references/CorePlatformModules.md @@ -0,0 +1 @@ +../../../../tools/notes/CorePlatformModules.md \ No newline at end of file diff --git a/.agent/skills/feat/references/WritingUnitTests.md b/.agent/skills/feat/references/WritingUnitTests.md new file mode 120000 index 0000000000..23c09ad452 --- /dev/null +++ b/.agent/skills/feat/references/WritingUnitTests.md @@ -0,0 +1 @@ +../../../../tools/notes/WritingUnitTests.md \ No newline at end of file diff --git a/.agent/skills/fix/SKILL.md b/.agent/skills/fix/SKILL.md new file mode 100644 index 0000000000..762635f9d4 --- /dev/null +++ b/.agent/skills/fix/SKILL.md @@ -0,0 +1,45 @@ +--- +name: fix +description: Debug and fix a bug end-to-end (GitHub issue or free-text report → trace → root cause → fix → test → PR). Use anytime you need to fix broken, throwing, or misbehaving code in this workspace. +argument-hint: [issue-number-or-description] +--- + +# Bug Fix Workflow + +Conventions this workflow depends on (read the ones your change touches before writing code): + +- [CorePlatformModules.md](./references/CorePlatformModules.md) — required before touching `packages/core` (platform-split files, handwritten `.d.ts`). +- [CodeComments.md](./references/CodeComments.md) — comment rules for all TypeScript edits. +- [WritingUnitTests.md](./references/WritingUnitTests.md) — test expectations and environment constraints. +- [CONTRIBUTING.md](./references/CONTRIBUTING.md) — commit message format. + +## Phase 1: Get the bug + +- If given a GitHub issue number, fetch it: `gh issue view --json title,body,labels,comments`. Otherwise collect the report from the user. +- Extract the reproduction steps, environment (platform, versions), and any stack trace or error message — these point at the files to read first. + +## Phase 2: Trace the code path + +- Follow the failure from entry point to the error site. For `packages/core`, read BOTH platform files — a bug on one platform often means the implementations diverged. +- Keep claims honest: distinguish what you proved by reading code (quote `file:line`) from what you suspect. Do not present a hunch as the root cause. + +## Phase 3: Root cause + +- Confirm the mechanism before fixing. When the logic is unit-testable, write a failing spec that reproduces the bug — it becomes the proof and the regression guard. +- Grep for the same pattern elsewhere in the package; a bug rarely lives in only one place. List every instance found. + +## Phase 4: Fix + +- Apply the smallest change that fixes the root cause, not the symptom. Fix all instances found in the spread check. +- Keep `.ios.ts` / `.android.ts` in parity; if the fix changes a public API, update the neighboring `.d.ts` in the same change. + +## Phase 5: Verify + +- The failing spec from Phase 3 must go green, and the full package target (`npx nx run core:test`) must pass. +- Behavior only observable on a real device/simulator belongs in `apps/automated`; note it for the PR's manual test scenarios. + +## Phase 6: Finish + +- Format: `npx nx format:write`. +- Commit with the conventional format, e.g. `fix(core): `. +- Open the PR with `gh`, following [the PR template](../../../.github/PULL_REQUEST_TEMPLATE.md): reference the issue and include the regression test. diff --git a/.agent/skills/fix/references/CONTRIBUTING.md b/.agent/skills/fix/references/CONTRIBUTING.md new file mode 120000 index 0000000000..0e3ea98d32 --- /dev/null +++ b/.agent/skills/fix/references/CONTRIBUTING.md @@ -0,0 +1 @@ +../../../../tools/notes/CONTRIBUTING.md \ No newline at end of file diff --git a/.agent/skills/fix/references/CodeComments.md b/.agent/skills/fix/references/CodeComments.md new file mode 120000 index 0000000000..09fd9fc39a --- /dev/null +++ b/.agent/skills/fix/references/CodeComments.md @@ -0,0 +1 @@ +../../../../tools/notes/CodeComments.md \ No newline at end of file diff --git a/.agent/skills/fix/references/CorePlatformModules.md b/.agent/skills/fix/references/CorePlatformModules.md new file mode 120000 index 0000000000..ae7886b24a --- /dev/null +++ b/.agent/skills/fix/references/CorePlatformModules.md @@ -0,0 +1 @@ +../../../../tools/notes/CorePlatformModules.md \ No newline at end of file diff --git a/.agent/skills/fix/references/WritingUnitTests.md b/.agent/skills/fix/references/WritingUnitTests.md new file mode 120000 index 0000000000..23c09ad452 --- /dev/null +++ b/.agent/skills/fix/references/WritingUnitTests.md @@ -0,0 +1 @@ +../../../../tools/notes/WritingUnitTests.md \ No newline at end of file diff --git a/.agent/skills/refactor/SKILL.md b/.agent/skills/refactor/SKILL.md new file mode 100644 index 0000000000..48dc130154 --- /dev/null +++ b/.agent/skills/refactor/SKILL.md @@ -0,0 +1,36 @@ +--- +name: refactor +description: Restructure existing code without changing behavior (scope → baseline tests → small verified steps → PR). Use anytime you need to clean up, reorganize, or simplify code in this workspace. +argument-hint: [scope-or-description] +--- + +# Refactor Workflow + +Conventions this workflow depends on (read the ones your change touches before writing code): + +- [CorePlatformModules.md](./references/CorePlatformModules.md) — required before touching `packages/core` (platform-split files, handwritten `.d.ts`). +- [CodeComments.md](./references/CodeComments.md) — comment rules for all TypeScript edits. +- [WritingUnitTests.md](./references/WritingUnitTests.md) — test expectations and environment constraints. +- [CONTRIBUTING.md](./references/CONTRIBUTING.md) — commit message format. + +## Phase 1: Scope + +- Define what is being restructured and, explicitly, what "unchanged behavior" means for it: same public API, same observable behavior on both platforms. +- Public API changes are NOT refactors — if the `.d.ts` surface must change, stop and confirm the scope with the user first. + +## Phase 2: Baseline + +- Run the affected package's tests BEFORE touching anything (`npx nx run core:test`); they must be green. +- Where the code being restructured has no spec coverage, add specs first to lock in current behavior — they are the safety net for every step that follows. + +## Phase 3: Restructure in small steps + +- Work in small, independently green steps; run the focused tests between steps. +- Keep `.ios.ts` / `.android.ts` in parity at every step — moving shared logic into `-common.ts` is a common refactor here, and both platform files must be updated together. +- Delete dead and commented-out code you uncover; do not carry it along. + +## Phase 4: Finish + +- Full package target green (`npx nx run core:test`), then format: `npx nx format:write`. +- Commit with the conventional format, e.g. `refactor(core): `. +- Open the PR with `gh`, following [the PR template](../../../.github/PULL_REQUEST_TEMPLATE.md); state that behavior is unchanged and how that was verified. diff --git a/.agent/skills/refactor/references/CONTRIBUTING.md b/.agent/skills/refactor/references/CONTRIBUTING.md new file mode 120000 index 0000000000..0e3ea98d32 --- /dev/null +++ b/.agent/skills/refactor/references/CONTRIBUTING.md @@ -0,0 +1 @@ +../../../../tools/notes/CONTRIBUTING.md \ No newline at end of file diff --git a/.agent/skills/refactor/references/CodeComments.md b/.agent/skills/refactor/references/CodeComments.md new file mode 120000 index 0000000000..09fd9fc39a --- /dev/null +++ b/.agent/skills/refactor/references/CodeComments.md @@ -0,0 +1 @@ +../../../../tools/notes/CodeComments.md \ No newline at end of file diff --git a/.agent/skills/refactor/references/CorePlatformModules.md b/.agent/skills/refactor/references/CorePlatformModules.md new file mode 120000 index 0000000000..ae7886b24a --- /dev/null +++ b/.agent/skills/refactor/references/CorePlatformModules.md @@ -0,0 +1 @@ +../../../../tools/notes/CorePlatformModules.md \ No newline at end of file diff --git a/.agent/skills/refactor/references/WritingUnitTests.md b/.agent/skills/refactor/references/WritingUnitTests.md new file mode 120000 index 0000000000..23c09ad452 --- /dev/null +++ b/.agent/skills/refactor/references/WritingUnitTests.md @@ -0,0 +1 @@ +../../../../tools/notes/WritingUnitTests.md \ No newline at end of file diff --git a/.agent/skills/unit-testing/SKILL.md b/.agent/skills/unit-testing/SKILL.md new file mode 100644 index 0000000000..5ecd492ef0 --- /dev/null +++ b/.agent/skills/unit-testing/SKILL.md @@ -0,0 +1,31 @@ +--- +name: unit-testing +description: How to run and write unit tests in this workspace (Vitest, colocated .spec.ts files, mocked native globals). Use when writing, changing, or running tests. +--- + +# Unit Testing + +Tests are `*.spec.ts` files colocated with the source they cover, written with Vitest and run per package through Nx. See [WritingUnitTests.md](./references/WritingUnitTests.md) for the full guide. + +## Running + +```bash +npx nx run core:test # all core tests +npx nx run core:test -t 'Name' # isolate by describe/it name +npx nx run core:test --watch # watch mode +``` + +Use `-t` to run only the tests related to your change first; run the full package target before finishing. + +## Writing + +- Use the standard Vitest API (`describe`, `it`, `expect`, `beforeEach`, `vi`). Globals are enabled, so imports from `vitest` are optional — match the style of neighboring specs. +- Place the spec next to the module: `packages/core/xml/index.spec.ts` covers `packages/core/xml/index.ts`. +- Every behavior change in shared (non-platform-specific) logic should add or extend a spec. + +## Environment Constraints + +- Tests run in Node, not on a device. `packages/core/vitest.setup.ts` stubs the platform globals (`__IOS__`, `__ANDROID__`, minimal `NSObject`-style mocks) so modules can load — real iOS/Android APIs do NOT exist here. +- If a test needs more native surface, extend the mocks in `vitest.setup.ts`; do not stub natives inline per spec. +- Behavior that depends on the real native runtime cannot be unit tested — it belongs in the `apps/automated` e2e suite, which requires a simulator/emulator. Do not attempt to run it unless explicitly asked. +- Keep tests deterministic: no network calls, no timing-sensitive assertions without fake timers (`vi.useFakeTimers()`). diff --git a/.agent/skills/unit-testing/references/WritingUnitTests.md b/.agent/skills/unit-testing/references/WritingUnitTests.md new file mode 120000 index 0000000000..23c09ad452 --- /dev/null +++ b/.agent/skills/unit-testing/references/WritingUnitTests.md @@ -0,0 +1 @@ +../../../../tools/notes/WritingUnitTests.md \ No newline at end of file diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000000..9b058317d1 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agent/skills \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 3eab9c6ec0..743d7ebb2f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,12 +1,12 @@ { "root": true, "ignorePatterns": ["**/*"], - "plugins": ["@nrwl/nx"], + "plugins": ["@nx"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { - "@nrwl/nx/enforce-module-boundaries": [ + "@nx/enforce-module-boundaries": [ "error", { "enforceBuildableLibDependency": true, @@ -23,7 +23,7 @@ }, { "files": ["*.ts", "*.tsx"], - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "plugin:@nrwl/nx/typescript"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "plugin:@nx/typescript"], "rules": { "no-empty": "off", "no-useless-escape": "off", @@ -38,19 +38,32 @@ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-this-alias": "warn", "@typescript-eslint/no-namespace": "off", - "@typescript-eslint/no-inferrable-types": "off" + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-empty-object-type": "off", + "@typescript-eslint/no-unsafe-function-type": "off", + "@typescript-eslint/no-wrapper-object-types": "off", + "@typescript-eslint/no-unsafe-declaration-merging": "off", + "@typescript-eslint/no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }], + "no-extra-semi": "off" } }, { "files": ["*.js", "*.jsx"], - "extends": ["plugin:@nrwl/nx/javascript"], - "rules": {} + "extends": ["plugin:@nx/javascript"], + "rules": { + "no-extra-semi": "off" + } }, { "files": ["references.d.ts"], "rules": { "@typescript-eslint/triple-slash-reference": "off" } + }, + { + "files": "*.json", + "parser": "jsonc-eslint-parser", + "rules": {} } ] } diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..00c0cf4037 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,226 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + + - package-ecosystem: npm + directory: /apps/automated + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/pages + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/ui/lifecycle + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/ui/root-view/mymodule + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/xml-declaration/mymodulewithxml + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/automated/src/xml-declaration + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/toolbox + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /apps/ui + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: / + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/core/css-value + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/core/css + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/core/js-libs/easysax + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/core + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/devtools + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/types-android + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/types-ios + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/types-minimal + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/types + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/ui-mobile-base + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/webpack5 + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /packages/winter-tc + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: npm + directory: /tools/workspace-plugin + schedule: + interval: monthly + time: "23:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] \ No newline at end of file diff --git a/.github/workflows/apps_automated.yml b/.github/workflows/apps_automated.yml deleted file mode 100644 index 1fce002040..0000000000 --- a/.github/workflows/apps_automated.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: 'apps/automated' - -on: - push: - branches: - - main - - 'ci/**' - pull_request: - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - test: - runs-on: macos-12 - - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-node@v3 - with: - node-version: 18 - - - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '11' - - - name: Install Python - uses: actions/setup-python@v4 - with: - python-version: '3' - - - name: Install NativeScript - run: | - python3 -m pip install --upgrade pip six - npm i -g nativescript --ignore-scripts --legacy-peer-deps - ns usage-reporting disable - ns error-reporting disable - ns doctor - - - name: Setup - run: npm run setup - - - name: Unit Tests - run: npx nx run-many --target=test --configuration=ci --projects=core - - - name: Create Emulator - uses: rigor789/action-create-emulator@main - - - name: Test (Android) - run: node tools/scripts/run-automated.js android - - - name: Start iOS Simulator - if: ${{ always() && !cancelled() }} # run iOS tests even if Android tests failed - uses: futureware-tech/simulator-action@v2 - with: - model: 'iPhone 13 Pro Max' - os_version: '>=15.0' - - - name: Test (iOS) - if: ${{ always() && !cancelled() }} # run iOS tests even if Android tests failed - run: node tools/scripts/run-automated.js ios diff --git a/.github/workflows/apps_automated_android.yml b/.github/workflows/apps_automated_android.yml new file mode 100644 index 0000000000..ce0c06f0df --- /dev/null +++ b/.github/workflows/apps_automated_android.yml @@ -0,0 +1,72 @@ +name: 'apps/automated/android' +permissions: + contents: read + pull-requests: write + +on: + push: + branches: + - main + - 'ci/**' + pull_request: + workflow_dispatch: + +env: + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-android: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: 23.5.0 + + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1 + with: + main-branch-name: 'main' + + - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + with: + distribution: 'temurin' + java-version: '21' + + - name: Install Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: '3' + + - name: Install NativeScript + run: | + python3 -m pip install --upgrade pip six + npm i -g nativescript --ignore-scripts + ns usage-reporting disable + ns error-reporting disable + + - name: Setup + run: npm run setup + + - name: Unit Tests + run: npx nx run-many --target=test --configuration=ci --projects=core + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Run tests on Android Emulator + uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0 + with: + api-level: 35 + arch: x86_64 + script: npx nx test apps-automated -c=android diff --git a/.github/workflows/apps_automated_ios.yml b/.github/workflows/apps_automated_ios.yml new file mode 100644 index 0000000000..e1ec465a18 --- /dev/null +++ b/.github/workflows/apps_automated_ios.yml @@ -0,0 +1,60 @@ +name: 'apps/automated/ios' +permissions: + contents: read + pull-requests: write + +on: + push: + branches: + - main + - 'ci/**' + pull_request: + workflow_dispatch: + +env: + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test-ios: + # runs-on: macos-latest + runs-on: warp-macos-15-arm64-6x + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + # - name: ActionDebugger By Warpbuild + # uses: Warpbuilds/action-debugger@v1.3 + + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: 23.5.0 + + - name: Derive appropriate SHAs for base and head for `nx affected` commands + uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1 + with: + main-branch-name: 'main' + + - name: Install NativeScript + run: | + npm i -g nativescript --ignore-scripts + ns usage-reporting disable + ns error-reporting disable + # ns doctor + + - name: Setup + run: npm run setup + + - name: Unit Tests + run: npx nx run-many --target=test --configuration=ci --projects=core + + - name: Start iOS Simulator + uses: futureware-tech/simulator-action@e89aa8f93d3aec35083ff49d2854d07f7186f7f5 # v5 + with: + model: 'iPhone 16 Pro' + + - name: Run tests on iOS Simulator + run: npx nx test apps-automated -c=ios diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000000..f9f787cac4 --- /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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: 'Dependency Review' + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 \ No newline at end of file diff --git a/.github/workflows/native_artifacts_check.yml b/.github/workflows/native_artifacts_check.yml new file mode 100644 index 0000000000..46406c9a9f --- /dev/null +++ b/.github/workflows/native_artifacts_check.yml @@ -0,0 +1,35 @@ +# Verifies that the committed native artifacts (packages/core/platforms) are in +# sync with their sources in packages/ui-mobile-base. Native builds are expensive +# and not rebuilt in CI, so this guards against changing native sources without +# rebuilding + committing the artifacts. Pure content hashing — no Xcode/Android +# toolchain required. +name: 'Native artifacts check' +on: + pull_request: + paths: + - 'packages/ui-mobile-base/**' + - 'packages/core/platforms/**' + +permissions: + contents: read + +env: + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + +jobs: + native-artifacts-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: 23.5.0 + cache: 'npm' + - name: 'Derive appropriate SHAs for base and head for `nx affected` commands' + uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1 + with: + main-branch-name: 'main' + - name: 'Install dependencies' + run: npm ci + - name: 'Verify native build state' + run: npx nx verify-native ui-mobile-base diff --git a/.github/workflows/notify_docs_core_release.yml b/.github/workflows/notify_docs_core_release.yml new file mode 100644 index 0000000000..ec8f2a1cff --- /dev/null +++ b/.github/workflows/notify_docs_core_release.yml @@ -0,0 +1,45 @@ +name: 'notify docs on core release' + +# When a @nativescript/core release tag is pushed (Nx releaseTag pattern +# "{version}-core", e.g. "9.0.21-core"), notify NativeScript/docs so it +# regenerates the API reference for the new version. +# +# Requires the DOCS_DISPATCH_TOKEN secret: a fine-grained PAT (or classic PAT +# with repo scope) that has contents read/write on NativeScript/docs. +# +# Note: tags pushed by other workflows using the default GITHUB_TOKEN do not +# trigger this workflow (GitHub suppresses recursive workflow runs). Tags +# pushed by maintainers or with a PAT/deploy key trigger it normally. + +on: + push: + tags: + - '[0-9]*-core' + +permissions: + contents: read + +jobs: + dispatch: + runs-on: ubuntu-latest + steps: + - name: Extract version from tag + id: version + run: | + TAG="${GITHUB_REF_NAME}" + VERSION="${TAG%-core}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + # skip prereleases (e.g. 9.1.0-alpha.11-core) + if [[ "$VERSION" == *-* ]]; then + echo "prerelease=true" >> "$GITHUB_OUTPUT" + else + echo "prerelease=false" >> "$GITHUB_OUTPUT" + fi + - name: Trigger docs API regeneration + if: steps.version.outputs.prerelease == 'false' + env: + GH_TOKEN: ${{ secrets.DOCS_DISPATCH_TOKEN }} + run: | + gh api repos/NativeScript/docs/dispatches \ + -f event_type=core-release \ + -f 'client_payload[version]=${{ steps.version.outputs.version }}' 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..91957abb1d 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - 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..6ba2dc2816 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + 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..333f416536 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@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - 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..5099880b6a --- /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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 + 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@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 + 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..ccd7a71198 --- /dev/null +++ b/.github/workflows/secure_nx_release.yml @@ -0,0 +1,502 @@ +name: Release Workflow + +on: + push: + branches: + - main + tags: + # Matches the Nx releaseTag pattern in nx.json: "{version}-{projectName}" + - '*-*' + workflow_dispatch: + inputs: + version: + description: "Exact version to release (e.g. 8.0.0-rc.0). Overrides release-type/preid; npm dist-tag is derived from it (8.0.0-rc.0 -> rc, 8.0.0 -> latest)" + required: false + type: string + default: "" + release-type: + description: "Version bump when 'version' is empty (patch/minor/major publish to npm 'latest'; prerelease uses 'preid')" + required: false + type: choice + options: + - prerelease + - patch + - minor + - major + default: prerelease + preid: + description: "Prerelease identifier (used only when release-type=prerelease and 'version' is empty; also becomes the npm dist-tag, e.g. next | canary)" + required: false + type: string + default: next + dry-run: + description: "Run release steps without making changes (no git push, no publish)" + required: false + type: boolean + default: false + release-group: + description: "Optional Nx project pattern to scope the release (empty = default behavior)" + required: false + type: string + default: "" + +concurrency: + # Avoid overlapping publishes on the same ref/branch + group: nx-release-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write # needed to push version commits and tags + id-token: write # required for npm provenance / trusted publishing (OIDC) + +jobs: + release: + name: Version and Publish (gated by environment) + if: ${{ github.actor != 'github-actions[bot]' }} + runs-on: ubuntu-latest + environment: + name: ${{ (github.event_name == 'workflow_dispatch' && inputs.dry-run) && 'npm-publish-dry-run' || 'npm-publish' }} + + env: + # Optional: provide Nx Cloud token if used in this repo + NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} + NEXT_PRERELEASE_PROJECT_ALLOWLIST: core,webpack5 + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout repository (full history for tagging) + uses: actions/checkout@v7.0.1 + 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 + env: + INPUT_VERSION: ${{ inputs.version }} + INPUT_RELEASE_TYPE: ${{ inputs['release-type'] }} + INPUT_PREID: ${{ inputs.preid }} + INPUT_SCOPE: ${{ inputs['release-group'] }} + INPUT_DRY_RUN: ${{ inputs['dry-run'] }} + run: | + set -euo pipefail + + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + version="${INPUT_VERSION}" + release_type="${INPUT_RELEASE_TYPE}" + preid="${INPUT_PREID}" + scope="${INPUT_SCOPE}" + dry_run="${INPUT_DRY_RUN}" + mode="dispatch" + + if [[ -n "$version" ]]; then + specifier="$version" + release_type="" + preid="" + if [[ "$version" == *-* ]]; then + # npm dist-tag is the alphabetic prerelease label (8.0.0-rc.0 -> rc); purely numeric labels fall back to next. + prerelease="${version#*-}" + dist_tag="${prerelease%%.*}" + if [[ "$dist_tag" =~ ^[0-9]+$ ]]; then + dist_tag="next" + fi + else + dist_tag="latest" + fi + else + specifier="$release_type" + # npm dist-tag follows release type: prerelease -> preid, stable -> latest + if [[ "$release_type" == "prerelease" ]]; then + dist_tag="$preid" + else + preid="" + dist_tag="latest" + fi + fi + elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then + specifier="" + release_type="" + preid="" + dist_tag="" + scope="" + dry_run="false" + mode="tag" + else + specifier="prerelease" + release_type="prerelease" + preid="next" + dist_tag="next" + scope="" + dry_run="false" + mode="main" + fi + + echo "mode=${mode}" >> "$GITHUB_OUTPUT" + echo "specifier=${specifier}" >> "$GITHUB_OUTPUT" + echo "release_type=${release_type}" >> "$GITHUB_OUTPUT" + echo "preid=${preid}" >> "$GITHUB_OUTPUT" + echo "dist_tag=${dist_tag}" >> "$GITHUB_OUTPUT" + echo "scope=${scope}" >> "$GITHUB_OUTPUT" + echo "dry_run=${dry_run}" >> "$GITHUB_OUTPUT" + + - name: Determine affected release projects (main) + id: affected + if: ${{ steps.ctx.outputs.mode == 'main' }} + shell: bash + run: | + set -euo pipefail + + base='${{ github.event.before }}' + head='${{ github.sha }}' + + # Handle edge cases where base commit doesn't exist (first push, force-push, etc.) + # Use HEAD~1 as fallback, or just compare against HEAD if no parent exists + if [[ "$base" == "0000000000000000000000000000000000000000" ]] || ! git cat-file -e "$base" 2>/dev/null; then + echo "Base commit not available, falling back to HEAD~1" + base="HEAD~1" + # If HEAD~1 doesn't exist (first commit), use empty tree + if ! git cat-file -e "$base" 2>/dev/null; then + base="$(git hash-object -t tree /dev/null)" + fi + fi + + # Only consider main-branch prerelease libraries allowed for automatic next publishes. + affected_json=$(npx nx show projects --affected --base "$base" --head "$head" --type lib --projects "$NEXT_PRERELEASE_PROJECT_ALLOWLIST" --json) + affected_list=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(a.join(","));});') + affected_count=$(printf '%s' "$affected_json" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const a=JSON.parse(s||"[]");process.stdout.write(String(a.length));});') + + echo "projects=${affected_list}" >> "$GITHUB_OUTPUT" + echo "count=${affected_count}" >> "$GITHUB_OUTPUT" + + - name: Determine tag release project and dist-tag (tags) + id: taginfo + if: ${{ steps.ctx.outputs.mode == 'tag' }} + shell: bash + run: | + set -euo pipefail + + tag_name="${GITHUB_REF_NAME}" + + # Find the project by matching the tag suffix against known releaseable packages. + projects=$(npx nx show projects --projects "packages/*" --type lib --exclude "ui-mobile-base,types-minimal,winter-tc,types,types-ios,types-android" --sep ' ') + + best_match="" + best_len=0 + for p in $projects; do + suffix="-${p}" + if [[ "$tag_name" == *"$suffix" ]]; then + if (( ${#p} > best_len )); then + best_match="$p" + best_len=${#p} + fi + fi + done + + if [[ -z "$best_match" ]]; then + echo "Could not determine project from tag '$tag_name'. Expected '{version}-{projectName}'." >&2 + exit 1 + fi + + version_part="${tag_name%-$best_match}" + if [[ "$version_part" == *-* ]]; then + dist_tag="next" + else + dist_tag="latest" + fi + + echo "project=${best_match}" >> "$GITHUB_OUTPUT" + echo "version=${version_part}" >> "$GITHUB_OUTPUT" + echo "dist_tag=${dist_tag}" >> "$GITHUB_OUTPUT" + + - name: Configure git user for automated commits + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # VERSION: updates versions and creates git tags following nx.json releaseTag.pattern. + - name: nx release version (main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' }} + shell: bash + run: | + set -euo pipefail + npx nx release version prerelease \ + --preid next \ + --projects "${{ steps.affected.outputs.projects }}" \ + --git-commit \ + --git-push \ + --verbose + + - name: nx release version (main, no-op) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count == '0' }} + run: echo "No affected release projects on main; skipping version + publish." + + # Orchestrated release: bumps versions, generates changelogs, commits + tags in one shot. + # The orchestrator does not accept --git-* flags (config-driven only); push is handled in the next step. + # --skip-publish keeps publishing as a separate step below so the OIDC token-clearing logic still runs. + # A project with no prior release tag gets its changelog from its first commit (release.changelog.automaticFromRef in nx.json). + - name: nx release version + changelog (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }} + shell: bash + run: | + set -euo pipefail + + scope="${{ steps.ctx.outputs.scope }}" + projects_arg=() + if [[ -n "$scope" ]]; then + projects_arg=(--projects "$scope") + fi + + preid="${{ steps.ctx.outputs.preid }}" + preid_arg=() + if [[ -n "$preid" ]]; then + preid_arg=(--preid "$preid") + fi + + npx nx release "${{ steps.ctx.outputs.specifier }}" \ + "${preid_arg[@]}" \ + "${projects_arg[@]}" \ + --skip-publish \ + --verbose + + - name: Push release commit and tags (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && !inputs.dry-run }} + run: git push --follow-tags + + - name: nx release version + changelog (dispatch, dry-run) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run }} + shell: bash + run: | + set -euo pipefail + + scope="${{ steps.ctx.outputs.scope }}" + projects_arg=() + if [[ -n "$scope" ]]; then + projects_arg=(--projects "$scope") + fi + + preid="${{ steps.ctx.outputs.preid }}" + preid_arg=() + if [[ -n "$preid" ]]; then + preid_arg=(--preid "$preid") + fi + + npx nx release "${{ steps.ctx.outputs.specifier }}" \ + "${preid_arg[@]}" \ + "${projects_arg[@]}" \ + --skip-publish \ + --verbose \ + --dry-run + + # BUILD: Ensure projects are built before publishing + - name: Build affected projects (main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' }} + run: npx nx run-many -t build --projects "${{ steps.affected.outputs.projects }}" --verbose + + - name: Build projects (dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' }} + shell: bash + run: | + set -euo pipefail + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + npx nx run-many -t build --projects "$scope" --verbose + else + npx nx run-many -t build --all --verbose + fi + + # PUBLISH: OIDC trusted publishing (default). Avoid any lingering token auth. + - name: nx release publish (OIDC, main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + npx nx release publish \ + --projects "${{ steps.affected.outputs.projects }}" \ + --tag "${{ steps.ctx.outputs.dist_tag }}" \ + --access public \ + --verbose + + - name: nx release publish (OIDC, dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && steps.ctx.outputs.dry_run != 'true' && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + + npx nx release publish \ + $projects_arg \ + --tag "${{ steps.ctx.outputs.dist_tag }}" \ + --access public \ + --verbose + + - name: nx release publish (OIDC, dispatch dry-run) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + + npx nx release publish \ + $projects_arg \ + --tag "${{ steps.ctx.outputs.dist_tag }}" \ + --access public \ + --verbose \ + --dry-run + + # PUBLISH: token fallback (only when explicitly enabled via repo/environment variable USE_NPM_TOKEN=true). + - name: nx release publish (token, main) + if: ${{ steps.ctx.outputs.mode == 'main' && steps.affected.outputs.count != '0' && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + npx nx release publish --projects "${{ steps.affected.outputs.projects }}" --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose + + - name: nx release publish (token, dispatch) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && steps.ctx.outputs.dry_run != 'true' && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + set -euo pipefail + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + npx nx release publish $projects_arg --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose + + - name: nx release publish (token, dispatch dry-run) + if: ${{ steps.ctx.outputs.mode == 'dispatch' && inputs.dry-run && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + set -euo pipefail + scope="${{ steps.ctx.outputs.scope }}" + if [[ -n "$scope" ]]; then + projects_arg="--projects $scope" + else + projects_arg="" + fi + npx nx release publish $projects_arg --tag "${{ steps.ctx.outputs.dist_tag }}" --access public --verbose --dry-run + + # Tag-triggered publishing: publish the single package referenced by the tag. + - name: Build project before publish (tag) + if: ${{ steps.ctx.outputs.mode == 'tag' }} + run: npx nx build "${{ steps.taginfo.outputs.project }}" --verbose + + - name: nx release publish (tag) + if: ${{ steps.ctx.outputs.mode == 'tag' && vars.USE_NPM_TOKEN != 'true' }} + shell: bash + env: + NPM_CONFIG_PROVENANCE: true + NODE_AUTH_TOKEN: "" + run: | + set -euo pipefail + unset NODE_AUTH_TOKEN + rm -f ~/.npmrc || true + if [[ -n "${NPM_CONFIG_USERCONFIG:-}" ]]; then + rm -f "$NPM_CONFIG_USERCONFIG" || true + fi + + npx nx release publish \ + --projects "${{ steps.taginfo.outputs.project }}" \ + --tag "${{ steps.taginfo.outputs.dist_tag }}" \ + --access public \ + --verbose + + - name: nx release publish (tag, token) + if: ${{ steps.ctx.outputs.mode == 'tag' && vars.USE_NPM_TOKEN == 'true' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: true + run: | + npx nx release publish --projects "${{ steps.taginfo.outputs.project }}" --tag "${{ steps.taginfo.outputs.dist_tag }}" --access public --verbose + + - name: Summary + if: always() + run: | + mode="${{ steps.ctx.outputs.mode }}" + + echo "Nx Release completed." + echo "- mode: ${mode}" + echo "- specifier: '${{ steps.ctx.outputs.specifier }}'" + echo "- release-type: '${{ steps.ctx.outputs.release_type }}'" + echo "- preid: '${{ steps.ctx.outputs.preid }}'" + echo "- dist-tag: ${{ steps.ctx.outputs.mode == 'tag' && steps.taginfo.outputs.dist_tag || steps.ctx.outputs.dist_tag }}" + echo "- scope: '${{ steps.ctx.outputs.scope }}'" + echo "- next-prerelease-allowlist: ${NEXT_PRERELEASE_PROJECT_ALLOWLIST}" + if [[ "$mode" == "main" ]]; then + echo "- projects: ${{ steps.affected.outputs.projects }}" + elif [[ "$mode" == "dispatch" ]]; then + if [[ -n "${{ steps.ctx.outputs.scope }}" ]]; then + echo "- projects: ${{ steps.ctx.outputs.scope }}" + else + echo "- projects: all configured release projects" + fi + else + echo "- project: ${{ steps.taginfo.outputs.project }}" + fi + echo "- dry-run: ${{ steps.ctx.outputs.dry_run }}" + echo "- use-token: ${{ vars.USE_NPM_TOKEN == 'true' }}" diff --git a/.gitignore b/.gitignore index d9476c44e2..05192106d7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,10 @@ /dist /tmp /out-tsc +.ns-vite-build # dependencies **/node_modules -**/package-lock.json **/yarn.lock **/pnpm-lock.yaml .npmrc @@ -41,6 +41,7 @@ apps/**/*/*.js apps/**/*/*.map apps/**/*/platforms apps/**/*/webpack.*.js +apps/**/package-lock.json *.tgz .npmrc **/**/*.log @@ -55,3 +56,10 @@ Thumbs.db # types ios-typings-prj + +.nx/cache +.nx/workspace-data +vite.config.*.timestamp* +vitest.config.*.timestamp* +.cursor/rules/nx-rules.mdc +.github/instructions/nx.instructions.md diff --git a/.husky/pre-commit b/.husky/pre-commit index a3cebf1ec5..abf1181ca3 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,7 +1,4 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" -export NVM_DIR="$HOME/.nvm" -[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" - -npx lint-staged +npx lint-staged --allow-empty diff --git a/.prettierignore b/.prettierignore index 7cc93b890e..ad9f2caf8a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,6 +10,7 @@ e2e js-libs platforms tests +apps/**/*.xml packages/types-android packages/types-ios packages/types-minimal @@ -24,3 +25,6 @@ package.json *.scss *.sh !packages/webpack/templates/*.js + +/.nx/cache +/.nx/workspace-data \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 5acd7ae98f..88a01adfab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,11 @@ -// Place your settings in this file to overwrite default and user settings. { - "search.exclude": { - "**/node_modules": true, - "**/bower_components": true, - "**/platforms": true, - "**/*.js": true, - "**/*.js.map": true - } -} \ No newline at end of file + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "**/platforms": true, + "**/*.js": true, + "**/*.js.map": true + }, + "eslint.validate": ["json"], + "js/ts.tsdk.path": "./node_modules/typescript/lib" +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..12f9652790 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,70 @@ +# AGENTS.md - NativeScript + +This is the source code for NativeScript — `@nativescript/core` and related packages. This guide outlines standard practices for AI agents working in this repository. + +## Environment + +- Use `npm` for package management. First-time setup: `npm run setup` (cleans and installs). +- This is an [Nx](https://nx.dev) workspace. Run targets with `npx nx run :`, e.g. `npx nx run core:test`. +- `npm start` opens an interactive menu of every workspace command (type to filter, ENTER to run). Each entry maps to an Nx command you can also run directly. + +## Repository Layout + +- `packages/core` — `@nativescript/core`, the framework itself (UI, styling, application lifecycle, native bridging). +- `packages/webpack5` — `@nativescript/webpack` build tooling. +- `packages/vite` — `@nativescript/vite` build tooling. +- `packages/types-ios`, `packages/types-android`, `packages/types-minimal`, `packages/types` — native platform TypeScript declarations. +- `packages/ui-mobile-base` — native (Java/Objective-C) UI base components. +- `packages/winter-tc` — WinterTC (web-interoperable runtime) compliance. +- `apps/toolbox` — preferred playground for local development and debugging core. Simple; use this most often. +- `apps/automated` — automated e2e test suite that runs on a device/simulator. +- `apps/ui` — more sophisticated test app for UI scenarios. + +## Key Documentation + +- [Development Workflow](tools/notes/DevelopmentWorkflow.md): definitive guide for setup, running tests, and test apps. +- [Coding Conventions](tools/notes/CodingConvention.md): style guide (tabs width 2, single quotes, semicolons, same-line braces). +- [Code Comments](tools/notes/CodeComments.md): comment rules for all TypeScript edits (minimal, JSDoc on public API only). +- [Core Platform Modules](tools/notes/CorePlatformModules.md): required reading before touching `packages/core` modules (`.ios.ts`/`.android.ts` split, handwritten `.d.ts`). +- [Contributing / Commit Guidelines](tools/notes/CONTRIBUTING.md): commit message format used to generate changelogs. +- [Writing Unit Tests](tools/notes/WritingUnitTests.md): expectations for test coverage with changes. + +## Agent Skills + +Repository skills live in `.agent/skills//SKILL.md` — each is a workflow for one kind of task (feat, fix, refactor, ...), with YAML frontmatter (`name`, `description`). Before starting a task a skill's description covers, read that SKILL.md and follow it. (`.claude/skills` is a symlink to `.agent/skills` so Claude Code discovers them automatically.) + +- [feat](.agent/skills/feat/SKILL.md) — build a feature end-to-end (issue → plan → implement → test → PR). +- [fix](.agent/skills/fix/SKILL.md) — debug and fix a bug end-to-end (trace → root cause → fix → test → PR). +- [refactor](.agent/skills/refactor/SKILL.md) — restructure code without changing behavior. +- [unit-testing](.agent/skills/unit-testing/SKILL.md) — running and writing Vitest specs. + +Conventions and how-to knowledge live as docs in `tools/notes/` (see [Key Documentation](#key-documentation)) — the single source of truth. A skill references the docs it depends on through relative symlinks in its `references/` folder, created with: + +```bash +cd .agent/skills//references/ +ln -s ../../../../tools/notes/.md .md +``` + +To add a skill: create `.agent/skills//SKILL.md` with `name` and `description` frontmatter (the description states *when* to use it — that is what triggers loading), keep the body a short workflow that links docs via `./references/`, and list it here. When moving or renaming a doc, check for skill symlinks pointing at it: `grep -rl '' .agent/skills/*/references/`. + +## Platform-Specific Code + +`packages/core` uses filename suffixes (`foo.ios.ts` / `foo.android.ts`, shared `foo-common.ts`, handwritten `foo.d.ts`) to split implementations per platform; the bundler picks the right file at build time. Read [Core Platform Modules](tools/notes/CorePlatformModules.md) before touching any module there — the short version: keep both platform files in parity, and update the neighboring `.d.ts` whenever a public API changes. + +## Testing + +- Unit tests (Vitest) are colocated `*.spec.ts` files. Run with `npx nx run core:test`. + - Watch mode: `npx nx run core:test --watch` + - Single suite by describe name: `npx nx run core:test -t 'XmlParser'` +- Unit tests run in Node with NativeScript platform globals mocked in `packages/core/vitest.setup.ts` — they cannot exercise real native APIs. Behavior that touches iOS/Android at runtime is covered by the e2e suite: `npx nx run apps-automated:ios` or `npx nx run apps-automated:android` (requires a configured NativeScript environment with simulators/emulators). +- Prefer adding a unit test for logic changes; add or extend an `apps/automated` test for native runtime behavior. + +## Formatting & Commits + +- Prettier formats the workspace: `npx nx format:write` (also auto-runs on the pre-commit hook). +- Commit messages and PR titles follow the conventional format `type(scope): message` (e.g. `fix(core): ...`, `feat(ios): ...`) per the [commit guidelines](tools/notes/CONTRIBUTING.md#commit-messages). + +## Pull Requests + +- Use the `gh` CLI (GitHub CLI) for creating and managing pull requests. +- Follow the [PR template](.github/PULL_REQUEST_TEMPLATE.md): reference the related issue, ensure existing tests pass, and include tests for the change. diff --git a/README.md b/README.md index 1025e582fb..02b3bc6655 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,55 @@ -

- - NativeScript - -

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

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

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

Made with ❤️

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