diff --git a/.github/workflows/qunit_tests.yml b/.github/workflows/qunit_tests.yml index b019e242b351..9a9dfcea6e33 100644 --- a/.github/workflows/qunit_tests.yml +++ b/.github/workflows/qunit_tests.yml @@ -111,7 +111,7 @@ jobs: shell: bash env: DEVEXTREME_TEST_CI: "true" - run: pnpm exec nx build:systemjs + run: pnpm exec nx build:dev - name: Zip artifacts working-directory: ./packages/devextreme @@ -127,7 +127,7 @@ jobs: qunit-tests: needs: build - runs-on: devextreme-shr2 + runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} name: Constel ${{ matrix.CONSTEL }}${{ matrix.TIMEZONE != '' && format('-{0}', matrix.TIMEZONE) || '' }}${{ matrix.CSP == 'false' && '-no-csp' || '' }} timeout-minutes: 25 strategy: diff --git a/.github/workflows/repository-check.yml b/.github/workflows/repository-check.yml index 095674fa2d4e..2eae3e61ae00 100644 --- a/.github/workflows/repository-check.yml +++ b/.github/workflows/repository-check.yml @@ -8,9 +8,95 @@ on: branches: - main +permissions: {} + jobs: + collect-flaky-reports: + name: Collect flaky reports + runs-on: ubuntu-slim + permissions: + contents: read # checkout + actions: read # list workflow runs and download their artifacts + # Flaky collection must never break the repository check it feeds. + continue-on-error: true + outputs: + details: ${{ steps.collect.outputs.details }} + + steps: + - name: Get sources + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + + - name: Use Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + with: + node-version-file: '.node-version' + + - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 + with: + run_install: false + + - name: Install dependencies + run: pnpm install --frozen-lockfile --filter flaky-test-reporter... + + - name: Collect flaky test candidates + id: collect + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WINDOW_HOURS: 72 + run: | + # tee opens the log before the tool runs, so the directory must already exist. + mkdir -p "$GITHUB_WORKSPACE/artifacts" + report="$GITHUB_WORKSPACE/artifacts/flaky-tests-report.json" + markdown="$GITHUB_WORKSPACE/artifacts/flaky-tests-report.md" + log="$GITHUB_WORKSPACE/artifacts/collect.log" + + # --silent keeps pnpm's own banner out of the summary; 2>&1 keeps the tool's warnings in. + # tee stores the plain text for the summary, then sed turns the tool's warnings into + # GitHub annotations so they surface on the run page. + pnpm --silent --filter flaky-test-reporter run collect -- \ + --repo "${{ github.repository }}" \ + --workflow testcafe_tests.yml \ + --artifact flaky-candidates \ + --branch "${{ github.ref_name }}" \ + --window-hours "$WINDOW_HOURS" \ + --out-json "$report" \ + --out-markdown "$markdown" 2>&1 \ + | tee "$log" \ + | sed -e 's/^warning: /::warning::/' -e 's/^Collection failed: /::error::/' + + { + echo "## Flaky test candidates" + echo "" + echo '```' + cat "$log" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + # Multiline output form, so the markdown crosses the job boundary as-is. The delimiter + # must not occur in the content, which markdown headings and tables never produce. + { + echo 'details<> "$GITHUB_OUTPUT" + + - name: Upload flaky test report + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: flaky-tests-report + path: | + ${{ github.workspace }}/artifacts/flaky-tests-report.json + ${{ github.workspace }}/artifacts/collect.log + retention-days: 7 + if-no-files-found: ignore + invoke: name: Invoke + needs: [collect-flaky-reports] + permissions: + contents: read # the reusable workflow checks this repository out + if: ${{ !cancelled() }} uses: DevExpress/github-actions/.github/workflows/repository-check.yml@repository-check with: config: | @@ -20,3 +106,6 @@ jobs: 'GHSA-848j-6mx2-7j84', // elliptic ] } + # Markdown appended to this repo's monitor issue body. Empty when the collect job + # produced nothing, which makes the shared workflow skip publishing it. + extraDetails: ${{ needs.collect-flaky-reports.outputs.details }} diff --git a/.github/workflows/testcafe_tests.yml b/.github/workflows/testcafe_tests.yml index 1c7167fab625..432134804233 100644 --- a/.github/workflows/testcafe_tests.yml +++ b/.github/workflows/testcafe_tests.yml @@ -470,8 +470,15 @@ jobs: quarantined_failed=0 result=./artifacts/quarantine-run-result.json new_flakes_summary=./artifacts/new-flakes-summary.txt + new_flakes=./artifacts/flaky-candidates.jsonl + report_json=./artifacts/flaky-candidates.json + # base_ref is empty for push and merge_group, which carry the target elsewhere. + base_branch="${{ github.base_ref || github.event.merge_group.base_ref || github.ref_name }}" + base_branch="${base_branch#refs/heads/}" mkdir -p ./artifacts : > "$new_flakes_summary" + : > "$new_flakes" + rm -f "$report_json" # Every test gets up to 3 attempts (initial run + 2 retries) and passes # if at least one attempt succeeds. A reported failure means all attempts failed. @@ -549,12 +556,23 @@ jobs: while IFS= read -r name; do if jq -e --arg n "$name" '.tests | index($n)' "$result" > /dev/null; then echo "FAILED | $name | $job_name" >> "$new_flakes_summary" + verdict=failed else echo "passed | $name | $job_name" >> "$new_flakes_summary" + verdict=passed fi + jq -cn --arg test "$name" --arg sourceJob "$job_name" --arg theme "$theme" \ + --arg componentFolder "$componentFolder" --arg timezone "$timezone" \ + --arg platform "$platform" --arg verdict "$verdict" --arg baseBranch "$base_branch" \ + '{ test: $test, sourceJob: $sourceJob, theme: $theme, componentFolder: $componentFolder, timezone: $timezone, platform: $platform, verdict: $verdict, baseBranch: $baseBranch }' \ + >> "$new_flakes" done < <(jq -r '.tests[]' "$report") done + if [ -s "$new_flakes" ]; then + jq -s '{ schemaVersion: 1, candidates: . }' "$new_flakes" > "$report_json" + fi + sudo ln -sf /usr/share/zoneinfo/GMT /etc/localtime echo "" @@ -631,6 +649,15 @@ jobs: path: ${{ github.workspace }}/e2e/testcafe-devextreme/artifacts/failedtests/**/* if-no-files-found: ignore + - name: Upload flaky candidates report + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: flaky-candidates + path: ${{ github.workspace }}/e2e/testcafe-devextreme/artifacts/flaky-candidates.json + retention-days: 7 + if-no-files-found: ignore + merge-artifacts: runs-on: ubuntu-latest needs: [testcafe, testcafe-quarantine] diff --git a/.github/workflows/visual-tests-demos.yml b/.github/workflows/visual-tests-demos.yml index 8a5f3107baf4..6bf84c43dd7c 100644 --- a/.github/workflows/visual-tests-demos.yml +++ b/.github/workflows/visual-tests-demos.yml @@ -26,130 +26,13 @@ jobs: id: check run: echo "should-run=${{ env.RUN_TESTS }}" >> $GITHUB_OUTPUT - get-changes: - runs-on: ubuntu-latest - needs: check-should-run - if: github.event_name == 'pull_request' && needs.check-should-run.outputs.should-run == 'true' - name: Get changed demos - timeout-minutes: 5 - outputs: - has-changed-demos: ${{ steps.check-changes.outputs.has-changed-demos }} - - steps: - - name: Get sources - uses: actions/checkout@v6 - - - name: Get changed files - uses: DevExpress/github-actions/get-changed-files@get-changed-files - with: - gh-token: ${{ secrets.GITHUB_TOKEN }} - paths: 'apps/demos/Demos/**/*' - output: apps/demos/changed-files.json - - - name: Display changed files - id: check-changes - run: | - HAS_CHANGED="false" - if [ -f "apps/demos/changed-files.json" ]; then - DEMO_COUNT=$(jq length apps/demos/changed-files.json) - echo "Found changed-files.json" - echo "Content of changed-files.json:" - cat apps/demos/changed-files.json - echo "Number of changed files: $DEMO_COUNT" - if [ "$DEMO_COUNT" -gt 0 ]; then - HAS_CHANGED="true" - fi - else - echo "changed-files.json not found" - fi - echo "has-changed-demos=${HAS_CHANGED}" >> $GITHUB_OUTPUT - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: changed-demos - path: apps/demos/changed-files.json - retention-days: 1 - - determine-framework-tests-scope: - runs-on: ubuntu-slim - name: Determine scope for framework tests - needs: [check-should-run, get-changes] - if: | - !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - (needs.get-changes.result == 'success' || needs.get-changes.result == 'skipped') - outputs: - framework-tests-scope: ${{ steps.determine.outputs.framework-tests-scope }} - - steps: - - name: Determine framework tests scope - id: determine - run: | - if [ "${{ github.event_name }}" != "pull_request" ] || [ "${{ contains(github.event.pull_request.labels.*.name, 'force all tests') }}" = "true" ]; then - echo "Framework tests scope: all demos" - echo "framework-tests-scope=all" >> $GITHUB_OUTPUT - elif [ "${{ needs.get-changes.outputs.has-changed-demos }}" = "true" ]; then - echo "Framework tests scope: changed demos" - echo "framework-tests-scope=changed" >> $GITHUB_OUTPUT - else - echo "Framework tests NOT needed" - echo "framework-tests-scope=none" >> $GITHUB_OUTPUT - fi - - determine-jquery-test-matrix: - runs-on: ubuntu-slim - name: Determine jQuery test matrix - needs: [check-should-run, determine-framework-tests-scope] - if: | - !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' - outputs: - matrix: ${{ steps.matrix.outputs.matrix }} - - steps: - - name: Build matrix - id: matrix - run: | - base_matrix=$(cat <<'JSON' - [ - { "STRATEGY": "screenshots", "THEME": "material.blue.light", "CONSTEL": "jquery(1/3)" }, - { "STRATEGY": "screenshots", "THEME": "material.blue.light", "CONSTEL": "jquery(2/3)" }, - { "STRATEGY": "screenshots", "THEME": "material.blue.light", "CONSTEL": "jquery(3/3)" }, - { "STRATEGY": "screenshots", "THEME": "fluent.blue.light", "CONSTEL": "jquery(1/3)" }, - { "STRATEGY": "screenshots", "THEME": "fluent.blue.light", "CONSTEL": "jquery(2/3)" }, - { "STRATEGY": "screenshots", "THEME": "fluent.blue.light", "CONSTEL": "jquery(3/3)" }, - { "STRATEGY": "accessibility", "THEME": "material.blue.light", "CONSTEL": "jquery" }, - { "STRATEGY": "accessibility", "THEME": "fluent.blue.light", "CONSTEL": "jquery" } - ] - JSON - ) - - dark_accessibility_matrix=$(cat <<'JSON' - [ - { "STRATEGY": "accessibility", "THEME": "material.blue.dark", "CONSTEL": "jquery" }, - { "STRATEGY": "accessibility", "THEME": "fluent.blue.dark", "CONSTEL": "jquery" } - ] - JSON - ) - - matrix=$(jq -c -n \ - --argjson base "$base_matrix" \ - --argjson darkAccessibility "$dark_accessibility_matrix" \ - --arg frameworkTestsScope "${{ needs.determine-framework-tests-scope.outputs.framework-tests-scope }}" \ - '{ include: ($base + (if $frameworkTestsScope != "none" then $darkAccessibility else [] end)) }') - - echo "matrix=$matrix" >> $GITHUB_OUTPUT - build-devextreme: runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} name: Build DevExtreme - needs: [check-should-run, determine-framework-tests-scope] + needs: [check-should-run] if: | !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' + needs.check-should-run.outputs.should-run == 'true' env: NODE_OPTIONS: --max-old-space-size=8192 timeout-minutes: 30 @@ -228,15 +111,7 @@ jobs: ${{ runner.os }}-nx-v2-${{ github.workflow }}-${{ github.job }}- ${{ runner.os }}-nx-v2- - - name: DevExtreme - Build - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'none' - shell: bash - run: | - pnpm exec nx build devextreme-scss - pnpm exec nx build devextreme -c testing - - name: DevExtreme - Build-all - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' run: pnpm nx all:build-testing workflows - name: Zip artifacts (for jQuery tests) @@ -252,7 +127,6 @@ jobs: retention-days: 1 - name: Move packages - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' run: | mv ./packages/devextreme/artifacts/npm/devextreme/*.tgz ./devextreme-installer.tgz mv ./packages/devextreme/artifacts/npm/devextreme-dist/*.tgz ./devextreme-dist-installer.tgz @@ -261,7 +135,6 @@ jobs: mv ./packages/devextreme-vue/npm/*.tgz ./devextreme-vue-installer.tgz - name: Copy build artifacts - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' uses: actions/upload-artifact@v7 with: name: devextreme-sources @@ -276,12 +149,10 @@ jobs: name: Build vendor bundles (React, Vue) runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} timeout-minutes: 15 - needs: [check-should-run, determine-framework-tests-scope, build-devextreme] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && needs.build-devextreme.result == 'success' env: NODE_OPTIONS: --max-old-space-size=8192 @@ -337,12 +208,10 @@ jobs: name: ${{ matrix.SHARD_TOTAL == 1 && format('Build demos ({0})', matrix.FRAMEWORK) || format('Build demos ({0} {1}/{2})', matrix.FRAMEWORK, matrix.SHARD_INDEX, matrix.SHARD_TOTAL) }} runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} timeout-minutes: 30 - needs: [check-should-run, determine-framework-tests-scope, build-devextreme, build-vendor-bundles] + needs: [check-should-run, build-devextreme, build-vendor-bundles] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && needs.build-devextreme.result == 'success' && needs.build-vendor-bundles.result == 'success' env: @@ -437,12 +306,10 @@ jobs: lint: name: Check TS and lint code base - needs: [check-should-run, determine-framework-tests-scope, build-devextreme] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && needs.build-devextreme.result == 'success' runs-on: ubuntu-latest @@ -452,14 +319,6 @@ jobs: - name: Get sources uses: actions/checkout@v6 - - name: Download artifacts - if: github.event_name == 'pull_request' - uses: actions/download-artifact@v8 - with: - name: changed-demos - path: apps/demos - continue-on-error: true - - name: Download devextreme sources uses: actions/download-artifact@v8 with: @@ -512,7 +371,6 @@ jobs: - name: Run lint on all demos id: lint_all - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'all' continue-on-error: true working-directory: apps/demos env: @@ -520,51 +378,19 @@ jobs: NODE_OPTIONS: --max-old-space-size=8192 run: pnpm exec nx lint - - name: Run lint on changed demos - id: lint_changed - if: needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'changed' - continue-on-error: true - working-directory: apps/demos - env: - DEBUG: 'eslint:cli-engine,stylelint:standalone' - run: | - pnpm exec nx lint-non-demos - - if [ -f "changed-files.json" ]; then - echo "Running lint-demos on changed files" - CHANGED_DEMOS=$(jq -r '.[].filename' changed-files.json \ - | grep '^apps/demos/Demos/' \ - | sed 's|^apps/demos/||' \ - | while read f; do - [ -f "$f" ] && echo "$f" - done \ - | tr '\n' ' ') - if [ ! -z "$CHANGED_DEMOS" ]; then - echo "Changed demo files: $CHANGED_DEMOS" - pnpm run eslint $CHANGED_DEMOS - else - echo "No demo files changed, skipping lint-demos" - fi - else - echo "changed-files.json not found" - pnpm run lint-demos - fi - - name: Check TS and lint results if: always() env: TS_VUE_OUTCOME: ${{ steps.ts_vue.outcome }} TS_REACT_OUTCOME: ${{ steps.ts_react.outcome }} LINT_ALL_OUTCOME: ${{ steps.lint_all.outcome }} - LINT_CHANGED_OUTCOME: ${{ steps.lint_changed.outcome }} run: | failed=0 for check in \ "Demos - Check Vue TS:$TS_VUE_OUTCOME" \ "Demos - Check React TS:$TS_REACT_OUTCOME" \ - "Run lint on all demos:$LINT_ALL_OUTCOME" \ - "Run lint on changed demos:$LINT_CHANGED_OUTCOME"; do + "Run lint on all demos:$LINT_ALL_OUTCOME"; do name="${check%%:*}" outcome="${check#*:}" @@ -576,124 +402,14 @@ jobs: exit "$failed" - check-generated-demos-changed: - name: Check generated demos (changed only) - runs-on: devextreme-shr2 - timeout-minutes: 20 - needs: [check-should-run, get-changes, build-devextreme, determine-framework-tests-scope] - if: | - !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'changed' && - needs.build-devextreme.result == 'success' - - steps: - - name: Get sources - uses: actions/checkout@v6 - - - name: Download changed demos - uses: actions/download-artifact@v8 - with: - name: changed-demos - path: apps/demos - continue-on-error: true - - - name: Detect changed React TS demos - id: changed-react-demos - working-directory: apps/demos - run: | - if [ ! -f "changed-files.json" ]; then - echo "changed-files.json not found, skipping generated JS demos check" - echo "has-react-demos=false" >> $GITHUB_OUTPUT - exit 0 - fi - - jq -r '.[].filename' changed-files.json \ - | grep '/React/' \ - | grep -E '\.tsx?$' \ - | sed 's|^apps/demos/||' \ - | sed -E 's|/[^/]*\.tsx?$||' \ - | sort \ - | uniq > changed-react-demos.txt || true - - if [ -s changed-react-demos.txt ]; then - echo "Changed React demos:" - cat changed-react-demos.txt - echo "has-react-demos=true" >> $GITHUB_OUTPUT - else - echo "No React demos found in changed files, skipping conversion" - echo "has-react-demos=false" >> $GITHUB_OUTPUT - fi - - - uses: pnpm/action-setup@v6 - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - with: - run_install: false - - - name: Use Node.js - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - uses: actions/setup-node@v6 - with: - node-version-file: '.node-version' - - - name: Download devextreme sources - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - uses: actions/download-artifact@v8 - with: - name: devextreme-sources - - - name: Get pnpm store directory - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache/restore@v5 - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - name: Restore pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-cache - - - name: Install dependencies - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - run: pnpm install --frozen-lockfile - - - name: Install tgz - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - working-directory: apps/demos - run: pnpm add --ignore-workspace --allow-build=core-js --allow-build=inferno devextreme-aspnet-data@5.1.0 devextreme-aspnet-data-nojquery@5.1.0 ../../devextreme-installer.tgz ../../devextreme-dist-installer.tgz ../../devextreme-react-installer.tgz ../../devextreme-vue-installer.tgz ../../devextreme-angular-installer.tgz && rm -f pnpm-workspace.yaml pnpm-lock.yaml - - - name: Check generated JS demos - if: steps.changed-react-demos.outputs.has-react-demos == 'true' - working-directory: apps/demos - run: | - echo "Running convert-to-js on changed files only" - xargs -r pnpm run convert-to-js < changed-react-demos.txt - - git add ./Demos -N - - if git diff --exit-code . ':!package.json' ; then - echo "Generated JS demos are up-to-date" - else - echo "Generated JS demos are outdated. Execute 'pnpm run convert-to-js split' and commit changes." - echo "If you see another diff, ensure that extra listed files have LF endings." - exit 1 - fi - check-generated-demos-all: name: Check generated demos runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} timeout-minutes: 20 - needs: [check-should-run, build-devextreme, determine-framework-tests-scope] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'all' && needs.build-devextreme.result == 'success' steps: @@ -751,16 +467,25 @@ jobs: fi testcafe-jquery: - needs: [check-should-run, build-devextreme, determine-framework-tests-scope, determine-jquery-test-matrix] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-jquery-test-matrix.result == 'success' && needs.build-devextreme.result == 'success' strategy: fail-fast: false - matrix: ${{ fromJson(needs.determine-jquery-test-matrix.outputs.matrix) }} + matrix: + include: + - { STRATEGY: screenshots, THEME: material.blue.light, CONSTEL: jquery(1/3) } + - { STRATEGY: screenshots, THEME: material.blue.light, CONSTEL: jquery(2/3) } + - { STRATEGY: screenshots, THEME: material.blue.light, CONSTEL: jquery(3/3) } + - { STRATEGY: screenshots, THEME: fluent.blue.light, CONSTEL: jquery(1/3) } + - { STRATEGY: screenshots, THEME: fluent.blue.light, CONSTEL: jquery(2/3) } + - { STRATEGY: screenshots, THEME: fluent.blue.light, CONSTEL: jquery(3/3) } + - { STRATEGY: accessibility, THEME: material.blue.light, CONSTEL: jquery } + - { STRATEGY: accessibility, THEME: fluent.blue.light, CONSTEL: jquery } + - { STRATEGY: accessibility, THEME: material.blue.dark, CONSTEL: jquery } + - { STRATEGY: accessibility, THEME: fluent.blue.dark, CONSTEL: jquery } env: ACCESSIBILITY_TESTCAFE_REPORT_PATH: "accessibility_testcafe_report" @@ -909,12 +634,10 @@ jobs: if-no-files-found: ignore testcafe-frameworks-all: - needs: [check-should-run, determine-framework-tests-scope, build-demos, build-vendor-bundles] + needs: [check-should-run, build-demos, build-vendor-bundles] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'all' && needs.build-demos.result == 'success' strategy: fail-fast: false @@ -1096,176 +819,10 @@ jobs: path: ${{ github.workspace }}/apps/demos/testing/artifacts/compared-screenshots/**/* if-no-files-found: ignore - testcafe-frameworks-changed: - needs: [check-should-run, determine-framework-tests-scope, build-demos, build-vendor-bundles] - if: | - !cancelled() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'changed' && - needs.build-demos.result == 'success' - strategy: - fail-fast: false - matrix: - CONSTEL: [react, vue, angular] - THEME: ['fluent.blue.light'] - - runs-on: ${{ github.event_name == 'merge_group' && 'devextreme-shr2-ondemand' || 'devextreme-shr2' }} - name: ${{ matrix.CONSTEL }}-screenshots-${{ matrix.THEME }} - timeout-minutes: 60 - - steps: - - name: Get sources - uses: actions/checkout@v6 - - - name: Setup Chrome - uses: ./.github/actions/setup-chrome - with: - chrome-version: '150.0.7871.181' - - - name: Use Node.js - uses: actions/setup-node@v6 - with: - node-version-file: '.node-version' - - - name: Download devextreme sources - uses: actions/download-artifact@v8 - with: - name: devextreme-sources - - - name: Download changed demos - uses: actions/download-artifact@v8 - with: - name: changed-demos - path: apps/demos - continue-on-error: true - - - uses: pnpm/action-setup@v6 - with: - run_install: false - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache/restore@v5 - name: Restore pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-cache-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-cache - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Install tgz - working-directory: apps/demos - run: pnpm add --ignore-workspace --allow-build=core-js --allow-build=inferno devextreme-aspnet-data@5.1.0 devextreme-aspnet-data-nojquery@5.1.0 ../../devextreme-installer.tgz ../../devextreme-dist-installer.tgz ../../devextreme-react-installer.tgz ../../devextreme-vue-installer.tgz ../../devextreme-angular-installer.tgz && rm -f pnpm-workspace.yaml pnpm-lock.yaml - - - name: Download demo bundles (React) - if: matrix.CONSTEL == 'react' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-React - path: apps/demos/.demo-bundles/react - - - name: Download demo bundles (Vue) - if: matrix.CONSTEL == 'vue' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-Vue - path: apps/demos/.demo-bundles/vue - - # build-demos shards Angular 3 ways regardless of how it's tested here. - - name: Download demo bundles (Angular shard 1) - if: matrix.CONSTEL == 'angular' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-Angular-shard1 - path: apps/demos/.demo-bundles/angular-shard1 - - - name: Download demo bundles (Angular shard 2) - if: matrix.CONSTEL == 'angular' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-Angular-shard2 - path: apps/demos/.demo-bundles/angular-shard2 - - - name: Download demo bundles (Angular shard 3) - if: matrix.CONSTEL == 'angular' - uses: actions/download-artifact@v8 - with: - name: devextreme-bundles-Angular-shard3 - path: apps/demos/.demo-bundles/angular-shard3 - - - name: Download changes artifacts - uses: actions/download-artifact@v8 - with: - name: changed-demos - path: apps/demos - - # No-op for angular demos (no manifest — see build-vendor-bundles.js). - - name: Download vendor bundles - uses: actions/download-artifact@v8 - with: - name: devextreme-vendor-bundles - path: apps/demos/bundles/vendor - - - name: Unpack demo bundles - run: | - shopt -s nullglob - archives=(apps/demos/.demo-bundles/*/demo-bundles.tgz) - if [ ${#archives[@]} -eq 0 ]; then - echo "No demo bundle archives downloaded for constellation '${{ matrix.CONSTEL }}'" >&2 - exit 1 - fi - for archive in "${archives[@]}"; do - tar -xzf "$archive" -C apps/demos - done - rm -rf apps/demos/.demo-bundles - echo "Unpacked $(find apps/demos/Demos -name 'bundle.js' | wc -l) demo bundle(s)" - - - name: Run Web Server - run: | - python -m http.server 8080 & - python -m http.server 8081 & - python -m http.server 8082 & - python -m http.server 8083 & - - - name: Run TestCafe tests - shell: bash - working-directory: apps/demos - env: - CHANGEDFILEINFOSPATH: changed-files.json - BROWSERS: chrome:headless --window-size=1200,800 --disable-gpu --no-sandbox --disable-dev-shm-usage --disable-partial-raster --disable-skia-runtime-opts --run-all-compositor-stages-before-draw --disable-new-content-rendering-timeout --disable-threaded-animation --disable-threaded-scrolling --disable-checker-imaging --disable-image-animation-resync --use-gl=swiftshader --disable-features=PaintHolding --js-flags=--random-seed=2147483647 --font-render-hinting=none --disable-font-subpixel-positioning - # DEBUG: hammerhead:*,testcafe:* - CONCURRENCY: 1 - CONSTEL: ${{ matrix.CONSTEL }} - THEME: ${{ matrix.THEME }} - # DISABLE_DEMO_TEST_SETTINGS: all # Uncomment to ignore all the visualtestrc.json settings - # DISABLE_DEMO_TEST_SETTINGS: ignore # Uncomment to ignore the `ignore` field - # DISABLE_DEMO_TEST_SETTINGS: comparison-options # Uncomment to ignore the `comparison-options` field - CI_ENV: true # The `ignore` field in the visualtestrc.json should be disabled when running test locally - run: pnpm exec nx test-testcafe - - - name: Sanitize job name - if: ${{ failure() }} - run: echo "JOB_NAME=$(echo "${{ matrix.CONSTEL }}-${{ matrix.THEME }}" | tr '/' '-')" >> $GITHUB_ENV - - - name: Copy screenshots artifacts - if: ${{ failure() }} - uses: actions/upload-artifact@v7 - with: - name: screenshots-${{ env.JOB_NAME }} - path: ${{ github.workspace }}/apps/demos/testing/artifacts/compared-screenshots/**/* - if-no-files-found: ignore - merge-artifacts: runs-on: ubuntu-latest - needs: [testcafe-jquery, testcafe-frameworks-all, testcafe-frameworks-changed] - if: ${{ !cancelled() && (needs.testcafe-jquery.result == 'failure' || needs.testcafe-frameworks-all.result == 'failure' || needs.testcafe-frameworks-changed.result == 'failure') }} + needs: [testcafe-jquery, testcafe-frameworks-all] + if: ${{ !cancelled() && (needs.testcafe-jquery.result == 'failure' || needs.testcafe-frameworks-all.result == 'failure') }} steps: - name: Merge jQuery screenshot artifacts @@ -1278,7 +835,7 @@ jobs: delete-merged: true - name: Merge React screenshot artifacts - if: needs.testcafe-frameworks-all.result == 'failure' || needs.testcafe-frameworks-changed.result == 'failure' + if: needs.testcafe-frameworks-all.result == 'failure' uses: actions/upload-artifact/merge@v7 continue-on-error: true with: @@ -1287,7 +844,7 @@ jobs: delete-merged: true - name: Merge Vue screenshot artifacts - if: needs.testcafe-frameworks-all.result == 'failure' || needs.testcafe-frameworks-changed.result == 'failure' + if: needs.testcafe-frameworks-all.result == 'failure' uses: actions/upload-artifact/merge@v7 continue-on-error: true with: @@ -1296,7 +853,7 @@ jobs: delete-merged: true - name: Merge Angular screenshot artifacts - if: needs.testcafe-frameworks-all.result == 'failure' || needs.testcafe-frameworks-changed.result == 'failure' + if: needs.testcafe-frameworks-all.result == 'failure' uses: actions/upload-artifact/merge@v7 continue-on-error: true with: @@ -1386,12 +943,10 @@ jobs: csp-check-frameworks: name: ${{ matrix.SHARD_TOTAL == 1 && format('CSP check ({0})', matrix.FRAMEWORK) || format('CSP check ({0} {1}/{2})', matrix.FRAMEWORK, matrix.SHARD_INDEX, matrix.SHARD_TOTAL) }} - needs: [check-should-run, determine-framework-tests-scope, build-devextreme] + needs: [check-should-run, build-devextreme] if: | !cancelled() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && needs.build-devextreme.result == 'success' strategy: fail-fast: false diff --git a/apps/demos/Demos/Chat/PromptSuggestions/Angular/app/app.component.ts b/apps/demos/Demos/Chat/PromptSuggestions/Angular/app/app.component.ts index 9f82aa07c888..5bc0070c4992 100644 --- a/apps/demos/Demos/Chat/PromptSuggestions/Angular/app/app.component.ts +++ b/apps/demos/Demos/Chat/PromptSuggestions/Angular/app/app.component.ts @@ -1,5 +1,7 @@ import { bootstrapApplication } from '@angular/platform-browser'; -import { Component, enableProdMode, provideZoneChangeDetection } from '@angular/core'; +import { + ChangeDetectorRef, Component, enableProdMode, provideZoneChangeDetection, +} from '@angular/core'; import { AsyncPipe } from '@angular/common'; import { DxChatModule, DxSwitchModule } from 'devextreme-angular'; import type { DxChatTypes } from 'devextreme-angular/ui/chat'; @@ -43,7 +45,10 @@ export class AppComponent { suggestions: DxChatTypes.Properties['suggestions']; - constructor(private readonly appService: AppService) { + constructor( + private readonly appService: AppService, + private readonly changeDetectorRef: ChangeDetectorRef, + ) { loadMessages(this.appService.getDictionary()); this.dataSource = this.appService.dataSource; @@ -117,6 +122,7 @@ export class AppComponent { } } else { this.inputFieldText = prompt; + this.changeDetectorRef.detectChanges(); } } diff --git a/apps/demos/Demos/HtmlEditor/AITextEditing/Angular/app/app.service.ts b/apps/demos/Demos/HtmlEditor/AITextEditing/Angular/app/app.service.ts index f04866495dcc..7c99ac1335d9 100644 --- a/apps/demos/Demos/HtmlEditor/AITextEditing/Angular/app/app.service.ts +++ b/apps/demos/Demos/HtmlEditor/AITextEditing/Angular/app/app.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { type DxHtmlEditorTypes } from 'devextreme-angular/ui/html_editor'; +import { type DxHtmlEditorTypes } from 'devextreme-angular/ui/html-editor'; const extractKeywordsPrompt: DxHtmlEditorTypes.AICustomCommand['prompt'] = () => 'Extract a list of keywords from the text and return it as a comma-separated string'; diff --git a/apps/demos/Demos/PivotGrid/StandaloneFieldChooser/Angular/app/app.component.ts b/apps/demos/Demos/PivotGrid/StandaloneFieldChooser/Angular/app/app.component.ts index 9b0bcfb6a7c5..1079a7780bf7 100644 --- a/apps/demos/Demos/PivotGrid/StandaloneFieldChooser/Angular/app/app.component.ts +++ b/apps/demos/Demos/PivotGrid/StandaloneFieldChooser/Angular/app/app.component.ts @@ -1,20 +1,111 @@ import { bootstrapApplication } from '@angular/platform-browser'; -import { Component } from '@angular/core'; - -// TODO: restore the original demo (see git history for this file, app.component.html, -// app.component.css, app.service.ts). Disabled because combining DxPivotGridModule and -// DxPivotGridFieldChooserModule here reproducibly triggers, in CI only (not locally, likely a -// devextreme-angular artifacts staleness difference between environments): -// NG8023: Multiple components match node with tagname dxo-pivot-grid-field-chooser-texts: -// 'DxoPivotGridFieldChooserTextsComponent', 'DxoPivotGridFieldChooserTextsComponent'. -// Both modules generate their own nested-options component for that tag name, and combining -// them in one standalone component's imports puts both in scope at once. Needs a real fix in -// devextreme-angular's generated wrappers (or dropping one of the two imports if one turns out -// to be redundant here) rather than a demo-side workaround. +import { Component, enableProdMode, provideZoneChangeDetection } from '@angular/core'; +import { + DxRadioGroupComponent, + DxButtonComponent, + DxSelectBoxComponent, +} from 'devextreme-angular'; +import PivotGridDataSource from 'devextreme/ui/pivot_grid/data_source'; +import { + DxPivotGridComponent, + DxoPivotGridFieldChooserComponent, + DxPivotGridTypes, +} from 'devextreme-angular/ui/pivot-grid'; +import { + DxPivotGridFieldChooserComponent, + DxoPivotGridFieldChooserTextsComponent, +} from 'devextreme-angular/ui/pivot-grid-field-chooser'; +import { Service, Layout, Sale } from './app.service'; + +if (!/localhost/.test(document.location.host)) { + enableProdMode(); +} + @Component({ selector: 'demo-app', - template: '

This demo is temporarily disabled. See the TODO in app.component.ts.

', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'], + providers: [Service], + preserveWhitespaces: true, + imports: [ + DxPivotGridComponent, + DxoPivotGridFieldChooserComponent, + DxRadioGroupComponent, + DxPivotGridFieldChooserComponent, + DxoPivotGridFieldChooserTextsComponent, + DxSelectBoxComponent, + DxButtonComponent, + ], }) -export class AppComponent {} +export class AppComponent { + pivotGridDataSource: PivotGridDataSource; + + layouts: Layout[]; + + layout = 0; + + applyChangesModes: DxPivotGridTypes.ApplyChangesMode[] = ['instantly', 'onDemand']; + + applyChangesMode = this.applyChangesModes[0]; + + state: Record; + + constructor(service: Service) { + this.pivotGridDataSource = new PivotGridDataSource({ + fields: [{ + caption: 'Region', + width: 120, + dataField: 'region', + area: 'row', + headerFilter: { + search: { + enabled: true, + }, + }, + }, { + caption: 'City', + dataField: 'city', + width: 150, + area: 'row', + headerFilter: { + search: { + enabled: true, + }, + }, + selector(data: Sale) { + return `${data.city} (${data.country})`; + }, + }, { + dataField: 'date', + dataType: 'date', + area: 'column', + }, { + caption: 'Sales', + dataField: 'amount', + dataType: 'number', + summaryType: 'sum', + format: 'currency', + area: 'data', + }], + store: service.getSales(), + }); + + this.state = this.pivotGridDataSource.state(); + + this.layouts = service.getLayouts(); + } + + applyClick() { + this.pivotGridDataSource.state(this.state); + } + + cancelClick() { + this.state = this.pivotGridDataSource.state(); + } +} -bootstrapApplication(AppComponent); +bootstrapApplication(AppComponent, { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true, runCoalescing: true }), + ], +}); diff --git a/apps/demos/Demos/Stepper/StepTemplate/Angular/app/app.component.html b/apps/demos/Demos/Stepper/StepTemplate/Angular/app/app.component.html index 0128c7d521dd..e1c08fa86bc7 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/Angular/app/app.component.html +++ b/apps/demos/Demos/Stepper/StepTemplate/Angular/app/app.component.html @@ -41,6 +41,11 @@ [elementAttr]="{ 'aria-labelledby': 'label-iconOnly' }" >
- +
diff --git a/apps/demos/Demos/Stepper/StepTemplate/React/IconOnly.tsx b/apps/demos/Demos/Stepper/StepTemplate/React/IconOnly.tsx index d84e95907c85..bb0e60634491 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/React/IconOnly.tsx +++ b/apps/demos/Demos/Stepper/StepTemplate/React/IconOnly.tsx @@ -2,10 +2,11 @@ import React from 'react'; interface IconOnlyProps { icon: string; + label: string; } -export default function IconOnly({ icon }: IconOnlyProps) { +export default function IconOnly({ icon, label }: IconOnlyProps) { return ( - + ); } diff --git a/apps/demos/Demos/Stepper/StepTemplate/ReactJs/IconOnly.js b/apps/demos/Demos/Stepper/StepTemplate/ReactJs/IconOnly.js index 1344ad909c1d..466eafa507af 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/ReactJs/IconOnly.js +++ b/apps/demos/Demos/Stepper/StepTemplate/ReactJs/IconOnly.js @@ -1,5 +1,11 @@ import React from 'react'; -export default function IconOnly({ icon }) { - return ; +export default function IconOnly({ icon, label }) { + return ( + + ); } diff --git a/apps/demos/Demos/Stepper/StepTemplate/Vue/App.vue b/apps/demos/Demos/Stepper/StepTemplate/Vue/App.vue index a0a8ea0375a2..2fd89b68b2c9 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/Vue/App.vue +++ b/apps/demos/Demos/Stepper/StepTemplate/Vue/App.vue @@ -57,7 +57,11 @@ > diff --git a/apps/demos/Demos/Stepper/StepTemplate/jQuery/index.js b/apps/demos/Demos/Stepper/StepTemplate/jQuery/index.js index fd5945874d17..a80ea8666056 100644 --- a/apps/demos/Demos/Stepper/StepTemplate/jQuery/index.js +++ b/apps/demos/Demos/Stepper/StepTemplate/jQuery/index.js @@ -33,7 +33,9 @@ $(() => { linear: false, elementAttr: { 'aria-labelledby': 'label-iconOnly' }, itemTemplate(data) { - return ``; + return $('') + .addClass(`dx-icon dx-icon-${data.icon}`) + .attr({ role: 'img', 'aria-label': data.label }); }, }); }); diff --git a/apps/demos/Demos/TabPanel/Overview/Angular/app/app.component.css b/apps/demos/Demos/TabPanel/Overview/Angular/app/app.component.css index 046af40d3171..320a5f6dc23e 100644 --- a/apps/demos/Demos/TabPanel/Overview/Angular/app/app.component.css +++ b/apps/demos/Demos/TabPanel/Overview/Angular/app/app.component.css @@ -104,7 +104,7 @@ ::ng-deep .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } ::ng-deep .task-item-pseudo-button { diff --git a/apps/demos/Demos/TabPanel/Overview/React/styles.css b/apps/demos/Demos/TabPanel/Overview/React/styles.css index 7d2c7f569c05..71ad3ea17b02 100644 --- a/apps/demos/Demos/TabPanel/Overview/React/styles.css +++ b/apps/demos/Demos/TabPanel/Overview/React/styles.css @@ -104,7 +104,7 @@ .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } .task-item-pseudo-button { diff --git a/apps/demos/Demos/TabPanel/Overview/ReactJs/styles.css b/apps/demos/Demos/TabPanel/Overview/ReactJs/styles.css index 7d2c7f569c05..71ad3ea17b02 100644 --- a/apps/demos/Demos/TabPanel/Overview/ReactJs/styles.css +++ b/apps/demos/Demos/TabPanel/Overview/ReactJs/styles.css @@ -104,7 +104,7 @@ .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } .task-item-pseudo-button { diff --git a/apps/demos/Demos/TabPanel/Overview/Vue/TaskItem.vue b/apps/demos/Demos/TabPanel/Overview/Vue/TaskItem.vue index bfe6973f9762..76699ee28f0d 100644 --- a/apps/demos/Demos/TabPanel/Overview/Vue/TaskItem.vue +++ b/apps/demos/Demos/TabPanel/Overview/Vue/TaskItem.vue @@ -66,7 +66,7 @@ withDefaults(defineProps<{ .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } .task-item-pseudo-button { diff --git a/apps/demos/Demos/TabPanel/Overview/jQuery/styles.css b/apps/demos/Demos/TabPanel/Overview/jQuery/styles.css index 7d2c7f569c05..71ad3ea17b02 100644 --- a/apps/demos/Demos/TabPanel/Overview/jQuery/styles.css +++ b/apps/demos/Demos/TabPanel/Overview/jQuery/styles.css @@ -104,7 +104,7 @@ .task-item-info { font-size: 12px; - opacity: 0.6; + opacity: 0.65; } .task-item-pseudo-button { diff --git a/apps/demos/README.md b/apps/demos/README.md index 0fb102135541..07b8c7706398 100644 --- a/apps/demos/README.md +++ b/apps/demos/README.md @@ -28,17 +28,6 @@ You can pass a port as an extra argument. That is useful when you need to switch Angular, React, and Vue demos are bundled on demand when you open a page. jQuery demos load `dx.all.js` from `devextreme-dist`. -### Demo render signal - -Angular, React, and Vue demos are not bundled from their own entry point directly. `utils/server/demo-render-signal.js` generates a shim that becomes the bundle's entry point; the shim waits for `themes.initialized()` from `devextreme/ui/themes`, then imports the demo's entry (`index.tsx` / `index.ts` / `app/app.component.ts`), so nothing mounts before the theme CSS is applied. - -Once the demo has rendered, the shim posts one message to the embedding page: - -```js -window.parent.postMessage({ type: 'demo-rendered' }, '*'); -``` - - ### Before Commiting Changes Auto-fix lint errors: diff --git a/apps/demos/testing/common.test.ts b/apps/demos/testing/common.test.ts index d318a8b6cde1..192deec511ad 100644 --- a/apps/demos/testing/common.test.ts +++ b/apps/demos/testing/common.test.ts @@ -119,6 +119,15 @@ const getIgnoredRules = (testName) => { ]; }; +// The appointment title and date do not reach the AA contrast ratio: the date line is dimmed +// to 70% opacity and the appointments are painted with the resource colors. +const getAxeContext = (testName) => (testName.startsWith('Scheduler-') + ? { + include: ['.demo-container'], + exclude: ['.dx-scheduler-appointment-title', '.dx-scheduler-appointment-content-date'], + } + : '.demo-container'); + const getClientScripts = (approach: string) => { const scripts = [ { module: 'mockdate' }, @@ -250,7 +259,7 @@ Object.values(FRAMEWORKS).forEach((approach) => { options.rules[ruleName] = { enabled: false }; }); - const axeResult = await axeCheck(t, '.demo-container', options); + const axeResult = await axeCheck(t, getAxeContext(testName), options); const { error, results } = axeResult; if (results.violations.length > 0) { diff --git a/apps/demos/testing/skipped-tests.js b/apps/demos/testing/skipped-tests.js index 52bd9f86578d..cdd9e5ce7dcf 100644 --- a/apps/demos/testing/skipped-tests.js +++ b/apps/demos/testing/skipped-tests.js @@ -6,8 +6,6 @@ export const skippedTests = { Angular: { // WebGL initialization error at the provider. There is no point in adding a mask Map: ['ProvidersAndTypes', 'Markers', 'Routes'], - // Demo is stubbed out (see the TODO in its app.component.ts) pending a devextreme-angular fix - PivotGrid: ['StandaloneFieldChooser'], }, React: { // WebGL initialization error at the provider. There is no point in adding a mask diff --git a/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (fluent.blue.light)_mask.png b/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (fluent.blue.light)_mask.png new file mode 100644 index 000000000000..e4f261f4139e Binary files /dev/null and b/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (fluent.blue.light)_mask.png differ diff --git a/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light).png b/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light).png index ef926159c9c3..410371c30851 100644 Binary files a/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light).png and b/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light).png differ diff --git a/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light)_mask.png b/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light)_mask.png new file mode 100644 index 000000000000..bf66555a7025 Binary files /dev/null and b/apps/demos/testing/widgets/scheduler/etalons/scheduler_ResolveTimeConflicts_appointment-popup_recurrence-group_conflict-informer (material.blue.light)_mask.png differ diff --git a/apps/demos/utils/server/csp-bundle-angular.js b/apps/demos/utils/server/csp-bundle-angular.js index b6104c09749d..e28b2b36ad53 100644 --- a/apps/demos/utils/server/csp-bundle-angular.js +++ b/apps/demos/utils/server/csp-bundle-angular.js @@ -179,11 +179,6 @@ function findAngularEntry(srcDir) { return null; } -// TODO: remove once PivotGrid/StandaloneFieldChooser/Angular renders reliably (times out in csp-check.js) -const SKIPPED_DEMOS = new Set([ - 'PivotGrid/StandaloneFieldChooser/Angular', -]); - function findDemos() { const out = []; if (!fs.existsSync(SRC_DEMOS_DIR)) return out; @@ -194,7 +189,6 @@ function findDemos() { const demos = fs.readdirSync(widgetDir, { withFileTypes: true }).filter((d) => d.isDirectory()); for (const demo of demos) { const key = `${widget.name}/${demo.name}`; - if (SKIPPED_DEMOS.has(`${key}/${FRAMEWORK}`)) continue; // eslint-disable-line no-continue const fwDir = path.join(widgetDir, demo.name, FRAMEWORK); const matchesFilter = !FILTER || key.includes(FILTER); if (matchesFilter && fs.existsSync(path.join(fwDir, 'index.html'))) { diff --git a/apps/demos/utils/server/csp-check.js b/apps/demos/utils/server/csp-check.js index b3a8afdfac4a..0e19d27275fa 100644 --- a/apps/demos/utils/server/csp-check.js +++ b/apps/demos/utils/server/csp-check.js @@ -176,11 +176,6 @@ function applyShard(demos) { return applyShardGeneric(demos, (d) => `${d.widget}/${d.demo}`, { SHARD_TOTAL, SHARD_INDEX }); } -// TODO: remove once PivotGrid/StandaloneFieldChooser/Angular renders reliably (times out here) -const SKIPPED_DEMOS = new Set([ - 'PivotGrid/StandaloneFieldChooser/Angular', -]); - function findDemos() { const demosDirName = 'Demos'; const demosDir = join(DEMO_ROOT, demosDirName); @@ -202,7 +197,6 @@ function findDemos() { .filter((d) => d.isDirectory()); for (const demo of demos) { - if (SKIPPED_DEMOS.has(`${widget.name}/${demo.name}/${FRAMEWORK}`)) continue; // eslint-disable-line no-continue const fwDir = join(widgetDir, demo.name, FRAMEWORK); if (existsSync(join(fwDir, 'index.html'))) { result.push({ diff --git a/apps/demos/utils/server/demo-render-signal.js b/apps/demos/utils/server/demo-render-signal.js index 0e2f05cbc79d..8466c9631057 100644 --- a/apps/demos/utils/server/demo-render-signal.js +++ b/apps/demos/utils/server/demo-render-signal.js @@ -6,17 +6,29 @@ const RUNTIME_PATH = path.join(__dirname, 'demo-render-signal.runtime.js'); const VIRTUAL_ENTRY_PREFIX = 'demo-render-signal:'; const VIRTUAL_ENTRY_NAMESPACE = 'demo-render-signal'; +function allowedParentOrigins() { + const fromEnv = String(process.env.DEMO_PARENT_ORIGINS || '') + .split(',') + .map((origin) => origin.trim()) + .filter(Boolean); + return fromEnv.length > 0 ? fromEnv : null; +} + function toImportPath(filePath) { return JSON.stringify(filePath.split(path.sep).join('/')); } function createEntryShimSource(entryPath, { tsNoCheck = false } = {}) { + const origins = allowedParentOrigins(); + const imported = ['signal', 'signalWhenRendered']; + if (origins) imported.unshift('setAllowedOrigins'); + return ` ${tsNoCheck ? '// @ts-nocheck\n' : ''} // Generated by utils/server/demo-render-signal.js. import themes from ${JSON.stringify(THEMES_SPECIFIER)}; - import { signal, signalWhenRendered } from ${toImportPath(RUNTIME_PATH)}; - + import { ${imported.join(', ')} } from ${toImportPath(RUNTIME_PATH)}; +${origins ? `\n setAllowedOrigins(${JSON.stringify(origins)});\n` : ''} themes.initialized(function () { import(${toImportPath(entryPath)}).then(signalWhenRendered, function (error) { console.error('demo failed to load', error); diff --git a/apps/demos/utils/server/demo-render-signal.runtime.js b/apps/demos/utils/server/demo-render-signal.runtime.js index a82fb1f54b68..4fc043e37a52 100644 --- a/apps/demos/utils/server/demo-render-signal.runtime.js +++ b/apps/demos/utils/server/demo-render-signal.runtime.js @@ -5,13 +5,85 @@ export const ROOT_SELECTOR = '#app, demo-app'; export const RENDER_TIMEOUT_MS = 10000; +export const DEFAULT_ALLOWED_ORIGINS = [ + 'localhost', + 'https://js.devexpress.com', +]; + +const PATTERN = /^(?:([a-z][a-z0-9+.-]*):\/\/)?([^/:]+)(?::(\d+))?$/i; + +let allowedOrigins = DEFAULT_ALLOWED_ORIGINS; let posted = false; +export function setAllowedOrigins(origins) { + if (Array.isArray(origins) && origins.length > 0) { + allowedOrigins = origins; + } +} + +function hostMatches(hostname, patternHost) { + if (!patternHost.startsWith('*.')) return hostname === patternHost; + + const suffix = patternHost.slice(1); + return hostname.length > suffix.length && hostname.endsWith(suffix); +} + +function matchesPattern(url, pattern) { + const parts = PATTERN.exec(pattern); + if (!parts) return false; + + const [, scheme, host, port] = parts; + if (scheme && `${scheme.toLowerCase()}:` !== url.protocol) return false; + if (port && port !== url.port) return false; + + return hostMatches(url.hostname, host.toLowerCase()); +} + +function isAllowed(origin) { + let url; + try { + url = new URL(origin); + } catch { + return false; + } + return allowedOrigins.some((pattern) => matchesPattern(url, pattern)); +} + +function parentOrigin() { + const { ancestorOrigins } = window.location; + if (ancestorOrigins && ancestorOrigins.length > 0) return ancestorOrigins[0]; + + if (!document.referrer) return null; + try { + return new URL(document.referrer).origin; + } catch { + return null; + } +} + +export function resolveTargetOrigin() { + const ownOrigin = window.location.origin; + if (window.parent === window) return ownOrigin; + + const origin = parentOrigin(); + if (!origin) return null; + if (origin === ownOrigin) return origin; + + return isAllowed(origin) ? origin : null; +} + export function signal() { if (posted) return; posted = true; + + const targetOrigin = resolveTargetOrigin(); + if (!targetOrigin) { + console.warn(`Demo render signal was not sent: the embedding origin is unknown or not in [${allowedOrigins.join(', ')}].`); + return; + } + try { - window.parent.postMessage({ type: MESSAGE_TYPE }, '*'); + window.parent.postMessage({ type: MESSAGE_TYPE }, targetOrigin); } catch { // A parent that rejects the message is the parent's problem. } diff --git a/apps/demos/utils/tests/server/demo-render-signal.runtime.test.js b/apps/demos/utils/tests/server/demo-render-signal.runtime.test.js index 27af89bbffbd..e2f94564b2eb 100644 --- a/apps/demos/utils/tests/server/demo-render-signal.runtime.test.js +++ b/apps/demos/utils/tests/server/demo-render-signal.runtime.test.js @@ -1,7 +1,12 @@ +const OWN_ORIGIN = 'https://demos.test'; +const ALLOWED_PARENT_HOST = 'js.devexpress.com'; +const ALLOWED_PARENT = `https://${ALLOWED_PARENT_HOST}`; + let messages; let root; let rafCallbacks; let observers; +let warnings; function loadRuntime() { let runtime; @@ -22,6 +27,10 @@ function flushFrames() { } } +function embedIn(origin) { + global.window.location.ancestorOrigins = origin === null ? [] : [origin]; +} + beforeEach(() => { jest.useFakeTimers(); @@ -29,13 +38,15 @@ beforeEach(() => { root = null; rafCallbacks = []; observers = []; + warnings = []; global.window = { parent: { postMessage: (data, targetOrigin) => messages.push({ data, targetOrigin }), }, + location: { origin: OWN_ORIGIN, ancestorOrigins: [ALLOWED_PARENT] }, }; - global.document = { querySelector: () => root }; + global.document = { querySelector: () => root, referrer: '' }; global.requestAnimationFrame = (callback) => rafCallbacks.push(callback); global.MutationObserver = class { constructor(callback) { @@ -50,10 +61,13 @@ beforeEach(() => { this.disconnected = true; } }; + + jest.spyOn(console, 'warn').mockImplementation((message) => warnings.push(message)); }); afterEach(() => { jest.useRealTimers(); + jest.restoreAllMocks(); delete global.window; delete global.document; delete global.requestAnimationFrame; @@ -61,12 +75,12 @@ afterEach(() => { }); describe('signal', () => { - test('posts the contract message to the parent', () => { + test('posts the contract message to the embedding origin', () => { const { signal, MESSAGE_TYPE } = loadRuntime(); signal(); - expect(messages).toEqual([{ data: { type: MESSAGE_TYPE }, targetOrigin: '*' }]); + expect(messages).toEqual([{ data: { type: MESSAGE_TYPE }, targetOrigin: ALLOWED_PARENT }]); expect(MESSAGE_TYPE).toBe('demo-rendered'); }); @@ -85,6 +99,192 @@ describe('signal', () => { expect(() => signal()).not.toThrow(); }); + + test('warns instead of posting to an origin that is not allowed', () => { + embedIn('https://evil.example'); + const { signal } = loadRuntime(); + + signal(); + + expect(messages).toHaveLength(0); + expect(warnings).toHaveLength(1); + expect(warnings[0]).toContain(ALLOWED_PARENT_HOST); + }); + + test('gives up for good after a dropped message', () => { + embedIn('https://evil.example'); + const { signal } = loadRuntime(); + + signal(); + embedIn(ALLOWED_PARENT); + signal(); + + expect(messages).toHaveLength(0); + }); +}); + +describe('resolveTargetOrigin', () => { + test('takes the embedding origin from ancestorOrigins', () => { + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(ALLOWED_PARENT); + }); + + test.each([ + 'https://js.devexpress.com', + 'http://localhost:8080', + 'http://localhost:3000', + ])('allows the %s sandbox', (origin) => { + embedIn(origin); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(origin); + }); + + test('allows any scheme for an entry that omits one', () => { + embedIn('http://js.devexpress.com'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['js.devexpress.com']); + + expect(resolveTargetOrigin()).toBe('http://js.devexpress.com'); + }); + + test.each([ + ['the apex of an allowed host', 'https://devexpress.com'], + ['a host that merely ends with an allowed one', 'https://evil-js.devexpress.com'], + ['a host that merely starts with an allowed one', 'https://localhost.evil.example'], + ['an opaque origin', 'null'], + ])('rejects %s', (_, origin) => { + embedIn(origin); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('pins the scheme for an entry that specifies one', () => { + embedIn('http://js.devexpress.com'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['https://js.devexpress.com']); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('pins the port for an entry that specifies one', () => { + embedIn('http://localhost:9999'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['localhost:8080']); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('matches a pinned port', () => { + embedIn('http://localhost:8080'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['http://localhost:8080']); + + expect(resolveTargetOrigin()).toBe('http://localhost:8080'); + }); + + test('allows a subdomain of a wildcard entry', () => { + embedIn('https://demos.example.com'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['*.example.com']); + + expect(resolveTargetOrigin()).toBe('https://demos.example.com'); + }); + + test.each([ + ['the apex domain', 'https://example.com'], + ['a host that only ends with the wildcard suffix', 'https://evil-example.com'], + ['a host shorter than the wildcard suffix', 'https://ex.com'], + ])('rejects %s for a wildcard entry', (_, origin) => { + embedIn(origin); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['*.example.com']); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('ignores an entry that is not a bare origin', () => { + embedIn('https://js.devexpress.com'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['https://js.devexpress.com/Demos/']); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('allows an embedder on the demo own origin, allowlisted or not', () => { + embedIn(OWN_ORIGIN); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(OWN_ORIGIN); + }); + + test('targets the own origin when the demo is not framed', () => { + global.window.parent = global.window; + embedIn(null); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(OWN_ORIGIN); + }); + + test('falls back to the referrer origin without ancestorOrigins', () => { + delete global.window.location.ancestorOrigins; + global.document.referrer = `${ALLOWED_PARENT}/Demos/DataGrid/Overview/React/Light/`; + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(ALLOWED_PARENT); + }); + + test('falls back to the referrer origin with empty ancestorOrigins', () => { + embedIn(null); + global.document.referrer = `${ALLOWED_PARENT}/`; + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBe(ALLOWED_PARENT); + }); + + test('resolves nothing without ancestorOrigins or a referrer', () => { + embedIn(null); + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBeNull(); + }); + + test('resolves nothing from an unparsable referrer', () => { + embedIn(null); + global.document.referrer = 'not-a-url'; + const { resolveTargetOrigin } = loadRuntime(); + + expect(resolveTargetOrigin()).toBeNull(); + }); +}); + +describe('setAllowedOrigins', () => { + test('replaces the default allowlist', () => { + embedIn('https://sandbox.example'); + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(['https://sandbox.example']); + + expect(resolveTargetOrigin()).toBe('https://sandbox.example'); + }); + + test('keeps the default allowlist when the build injects nothing', () => { + const { setAllowedOrigins, resolveTargetOrigin } = loadRuntime(); + + setAllowedOrigins(null); + setAllowedOrigins([]); + + expect(resolveTargetOrigin()).toBe(ALLOWED_PARENT); + }); }); describe('signalWhenRendered', () => { diff --git a/e2e/testcafe-devextreme/runner.ts b/e2e/testcafe-devextreme/runner.ts index 0be6eeeafc80..ab47c5657686 100644 --- a/e2e/testcafe-devextreme/runner.ts +++ b/e2e/testcafe-devextreme/runner.ts @@ -16,6 +16,7 @@ import { getCurrentTheme } from './helpers/themeUtils'; const LAUNCH_RETRY_ATTEMPTS = 3; const LAUNCH_RETRY_TIMEOUT = 10000; const FAILED_TESTS_RETRY_ATTEMPTS = 0; +const TEST_EXECUTION_TIMEOUT = 3 * 60 * 1000; const wait = async ( timeout: number, @@ -328,6 +329,7 @@ async function main() { const runOptions: RunOptions = { quarantineMode: false, + testExecutionTimeout: TEST_EXECUTION_TIMEOUT, // @ts-expect-error ts-error hooks: { test: { diff --git a/e2e/testcafe-devextreme/tests/accessibility/scheduler/appointment.ts b/e2e/testcafe-devextreme/tests/accessibility/scheduler/appointment.ts index 6a7727e2871a..f9251fd8e70b 100644 --- a/e2e/testcafe-devextreme/tests/accessibility/scheduler/appointment.ts +++ b/e2e/testcafe-devextreme/tests/accessibility/scheduler/appointment.ts @@ -2,7 +2,7 @@ import Scheduler from 'devextreme-testcafe-models/scheduler'; import { a11yCheck } from '../../../helpers/accessibility/utils'; import { createWidget } from '../../../helpers/createWidget'; import url from '../../../helpers/getPageUrl'; -import { a11yCheckConfig } from './axe_options'; +import { a11yCheckConfig, a11yContext } from './axe_options'; fixture.disablePageReloads`Scheduler - Appointment` .page(url(__dirname, '../../container.html')); @@ -26,7 +26,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.hasAriaDescription()) .ok(); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -46,7 +46,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.hasAriaDescription()) .ok(); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -67,7 +67,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.getAriaDescription()) .contains('Group: resource1; Group 1: resource1'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -101,7 +101,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.getAriaDescription()) .contains('Group: resource1; Group 1: resource1'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -136,7 +136,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(await appointment.getAriaDescription()) .contains('Group: resource11, resource21; Group 1: resource11; Group 2: resource21, resource22'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -180,7 +180,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(recurrenceIcon.getAttribute('aria-label')) .eql('Recurring appointment'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -210,7 +210,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex .expect(appt.element.getAttribute('aria-activedescendant')) .eql(null); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -266,7 +266,7 @@ const appointmentTemplate = ({ appointmentData }) => `
${appointmentData.tex await t.expect(apptLabels[1]).eql(labels[1]); await t.expect(apptLabels[2]).eql(labels[2]); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'UTC', @@ -336,7 +336,7 @@ test('appointments & collector buttons can be navigated', async (t) => { scheduler.appointmentTooltip.element.count, ).eql(1); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: [ @@ -387,7 +387,7 @@ test('Scheduler a11y: Disabled time ranges are not supported', async (t) => { .expect(actualNextAriaLabel) .eql(expectedAriaLabels.next); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: [ @@ -410,7 +410,7 @@ test('Scheduler a11y: appointments does not have info about reccurence', async ( .expect(recurrenceIcon.getAttribute('aria-label')) .eql('Recurring appointment'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -440,7 +440,7 @@ test('Scheduler a11y: Appointment collector button doesn\'t have info about date .expect(schedulerCollector.element().getAttribute('aria-roledescription')) .contains(dateText); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -479,7 +479,7 @@ test('appointment aria label should contain date with right timezone', async (t) .expect(appointment.getAriaLabel()) .eql('Install New Router in Dev Room: March 29, 2021, 2:30 PM - 3:30 PM'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', diff --git a/e2e/testcafe-devextreme/tests/accessibility/scheduler/axe_options.ts b/e2e/testcafe-devextreme/tests/accessibility/scheduler/axe_options.ts index f0717745a987..c07b0adad4bb 100644 --- a/e2e/testcafe-devextreme/tests/accessibility/scheduler/axe_options.ts +++ b/e2e/testcafe-devextreme/tests/accessibility/scheduler/axe_options.ts @@ -1 +1,11 @@ export const a11yCheckConfig = {}; + +// The appointment title and date do not reach the AA contrast ratio: +// the date line is dimmed to 70% opacity over the accent-colored appointment. +export const a11yContext = { + include: ['#container'], + exclude: [ + '.dx-scheduler-appointment-title', + '.dx-scheduler-appointment-content-date', + ], +}; diff --git a/e2e/testcafe-devextreme/tests/accessibility/scheduler/scheduler.ts b/e2e/testcafe-devextreme/tests/accessibility/scheduler/scheduler.ts index 7ba23fe3582b..88297d703858 100644 --- a/e2e/testcafe-devextreme/tests/accessibility/scheduler/scheduler.ts +++ b/e2e/testcafe-devextreme/tests/accessibility/scheduler/scheduler.ts @@ -2,7 +2,7 @@ import Scheduler from 'devextreme-testcafe-models/scheduler'; import { a11yCheck } from '../../../helpers/accessibility/utils'; import { createWidget } from '../../../helpers/createWidget'; import url from '../../../helpers/getPageUrl'; -import { a11yCheckConfig } from './axe_options'; +import { a11yCheckConfig, a11yContext } from './axe_options'; fixture.disablePageReloads`Scheduler - a11y` .page(url(__dirname, '../../container.html')); @@ -20,7 +20,7 @@ test('Scheduler should have right aria attributes after view changed', async (t) await t.expect(scheduler.element.getAttribute('aria-label')).contains('Scheduler. Week view'); await t.expect(scheduler.getGeneralStatusContainer().textContent).contains('Scheduler. Week view'); - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: [], @@ -40,7 +40,7 @@ test('Scheduler table elements have right aria attributes', async (t) => { ).eql('true'); } - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: [], @@ -52,7 +52,7 @@ test('Scheduler table elements have right aria attributes', async (t) => { 'agenda', 'day', 'month', 'timelineDay', 'timelineMonth', 'timelineWeek', 'timelineWorkWeek', 'week', 'workWeek', ].forEach((currentView) => { test(`Scheduler has no axe errors on view ${currentView}`, async (t) => { - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { timeZone: 'America/Los_Angeles', @@ -108,7 +108,7 @@ const groupingCases = [ ] as const).forEach(({ name, dataSource, parentIdExpr }) => { groupingCases.forEach(({ view, orientation }) => { test(`Scheduler should pass accessibility checks with ${name} grouping on view ${view} (${orientation})`, async (t) => { - await a11yCheck(t, a11yCheckConfig, '#container'); + await a11yCheck(t, a11yCheckConfig, a11yContext); }).before(async () => { await createWidget('dxScheduler', { dataSource: groupedAppointments, diff --git a/e2e/testcafe-devextreme/tests/accessibility/tabPanel.ts b/e2e/testcafe-devextreme/tests/accessibility/tabPanel.ts index 33872a37b182..ae6dd7dc7d9b 100644 --- a/e2e/testcafe-devextreme/tests/accessibility/tabPanel.ts +++ b/e2e/testcafe-devextreme/tests/accessibility/tabPanel.ts @@ -14,7 +14,9 @@ const items: Item[] = [ { title: 'Olivia Peyton', text: 'Olivia Peyton' }, { title: 'Ed Holmes', text: 'Ed Holmes' }, { title: 'Wally Hobbs', text: 'Wally Hobbs' }, - { title: 'Brad Jameson', text: 'Brad Jameson' }, + { title: 'Brad Jameson', text: 'Brad Jameson', icon: 'user' }, + { icon: 'chart', text: 'Chart' }, + { icon: 'find', badge: '3', text: 'Find' }, ]; const options: Options = { diff --git a/e2e/testcafe-devextreme/tests/accessibility/tabs.ts b/e2e/testcafe-devextreme/tests/accessibility/tabs.ts index 9000466ab201..e156e60a4f15 100644 --- a/e2e/testcafe-devextreme/tests/accessibility/tabs.ts +++ b/e2e/testcafe-devextreme/tests/accessibility/tabs.ts @@ -14,7 +14,9 @@ const items: Item[] = [ { text: 'Olivia Peyton' }, { text: 'Ed Holmes' }, { text: 'Wally Hobbs' }, - { text: 'Brad Jameson' }, + { text: 'Brad Jameson', icon: 'user' }, + { icon: 'chart' }, + { icon: 'find', badge: '3' }, ]; const options: Options = { diff --git a/e2e/testcafe-devextreme/tests/cardView/columnChooser/functional.ts b/e2e/testcafe-devextreme/tests/cardView/columnChooser/functional.ts index 6daade06393f..71f616668eac 100644 --- a/e2e/testcafe-devextreme/tests/cardView/columnChooser/functional.ts +++ b/e2e/testcafe-devextreme/tests/cardView/columnChooser/functional.ts @@ -60,6 +60,7 @@ function testsFactory(testModel: { const cardView = new CardView('#container'); await cardView.apiShowColumnChooser(); + await testModel.assertFirstColumnVisible(t, cardView); await testModel.hideFirstColumn(t, cardView); await testModel.assertFirstColumnHidden(t, cardView); @@ -230,11 +231,13 @@ test('cards should update when column is hidden via column chooser (select mode) await t.expect(initialCaptions).eql(['A', 'B', 'C']); await cardView.apiShowColumnChooser(); + await t.expect(cardView.getColumnChooser().isCheckboxChecked(0)).ok(); await t.click(cardView.getColumnChooser().getCheckbox(0)); const captionsAfterHide = await getCardFieldCaptions(t, cardView, 2); await t.expect(captionsAfterHide).eql(['B', 'C']); + await t.expect(cardView.getColumnChooser().isCheckboxChecked(0)).notOk(); await t.click(cardView.getColumnChooser().getCheckbox(0)); diff --git a/e2e/testcafe-devextreme/tests/navigation/button/floatingActionViewport.ts b/e2e/testcafe-devextreme/tests/navigation/button/floatingActionViewport.ts new file mode 100644 index 000000000000..a9eb89df1ac8 --- /dev/null +++ b/e2e/testcafe-devextreme/tests/navigation/button/floatingActionViewport.ts @@ -0,0 +1,40 @@ +import { ClientFunction, Selector } from 'testcafe'; +import url from '../../../helpers/getPageUrl'; +import { createWidget } from '../../../helpers/createWidget'; +import { appendElementTo } from '../../../helpers/domUtils'; + +const OVERLAY_CONTENT_CLASS = 'dx-overlay-content'; +const FA_MAIN_BUTTON_CLASS = 'dx-fa-button-main'; +const FAB_OFFSET = 16; + +const getViewportClientSize = ClientFunction(() => ({ + width: document.documentElement.clientWidth, + height: document.documentElement.clientHeight, +})); + +fixture`FloatingAction - visible area` + .page(url(__dirname, '../../container.html')); + +test('FAB should keep its offset from the visible area edges after scrollbars appear (T1334663)', async (t) => { + const fabContent = Selector(`.${FA_MAIN_BUTTON_CLASS} .${OVERLAY_CONTENT_CLASS}`); + const initialSize = await getViewportClientSize(); + + await t + .expect(fabContent.getBoundingClientRectProperty('right')) + .within(initialSize.width - FAB_OFFSET - 1, initialSize.width - FAB_OFFSET + 1) + .expect(fabContent.getBoundingClientRectProperty('bottom')) + .within(initialSize.height - FAB_OFFSET - 1, initialSize.height - FAB_OFFSET + 1); + + await appendElementTo('#container', 'div', 'tall-content', { width: '3000px', height: '3000px' }); + + const sizeWithScrollbars = await getViewportClientSize(); + + await t + .expect(fabContent.getBoundingClientRectProperty('right')) + .within(sizeWithScrollbars.width - FAB_OFFSET - 1, sizeWithScrollbars.width - FAB_OFFSET + 1) + .expect(fabContent.getBoundingClientRectProperty('bottom')) + .within(sizeWithScrollbars.height - FAB_OFFSET - 1, sizeWithScrollbars.height - FAB_OFFSET + 1); +}).before(async () => createWidget('dxSpeedDialAction', { + icon: 'add', + visible: true, +})); diff --git a/packages/devextreme-scss/.stylelintrc.json b/packages/devextreme-scss/.stylelintrc.json index e34adf09d5ce..52bbdc7712f3 100644 --- a/packages/devextreme-scss/.stylelintrc.json +++ b/packages/devextreme-scss/.stylelintrc.json @@ -1,7 +1,9 @@ { "plugins": [ "stylelint-scss", - "@stylistic/stylelint-plugin" + "@stylistic/stylelint-plugin", + "./tools/stylelint/no-identical-branch-declarations.mjs", + "./tools/stylelint/module-variable-default.mjs" ], "extends": "stylelint-config-standard-scss", "ignoreFiles": "!**/*.{css,scss}", @@ -18,6 +20,8 @@ "@stylistic/declaration-colon-space-after": "always-single-line", "@stylistic/declaration-colon-space-before": "never", "declaration-no-important": true, + "dx/module-variable-default": true, + "dx/no-identical-branch-declarations": true, "font-family-name-quotes": "always-unless-keyword", "@stylistic/function-comma-space-after": "always-single-line", "@stylistic/indentation": [2, { "ignore": ["inside-parens"] }], diff --git a/packages/devextreme-scss/images/widgets/fluent/color-schemes/dark/pulldown.png b/packages/devextreme-scss/images/widgets/fluent/color-schemes/dark/pulldown.png deleted file mode 100644 index 436cc90d9666..000000000000 Binary files a/packages/devextreme-scss/images/widgets/fluent/color-schemes/dark/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/fluent/color-schemes/light/pulldown.png b/packages/devextreme-scss/images/widgets/fluent/color-schemes/light/pulldown.png deleted file mode 100644 index 436cc90d9666..000000000000 Binary files a/packages/devextreme-scss/images/widgets/fluent/color-schemes/light/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/fluent/color-schemes/dark/grid/text-stub.png b/packages/devextreme-scss/images/widgets/fluent/grid/text-stub.png similarity index 100% rename from packages/devextreme-scss/images/widgets/fluent/color-schemes/dark/grid/text-stub.png rename to packages/devextreme-scss/images/widgets/fluent/grid/text-stub.png diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/carmine/pulldown.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/carmine/pulldown.png deleted file mode 100644 index 436cc90d9666..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/carmine/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/contrast/pulldown.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/contrast/pulldown.png deleted file mode 100644 index 20367a0056d8..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/contrast/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/darkmoon/pulldown.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/darkmoon/pulldown.png deleted file mode 100644 index 5d3b6b3f0b88..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/darkmoon/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/darkviolet/pulldown.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/darkviolet/pulldown.png deleted file mode 100644 index 5d3b6b3f0b88..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/darkviolet/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/greenmist/pulldown.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/greenmist/pulldown.png deleted file mode 100644 index 436cc90d9666..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/greenmist/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/error.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/error.png deleted file mode 100644 index 0babe32c2b64..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/error.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/info.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/info.png deleted file mode 100644 index eda61e4358f3..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/info.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/success.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/success.png deleted file mode 100644 index 200ca204c58a..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/success.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/warning.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/warning.png deleted file mode 100644 index 7324d22ea49b..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/light/toast/warning.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/softblue/pulldown.png b/packages/devextreme-scss/images/widgets/generic/color-schemes/softblue/pulldown.png deleted file mode 100644 index 436cc90d9666..000000000000 Binary files a/packages/devextreme-scss/images/widgets/generic/color-schemes/softblue/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/dark/toast/error.png b/packages/devextreme-scss/images/widgets/generic/toast/error.png similarity index 100% rename from packages/devextreme-scss/images/widgets/generic/color-schemes/dark/toast/error.png rename to packages/devextreme-scss/images/widgets/generic/toast/error.png diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/dark/toast/info.png b/packages/devextreme-scss/images/widgets/generic/toast/info.png similarity index 100% rename from packages/devextreme-scss/images/widgets/generic/color-schemes/dark/toast/info.png rename to packages/devextreme-scss/images/widgets/generic/toast/info.png diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/dark/toast/success.png b/packages/devextreme-scss/images/widgets/generic/toast/success.png similarity index 100% rename from packages/devextreme-scss/images/widgets/generic/color-schemes/dark/toast/success.png rename to packages/devextreme-scss/images/widgets/generic/toast/success.png diff --git a/packages/devextreme-scss/images/widgets/generic/color-schemes/dark/toast/warning.png b/packages/devextreme-scss/images/widgets/generic/toast/warning.png similarity index 100% rename from packages/devextreme-scss/images/widgets/generic/color-schemes/dark/toast/warning.png rename to packages/devextreme-scss/images/widgets/generic/toast/warning.png diff --git a/packages/devextreme-scss/images/widgets/material/color-schemes/dark/grid/text-stub.png b/packages/devextreme-scss/images/widgets/material/color-schemes/dark/grid/text-stub.png deleted file mode 100644 index 77bf05a68647..000000000000 Binary files a/packages/devextreme-scss/images/widgets/material/color-schemes/dark/grid/text-stub.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/material/color-schemes/dark/pulldown.png b/packages/devextreme-scss/images/widgets/material/color-schemes/dark/pulldown.png deleted file mode 100644 index 436cc90d9666..000000000000 Binary files a/packages/devextreme-scss/images/widgets/material/color-schemes/dark/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/material/color-schemes/light/grid/text-stub.png b/packages/devextreme-scss/images/widgets/material/color-schemes/light/grid/text-stub.png deleted file mode 100644 index 77bf05a68647..000000000000 Binary files a/packages/devextreme-scss/images/widgets/material/color-schemes/light/grid/text-stub.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/material/color-schemes/light/pulldown.png b/packages/devextreme-scss/images/widgets/material/color-schemes/light/pulldown.png deleted file mode 100644 index 436cc90d9666..000000000000 Binary files a/packages/devextreme-scss/images/widgets/material/color-schemes/light/pulldown.png and /dev/null differ diff --git a/packages/devextreme-scss/images/widgets/fluent/color-schemes/light/grid/text-stub.png b/packages/devextreme-scss/images/widgets/material/grid/text-stub.png similarity index 100% rename from packages/devextreme-scss/images/widgets/fluent/color-schemes/light/grid/text-stub.png rename to packages/devextreme-scss/images/widgets/material/grid/text-stub.png diff --git a/packages/devextreme-scss/scss/widgets/base/_htmlEditor.scss b/packages/devextreme-scss/scss/widgets/base/_htmlEditor.scss index 5b3818d55082..79636b10de17 100644 --- a/packages/devextreme-scss/scss/widgets/base/_htmlEditor.scss +++ b/packages/devextreme-scss/scss/widgets/base/_htmlEditor.scss @@ -2,17 +2,17 @@ @use "sass:math"; @use "sass:selector"; -$list-style: decimal lower-alpha lower-roman; -$list-style-width: 1.2em; -$list-style-margin: 0.3em; -$list-style-outer-width: $list-style-margin + $list-style-width; -$max-indent: 9; -$resize-handle-size: 11px; -$touch-resize-handle-size: 15px; -$half-resize-handle-size: math.div($resize-handle-size, 2); -$half-touch-resize-handle-size: math.div($touch-resize-handle-size, 2); - -$transparent-border: 1px solid transparent; +$list-style: decimal lower-alpha lower-roman !default; +$list-style-width: 1.2em !default; +$list-style-margin: 0.3em !default; +$list-style-outer-width: $list-style-margin + $list-style-width !default; +$max-indent: 9 !default; +$resize-handle-size: 11px !default; +$touch-resize-handle-size: 15px !default; +$half-resize-handle-size: math.div($resize-handle-size, 2) !default; +$half-touch-resize-handle-size: math.div($touch-resize-handle-size, 2) !default; + +$transparent-border: 1px solid transparent !default; @function add-counter-reset($counter, $start: 1) { @if $counter > $start { diff --git a/packages/devextreme-scss/scss/widgets/base/_icon_fonts.scss b/packages/devextreme-scss/scss/widgets/base/_icon_fonts.scss index c6645d033729..2bc650a5f1c8 100644 --- a/packages/devextreme-scss/scss/widgets/base/_icon_fonts.scss +++ b/packages/devextreme-scss/scss/widgets/base/_icon_fonts.scss @@ -613,7 +613,7 @@ $icons: ( "gridkanban": "\f240", "listbartreeoffset": "\f241", "clearhistory": "\f242", -); +) !default; // stylelint-enable diff --git a/packages/devextreme-scss/scss/widgets/base/_loadIndicator.scss b/packages/devextreme-scss/scss/widgets/base/_loadIndicator.scss index ac6f0ec94503..eafdf848b572 100644 --- a/packages/devextreme-scss/scss/widgets/base/_loadIndicator.scss +++ b/packages/devextreme-scss/scss/widgets/base/_loadIndicator.scss @@ -1,7 +1,7 @@ @use "sass:math"; -$loadindicator-width: 32px; -$loadindicator-height: 32px; +$loadindicator-width: 32px !default; +$loadindicator-height: 32px !default; .dx-loadindicator { width: $loadindicator-width; diff --git a/packages/devextreme-scss/scss/widgets/base/_mixins.scss b/packages/devextreme-scss/scss/widgets/base/_mixins.scss index e38a7556b667..5a37174c9974 100644 --- a/packages/devextreme-scss/scss/widgets/base/_mixins.scss +++ b/packages/devextreme-scss/scss/widgets/base/_mixins.scss @@ -140,5 +140,5 @@ transform: translate(-50%, -50%); } -$overlay-zindex: 1000; -$max-integer: 2147483647; +$overlay-zindex: 1000 !default; +$max-integer: 2147483647 !default; diff --git a/packages/devextreme-scss/scss/widgets/base/_numberBox.scss b/packages/devextreme-scss/scss/widgets/base/_numberBox.scss index 569695d1163f..e2db3328c299 100644 --- a/packages/devextreme-scss/scss/widgets/base/_numberBox.scss +++ b/packages/devextreme-scss/scss/widgets/base/_numberBox.scss @@ -1,7 +1,7 @@ @use "./mixins" as *; -$spin-container-width: 22px; -$touch-friendly-spin-container-width: 64px; +$spin-container-width: 22px !default; +$touch-friendly-spin-container-width: 64px !default; .dx-numberbox { position: relative; diff --git a/packages/devextreme-scss/scss/widgets/base/_pagination.scss b/packages/devextreme-scss/scss/widgets/base/_pagination.scss index e4e03edf80db..b73aed4bc164 100644 --- a/packages/devextreme-scss/scss/widgets/base/_pagination.scss +++ b/packages/devextreme-scss/scss/widgets/base/_pagination.scss @@ -1,7 +1,7 @@ @use "./mixins" as *; -$pagination-paddings: 8px; -$pagination-info-paddings: 9px; +$pagination-paddings: 8px !default; +$pagination-info-paddings: 9px !default; .dx-pager, .dx-pagination { diff --git a/packages/devextreme-scss/scss/widgets/base/_recurrenceEditor.scss b/packages/devextreme-scss/scss/widgets/base/_recurrenceEditor.scss index 28f826acb54d..136bc1ebf27c 100644 --- a/packages/devextreme-scss/scss/widgets/base/_recurrenceEditor.scss +++ b/packages/devextreme-scss/scss/widgets/base/_recurrenceEditor.scss @@ -1,6 +1,6 @@ -$recurrence-interval-number-box-margin-top: 25px; -$recurrence-radio-group-item-padding: 10px; -$recurrence-button-group-item-padding: 8px; +$recurrence-interval-number-box-margin-top: 25px !default; +$recurrence-radio-group-item-padding: 10px !default; +$recurrence-button-group-item-padding: 8px !default; .dx-recurrence-repeat-on, .dx-recurrence-numberbox-interval-wrapper { diff --git a/packages/devextreme-scss/scss/widgets/base/_resizable.scss b/packages/devextreme-scss/scss/widgets/base/_resizable.scss index 8068ae0a863a..a7635864fec7 100644 --- a/packages/devextreme-scss/scss/widgets/base/_resizable.scss +++ b/packages/devextreme-scss/scss/widgets/base/_resizable.scss @@ -1,6 +1,6 @@ -$handle-offset: 0; -$one-side-handle-size: 3px + $handle-offset; -$two-side-handle-size: 6px + $handle-offset; +$handle-offset: 0 !default; +$one-side-handle-size: 3px + $handle-offset !default; +$two-side-handle-size: 6px + $handle-offset !default; .dx-resizable { display: block; diff --git a/packages/devextreme-scss/scss/widgets/base/_scrollView.scss b/packages/devextreme-scss/scss/widgets/base/_scrollView.scss index c125ad2d0d01..920800bdf699 100644 --- a/packages/devextreme-scss/scss/widgets/base/_scrollView.scss +++ b/packages/devextreme-scss/scss/widgets/base/_scrollView.scss @@ -1,7 +1,7 @@ @use "./mixins" as *; -$pulldown-height: 50px; -$pulldown-padding: 15px; +$pulldown-height: 50px !default; +$pulldown-padding: 15px !default; .dx-scrollable-native { &.dx-scrollable-native-generic { diff --git a/packages/devextreme-scss/scss/widgets/base/_slider.scss b/packages/devextreme-scss/scss/widgets/base/_slider.scss index 1c2ebfa9e3d4..2f6fe5294ea2 100644 --- a/packages/devextreme-scss/scss/widgets/base/_slider.scss +++ b/packages/devextreme-scss/scss/widgets/base/_slider.scss @@ -1,8 +1,8 @@ @use "./mixins" as *; @use "./popover/mixins" as *; -$slider-tooltip-content-height: 28px; -$slider-tooltip-arrow-height: 7px; +$slider-tooltip-content-height: 28px !default; +$slider-tooltip-arrow-height: 7px !default; .dx-slider-label { position: absolute; diff --git a/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/_index.scss b/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/_index.scss index 33789dd4a50e..f03e72471f49 100644 --- a/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/cardView/header_panel/_index.scss @@ -6,7 +6,7 @@ 2px maximal right header item padding + 4px maximal dxSortable placeholder (drop indicator) width We can increase this number up to the CardView root padding without issues */ -$sortable-side-offset: 6px; +$sortable-side-offset: 6px !default; .dx-cardview-header-item-sorting { display: flex; diff --git a/packages/devextreme-scss/scss/widgets/base/colorBox/_index.scss b/packages/devextreme-scss/scss/widgets/base/colorBox/_index.scss index 262086e375ee..4174f1803d22 100644 --- a/packages/devextreme-scss/scss/widgets/base/colorBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/colorBox/_index.scss @@ -2,7 +2,7 @@ @use "./mixins" as *; @use "sass:string" as string; -$colorbox-input-left-padding: 40px; +$colorbox-input-left-padding: 40px !default; .dx-colorbox-input-container { &.dx-colorbox-color-is-not-defined { diff --git a/packages/devextreme-scss/scss/widgets/base/colorView/_index.scss b/packages/devextreme-scss/scss/widgets/base/colorView/_index.scss index f226a5938c2a..9eb36c7c7061 100644 --- a/packages/devextreme-scss/scss/widgets/base/colorView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/colorView/_index.scss @@ -1,17 +1,17 @@ @use "../mixins" as *; -$color-view-container-width: 450px; -$color-view-palette-width: 288px; -$color-view-palette-height: 299px; -$color-view-alpha-width: 292px; -$color-view-hue-scale-width: 18px; -$color-view-hue-scale-height: $color-view-palette-height; - -$colorview-palette-handle-size: 28px; -$colorview-transparent-background-color: #ccc; - -$colorview-handle-first-side-size: 36px; -$colorview-handle-second-side-size: 17px; +$color-view-container-width: 450px !default; +$color-view-palette-width: 288px !default; +$color-view-palette-height: 299px !default; +$color-view-alpha-width: 292px !default; +$color-view-hue-scale-width: 18px !default; +$color-view-hue-scale-height: $color-view-palette-height !default; + +$colorview-palette-handle-size: 28px !default; +$colorview-transparent-background-color: #ccc !default; + +$colorview-handle-first-side-size: 36px !default; +$colorview-handle-second-side-size: 17px !default; .dx-colorview-container { width: $color-view-container-width; diff --git a/packages/devextreme-scss/scss/widgets/base/diagram/_index.scss b/packages/devextreme-scss/scss/widgets/base/diagram/_index.scss index e6d9d5683a3c..3b10c750588e 100644 --- a/packages/devextreme-scss/scss/widgets/base/diagram/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/diagram/_index.scss @@ -1,6 +1,6 @@ @use "../mixins" as *; -$transparent-border: 1px solid transparent; +$transparent-border: 1px solid transparent !default; .dx-diagram { border: $transparent-border; diff --git a/packages/devextreme-scss/scss/widgets/base/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/base/gridBase/_index.scss index 8a59bf54afba..2c56c22f1444 100644 --- a/packages/devextreme-scss/scss/widgets/base/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/gridBase/_index.scss @@ -17,15 +17,6 @@ flex: 0 0 auto; } -// NOTE: a11y aria-live container must be visible to allow screen readers read it -.dx-gridbase-a11y-status-container { - position: fixed; - left: 0; - top: 0; - clip: rect(1px, 1px, 1px, 1px); - clip-path: polygon(0 0); -} - @mixin grid-base($widget-name) { @include grid-base-cell($widget-name); diff --git a/packages/devextreme-scss/scss/widgets/base/gridBase/_variables.scss b/packages/devextreme-scss/scss/widgets/base/gridBase/_variables.scss index c1b6172d3926..e6a121dc6e89 100644 --- a/packages/devextreme-scss/scss/widgets/base/gridBase/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/base/gridBase/_variables.scss @@ -1,13 +1,13 @@ -$grid-cell-padding: 7px; -$grid-texteditor-input-padding: 32px; -$grid-total-footer-paddings: 7px; -$grid-command-select-column-width: 70px; -$grid-command-edit-column-width: 100px; -$grid-command-edit-column-with-icons-width: 80px; -$grid-command-expand-column-width: 30px; -$grid-command-adaptive-column-width: 36px; -$grid-column-header-indicator-width: 14px; -$grid-text-content-margin: 3px; -$grid-sort-index-width: 12px; -$grid-sort-index-offset: 3px; -$grid-ai-assistant-confirm-dialog-button-width: 50px; +$grid-cell-padding: 7px !default; +$grid-texteditor-input-padding: 32px !default; +$grid-total-footer-paddings: 7px !default; +$grid-command-select-column-width: 70px !default; +$grid-command-edit-column-width: 100px !default; +$grid-command-edit-column-with-icons-width: 80px !default; +$grid-command-expand-column-width: 30px !default; +$grid-command-adaptive-column-width: 36px !default; +$grid-column-header-indicator-width: 14px !default; +$grid-text-content-margin: 3px !default; +$grid-sort-index-width: 12px !default; +$grid-sort-index-offset: 3px !default; +$grid-ai-assistant-confirm-dialog-button-width: 50px !default; diff --git a/packages/devextreme-scss/scss/widgets/base/gridBase/layout/aiChat/_variables.scss b/packages/devextreme-scss/scss/widgets/base/gridBase/layout/aiChat/_variables.scss index 03c18025cb6e..887b8717f691 100644 --- a/packages/devextreme-scss/scss/widgets/base/gridBase/layout/aiChat/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/base/gridBase/layout/aiChat/_variables.scss @@ -1,6 +1,6 @@ -$ai-chat-toolbar-item-spacing: 4px; -$ai-chat-message-gap: 8px; -$ai-chat-message-content-gap: 4px; -$ai-chat-message-width: 280px; -$ai-chat-message-regenerate-button-border-radius: 4px; -$ai-chat-action-list-item-text-max-lines: 2; +$ai-chat-toolbar-item-spacing: 4px !default; +$ai-chat-message-gap: 8px !default; +$ai-chat-message-content-gap: 4px !default; +$ai-chat-message-width: 280px !default; +$ai-chat-message-regenerate-button-border-radius: 4px !default; +$ai-chat-action-list-item-text-max-lines: 2 !default; diff --git a/packages/devextreme-scss/scss/widgets/base/list/_index.scss b/packages/devextreme-scss/scss/widgets/base/list/_index.scss index 44c3baa238f4..ce602f27237d 100644 --- a/packages/devextreme-scss/scss/widgets/base/list/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/list/_index.scss @@ -1,7 +1,7 @@ @use "../mixins" as *; @use "../button/mixins" as *; -$list-searchbox-margin-bottom: 5px; +$list-searchbox-margin-bottom: 5px !default; .dx-list { margin: 0; diff --git a/packages/devextreme-scss/scss/widgets/base/pivotGrid/_index.scss b/packages/devextreme-scss/scss/widgets/base/pivotGrid/_index.scss index cfd91f6158e4..f207e0faa181 100644 --- a/packages/devextreme-scss/scss/widgets/base/pivotGrid/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/pivotGrid/_index.scss @@ -23,16 +23,16 @@ $pivotgrid-button-top-padding: null !default; @use "../icon_fonts" as *; @use "./common"; -$pivotgrid-drag-header-border: 1px solid; -$pivotgrid-drag-header-first-shadow: 0 0 1px $pivotgrid-drag-header-first-shadow-color; -$pivotgrid-drag-header-second-shadow: 0 1px 3px $pivotgrid-drag-header-second-shadow-color; -$pivotgrid-border-width: 1px; -$pivotgrid-border: $pivotgrid-border-width solid; -$pivotgrid-icon-size: 14px; -$pivotgrid-common-field-padding: 2px; -$pivotgrid-filter-area-border-spacing: $pivotgrid-common-left-right-padding - $pivotgrid-common-field-padding $pivotgrid-header-top-bottom-padding - $pivotgrid-common-field-padding; -$pivotgrid-expand-icon-offset: -5px; -$pivotgrid-expand-icon-text-offset: 0; +$pivotgrid-drag-header-border: 1px solid !default; +$pivotgrid-drag-header-first-shadow: 0 0 1px $pivotgrid-drag-header-first-shadow-color !default; +$pivotgrid-drag-header-second-shadow: 0 1px 3px $pivotgrid-drag-header-second-shadow-color !default; +$pivotgrid-border-width: 1px !default; +$pivotgrid-border: $pivotgrid-border-width solid !default; +$pivotgrid-icon-size: 14px !default; +$pivotgrid-common-field-padding: 2px !default; +$pivotgrid-filter-area-border-spacing: $pivotgrid-common-left-right-padding - $pivotgrid-common-field-padding $pivotgrid-header-top-bottom-padding - $pivotgrid-common-field-padding !default; +$pivotgrid-expand-icon-offset: -5px !default; +$pivotgrid-expand-icon-text-offset: 0 !default; .dx-pivotgrid-fields-container { .dx-position-indicator { diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/_common.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/_common.scss index 757318499b2b..996034b1de34 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/_common.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/_common.scss @@ -1,8 +1,8 @@ @use "sass:math"; @use "../../base/mixins" as *; -$scheduler-appointment-collector-margin: 3px; -$scheduler-appointment-collector-height: 22px; +$scheduler-appointment-collector-margin: 3px !default; +$scheduler-appointment-collector-height: 22px !default; .dx-scheduler { .dx-empty-message { diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/_index.scss index 5aed1e0f20f4..c42229cab532 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/_index.scss @@ -52,52 +52,52 @@ $agenda-appointment-recurrence-icon-color: null !default; $scheduler-tooltip-list-focused-bg: $scheduler-tooltip-list-focused-bg ); -$scheduler-appointment-min-size: 4px; -$scheduler-appointment-content-padding: 5px 7px; -$scheduler-appointment-reduced-content-padding: 40px; +$scheduler-appointment-min-size: 4px !default; +$scheduler-appointment-content-padding: 5px 7px !default; +$scheduler-appointment-reduced-content-padding: 40px !default; -$scheduler-hovered-appointment-top-shadow-size: 5px; -$scheduler-focused-cell-shadow: inset 0 0 0 1px $scheduler-appointment-base-color; +$scheduler-hovered-appointment-top-shadow-size: 5px !default; +$scheduler-focused-cell-shadow: inset 0 0 0 1px $scheduler-appointment-base-color !default; -$scheduler-vertical-appointment-resizable-hovering-shadow: inset 0 $scheduler-hovered-appointment-top-shadow-size 0 0 $scheduler-appointment-start-color, inset 0 -2px 0 0 $scheduler-appointment-start-color; -$scheduler-vertical-appointment-hovering-shadow: inset 0 $scheduler-hovered-appointment-top-shadow-size 0 0 $scheduler-appointment-start-color; -$scheduler-vertical-appointment-shadow: inset 0 2px 0 0 $scheduler-appointment-start-color; -$scheduler-vertical-appointment-resizing-shadow: inset 0 -2px 0 0 $scheduler-appointment-start-color, $scheduler-vertical-appointment-shadow; +$scheduler-vertical-appointment-resizable-hovering-shadow: inset 0 $scheduler-hovered-appointment-top-shadow-size 0 0 $scheduler-appointment-start-color, inset 0 -2px 0 0 $scheduler-appointment-start-color !default; +$scheduler-vertical-appointment-hovering-shadow: inset 0 $scheduler-hovered-appointment-top-shadow-size 0 0 $scheduler-appointment-start-color !default; +$scheduler-vertical-appointment-shadow: inset 0 2px 0 0 $scheduler-appointment-start-color !default; +$scheduler-vertical-appointment-resizing-shadow: inset 0 -2px 0 0 $scheduler-appointment-start-color, $scheduler-vertical-appointment-shadow !default; -$scheduler-appointment-dragging-shadow: 7px 7px 15px 0 rgba(50, 50, 50, 0.2); +$scheduler-appointment-dragging-shadow: 7px 7px 15px 0 rgba(50, 50, 50, 0.2) !default; -$scheduler-horizontal-appointment-resizable-hovering-shadow: inset $scheduler-hovered-appointment-top-shadow-size 0 0 0 $scheduler-appointment-start-color, inset -2px 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-resizable-hovering-shadow-rtl: inset (-$scheduler-hovered-appointment-top-shadow-size) 0 0 0 $scheduler-appointment-start-color, inset 2px 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-hovering-shadow: inset $scheduler-hovered-appointment-top-shadow-size 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-hovering-shadow-rtl: inset (-$scheduler-hovered-appointment-top-shadow-size) 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-shadow: inset 2px 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-shadow-rtl: inset -2px 0 0 0 $scheduler-appointment-start-color; -$scheduler-horizontal-appointment-resizing-shadow: inset -2px 0 0 0 $scheduler-appointment-start-color, $scheduler-horizontal-appointment-shadow; +$scheduler-horizontal-appointment-resizable-hovering-shadow: inset $scheduler-hovered-appointment-top-shadow-size 0 0 0 $scheduler-appointment-start-color, inset -2px 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-resizable-hovering-shadow-rtl: inset (-$scheduler-hovered-appointment-top-shadow-size) 0 0 0 $scheduler-appointment-start-color, inset 2px 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-hovering-shadow: inset $scheduler-hovered-appointment-top-shadow-size 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-hovering-shadow-rtl: inset (-$scheduler-hovered-appointment-top-shadow-size) 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-shadow: inset 2px 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-shadow-rtl: inset -2px 0 0 0 $scheduler-appointment-start-color !default; +$scheduler-horizontal-appointment-resizing-shadow: inset -2px 0 0 0 $scheduler-appointment-start-color, $scheduler-horizontal-appointment-shadow !default; -$scheduler-grouped-appointment-month-text-size: 12px; +$scheduler-grouped-appointment-month-text-size: 12px !default; -$scheduler-base-border: 1px solid; -$scheduler-transparent-top-border: 1px solid transparent; -$scheduler-accent-border: 1px solid; +$scheduler-base-border: 1px solid !default; +$scheduler-transparent-top-border: 1px solid transparent !default; +$scheduler-accent-border: 1px solid !default; -$scheduler-reduced-icon-offset: 5px; -$scheduler-recurrence-icon-offset: 20px; +$scheduler-reduced-icon-offset: 5px !default; +$scheduler-recurrence-icon-offset: 20px !default; -$scheduler-agenda-appointment-margin: 5px; -$scheduler-agenda-last-in-date-appointment-margin: 20px; +$scheduler-agenda-appointment-margin: 5px !default; +$scheduler-agenda-last-in-date-appointment-margin: 20px !default; -$scheduler-small-size-factor: 0.5; +$scheduler-small-size-factor: 0.5 !default; -$scheduler-small-size-element-offset: $scheduler-left-column-width * $scheduler-small-size-factor; +$scheduler-small-size-element-offset: $scheduler-left-column-width * $scheduler-small-size-factor !default; -$scheduler-time-indicator-font-size: 30px; -$scheduler-time-indicator-size: 1px; -$scheduler-time-indicator-top: 14px; -$scheduler-time-indicator-left: 12px; +$scheduler-time-indicator-font-size: 30px !default; +$scheduler-time-indicator-size: 1px !default; +$scheduler-time-indicator-top: 14px !default; +$scheduler-time-indicator-left: 12px !default; -$scheduler-time-indicator-shadow-color: rgba(255, 255, 255, 0.1); -$scheduler-time-indicator-shadow: 0 1px 0 0 $scheduler-time-indicator-shadow-color; -$scheduler-time-indicator-text-shadow: $scheduler-time-indicator-shadow-color 1px 0 0; +$scheduler-time-indicator-shadow-color: rgba(255, 255, 255, 0.1) !default; +$scheduler-time-indicator-shadow: 0 1px 0 0 $scheduler-time-indicator-shadow-color !default; +$scheduler-time-indicator-text-shadow: $scheduler-time-indicator-shadow-color 1px 0 0 !default; @use "./appointment" with ( $scheduler-transparent-top-border: $scheduler-transparent-top-border, diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/layout/_header.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/layout/_header.scss index f58cba3e9c35..76a342a3bfbb 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/layout/_header.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/layout/_header.scss @@ -1,4 +1,4 @@ -$arrow-down: "\f016"; +$arrow-down: "\f016" !default; @mixin header-base( $border, diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss index bdbe38bd195e..dec89907e065 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/_index.scss @@ -28,24 +28,24 @@ $scheduler-first-month-cell-background-color: null !default; $scheduler-panel-text-color: null !default; -$scheduler-timepanel-cell-offset: 20px; -$scheduler-workspace-info-font-size: 20px; -$scheduler-small-font-size: 14px; +$scheduler-timepanel-cell-offset: 20px !default; +$scheduler-workspace-info-font-size: 20px !default; +$scheduler-small-font-size: 14px !default; -$scheduler-current-time-cell-border-size: 2px; +$scheduler-current-time-cell-border-size: 2px !default; -$scheduler-workspace-date-table-cell-height: 50px; -$scheduler-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height * 1.5; -$scheduler-collapsed-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height * 0.5; -$scheduler-vertical-grouped-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height; -$scheduler-header-panel-table-cell-height: $scheduler-workspace-date-table-cell-height - 10px; -$scheduler-group-header-table-cell-height: 30px; +$scheduler-workspace-date-table-cell-height: 50px !default; +$scheduler-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height * 1.5 !default; +$scheduler-collapsed-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height * 0.5 !default; +$scheduler-vertical-grouped-all-day-table-cell-height: $scheduler-workspace-date-table-cell-height !default; +$scheduler-header-panel-table-cell-height: $scheduler-workspace-date-table-cell-height - 10px !default; +$scheduler-group-header-table-cell-height: 30px !default; -$scheduler-group-header-table-cell-width: 80px; +$scheduler-group-header-table-cell-width: 80px !default; -$scheduler-month-date-text-size: 16px; +$scheduler-month-date-text-size: 16px !default; -$scheduler-month-date-text-padding: 6px; +$scheduler-month-date-text-padding: 6px !default; @use "./day" with ( $scheduler-base-border: $scheduler-base-border, diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/agenda/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/agenda/_index.scss index 9602a3aaadc9..61291d143064 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/agenda/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/agenda/_index.scss @@ -4,8 +4,8 @@ $scheduler-small-font-size: null !default; $scheduler-workspace-info-font-size: null !default; $scheduler-other-month-cell-opacity: null !default; -$scheduler-agenda-left-column-width: 70px; -$scheduler-group-header-agenda-font-size: 18px; +$scheduler-agenda-left-column-width: 70px !default; +$scheduler-group-header-agenda-font-size: 18px !default; .dx-scheduler-agenda { .dx-scheduler-date-table-scrollable { diff --git a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss index b8469f2b1e83..8805dcef56c5 100644 --- a/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/scheduler/views/timelines/_index.scss @@ -6,7 +6,7 @@ $scheduler-base-border: null !default; $scheduler-base-border-color: null !default; $scheduler-accent-border-color: null !default; -$scheduler-timeline-min-height: 100px; +$scheduler-timeline-min-height: 100px !default; .dx-scheduler-timeline-week, .dx-scheduler-timeline-work-week, diff --git a/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss b/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss index 95667f2a6758..30e7affaa09d 100644 --- a/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss +++ b/packages/devextreme-scss/scss/widgets/base/treeList/_index.scss @@ -24,15 +24,15 @@ $base-focus-color: null !default; $treelist-focused-border-color: $datagrid-focused-border-color, ); -$treelist-border: $datagrid-border; -$treelist-drag-header-border-color: $datagrid-drag-header-border-color; -$treelist-row-error-bg: $datagrid-row-error-bg; -$treelist-base-color: $datagrid-base-color; -$treelist-base-background-color: $datagrid-base-background-color; -$treelist-border-color: $datagrid-border-color; -$treelist-focused-border-color: $datagrid-focused-border-color; -$treelist-text-stub-background-image-path: $datagrid-text-stub-background-image-path; -$treelist-row-error-color: $datagrid-row-error-color; +$treelist-border: $datagrid-border !default; +$treelist-drag-header-border-color: $datagrid-drag-header-border-color !default; +$treelist-row-error-bg: $datagrid-row-error-bg !default; +$treelist-base-color: $datagrid-base-color !default; +$treelist-base-background-color: $datagrid-base-background-color !default; +$treelist-border-color: $datagrid-border-color !default; +$treelist-focused-border-color: $datagrid-focused-border-color !default; +$treelist-text-stub-background-image-path: $datagrid-text-stub-background-image-path !default; +$treelist-row-error-color: $datagrid-row-error-color !default; .dx-treelist-borders { > .dx-treelist-headers, diff --git a/packages/devextreme-scss/scss/widgets/base/treeView/_common.scss b/packages/devextreme-scss/scss/widgets/base/treeView/_common.scss index 644abfdb53c3..86223bf07535 100644 --- a/packages/devextreme-scss/scss/widgets/base/treeView/_common.scss +++ b/packages/devextreme-scss/scss/widgets/base/treeView/_common.scss @@ -1,7 +1,7 @@ @use "../mixins" as *; @use "../../base/icon_fonts" as *; -$treeview-searchbox-margin-bottom: 5px; +$treeview-searchbox-margin-bottom: 5px !default; .dx-treeview-loadindicator-wrapper { text-align: center; diff --git a/packages/devextreme-scss/scss/widgets/fluent/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/_colors.scss index bbceda66881c..6729d30b88e8 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/_colors.scss @@ -122,8 +122,6 @@ $base-icon-color: null !default; $base-disabled-opacity: 0.38 !default; $base-border-color-disabled: null !default; -$scrollview-pulldown-path: null !default; - @if $color == "blue" { $fluent-color-accent-modificator: "blue" !default; } @@ -194,8 +192,6 @@ $scrollview-pulldown-path: null !default; $base-danger-hover: color.adjust($base-danger, $saturation: -3.05%, $space: hsl, $lightness: -5.10%) !default; $base-danger-active: color.adjust($base-danger, $saturation: -3.56%, $space: hsl, $lightness: -35.69%) !default; $base-danger-selected: color.adjust($base-danger, $saturation: -2.78%, $space: hsl, $lightness: -22.55%) !default; - - $scrollview-pulldown-path: data-uri("images/widgets/fluent/color-schemes/light/pulldown.png") !default; } @if $mode == "dark" { @@ -250,9 +246,8 @@ $scrollview-pulldown-path: null !default; $base-danger-hover: color.adjust($base-danger, $saturation: 0.44%, $space: hsl, $lightness: 7.45%) !default; $base-danger-active: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1.8deg, $saturation: -0.9%, $lightness: 28.8%) !default; - $base-danger-selected: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 0.7deg, $saturation: 1.3%, $lightness: 14.9%); + $base-danger-selected: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 0.7deg, $saturation: 1.3%, $lightness: 14.9%) !default; - $scrollview-pulldown-path: data-uri("images/widgets/fluent/color-schemes/dark/pulldown.png") !default; } /** diff --git a/packages/devextreme-scss/scss/widgets/fluent/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/_sizes.scss index a4017a22e04a..70129095fd6d 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/_sizes.scss @@ -7,7 +7,7 @@ $fluent-base-inline-block-height: null !default; $fluent-base-icon-size: null !default; $fluent-base-icon-margin: 8px !default; $fluent-base-inline-horizontal-padding: null !default; -$fluent-base-border-radius: null !default; +$fluent-base-border-radius: 4px !default; $fluent-html-editor-add-image-dialog-base-padding: 5px !default; /** @@ -39,7 +39,6 @@ $fluent-button-icon-margin: null !default; $fluent-base-inline-block-height: 20px !default; $fluent-base-icon-size: 20px !default; $fluent-base-inline-horizontal-padding: 12px !default; - $fluent-base-border-radius: 4px !default; $fluent-button-height: 32px !default; $fluent-button-icon-size: 20px !default; $fluent-button-icon-margin: 8px !default; @@ -56,7 +55,6 @@ $fluent-button-icon-margin: null !default; $fluent-base-inline-block-height: 16px !default; $fluent-base-icon-size: 16px !default; $fluent-base-inline-horizontal-padding: 8px !default; - $fluent-base-border-radius: 4px !default; $fluent-button-height: 24px !default; $fluent-button-icon-size: 16px !default; $fluent-button-icon-margin: 4px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/accordion/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/accordion/_sizes.scss index 176f3edb5ad4..78b26f936b03 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/accordion/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/accordion/_sizes.scss @@ -13,7 +13,7 @@ $accordion-item-border-radius: $fluent-base-border-radius !default; * $name 10. Item expanded margin * $type text */ -$fluent-accordion-item-opened-margin: null !default; +$fluent-accordion-item-opened-margin: 0 !default; @if $size == "default" { $fluent-accordion-title-horizontal-padding: 12px !default; @@ -21,7 +21,6 @@ $fluent-accordion-item-opened-margin: null !default; $fluent-accordion-title-height: 44px !default; $accordion-title-icon-margin: 8px !default; $accordion-title-icon-size: 20px !default; - $fluent-accordion-item-opened-margin: 0 !default; } @else if $size == "compact" { @@ -30,5 +29,4 @@ $fluent-accordion-item-opened-margin: null !default; $fluent-accordion-title-height: 32px !default; $accordion-title-icon-margin: 6px !default; $accordion-title-icon-size: 16px !default; - $fluent-accordion-item-opened-margin: 0 !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/actionSheet/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/actionSheet/_sizes.scss index 5694e65f75d3..15fdcab8742f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/actionSheet/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/actionSheet/_sizes.scss @@ -1,2 +1,2 @@ -$fluent-actionsheet-item-margin-value: 12px; +$fluent-actionsheet-item-margin-value: 12px !default; $fluent-actionsheet-item-margin: 0 0 $fluent-actionsheet-item-margin-value 0 !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/badge/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/badge/_sizes.scss index 2c99c408f03a..59af08ff7a51 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/badge/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/badge/_sizes.scss @@ -1,17 +1,15 @@ @use "../sizes" as *; -$fluent-badge-padding: null !default; +$fluent-badge-padding: 0 6px !default; $fluent-badge-font-size: null !default; $badge-line-height: null !default; @if $size == "default" { - $fluent-badge-padding: 0 6px !default; $fluent-badge-font-size: 12px !default; $badge-line-height: 20px !default; } @else if $size == "compact" { - $fluent-badge-padding: 0 6px !default; $fluent-badge-font-size: 10px !default; $badge-line-height: 16px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/button/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/button/_colors.scss index de2be8586ff2..354fabaa5455 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/button/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/button/_colors.scss @@ -84,13 +84,13 @@ $button-default-selected-color: null !default; * $name 30. Hover state background color * $type color */ -$button-default-hover-bg: null !default; +$button-default-hover-bg: $base-accent-hover !default; /** * $name 40. Focused state background color * $type color */ -$button-default-focused-bg: null !default; +$button-default-focused-bg: $base-accent-hover !default; /** * $name 50. Active state background color @@ -113,7 +113,6 @@ $button-default-outlined-border-color: $base-accent !default; $button-default-outlined-bg: transparent !default; $button-default-outlined-hover-bg: null !default; -$button-default-outlined-focused-bg: null !default; $button-default-outlined-active-bg: null !default; $button-default-outlined-selected-bg: null !default; @@ -168,11 +167,10 @@ $button-danger-selected-bg: $base-danger-selected !default; $button-danger-outlined-color: $base-danger !default; $button-danger-outlined-border-color: $base-danger !default; $button-danger-outlined-color-hover: null !default; -$button-danger-outlined-color-active: null !default; +$button-danger-outlined-color-active: $base-danger-active !default; $button-danger-outlined-selected-color: null !default; $button-danger-outlined-bg: transparent !default; $button-danger-outlined-hover-bg: null !default; -$button-danger-outlined-focused-bg: null !default; $button-danger-outlined-active-bg: null !default; $button-danger-outlined-selected-bg: null !default; @@ -200,12 +198,6 @@ $button-success-selected-color: null !default; */ $button-success-hover-bg: null !default; -/** -* $name 40. Focused state background color -* $type color -*/ -$button-success-focused-bg: null !default; - /** * $name 50. Active state background color * $type color @@ -222,10 +214,8 @@ $button-success-outlined-color: $base-success !default; $button-success-outlined-border-color: $base-success !default; $button-success-outlined-color-hover: null !default; $button-success-outlined-color-active: null !default; -$button-success-outlined-selected-color: null !default; $button-success-outlined-bg: transparent !default; $button-success-outlined-hover-bg: null !default; -$button-success-outlined-focused-bg: null !default; $button-success-outlined-active-bg: null !default; $button-success-outlined-selected-bg: null !default; @@ -233,14 +223,10 @@ $button-accent-foreground-color: null !default; @if $mode == "light" { $button-accent-foreground-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -2deg, $saturation: -9.10%, $lightness: -23.53%) !default; - - $button-default-hover-bg: $base-accent-hover !default; - $button-default-focused-bg: $base-accent-hover !default; $button-default-active-bg: $base-accent-active !default; $button-default-selected-bg: $base-accent-selected !default; $button-success-hover-bg: color.adjust($base-success, $saturation: 0.63%, $space: hsl, $lightness: -2.75%) !default; - $button-success-focused-bg: $button-success-hover-bg !default; $button-success-active-bg: color.adjust($base-success, $saturation: -0.95%, $space: hsl, $lightness: -19.22%) !default; $button-success-selected-bg: color.adjust($base-success, $saturation: -0.22%, $space: hsl, $lightness: -12.16%) !default; @@ -250,12 +236,10 @@ $button-accent-foreground-color: null !default; $button-default-outlined-active-bg: extcolor.adjust-if-not-achromatic($color: $button-default-outlined-hover-bg, $hue: -1deg, $saturation: 17.00%, $lightness: -17.06%) !default; $button-danger-outlined-hover-bg: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1.5deg, $saturation: 0.6%, $lightness: 46.7%) !default; - $button-danger-outlined-focused-bg: $button-danger-outlined-hover-bg !default; $button-danger-outlined-active-bg: extcolor.adjust-if-not-achromatic($color: $button-danger-outlined-hover-bg, $hue: 358.9deg, $saturation: 2.2%, $lightness: -13.9%) !default; $button-danger-outlined-selected-bg: extcolor.adjust-if-not-achromatic($color: $button-danger-outlined-active-bg, $hue: 359.1deg, $saturation: -0.6%, $lightness: 7.1%) !default; $button-success-outlined-hover-bg: color.adjust($base-success, $saturation: -29.8%, $space: hsl, $lightness: 68.8%) !default; - $button-success-outlined-focused-bg: $button-success-outlined-hover-bg !default; $button-success-outlined-active-bg: color.adjust($base-success, $saturation: -34.9%, $space: hsl, $lightness: 46.1%) !default; $button-success-outlined-selected-bg: color.adjust($base-success, $saturation: -35%, $space: hsl, $lightness: 57.6%) !default; } @@ -266,25 +250,19 @@ $button-accent-foreground-color: null !default; $button-default-outlined-active-bg: color.adjust($button-default-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; $button-danger-outlined-hover-bg: color.mix($base-bg, $base-danger, 90%) !default; - $button-danger-outlined-focused-bg: $button-danger-outlined-hover-bg !default; $button-danger-outlined-active-bg: color.adjust($button-danger-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; $button-danger-outlined-selected-bg: color.adjust($button-danger-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; $button-success-outlined-hover-bg: color.mix($base-bg, $base-success, 90%) !default; - $button-success-outlined-focused-bg: $button-success-outlined-hover-bg !default; $button-success-outlined-active-bg: color.adjust($button-success-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; $button-success-outlined-selected-bg: color.adjust($button-success-outlined-hover-bg, $lightness: -4%, $space: hsl) !default; } - $button-default-outlined-focused-bg: $button-default-outlined-hover-bg !default; - $button-danger-outlined-color-hover: $base-danger-selected !default; - $button-danger-outlined-color-active: $base-danger-active !default; $button-danger-outlined-selected-color: $base-danger-selected !default; $button-success-outlined-color-hover: color.adjust($base-success, $saturation: -0.22%, $space: hsl, $lightness: -12.16%) !default; $button-success-outlined-color-active: color.adjust($base-success, $saturation: -0.95%, $space: hsl, $lightness: -19.22%) !default; - $button-success-outlined-selected-color: $button-success-outlined-color-hover; $button-default-color: $base-inverted-text-color !default; $button-default-selected-color: $base-inverted-text-color !default; @@ -297,37 +275,28 @@ $button-accent-foreground-color: null !default; @if $mode == "dark" { $button-accent-foreground-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 0.9deg, $saturation: -2.2%, $lightness: 22.4%) !default; - - $button-default-hover-bg: $base-accent-hover !default; - $button-default-focused-bg: $base-accent-hover !default; $button-default-active-bg: $button-accent-foreground-color !default; $button-default-selected-bg: color.adjust($button-default-active-bg, $saturation: -6.4%, $space: hsl, $lightness: 5.3%) !default; $button-success-hover-bg: color.adjust($base-success, $saturation: 5.4%, $space: hsl, $lightness: 22.5%) !default; - $button-success-focused-bg: $button-success-hover-bg !default; $button-success-active-bg: color.adjust($base-success, $saturation: 10.6%, $space: hsl, $lightness: 45.3%) !default; $button-success-selected-bg: color.adjust($base-success, $saturation: 5.3%, $space: hsl, $lightness: 34.1%) !default; $button-default-outlined-hover-bg: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 356.3deg, $saturation: -14.7%, $lightness: -49.4%) !default; - $button-default-outlined-focused-bg: $button-default-outlined-hover-bg !default; $button-default-outlined-active-bg: $base-accent-selected !default; $button-default-outlined-selected-bg: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -4deg, $saturation: -12.33%, $lightness: -41.18%) !default; $button-danger-outlined-color-hover: $base-danger-active !default; - $button-danger-outlined-color-active: $base-danger-active !default; $button-danger-outlined-selected-color: $base-danger-active !default; $button-danger-outlined-hover-bg: color.adjust($base-danger, $saturation: -5.1%, $space: hsl, $lightness: -53.5%) !default; - $button-danger-outlined-focused-bg: $button-danger-outlined-hover-bg !default; $button-danger-outlined-active-bg: color.adjust($base-danger, $saturation: -4.6%, $space: hsl, $lightness: -22.9%) !default; $button-danger-outlined-selected-bg: color.adjust($base-danger, $saturation: -4.3%, $space: hsl, $lightness: -40.4%) !default; $button-success-outlined-color-hover: color.adjust($base-success, $saturation: 10.6%, $space: hsl, $lightness: 45.3%) !default; $button-success-outlined-color-active: $button-success-outlined-color-hover !default; - $button-success-outlined-selected-color: $button-success-outlined-color-hover !default; $button-success-outlined-hover-bg: color.adjust($base-success, $saturation: 39.4%, $space: hsl, $lightness: -42.7%) !default; - $button-success-outlined-focused-bg: $button-success-outlined-hover-bg !default; $button-success-outlined-active-bg: color.adjust($base-success, $saturation: 39%, $space: hsl, $lightness: -33.1%) !default; $button-success-outlined-selected-bg: color.adjust($base-success, $saturation: 40.1%, $space: hsl, $lightness: -35.7%) !default; @@ -340,6 +309,16 @@ $button-accent-foreground-color: null !default; $button-shadow-color: color.change($base-shadow-color, $alpha: 0.4) !default; } +/** +* $name 40. Focused state background color +* $type color +*/ +$button-success-focused-bg: $button-success-hover-bg !default; +$button-danger-outlined-focused-bg: $button-danger-outlined-hover-bg !default; +$button-success-outlined-focused-bg: $button-success-outlined-hover-bg !default; +$button-default-outlined-focused-bg: $button-default-outlined-hover-bg !default; +$button-success-outlined-selected-color: $button-success-outlined-color-hover !default; + $button-default-icon-color: $button-default-color !default; $button-normal-icon-color: $button-normal-color !default; $button-danger-icon-color: $button-danger-color !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/button/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/button/_sizes.scss index 72d7c8a9e8cd..44ca7a7ee796 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/button/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/button/_sizes.scss @@ -2,8 +2,6 @@ $fluent-button-horizontal-padding: null !default; $fluent-button-vertical-padding: null !default; -$fluent-button-icon-padding: null !default; -$fluent-button-icon-text-padding: null !default; $fluent-button-content-line-height: 0 !default; $fluent-button-text-line-height: normal !default; $fluent-button-text-transform: none !default; @@ -15,17 +13,15 @@ $fluent-button-box-shadow-size: 0 1px 3px !default; @if $size == "default" { $fluent-button-horizontal-padding: 12px !default; $fluent-button-vertical-padding: 6px !default; - $fluent-button-icon-padding: 6px; - $fluent-button-icon-text-padding: $fluent-button-horizontal-padding !default; } @else if $size == "compact" { $fluent-button-horizontal-padding: 8px !default; $fluent-button-vertical-padding: 4px !default; - $fluent-button-icon-padding: 4px !default; - $fluent-button-icon-text-padding: $fluent-button-horizontal-padding !default; } +$fluent-button-icon-text-padding: $fluent-button-horizontal-padding !default; + :root { --dx-button-padding-inline: #{$fluent-button-horizontal-padding}; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/calendar/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/calendar/_colors.scss index 6fdc41b02872..b24780264b8f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/calendar/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/calendar/_colors.scss @@ -24,7 +24,6 @@ $calendar-header-color: $calendar-color !default; $calendar-cell-other-color: null !default; $calendar-bg: transparent !default; $calendar-hover-bg: null !default; -$calendar-cell-hover-border-color: null !default; $calendar-cell-hover-color: $calendar-color !default; /** @@ -44,7 +43,6 @@ $calendar-week-number-border-radius: 0 !default; @if $mode == "light" { $calendar-hover-bg: $button-default-outlined-selected-bg !default; - $calendar-cell-hover-border-color: $calendar-hover-bg !default; $calendar-cell-active-bg: $button-default-outlined-active-bg !default; $calendar-cell-other-color: color.adjust($calendar-color, $lightness: 23.92%, $space: hsl) !default; $calendar-week-number-cell-color: color.adjust($calendar-color, $lightness: 23.92%, $space: hsl) !default; @@ -54,7 +52,6 @@ $calendar-week-number-border-radius: 0 !default; @if $mode == "dark" { $calendar-hover-bg: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 358.4deg, $saturation: -8.6%, $lightness: -26.7%) !default; - $calendar-cell-hover-border-color: $calendar-hover-bg !default; $calendar-cell-active-bg: $base-accent-selected !default; $calendar-cell-other-color: color.adjust($calendar-color, $lightness: -32.16%, $space: hsl) !default; $calendar-week-number-cell-color: color.adjust($calendar-color, $lightness: -32.16%, $space: hsl) !default; @@ -62,4 +59,6 @@ $calendar-week-number-border-radius: 0 !default; $calendar-week-number-cell-bg: $base-hover-bg !default; } +$calendar-cell-hover-border-color: $calendar-hover-bg !default; + $calendar-cell-other-hover-color: $calendar-cell-other-color !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss index 7c1d3a4c5b92..7d0f1ab8613e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/cardView/_colors.scss @@ -19,19 +19,19 @@ * $name 100. Background color * $type color */ -$cardview-fluent-common-bg-color: null !default; +$cardview-fluent-common-bg-color: $base-typography-bg !default; /** * $name 102. Link color * $type color */ -$cardview-fluent-common-link-color: null !default; +$cardview-fluent-common-link-color: $base-link-color !default; /** * $name 103. Text color * $type color */ -$cardview-fluent-common-text-color: null !default; +$cardview-fluent-common-text-color: $base-text-color !default; // #endregion @@ -41,31 +41,31 @@ $cardview-fluent-common-text-color: null !default; * $name 200. Background color * $type color */ -$cardview-fluent-card-bg-color: null !default; +$cardview-fluent-card-bg-color: $base-bg !default; /** * $name 201. Border color * $type color */ -$cardview-fluent-card-border-color: null !default; +$cardview-fluent-card-border-color: $base-border-color !default; /** * $name 203. Divider color * $type color */ -$cardview-fluent-card-divider-color: null !default; +$cardview-fluent-card-divider-color: $cardview-fluent-card-border-color !default; /** * $name 204. Text color * $type color */ -$cardview-fluent-card-text-color: null !default; +$cardview-fluent-card-text-color: $cardview-fluent-common-text-color !default; /** * $name 206. Cover background color * $type color */ -$cardview-fluent-card__cover-bg-color: null !default; +$cardview-fluent-card__cover-bg-color: #FFF !default; /** * $name 207. "Empty cover" background color @@ -79,7 +79,7 @@ $cardview-fluent-card__cover--noimage-bg-color: null !default; */ $cardview-fluent-card__cover--noimage__icon-color: null !default; -$cardview-fluent-card--focused-outline-color: null !default; +$cardview-fluent-card--focused-outline-color: $base-accent !default; // #endregion @@ -97,35 +97,17 @@ $cardview-fluent-card--selected-bg-color: null !default; */ $cardview-fluent-card--selected-border-color: null !default; -/** -* $name 302. Divider color -* $type color -*/ -$cardview-fluent-card--selected-divider-color: null !default; - /** * $name 303. Text color * $type color */ -$cardview-fluent-card--selected-text-color: null !default; +$cardview-fluent-card--selected-text-color: $cardview-fluent-card-text-color !default; /** * $name 305. Cover background color * $type color */ -$cardview-fluent-card--selected__cover-bg-color: null !default; - -/** -* $name 306. "Empty cover" background color -* $type color -*/ -$cardview-fluent-card--selected__cover--noimage-bg-color: null !default; - -/** -* $name 307. "Empty cover" icon color -* $type color -*/ -$cardview-fluent-card--selected__cover--noimage__icon-color: null !default; +$cardview-fluent-card--selected__cover-bg-color: $cardview-fluent-card__cover-bg-color !default; // #endregion @@ -141,33 +123,33 @@ $cardview-fluent-header-panel__item-bg-color: null !default; * $name 401. Border color * $type color */ -$cardview-fluent-header-panel__item-border-color: null !default; +$cardview-fluent-header-panel__item-border-color: $base-border-color !default; /** * $name 403. Text color * $type color */ -$cardview-fluent-header-panel__item-text-color: null !default; +$cardview-fluent-header-panel__item-text-color: $cardview-fluent-common-text-color !default; /** * $name 405. Icon color * $type color */ -$cardview-fluent-header-panel__item__icon-color: null !default; +$cardview-fluent-header-panel__item__icon-color: $cardview-fluent-common-text-color !default; /** * $name 406. Active "Filter" icon color * $type color */ -$cardview-fluent-header-panel__item__header-filter-icon--selected-color: null !default; +$cardview-fluent-header-panel__item__header-filter-icon--selected-color: $base-accent !default; /** * $name 407. Sort index text color * $type color */ -$cardview-fluent-header-panel__item__sort-index-text-color: null !default; +$cardview-fluent-header-panel__item__sort-index-text-color: $cardview-fluent-common-text-color !default; -$cardview-fluent-header-panel__item--focused-outline-color: null !default; +$cardview-fluent-header-panel__item--focused-outline-color: $base-accent !default; // #endregion @@ -213,17 +195,11 @@ $cardview-fluent-header-panel__item--hovered__sort-index-text-color: null !defau // #region HEADER PANEL ITEM (DISABLED) -/** -* $name 600. Background color -* $type color -*/ -$cardview-fluent-header-panel__item--disabled-bg-color: null !default; - /** * $name 601. Border color * $type color */ -$cardview-fluent-header-panel__item--disabled-border-color: null !default; +$cardview-fluent-header-panel__item--disabled-border-color: $cardview-fluent-header-panel__item-border-color !default; /** * $name 602. Text color @@ -253,29 +229,23 @@ $cardview-fluent-header-panel__item--disabled__sort-index-text-color: null !defa // #region HEADER PANEL ITEM (ON DRAG) -/** -* $name 700. Background color -* $type color -*/ -$cardview-fluent-header-panel__item--dragged-bg-color: null !default; - /** * $name 701. Border color * $type color */ -$cardview-fluent-header-panel__item--dragged-border-color: null !default; +$cardview-fluent-header-panel__item--dragged-border-color: $cardview-fluent-header-panel__item-border-color !default; /** * $name 702. Text color * $type color */ -$cardview-fluent-header-panel__item--dragged-text-color: null !default; +$cardview-fluent-header-panel__item--dragged-text-color: $cardview-fluent-header-panel__item-text-color !default; /** * $name 704. "Drag allowed" icon color * $type color */ -$cardview-fluent-header-panel__item--dragged__allowance-icon-color: null !default; +$cardview-fluent-header-panel__item--dragged__allowance-icon-color: $cardview-fluent-common-text-color !default; /** * $name 705. "Drag blocked" icon color @@ -287,7 +257,7 @@ $cardview-fluent-header-panel__item--dragged__prohibition-icon-color: null !defa * $name 706. Box shadow color * $type color */ -$cardview-fluent-header-panel__item--dragged-box-shadow-color: null !default; +$cardview-fluent-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // #endregion @@ -303,19 +273,19 @@ $cardview-fluent-header-panel__dropzone-bg-color: null !default; * $name 802. Border color * $type color */ -$cardview-fluent-header-panel__dropzone-border-color: null !default; +$cardview-fluent-header-panel__dropzone-border-color: $base-accent !default; /** * $name 803. Text color * $type color */ -$cardview-fluent-header-panel__dropzone-text-color: null !default; +$cardview-fluent-header-panel__dropzone-text-color: $base-accent !default; /** * $name 804. Icon color * $type color */ -$cardview-fluent-header-panel__dropzone__icon-color: null !default; +$cardview-fluent-header-panel__dropzone__icon-color: $base-accent !default; // #endregion @@ -325,25 +295,25 @@ $cardview-fluent-header-panel__dropzone__icon-color: null !default; * $name 900. Background color * $type color */ -$cardview-fluent-filter-panel-bg-color: null !default; +$cardview-fluent-filter-panel-bg-color: $cardview-fluent-common-bg-color !default; /** * $name 902. Border color * $type color */ -$cardview-fluent-filter-panel-border-color: null !default; +$cardview-fluent-filter-panel-border-color: $base-border-color !default; /** * $name 903. Text color * $type color */ -$cardview-fluent-filter-panel-text-color: null !default; +$cardview-fluent-filter-panel-text-color: $base-accent !default; /** * $name 904. Icon color * $type color */ -$cardview-fluent-filter-panel__icon-color: null !default; +$cardview-fluent-filter-panel__icon-color: $base-accent !default; // #endregion @@ -353,7 +323,7 @@ $cardview-fluent-filter-panel__icon-color: null !default; * $name 1000. Background color * $type color */ -$cardview-fluent-nodata-view-bg-color: null !default; +$cardview-fluent-nodata-view-bg-color: $cardview-fluent-common-bg-color !default; /** * $name 1001. Text color @@ -381,7 +351,7 @@ $cardview-fluent-nodata-view__icon-bg-color: null !default; * $name 1100. Background color * $type color */ -$cardview-fluent-card__content__field-value--highlighted-bg-color: null !default; +$cardview-fluent-card__content__field-value--highlighted-bg-color: $base-warning !default; /** * $name 1102. Text color @@ -393,52 +363,28 @@ $cardview-fluent-card__content__field-value--highlighted-text-color: null !defau * $name 1104. Box shadow color * $type color */ -$cardview-fluent-card__content__field-value--highlighted-box-shadow-color: null !default; +$cardview-fluent-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; // #endregion // #region COLUMN CHOOSER -$cardview-fluent-columnchooser--empty__message-color: null !default; +$cardview-fluent-columnchooser--empty__message-color: color.adjust($cardview-fluent-common-text-color, $lightness: 26%, $space: hsl) !default; // #endregion @if $mode == "light" { - // Common - $cardview-fluent-common-bg-color: $base-typography-bg !default; - $cardview-fluent-common-link-color: $base-link-color !default; - $cardview-fluent-common-text-color: $base-text-color !default; - // Card - $cardview-fluent-card-bg-color: $base-bg !default; - $cardview-fluent-card-border-color: $base-border-color !default; - $cardview-fluent-card-divider-color: $cardview-fluent-card-border-color !default; - $cardview-fluent-card-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-card__cover-bg-color: #FFF !default; $cardview-fluent-card__cover--noimage-bg-color: color.adjust($base-bg, $lightness: -12.16%, $space: hsl) !default; $cardview-fluent-card__cover--noimage__icon-color: color.adjust($cardview-fluent-common-text-color, $lightness: 23.92%, $space: hsl) !default; - $cardview-fluent-card--focused-outline-color: $base-accent !default; - // Card - selected $cardview-fluent-card--selected-bg-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 4deg, $saturation: -11.38%, $lightness: 55.49%) !default; $cardview-fluent-card--selected-border-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 3deg, $saturation: 2.21%, $lightness: 44.31%) !default; - $cardview-fluent-card--selected-divider-color: $cardview-fluent-card--selected-border-color !default; - $cardview-fluent-card--selected-text-color: $cardview-fluent-card-text-color !default; - $cardview-fluent-card--selected__cover-bg-color: $cardview-fluent-card__cover-bg-color !default; - $cardview-fluent-card--selected__cover--noimage-bg-color: $cardview-fluent-card__cover--noimage-bg-color !default; - $cardview-fluent-card--selected__cover--noimage__icon-color: $cardview-fluent-card__cover--noimage__icon-color !default; // Header panel item - default state $cardview-fluent-header-panel__item-bg-color: color.adjust($base-bg, $lightness: -5.88%, $space: hsl) !default; - $cardview-fluent-header-panel__item-border-color: $base-border-color !default; - $cardview-fluent-header-panel__item-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-header-panel__item__icon-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-header-panel__item__header-filter-icon--selected-color: $base-accent !default; - $cardview-fluent-header-panel__item__sort-index-text-color: $cardview-fluent-common-text-color !default; - - $cardview-fluent-header-panel__item--focused-outline-color: $base-accent !default; // Header panel item - hovered $cardview-fluent-header-panel__item--hovered-bg-color: color.adjust($cardview-fluent-header-panel__item-bg-color, $lightness: -1.96%, $space: hsl) !default; @@ -449,84 +395,38 @@ $cardview-fluent-columnchooser--empty__message-color: null !default; $cardview-fluent-header-panel__item--hovered__sort-index-text-color: $cardview-fluent-header-panel__item__sort-index-text-color !default; // Header panel item - disabled - $cardview-fluent-header-panel__item--disabled-bg-color: $cardview-fluent-header-panel__item-bg-color !default; - $cardview-fluent-header-panel__item--disabled-border-color: $cardview-fluent-header-panel__item-border-color !default; $cardview-fluent-header-panel__item--disabled-text-color: color.adjust($cardview-fluent-header-panel__item-text-color, $lightness: 60.00%, $space: hsl) !default; $cardview-fluent-header-panel__item--disabled__icon-color: color.adjust($cardview-fluent-header-panel__item__icon-color, $lightness: 60.00%, $space: hsl) !default; $cardview-fluent-header-panel__item--disabled__header-filter-icon--selected-color: color.adjust($cardview-fluent-header-panel__item__header-filter-icon--selected-color, $saturation: -34.10%, $space: hsl, $lightness: 27.06%) !default; $cardview-fluent-header-panel__item--disabled__sort-index-text-color: color.adjust($cardview-fluent-header-panel__item__sort-index-text-color, $lightness: 60.00%, $space: hsl) !default; // Header panel item - dragged - $cardview-fluent-header-panel__item--dragged-bg-color: $cardview-fluent-header-panel__item-bg-color !default; - $cardview-fluent-header-panel__item--dragged-border-color: $cardview-fluent-header-panel__item-border-color !default; - $cardview-fluent-header-panel__item--dragged-text-color: $cardview-fluent-header-panel__item-text-color !default; - $cardview-fluent-header-panel__item--dragged__allowance-icon-color: $cardview-fluent-common-text-color !default; $cardview-fluent-header-panel__item--dragged__prohibition-icon-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -4deg, $saturation: 22.80%, $lightness: -9.61%) !default; - $cardview-fluent-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // Header panel Drop-zone $cardview-fluent-header-panel__dropzone-bg-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 4deg, $saturation: -11.38%, $lightness: 55.49%) !default; - $cardview-fluent-header-panel__dropzone-border-color: $base-accent !default; - $cardview-fluent-header-panel__dropzone-text-color: $base-accent !default; - $cardview-fluent-header-panel__dropzone__icon-color: $base-accent !default; - - // Filter panel - $cardview-fluent-filter-panel-bg-color: $cardview-fluent-common-bg-color !default; - $cardview-fluent-filter-panel-border-color: $base-border-color !default; - $cardview-fluent-filter-panel-text-color: $base-accent !default; - $cardview-fluent-filter-panel__icon-color: $base-accent !default; // Empty view - $cardview-fluent-nodata-view-bg-color: $cardview-fluent-common-bg-color !default; $cardview-fluent-nodata-view-text-color: color.adjust($cardview-fluent-common-text-color, $lightness: 23.92%, $space: hsl) !default; $cardview-fluent-nodata-view__icon-color: $cardview-fluent-nodata-view-text-color !default; $cardview-fluent-nodata-view__icon-bg-color: color.adjust($base-bg, $lightness: -9.80%, $space: hsl) !default; // Search highlight - $cardview-fluent-card__content__field-value--highlighted-bg-color: $base-warning !default; $cardview-fluent-card__content__field-value--highlighted-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; - - // Column chooser - $cardview-fluent-columnchooser--empty__message-color: color.adjust($cardview-fluent-common-text-color, $lightness: 26%, $space: hsl) !default; } @if $mode == "dark" { - // Common - $cardview-fluent-common-bg-color: $base-typography-bg !default; - $cardview-fluent-common-link-color: $base-link-color !default; - $cardview-fluent-common-text-color: $base-text-color !default; - // Card - $cardview-fluent-card-bg-color: $base-bg !default; - $cardview-fluent-card-border-color: $base-border-color !default; - $cardview-fluent-card-divider-color: $cardview-fluent-card-border-color !default; - $cardview-fluent-card-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-card__cover-bg-color: #FFF !default; $cardview-fluent-card__cover--noimage-bg-color: color.adjust($base-bg, $lightness: -5.88%, $space: hsl) !default; $cardview-fluent-card__cover--noimage__icon-color: color.adjust($base-text-color, $lightness: -32.16%, $space: hsl) !default; - $cardview-fluent-card--focused-outline-color: $base-accent !default; - // Card - selected $cardview-fluent-card--selected-bg-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -4deg, $saturation: -14.69%, $lightness: -49.41%) !default; $cardview-fluent-card--selected-border-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -3deg, $saturation: -11.06%, $lightness: -36.27%) !default; - $cardview-fluent-card--selected-divider-color: $cardview-fluent-card--selected-border-color !default; - $cardview-fluent-card--selected-text-color: $cardview-fluent-card-text-color !default; - $cardview-fluent-card--selected__cover-bg-color: $cardview-fluent-card__cover-bg-color !default; - $cardview-fluent-card--selected__cover--noimage-bg-color: $cardview-fluent-card__cover--noimage-bg-color !default; - $cardview-fluent-card--selected__cover--noimage__icon-color: $cardview-fluent-card__cover--noimage__icon-color !default; // Header panel item - default state $cardview-fluent-header-panel__item-bg-color: color.adjust($base-bg, $lightness: -12.16%, $space: hsl) !default; - $cardview-fluent-header-panel__item-border-color: $base-border-color !default; - $cardview-fluent-header-panel__item-text-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-header-panel__item__icon-color: $cardview-fluent-common-text-color !default; - $cardview-fluent-header-panel__item__header-filter-icon--selected-color: $base-accent !default; - $cardview-fluent-header-panel__item__sort-index-text-color: $cardview-fluent-common-text-color !default; - - $cardview-fluent-header-panel__item--focused-outline-color: $base-accent !default; // Header panel item - hovered $cardview-fluent-header-panel__item--hovered-bg-color: color.adjust($cardview-fluent-header-panel__item-bg-color, $lightness: 18.04%, $space: hsl) !default; @@ -537,48 +437,56 @@ $cardview-fluent-columnchooser--empty__message-color: null !default; $cardview-fluent-header-panel__item--hovered__sort-index-text-color: $cardview-fluent-common-text-color !default; // Header panel item - disabled - $cardview-fluent-header-panel__item--disabled-bg-color: $cardview-fluent-header-panel__item-bg-color !default; - $cardview-fluent-header-panel__item--disabled-border-color: $cardview-fluent-header-panel__item-border-color !default; $cardview-fluent-header-panel__item--disabled-text-color: color.adjust($cardview-fluent-header-panel__item-text-color, $lightness: -56.08%, $space: hsl) !default; $cardview-fluent-header-panel__item--disabled__icon-color: color.adjust($cardview-fluent-header-panel__item__icon-color, $lightness: -56.08%, $space: hsl) !default; $cardview-fluent-header-panel__item--disabled__header-filter-icon--selected-color: color.adjust($cardview-fluent-header-panel__item__header-filter-icon--selected-color, $saturation: -6.72%, $space: hsl, $lightness: -18.04%) !default; $cardview-fluent-header-panel__item--disabled__sort-index-text-color: color.adjust($cardview-fluent-header-panel__item__sort-index-text-color, $lightness: -56.08%, $space: hsl) !default; // Header panel item - dragged - $cardview-fluent-header-panel__item--dragged-bg-color: $cardview-fluent-header-panel__item-bg-color !default; - $cardview-fluent-header-panel__item--dragged-border-color: $cardview-fluent-header-panel__item-border-color !default; - $cardview-fluent-header-panel__item--dragged-text-color: $cardview-fluent-header-panel__item-text-color !default; - $cardview-fluent-header-panel__item--dragged__allowance-icon-color: $cardview-fluent-common-text-color !default; $cardview-fluent-header-panel__item--dragged__prohibition-icon-color: color.adjust($base-danger, $saturation: -0.27%, $space: hsl, $lightness: -7.45%) !default; - $cardview-fluent-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // Header panel Drop-zone $cardview-fluent-header-panel__dropzone-bg-color: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -4deg, $saturation: -14.69%, $lightness: -49.41%) !default; - $cardview-fluent-header-panel__dropzone-border-color: $base-accent !default; - $cardview-fluent-header-panel__dropzone-text-color: $base-accent !default; - $cardview-fluent-header-panel__dropzone__icon-color: $base-accent !default; - - // Filter panel - $cardview-fluent-filter-panel-bg-color: $cardview-fluent-common-bg-color !default; - $cardview-fluent-filter-panel-border-color: $base-border-color !default; - $cardview-fluent-filter-panel-text-color: $base-accent !default; - $cardview-fluent-filter-panel__icon-color: $base-accent !default; // Empty view - $cardview-fluent-nodata-view-bg-color: $cardview-fluent-common-bg-color !default; $cardview-fluent-nodata-view-text-color: color.adjust($cardview-fluent-common-text-color, $lightness: -32.16%, $space: hsl) !default; $cardview-fluent-nodata-view__icon-color: color.adjust($cardview-fluent-common-text-color, $lightness: -32.16%, $space: hsl) !default; $cardview-fluent-nodata-view__icon-bg-color: color.adjust($base-bg, $lightness: 3.92%, $space: hsl) !default; // Search highlight - $cardview-fluent-card__content__field-value--highlighted-bg-color: $base-warning !default; $cardview-fluent-card__content__field-value--highlighted-text-color: $base-inverted-text-color !default; - $cardview-fluent-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; - - // Column chooser - $cardview-fluent-columnchooser--empty__message-color: color.adjust($cardview-fluent-common-text-color, $lightness: 26%, $space: hsl) !default; } +/** +* $name 302. Divider color +* $type color +*/ +$cardview-fluent-card--selected-divider-color: $cardview-fluent-card--selected-border-color !default; + +/** +* $name 306. "Empty cover" background color +* $type color +*/ +$cardview-fluent-card--selected__cover--noimage-bg-color: $cardview-fluent-card__cover--noimage-bg-color !default; + +/** +* $name 307. "Empty cover" icon color +* $type color +*/ +$cardview-fluent-card--selected__cover--noimage__icon-color: $cardview-fluent-card__cover--noimage__icon-color !default; + +/** +* $name 600. Background color +* $type color +*/ +$cardview-fluent-header-panel__item--disabled-bg-color: $cardview-fluent-header-panel__item-bg-color !default; + +/** +* $name 700. Background color +* $type color +*/ +$cardview-fluent-header-panel__item--dragged-bg-color: $cardview-fluent-header-panel__item-bg-color !default; + $cardview-bg-color: $cardview-fluent-common-bg-color !default; $cardview-link-color: $cardview-fluent-common-link-color !default; $cardview-text-color: $cardview-fluent-common-text-color !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss index a47f1f1d3df2..92390ea11dec 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/cardView/_sizes.scss @@ -17,14 +17,14 @@ * $name 101. Border radius * $type text */ -$cardview-fluent-common-border-radius: null !default; +$cardview-fluent-common-border-radius: 16px !default; /** * $name 104. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-common-text-weight: null !default; +$cardview-fluent-common-text-weight: 400 !default; /** * $name 105. Vertical gap between cards @@ -46,9 +46,9 @@ $cardview-fluent-common-padding: null !default; // #region CARD -$cardview-fluent-card-min-width: null !default; +$cardview-fluent-card-min-width: 250px !default; -$cardview-fluent-card-border-width: null !default; +$cardview-fluent-card-border-width: 1px !default; /** * $name 202. Border radius @@ -67,7 +67,7 @@ $cardview-fluent-card__header__selection-checkbox-padding-right: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-card__field-caption-weight: null !default; +$cardview-fluent-card__field-caption-weight: 600 !default; $cardview-fluent-card__cover-max-height: null !default; @@ -90,7 +90,7 @@ $cardview-fluent-card__content__cell-padding-horizontal: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-card--selected__field-caption-weight: null !default; +$cardview-fluent-card--selected__field-caption-weight: $cardview-fluent-card__field-caption-weight !default; // #endregion @@ -110,13 +110,13 @@ $cardview-fluent-header-panel--empty-line-height: null !default; // #region HEADER PANEL ITEM -$cardview-fluent-header-panel__item-border-width: null !default; +$cardview-fluent-header-panel__item-border-width: 1px !default; /** * $name 402. Border radius * $type text */ -$cardview-fluent-header-panel__item-border-radius: null !default; +$cardview-fluent-header-panel__item-border-radius: 6px !default; $cardview-fluent-header-panel__item-padding-vertical: null !default; @@ -127,16 +127,16 @@ $cardview-fluent-header-panel__item-padding-horizontal: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item-text-weight: null !default; +$cardview-fluent-header-panel__item-text-weight: $cardview-fluent-common-text-weight !default; /** * $name 408. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item__sort-index-text-weight: null !default; +$cardview-fluent-header-panel__item__sort-index-text-weight: 400 !default; -$cardview-fluent-header-panel__item__icon-size: null !default; +$cardview-fluent-header-panel__item__icon-size: $fluent-base-icon-size !default; $cardview-fluent-header-item__left-padding: null !default; $cardview-fluent-header-item__right-padding: null !default; @@ -150,14 +150,14 @@ $cardview-fluent-header-item__right-padding: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--hovered-text-weight: null !default; +$cardview-fluent-header-panel__item--hovered-text-weight: $cardview-fluent-header-panel__item-text-weight !default; /** * $name 507. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--hovered__sort-index-text-weight: null !default; +$cardview-fluent-header-panel__item--hovered__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; // #endregion @@ -168,14 +168,14 @@ $cardview-fluent-header-panel__item--hovered__sort-index-text-weight: null !defa * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--disabled-text-weight: null !default; +$cardview-fluent-header-panel__item--disabled-text-weight: $cardview-fluent-header-panel__item-text-weight !default; /** * $name 607. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--disabled__sort-index-text-weight: null !default; +$cardview-fluent-header-panel__item--disabled__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; // #endregion @@ -186,10 +186,10 @@ $cardview-fluent-header-panel__item--disabled__sort-index-text-weight: null !def * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-header-panel__item--dragged-text-weight: null !default; +$cardview-fluent-header-panel__item--dragged-text-weight: $cardview-fluent-header-panel__item-text-weight !default; -$cardview-fluent-header-item__sort-indicator__border-size: null; -$cardview-fluent-header-item__sort-indicator__border-radius: null; +$cardview-fluent-header-item__sort-indicator__border-size: null !default; +$cardview-fluent-header-item__sort-indicator__border-radius: null !default; // #endregion @@ -199,7 +199,7 @@ $cardview-fluent-header-panel__dropzone-height: null !default; $cardview-fluent-header-panel__dropzone-top: null !default; -$cardview-fluent-header-panel__dropzone-border-width: null !default; +$cardview-fluent-header-panel__dropzone-border-width: 1.5px !default; /** * $name 801. Border radius @@ -215,7 +215,7 @@ $cardview-fluent-header-panel__dropzone-border-radius: null !default; * $name 901. Border radius * $type text */ -$cardview-fluent-filter-panel-border-radius: null !default; +$cardview-fluent-filter-panel-border-radius: 0 !default; // #endregion @@ -235,16 +235,16 @@ $cardview-fluent-nodata-view__icon-size: null !default; * $name 1101. Border radius * $type text */ -$cardview-fluent-card__content__field-value--highlighted-border-radius: null !default; +$cardview-fluent-card__content__field-value--highlighted-border-radius: 5px !default; -$cardview-fluent-card__content__field-value--highlighted-border-padding: null !default; +$cardview-fluent-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; /** * $name 1103. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-fluent-card__content__field-value--highlighted-text-weight: null !default; +$cardview-fluent-card__content__field-value--highlighted-text-weight: 500 !default; // #endregion @@ -259,21 +259,16 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau @if $size == "default" { // Common - $cardview-fluent-common-border-radius: 16px !default; - $cardview-fluent-common-text-weight: 400 !default; $cardview-fluent-common-cards-row-gap: 16px !default; $cardview-fluent-common-cards-column-gap: 16px !default; $cardview-fluent-common-gap: 24px !default; $cardview-fluent-common-padding: 24px !default; // Card - $cardview-fluent-card-min-width: 250px !default; - $cardview-fluent-card-border-width: 1px !default; $cardview-fluent-card-border-radius: 8px !default; $cardview-fluent-card__header-height: 48px !default; $cardview-fluent-card__header-text-size: 16px !default; $cardview-fluent-card__header__selection-checkbox-padding-right: 12px !default; - $cardview-fluent-card__field-caption-weight: 600 !default; $cardview-fluent-card__cover-max-height: 180px !default; $cardview-fluent-card__cover--noimage__icon-size: 48px !default; $cardview-fluent-card__content-padding: 12px !default; @@ -281,9 +276,6 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau $cardview-fluent-card__content__cell-padding-vertical: 6px !default; $cardview-fluent-card__content__cell-padding-horizontal: 12px !default; - // Card - selected - $cardview-fluent-card--selected__field-caption-weight: $cardview-fluent-card__field-caption-weight !default; - // Header $cardview-fluent-header-gap: 0 !default; @@ -292,48 +284,25 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau $cardview-fluent-header-panel--empty-line-height: 32px !default; // Header panel item - default - $cardview-fluent-header-panel__item-border-width: 1px !default; - $cardview-fluent-header-panel__item-border-radius: 6px !default; $cardview-fluent-header-panel__item-padding-horizontal: 8px !default; $cardview-fluent-header-panel__item-padding-vertical: 5px !default; $cardview-fluent-header-item__left-padding: 6px !default; $cardview-fluent-header-item__right-padding: 2px !default; - $cardview-fluent-header-panel__item-text-weight: $cardview-fluent-common-text-weight !default; - $cardview-fluent-header-panel__item__sort-index-text-weight: 400 !default; - $cardview-fluent-header-panel__item__icon-size: $fluent-base-icon-size !default; - - // Header panel item - hovered - $cardview-fluent-header-panel__item--hovered-text-weight: $cardview-fluent-header-panel__item-text-weight !default; - $cardview-fluent-header-panel__item--hovered__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-fluent-header-panel__item--disabled-text-weight: $cardview-fluent-header-panel__item-text-weight !default; - $cardview-fluent-header-panel__item--disabled__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; // Header panel item - dragged - $cardview-fluent-header-panel__item--dragged-text-weight: $cardview-fluent-header-panel__item-text-weight !default; $cardview-fluent-header-item__sort-indicator__border-size: 2px !default; $cardview-fluent-header-item__sort-indicator__border-radius: 8px !default; // Header panel Drop-zone $cardview-fluent-header-panel__dropzone-height: 44px !default; $cardview-fluent-header-panel__dropzone-top: -6px !default; - $cardview-fluent-header-panel__dropzone-border-width: 1.5px !default; $cardview-fluent-header-panel__dropzone-border-radius: 8px !default; - // Filter panel - $cardview-fluent-filter-panel-border-radius: 0 !default; - // Empty view $cardview-fluent-nodata-view__icon-container-size: 64px !default; $cardview-fluent-nodata-view__icon-container-margin: 12px !default; $cardview-fluent-nodata-view__icon-size: 40px !default; - // Search highlight - $cardview-fluent-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-fluent-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-fluent-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-fluent-column-chooser--empty__message-horizontal-padding: 24px !default; $cardview-fluent-column-chooser__select-all-item-horizontal-padding: 14px !default; @@ -341,21 +310,16 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau @else if $size == "compact" { // Common - $cardview-fluent-common-border-radius: 16px !default; - $cardview-fluent-common-text-weight: 400 !default; $cardview-fluent-common-cards-row-gap: 12px !default; $cardview-fluent-common-cards-column-gap: 12px !default; $cardview-fluent-common-gap: 16px !default; $cardview-fluent-common-padding: 16px !default; // Card - $cardview-fluent-card-min-width: 250px !default; - $cardview-fluent-card-border-width: 1px !default; $cardview-fluent-card-border-radius: 6px !default; $cardview-fluent-card__header-height: 36px !default; $cardview-fluent-card__header-text-size: 14px !default; $cardview-fluent-card__header__selection-checkbox-padding-right: 8px !default; - $cardview-fluent-card__field-caption-weight: 600 !default; $cardview-fluent-card__cover-max-height: 140px !default; $cardview-fluent-card__cover--noimage__icon-size: 32px !default; $cardview-fluent-card__content-padding: 8px !default; @@ -363,9 +327,6 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau $cardview-fluent-card__content__cell-padding-vertical: 4px !default; $cardview-fluent-card__content__cell-padding-horizontal: 8px !default; - // Card - selected - $cardview-fluent-card--selected__field-caption-weight: $cardview-fluent-card__field-caption-weight !default; - // Header $cardview-fluent-header-gap: 4px !default; @@ -374,48 +335,25 @@ $cardview-fluent-column-chooser__select-all-item-horizontal-padding: null !defau $cardview-fluent-header-panel--empty-line-height: 24px !default; // Header panel item - default - $cardview-fluent-header-panel__item-border-width: 1px !default; - $cardview-fluent-header-panel__item-border-radius: 6px !default; $cardview-fluent-header-panel__item-padding-horizontal: 6px !default; $cardview-fluent-header-panel__item-padding-vertical: 3px !default; $cardview-fluent-header-item__left-padding: 3px !default; $cardview-fluent-header-item__right-padding: 1px !default; - $cardview-fluent-header-panel__item-text-weight: $cardview-fluent-common-text-weight !default; - $cardview-fluent-header-panel__item__sort-index-text-weight: 400 !default; - $cardview-fluent-header-panel__item__icon-size: $fluent-base-icon-size !default; - - // Header panel item - hovered - $cardview-fluent-header-panel__item--hovered-text-weight: $cardview-fluent-header-panel__item-text-weight !default; - $cardview-fluent-header-panel__item--hovered__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-fluent-header-panel__item--disabled-text-weight: $cardview-fluent-header-panel__item-text-weight !default; - $cardview-fluent-header-panel__item--disabled__sort-index-text-weight: $cardview-fluent-header-panel__item__sort-index-text-weight !default; // Header panel item - dragged - $cardview-fluent-header-panel__item--dragged-text-weight: $cardview-fluent-header-panel__item-text-weight !default; $cardview-fluent-header-item__sort-indicator__border-size: 1px !default; $cardview-fluent-header-item__sort-indicator__border-radius: 4px !default; // Header panel Drop-zone $cardview-fluent-header-panel__dropzone-height: 32px !default; $cardview-fluent-header-panel__dropzone-top: -4px !default; - $cardview-fluent-header-panel__dropzone-border-width: 1.5px !default; $cardview-fluent-header-panel__dropzone-border-radius: 6px !default; - // Filter panel - $cardview-fluent-filter-panel-border-radius: 0 !default; - // Empty view $cardview-fluent-nodata-view__icon-container-size: 48px !default; $cardview-fluent-nodata-view__icon-container-margin: 8px !default; $cardview-fluent-nodata-view__icon-size: 32px !default; - // Search highlight - $cardview-fluent-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-fluent-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-fluent-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-fluent-column-chooser--empty__message-horizontal-padding: 16px !default; $cardview-fluent-column-chooser__select-all-item-horizontal-padding: 12px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/chat/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/chat/_colors.scss index 2d7ee1140d81..7f53705ade4e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/chat/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/chat/_colors.scss @@ -125,7 +125,7 @@ $chat-typingindicator-circle-bg-color: null !default; $chat-file-container-background-color: $base-bg !default; $chat-file-container-box-shadow: null !default; -$chat-file-secondary-color: null !default; +$chat-file-secondary-color: $base-icon-color !default; $chat-suggestions-box-shadow: null !default; @@ -144,7 +144,6 @@ $chat-suggestions-box-shadow: null !default; $chat-typingindicator-circle-bg-color-center: color.adjust($base-bg, $lightness: -56.08%, $space: hsl) !default; $chat-file-container-box-shadow: 0 0 2px 0 color.change($base-shadow-color, $alpha: 0.12), 0 1px 2px 0 color.change($base-shadow-color, $alpha: 0.14) !default; - $chat-file-secondary-color: $base-icon-color !default; $chat-suggestions-box-shadow: 0 4px 12px 0 color.change($base-shadow-color, $alpha: 0.04), 0 4px 24px 0 color.change($base-shadow-color, $alpha: 0.02) !default; @@ -176,7 +175,6 @@ $chat-suggestions-box-shadow: null !default; $chat-typingindicator-circle-bg-color-center: color.adjust($base-bg, $lightness: 43.92%, $space: hsl) !default; $chat-file-container-box-shadow: 0 0 2px 0 color.change($base-shadow-color, $alpha: 0.24), 0 1px 2px 0 color.change($base-shadow-color, $alpha: 0.28) !default; - $chat-file-secondary-color: $base-icon-color !default; $chat-suggestions-box-shadow: 0 4px 24px 0 color.change($base-shadow-color, $alpha: 0.02), 0 4px 12px 0 color.change($base-shadow-color, $alpha: 0.04) !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/chat/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/chat/_sizes.scss index f8069845cec3..cad0368631dd 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/chat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/chat/_sizes.scss @@ -14,7 +14,7 @@ $chat-bubble-delete-gap: null !default; $chat-bubble-delete-icon-size: null !default; $chat-messagebox-padding: null !default; $chat-messagebox-textarea-padding: null !default; -$chat-messagebox-textarea-border-radius: null !default; +$chat-messagebox-textarea-border-radius: 8px !default; $chat-messagebox-toolbar-min-height: null !default; $chat-messagebox-gap: 8px !default; $chat-messagebox-files-container-padding: 3px !default; @@ -32,7 +32,7 @@ $chat-information-edited-gap: 2px !default; $chat-alerts-padding-block: null !default; $chat-alerts-padding-inline: 8px !default; -$chat-alerts-row-gap: null !default; +$chat-alerts-row-gap: 2px !default; $chat-alert-padding-block: null !default; $chat-suggestions-padding: null !default; @@ -57,12 +57,10 @@ $chat-messagelist-day-header-first-padding-top: 20px !default; $chat-messagelist-day-header-font-size: 12px !default; $chat-typingindicator-template: $chat-messagegroup-alignment-start-template !default; -$chat-typingindicator-padding: null !default; $chat-typingindicator-row-gap: $chat-messagegroup-gap !default; $chat-typingindicator-font-size: $chat-information-font-size !default; $chat-typingindicator-bubble-padding-block: null !default; $chat-typingindicator-bubble-padding-inline: null !default; -$chat-typingindicator-bubble-border-radius: null !default; $chat-typingindicator-bubble-column-gap: 4px !default; $chat-typingindicator-circle-size: null !default; $chat-typingindicator-circle-size-center: null !default; @@ -90,14 +88,12 @@ $chat-bubble-gap: 8px !default; $chat-bubble-delete-gap: 4px !default; $chat-bubble-delete-icon-size: 20px !default; $chat-messagebox-padding: 20px !default; - $chat-messagebox-textarea-border-radius: 8px !default; $chat-messagebox-textarea-padding: 12px !default; $chat-messagebox-toolbar-min-height: 32px !default; $chat-messagelist-padding: 20px !default; $chat-messagegroup-padding: 24px !default; $chat-alerts-padding-block: 12px !default; - $chat-alerts-row-gap: 2px !default; $chat-alert-padding-block: 6px !default; $chat-suggestions-padding: 20px !default; @@ -116,8 +112,6 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 14) * 1em !default; $chat-typingindicator-bubble-padding-block: 12px !default; $chat-typingindicator-bubble-padding-inline: 20px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; - $chat-typingindicator-bubble-border-radius: $chat-bubble-border-radius !default; $chat-confirmation-popup-toolbar-gap: 8px !default; @@ -130,14 +124,12 @@ $chat-bubble-gap: 8px !default; $chat-bubble-delete-gap: 2px !default; $chat-bubble-delete-icon-size: 16px !default; $chat-messagebox-padding: 16px !default; - $chat-messagebox-textarea-border-radius: 8px !default; $chat-messagebox-textarea-padding: 8px !default; $chat-messagebox-toolbar-min-height: 24px !default; $chat-messagelist-padding: 16px !default; $chat-messagegroup-padding: 12px !default; $chat-alerts-padding-block: 8px !default; - $chat-alerts-row-gap: 2px !default; $chat-alert-padding-block: 5px !default; $chat-suggestions-padding: 16px !default; @@ -156,10 +148,11 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 12) * 1em !default; $chat-typingindicator-bubble-padding-block: 8px !default; $chat-typingindicator-bubble-padding-inline: 12px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; - $chat-typingindicator-bubble-border-radius: $chat-bubble-border-radius !default; $chat-confirmation-popup-toolbar-gap: 4px !default; $chat-file-icon-font-size: 16px !default; } + +$chat-typingindicator-padding: $chat-messagegroup-padding !default; +$chat-typingindicator-bubble-border-radius: $chat-bubble-border-radius !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/colorView/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/colorView/_index.scss index bef18943d2af..4259a2430fb2 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/colorView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/colorView/_index.scss @@ -4,8 +4,8 @@ @use "../../base/colorView"; @include dx-base-colorview-styles($colorview-handle-color, $colorview-handle-border-color); -$fluent-colorview-alpha-width: $fluent-colorview-palette-width + 4; -$fluent-colorview-hue-scale-height: $fluent-colorview-palette-height; +$fluent-colorview-alpha-width: $fluent-colorview-palette-width + 4 !default; +$fluent-colorview-hue-scale-height: $fluent-colorview-palette-height !default; .dx-colorview-container { width: $fluent-colorview-container-width; diff --git a/packages/devextreme-scss/scss/widgets/fluent/common/_mixins.scss b/packages/devextreme-scss/scss/widgets/fluent/common/_mixins.scss index 07d36367e858..83f2458bf9b9 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/common/_mixins.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/common/_mixins.scss @@ -7,7 +7,7 @@ @use "../../base/validation" as *; @use "../list/sizes" as *; -$badge-invalid-bg: $base-invalid-color; +$badge-invalid-bg: $base-invalid-color !default; @mixin dx-base-typography() { @include dx-base-typography-mixin( diff --git a/packages/devextreme-scss/scss/widgets/fluent/dateRangeBox/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/dateRangeBox/_index.scss index c9c93f884a25..7540a81f6463 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/dateRangeBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/dateRangeBox/_index.scss @@ -9,7 +9,7 @@ @use "../../base/icon_fonts" as *; @use "../textEditor" as *; -$fluent-editor-custom-button-margin: 6px; +$fluent-editor-custom-button-margin: 6px !default; .dx-daterangebox { @include dx-dropdowneditor-button-icon-styling(daterangepicker); diff --git a/packages/devextreme-scss/scss/widgets/fluent/dateView/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/dateView/_index.scss index 276fbf644f98..67bb91377d8d 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/dateView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/dateView/_index.scss @@ -6,7 +6,7 @@ @use "../../base/dateView"; @use "../scrollable"; -$fluent-dateview-roller-padding: math.div($fluent-dateview-roller-height - $fluent-dateview-roller-item-height, 2); +$fluent-dateview-roller-padding: math.div($fluent-dateview-roller-height - $fluent-dateview-roller-item-height, 2) !default; .dx-dateview-rollers { width: auto; diff --git a/packages/devextreme-scss/scss/widgets/fluent/diagram/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/diagram/_index.scss index 9b8af08066b7..d7d9b61bed19 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/diagram/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/diagram/_index.scss @@ -15,10 +15,10 @@ @use "../fieldset/sizes" as *; @use "../menu/colors" as *; -$fluent-diagram-toolbar-icon-size: $fluent-base-icon-size; -$diagram-toolbar-colorbutton-border-width: 3px; +$fluent-diagram-toolbar-icon-size: $fluent-base-icon-size !default; +$diagram-toolbar-colorbutton-border-width: 3px !default; -$diagram-toolbox-title-size: 18px; +$diagram-toolbox-title-size: 18px !default; .dx-diagram { border-color: $diagram-border-color; diff --git a/packages/devextreme-scss/scss/widgets/fluent/dropDownButton/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/dropDownButton/_sizes.scss index b6f005a34d5d..b970e8c4cacd 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/dropDownButton/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/dropDownButton/_sizes.scss @@ -6,12 +6,12 @@ $fluent-dropdownbutton-icon-spindown-right-size: null !default; @if $size == "default" { $fluent-dropdownbutton-action-horizontal-padding: 12px !default; - $fluent-dropdownbutton-icon-spindown-size: 24px; + $fluent-dropdownbutton-icon-spindown-size: 24px !default; $fluent-dropdownbutton-icon-spindown-right-size: 16px !default; } @else if $size == "compact" { $fluent-dropdownbutton-action-horizontal-padding: 8px !default; - $fluent-dropdownbutton-icon-spindown-size: 18px; + $fluent-dropdownbutton-icon-spindown-size: 18px !default; $fluent-dropdownbutton-icon-spindown-right-size: 12px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/dropDownEditor/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/dropDownEditor/_index.scss index c21aed8508d0..1a767ca16beb 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/dropDownEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/dropDownEditor/_index.scss @@ -8,17 +8,17 @@ @use "../../base/dropDownEditor"; @use "../../base/dropDownEditor/mixins" as *; -$fluent-dropdowneditor-container-button-size: $fluent-base-icon-size; +$fluent-dropdowneditor-container-button-size: $fluent-base-icon-size !default; $fluent-dropdowneditor-invalid-badge-offset: $fluent-dropdowneditor-container-button-size + - $fluent-invalid-badge-halfhorizontal-padding; + $fluent-invalid-badge-halfhorizontal-padding !default; $fluent-dropdowneditor-invalid-badge-with-clear-offset: $fluent-dropdowneditor-container-button-size * 2 + - $fluent-invalid-badge-halfhorizontal-padding; + $fluent-invalid-badge-halfhorizontal-padding !default; -$fluent-dropdowneditor-invalid-badge-size: $fluent-invalid-badge-size + $fluent-invalid-badge-horizontal-padding; +$fluent-dropdowneditor-invalid-badge-size: $fluent-invalid-badge-size + $fluent-invalid-badge-horizontal-padding !default; @mixin dx-dropdowneditor-button-icon() { width: $fluent-dropdowneditor-container-button-size; diff --git a/packages/devextreme-scss/scss/widgets/fluent/fileManager/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/fileManager/_colors.scss index 9c5aed4a9f7a..f0a6358c9177 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/fileManager/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/fileManager/_colors.scss @@ -16,14 +16,11 @@ $filemanager-file-item-hover-bg: $base-hover-bg !default; $filemanager-file-item-focused-border: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; $filemanager-dirs-tree-item-focus-bg: null !default; -$filemanager-progressbox-shadow-color: null !default; @if $mode == "light" { $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $mode == "dark" { $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/fileManager/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/fileManager/_sizes.scss index c20681b38015..6d827e99f941 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/fileManager/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/fileManager/_sizes.scss @@ -15,7 +15,7 @@ $filemanager-large-icon-size: null !default; $filemanager-progress-panel-title-padding: null !default; $filemanager-progress-box-button-close-margin-top: null !default; -$filemanager-toolbar-height: $fluent-toolbar-height; +$filemanager-toolbar-height: $fluent-toolbar-height !default; @if $size == "default" { $filemanager-toolbar-separator-height: 36px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_index.scss index 985d8a670669..feaf4bcd5aa4 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_index.scss @@ -7,8 +7,8 @@ @use "../../base/mixins" as *; @use "../../base/fileUploader"; -$fluent-fileuploader-file-wrapper-border-size: 3px; -$fluent-fileuploader-file-container-boxshadow: 0 1px 3px 0 color.change($base-shadow-color, $alpha: 0.1); +$fluent-fileuploader-file-wrapper-border-size: 3px !default; +$fluent-fileuploader-file-container-boxshadow: 0 1px 3px 0 color.change($base-shadow-color, $alpha: 0.1) !default; .dx-fileuploader-wrapper { padding: $fluent-fileuploader-vertical-padding; diff --git a/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_sizes.scss index fa118fa0c463..c41cfcdc2c1b 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/fileUploader/_sizes.scss @@ -7,7 +7,7 @@ $fluent-fileuploader-file-cancel-button-margin-left: 4px !default; $fluent-fileuploader-file-container-button-size: null !default; $fluent-fileuploader-vertical-padding: 7px !default; -$fluent-fileuploader-files-container-padding: null !default; +$fluent-fileuploader-files-container-padding: 12px 4px 0 !default; $fluent-fileuploader-file-size-margin: null !default; $fluent-fileuploader-file-icon-font-size: null !default; $fluent-fileuploader-files-container-rounding: 4px !default; @@ -17,7 +17,6 @@ $fluent-fileuploader-files-container-rounding: 4px !default; $fluent-fileuploader-file-container-text-margin: 15px !default; $fluent-fileuploader-file-container-button-margin: 8px !default; $fluent-fileuploader-file-container-button-size: 32px !default; - $fluent-fileuploader-files-container-padding: 12px 4px 0 !default; $fluent-fileuploader-file-size-margin: 0 0 0 4px !default; $fluent-fileuploader-file-icon-font-size: 20px !default; } @@ -27,7 +26,6 @@ $fluent-fileuploader-files-container-rounding: 4px !default; $fluent-fileuploader-file-container-text-margin: 6px !default; $fluent-fileuploader-file-container-button-margin: 2px !default; $fluent-fileuploader-file-container-button-size: 28px !default; - $fluent-fileuploader-files-container-padding: 12px 4px 0 !default; $fluent-fileuploader-file-size-margin: 0 0 4px !default; $fluent-fileuploader-file-icon-font-size: 16px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/form/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/form/_sizes.scss index 384113d46730..465839839624 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/form/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/form/_sizes.scss @@ -1,6 +1,6 @@ @use "../sizes" as *; -$fluent-form-group-caption-bottom-margin: null !default; +$fluent-form-group-caption-bottom-margin: 4px !default; $fluent-form-group-caption-top-padding: null !default; $fluent-form-group-content-bottom-padding: null !default; $fluent-form-group-caption-font-size: null !default; @@ -17,7 +17,6 @@ $fluent-form-label-icon-size: $fluent-base-icon-size !default; $fluent-form-group-custom-caption-icon-margin: null !default; @if $size == "default" { - $fluent-form-group-caption-bottom-margin: 4px !default; $fluent-form-group-caption-top-padding: 16px !default; $fluent-form-group-content-bottom-padding: 16px !default; $fluent-form-group-caption-font-size: 20px !default; @@ -30,7 +29,6 @@ $fluent-form-group-custom-caption-icon-margin: null !default; } @else if $size == "compact" { - $fluent-form-group-caption-bottom-margin: 4px !default; $fluent-form-group-caption-top-padding: 12px !default; $fluent-form-group-content-bottom-padding: 12px !default; $fluent-form-group-caption-font-size: 16px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/gantt/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/gantt/_index.scss index c6e20ce847e9..06d100805263 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gantt/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gantt/_index.scss @@ -9,7 +9,7 @@ @use "../form/sizes" as *; @use "../splitterBar"; -$gantt-successor-background-color: white; +$gantt-successor-background-color: white !default; .dx-gantt { border: 1px solid; diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_colors.scss index c154f911500c..1ece29b5e750 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_colors.scss @@ -34,7 +34,7 @@ $datagrid-drag-header-border-color: color.change($base-accent, $alpha: 0.5) !def $datagrid-drag-header-shadow-color: null !default; $datagrid-drag-header-second-shadow-color: null !default; $datagrid-header-drag-bg: $datagrid-base-background-color !default; -$header-filter-color: null !default; +$header-filter-color: $datagrid-columnchooser-item-color !default; /** * $name 51. Selected row background color @@ -75,7 +75,7 @@ $datagrid-row-hovered-color: $base-hover-color !default; $datagrid-cell-modified-border-color: $button-success-text-hover-bg !default; $datagrid-cell-removed-border-color: $datagrid-border-color !default; $datagrid-row-removed-bg: $datagrid-hover-bg !default; -$datagrid-cell-removed-text-color: null !default; +$datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; /** * $name 45. Invalid data faded color @@ -88,7 +88,7 @@ $datagrid-nodata-color: $base-label-color !default; * $name 90. Group row text color * $type color */ -$datagrid-group-row-color: null !default; +$datagrid-group-row-color: $datagrid-columnchooser-item-color !default; /** * $name 95. Group row background color @@ -155,12 +155,13 @@ $datagrid-summary-bg: $datagrid-base-background-color !default; $datagrid-column-separator-bg: $base-accent !default; $datagrid-filter-row-background-color: null !default; $datagrid-active-header-filter-icon-color: $base-accent !default; -$datagrid-text-stub-background-image-path: null !default; + +$datagrid-text-stub-background-image-path: data-uri("images/widgets/fluent/grid/text-stub.png") !default; $datagrid-filter-panel-color: $base-accent !default; $datagrid-draggable-column-text-color: null !default; -$datagrid-text-link-disabled-opacity: $base-disabled-opacity; -$datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; +$datagrid-text-link-disabled-opacity: $base-disabled-opacity !default; +$datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity !default; @if $mode == "light" { @if $color == "blue" { @@ -172,31 +173,22 @@ $datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; $datagrid-row-focused-bg: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: -1deg, $saturation: 0%, $lightness: 26.47%) !default; $datagrid-filter-row-background-color: $base-typography-bg !default; } - - $datagrid-group-row-color: $datagrid-columnchooser-item-color !default; $datagrid-block-separator-bg: color.adjust($datagrid-base-background-color, $lightness: -12%, $space: hsl) !default; $datagrid-drag-header-shadow-color: color.change($base-shadow-color, $alpha: 0.1) !default; $datagrid-drag-header-second-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $header-filter-color: $datagrid-columnchooser-item-color !default; $datagrid-columnchooser-item-bg: $datagrid-columnchooser-bg !default; $datagrid-row-alternation-bg: color.adjust($datagrid-base-background-color, $lightness: -4%, $space: hsl) !default; - $datagrid-text-stub-background-image-path: data-uri("images/widgets/fluent/color-schemes/light/grid/text-stub.png") !default; - $datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; $datagrid-draggable-column-text-color: color.adjust($base-text-color, $lightness: 60%, $space: hsl) !default; } @if $mode == "dark" { $datagrid-row-focused-bg: #0c3b5e !default; $datagrid-filter-row-background-color: color.adjust($datagrid-base-background-color, $lightness: 2%, $space: hsl) !default; - $datagrid-group-row-color: $datagrid-columnchooser-item-color !default; $datagrid-block-separator-bg: color.adjust($datagrid-base-background-color, $lightness: 12%, $space: hsl) !default; $datagrid-drag-header-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; $datagrid-drag-header-second-shadow-color: color.change($base-shadow-color, $alpha: 0.4) !default; - $header-filter-color: $datagrid-columnchooser-item-color !default; $datagrid-columnchooser-item-bg: color.adjust($datagrid-columnchooser-bg, $lightness: 3%, $space: hsl) !default; $datagrid-row-alternation-bg: color.adjust($datagrid-base-background-color, $lightness: 4%, $space: hsl) !default; - $datagrid-text-stub-background-image-path: data-uri("images/widgets/fluent/color-schemes/dark/grid/text-stub.png") !default; - $datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; $datagrid-draggable-column-text-color: color.adjust($base-text-color, $lightness: -63.9%, $space: hsl) !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss index 26ed9c35829f..27842a42af13 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_index.scss @@ -28,11 +28,11 @@ @use "layout/cell" as *; @use "layout/aiChat" as *; -$fluent-grid-base-border-hidden: 1px solid transparent; -$fluent-grid-base-row-border: 1px solid transparent; -$fluent-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color; -$fluent-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color; -$fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-height; +$fluent-grid-base-border-hidden: 1px solid transparent !default; +$fluent-grid-base-row-border: 1px solid transparent !default; +$fluent-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color !default; +$fluent-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color !default; +$fluent-grid-base-group-panel-message-line-height: $fluent-button-text-line-height !default; @mixin grid-base($widget-name) { @include fluent-grid-base-cell($widget-name); diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_sizes.scss index caaa91c8ec2f..bc836c3c34a0 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_sizes.scss @@ -17,14 +17,13 @@ $fluent-grid-base-menu-item-padding: null !default; $fluent-grid-base-column-chooser-horizontal-padding: null !default; $fluent-grid-base-column-chooser-paddings: null !default; $fluent-grid-base-header-panel-padding: null !default; -$fluent-grid-base-grouppanel-item-margin: null !default; +$fluent-grid-base-grouppanel-item-margin: 6px !default; $fluent-grid-base-command-edit-column-width: null !default; $fluent-grid-base-command-edit-column-with-icons-width: null !default; $fluent-grid-base-adaptive-column-height: null !default; -$fluent-grid-base-treeview-search-margin: null !default; +$fluent-grid-base-treeview-search-margin: 10px !default; $fluent-grid-base-nodata-font-size: null !default; $fluent-filter-row-between-editor-padding: null !default; -$fluent-command-column-additional-right-margin: null !default; $fluent-grid-base-treeview-select-all-item-offset: null !default; $fluent-grid-base-header-line-height: 18px !default; @@ -57,16 +56,13 @@ $fluent-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $fluent-grid-base-column-chooser-horizontal-padding: 24px !default; $fluent-grid-base-column-chooser-paddings: 16px $fluent-grid-base-column-chooser-horizontal-padding 9px !default; $fluent-grid-base-header-panel-padding: 8px 0 !default; - $fluent-grid-base-grouppanel-item-margin: 6px !default; $fluent-grid-base-command-edit-column-width: 115px !default; $fluent-grid-base-command-edit-column-with-icons-width: 93px !default; $fluent-grid-base-adaptive-column-height: 40px !default; - $fluent-grid-base-treeview-search-margin: 10px !default; $fluent-grid-base-nodata-font-size: 17px !default; $fluent-filter-row-between-editor-padding: 12px !default; - $fluent-command-column-additional-right-margin: 0 !default; $fluent-grid-base-treeview-select-all-item-offset: 34px !default; @@ -92,16 +88,13 @@ $fluent-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $fluent-grid-base-column-chooser-horizontal-padding: 8px !default; $fluent-grid-base-column-chooser-paddings: 8px $fluent-grid-base-column-chooser-horizontal-padding 8px !default; $fluent-grid-base-header-panel-padding: 6px 0 !default; - $fluent-grid-base-grouppanel-item-margin: 6px !default; $fluent-grid-base-command-edit-column-width: 105px !default; $fluent-grid-base-command-edit-column-with-icons-width: 80px !default; $fluent-grid-base-adaptive-column-height: 26px !default; - $fluent-grid-base-treeview-search-margin: 10px !default; $fluent-grid-base-nodata-font-size: 14px !default; $fluent-filter-row-between-editor-padding: 8px !default; - $fluent-command-column-additional-right-margin: 2px !default; $fluent-grid-base-treeview-select-all-item-offset: 28px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_variables.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_variables.scss index 1ec5dd5b0875..2c57c3b85b93 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/_variables.scss @@ -2,9 +2,9 @@ @use "colors" as *; @use "sizes" as *; -$fluent-grid-base-border-color: $datagrid-border-color; -$fluent-grid-base-cell-vertical-padding: math.round(math.div($fluent-grid-base-cell-height - $fluent-grid-base-cell-line-height, 2)); -$fluent-grid-base-cell-padding: $fluent-grid-base-cell-vertical-padding $fluent-grid-base-cell-horizontal-padding; -$fluent-grid-base-header-cell-vertical-padding: math.round(math.div($fluent-grid-base-header-height - $fluent-grid-base-header-line-height, 2)); -$fluent-grid-base-command-select-column-width: $fluent-grid-base-checkbox-size + 2 * $fluent-grid-base-cell-horizontal-padding; -$grid-masterdetail-padding: 16px; +$fluent-grid-base-border-color: $datagrid-border-color !default; +$fluent-grid-base-cell-vertical-padding: math.round(math.div($fluent-grid-base-cell-height - $fluent-grid-base-cell-line-height, 2)) !default; +$fluent-grid-base-cell-padding: $fluent-grid-base-cell-vertical-padding $fluent-grid-base-cell-horizontal-padding !default; +$fluent-grid-base-header-cell-vertical-padding: math.round(math.div($fluent-grid-base-header-height - $fluent-grid-base-header-line-height, 2)) !default; +$fluent-grid-base-command-select-column-width: $fluent-grid-base-checkbox-size + 2 * $fluent-grid-base-cell-horizontal-padding !default; +$grid-masterdetail-padding: 16px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/gridBase/layout/aiChat/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/gridBase/layout/aiChat/_sizes.scss index 23463e560ea2..bf760a009072 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/gridBase/layout/aiChat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/gridBase/layout/aiChat/_sizes.scss @@ -1,3 +1,3 @@ @use "../../../sizes" as *; -$ai-chat-message-icon-size: $fluent-base-icon-size; +$ai-chat-message-icon-size: $fluent-base-icon-size !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/icons/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/icons/_index.scss index 4bc6a54ebb1b..c3f928382586 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/icons/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/icons/_index.scss @@ -20,12 +20,6 @@ } } -.dx-scrollview-pulldown { - background-image: $scrollview-pulldown-path; - background-position: 0 0; - background-repeat: no-repeat; -} - .dx-loadindicator-image { background-position: center center; background-repeat: no-repeat; diff --git a/packages/devextreme-scss/scss/widgets/fluent/informer/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/informer/_colors.scss index 8fc917a37748..256a14dca7d3 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/informer/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/informer/_colors.scss @@ -8,11 +8,11 @@ $informer-info-color: $base-text-color !default; $informer-info-background-color: null !default; @if $mode == "light" { - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -358deg, $saturation: 0.58%, $lightness: 46.67%); - $informer-info-background-color: color.adjust($base-text-color, $lightness: 74.90%, $space: hsl); + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -358deg, $saturation: 0.58%, $lightness: 46.67%) !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: 74.90%, $space: hsl) !default; } @if $mode == "dark" { - $informer-error-background-color: color.adjust($base-danger, $saturation: -5.06%, $space: hsl, $lightness: -53.53%); - $informer-info-background-color: color.adjust($base-text-color, $lightness: -76.08%, $space: hsl); + $informer-error-background-color: color.adjust($base-danger, $saturation: -5.06%, $space: hsl, $lightness: -53.53%) !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: -76.08%, $space: hsl) !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/list/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/list/_index.scss index 12d1d175891c..21aefb71d99d 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/list/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/list/_index.scss @@ -13,10 +13,10 @@ @use "../radioGroup/colors" as *; @use "../badge"; -$fluent-list-item-border-width: 1px; -$fluent-list-border-width: 1px; -$fluent-list-border: $fluent-list-border-width solid; -$fluent-list-item-border: $fluent-list-item-border-width solid; +$fluent-list-item-border-width: 1px !default; +$fluent-list-border-width: 1px !default; +$fluent-list-border: $fluent-list-border-width solid !default; +$fluent-list-item-border: $fluent-list-item-border-width solid !default; .dx-state-disabled { &.dx-list { diff --git a/packages/devextreme-scss/scss/widgets/fluent/menuBase/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/menuBase/_colors.scss index f55789e57034..1f1279d587f3 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/menuBase/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/menuBase/_colors.scss @@ -1,3 +1,3 @@ @use "../colors" as *; -$menu-item-color-disabled: $base-foreground-disabled; +$menu-item-color-disabled: $base-foreground-disabled !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/menuBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/menuBase/_sizes.scss index 63d09d1d7dbf..dd2ef1a21d2f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/menuBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/menuBase/_sizes.scss @@ -3,7 +3,7 @@ $fluent-menu-item-line-height: null !default; $fluent-menu-item-with-icon-horizontal-padding: null !default; $fluent-menu-horizontal-padding: null !default; -$fluent-menu-icon-to-text-offset: 4px; +$fluent-menu-icon-to-text-offset: 4px !default; @if $size == "default" { $fluent-menu-item-line-height: 20px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/numberBox/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/numberBox/_sizes.scss index 09bdf6b8492b..a166d36fbe1f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/numberBox/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/numberBox/_sizes.scss @@ -1,12 +1,4 @@ @use "../sizes" as *; $fluent-numberbox-spin-container-width: 24px !default; -$fluent-numberbox-touch-friendly-spin-container-width: null !default; - -@if $size == "default" { - $fluent-numberbox-touch-friendly-spin-container-width: 48px !default; -} - -@else if $size == "compact" { - $fluent-numberbox-touch-friendly-spin-container-width: 48px !default; -} +$fluent-numberbox-touch-friendly-spin-container-width: 48px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/pivotGrid/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/pivotGrid/_index.scss index 3871987a599d..0afa3b10b188 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/pivotGrid/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/pivotGrid/_index.scss @@ -32,12 +32,12 @@ $pivotgrid-button-top-padding: $pivotgrid-button-top-padding, ); -$fluent-pivotgrid-headers-font-size: 14px; -$fluent-pivotgrid-area-font-size: 13px; -$fluent-pivotgrid-chevron-icon-size: 20px; +$fluent-pivotgrid-headers-font-size: 14px !default; +$fluent-pivotgrid-area-font-size: 13px !default; +$fluent-pivotgrid-chevron-icon-size: 20px !default; -$fluent-pivotgrid-drag-header-shadow: 0 3px 4px color.change($base-shadow-color, $alpha: 0.15); -$fluent-pivotgridfieldchooser-field-min-height: 36px; +$fluent-pivotgrid-drag-header-shadow: 0 3px 4px color.change($base-shadow-color, $alpha: 0.15) !default; +$fluent-pivotgridfieldchooser-field-min-height: 36px !default; .dx-pivotgrid-fields-container { &.dx-drag { diff --git a/packages/devextreme-scss/scss/widgets/fluent/popup/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/popup/_index.scss index 75423b3fb670..3f1362182fa1 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/popup/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/popup/_index.scss @@ -8,7 +8,7 @@ @use "../../base/popup"; @use "../overlay"; -$fluent-popup-toolbar-label-font-size: $fluent-m-font-size; +$fluent-popup-toolbar-label-font-size: $fluent-m-font-size !default; .dx-popup-wrapper { & > .dx-overlay-content { diff --git a/packages/devextreme-scss/scss/widgets/fluent/popup/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/popup/_sizes.scss index d2d88fcd38d3..5553608c8b33 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/popup/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/popup/_sizes.scss @@ -12,8 +12,8 @@ $fluent-popup-toolbar-height: null !default; $fluent-popup-button-margin: 0 8px !default; $fluent-popup-toolbar-item-spacing: 8px !default; -$fluent-popup-content-shadow: null !default; -$fluent-popup-focused-content-shadow: null !default; +$fluent-popup-content-shadow: 0 0 8px 0 $popup-content-shadow-color1, 0 32px 64px 0 $popup-content-shadow-color2 !default; +$fluent-popup-focused-content-shadow: 0 0 12px 0 $popup-focused-content-shadow-color, 0 32px 64px 0 $popup-focused-content-shadow-color !default; @if $size == "default" { $fluent-popup-content-padding: 24px !default; @@ -24,9 +24,6 @@ $fluent-popup-focused-content-shadow: null !default; $fluent-popup-toolbar-horizontal-padding: 24px !default; $fluent-popup-bottom-toolbar-height: 48px !default; $fluent-popup-toolbar-height: 48px !default; - - $fluent-popup-content-shadow: 0 0 8px 0 $popup-content-shadow-color1, 0 32px 64px 0 $popup-content-shadow-color2 !default; - $fluent-popup-focused-content-shadow: 0 0 12px 0 $popup-focused-content-shadow-color, 0 32px 64px 0 $popup-focused-content-shadow-color !default; } @else if $size == "compact" { @@ -38,9 +35,6 @@ $fluent-popup-focused-content-shadow: null !default; $fluent-popup-toolbar-horizontal-padding: 16px !default; $fluent-popup-bottom-toolbar-height: 36px !default; $fluent-popup-toolbar-height: 36px !default; - - $fluent-popup-content-shadow: 0 0 8px 0 $popup-content-shadow-color1, 0 32px 64px 0 $popup-content-shadow-color2 !default; - $fluent-popup-focused-content-shadow: 0 0 12px 0 $popup-focused-content-shadow-color, 0 32px 64px 0 $popup-focused-content-shadow-color !default; } :root { diff --git a/packages/devextreme-scss/scss/widgets/fluent/progressBar/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/progressBar/_colors.scss index 85165ce798f0..a81d38c91d00 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/progressBar/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/progressBar/_colors.scss @@ -18,9 +18,9 @@ $progressbar-range-disabled-bg: $base-foreground-disabled !default; $progressbar-range-indeterminate-color: $base-accent !default; $progressbar-range-invalid-color: $base-invalid-color !default; -$progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95%; -$progressbar-disabled-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-disabled-bg, transparent 95%; -$progressbar-invalid-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-invalid-color, transparent 95%; +$progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95% !default; +$progressbar-disabled-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-disabled-bg, transparent 95% !default; +$progressbar-invalid-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-invalid-color, transparent 95% !default; /** * $name 30. Label color diff --git a/packages/devextreme-scss/scss/widgets/fluent/progressBar/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/progressBar/_sizes.scss index 91677287e082..d093147ac5d1 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/progressBar/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/progressBar/_sizes.scss @@ -3,7 +3,7 @@ $progressbar-height: null !default; $progressbar-border-radius: $base-border-radius !default; $progressbar-status-margin-top: null !default; -$progressbar-indeterminate-background-width: 90%; +$progressbar-indeterminate-background-width: 90% !default; @if $size == "default" { $progressbar-height: 4px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/radioGroup/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/radioGroup/_sizes.scss index 47f7cd1cd4ed..ada30c55a57e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/radioGroup/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/radioGroup/_sizes.scss @@ -1,14 +1,11 @@ @use "../sizes" as *; $fluent-radio-button-vertical-margin: null !default; -$fluent-radio-group-horizontal-gap: null !default; @if $size == "default" { - $fluent-radio-group-horizontal-gap: 16px !default; $fluent-radio-button-vertical-margin: 12px !default; } @else if $size == "compact" { - $fluent-radio-group-horizontal-gap: 12px !default; $fluent-radio-button-vertical-margin: 6px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/recurrenceEditor/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/recurrenceEditor/_index.scss index a3bad1a3542a..cded1ffcecac 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/recurrenceEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/recurrenceEditor/_index.scss @@ -4,9 +4,9 @@ @use "../scheduler/sizes" as *; @use "../../base/recurrenceEditor" as *; -$fluent-recurrence-number-box-margin: 11px; -$fluent-recurrence-button-group-padding: 6px; -$fluent-recurrence-radio-group-margin: 15px; +$fluent-recurrence-number-box-margin: 11px !default; +$fluent-recurrence-button-group-padding: 6px !default; +$fluent-recurrence-radio-group-margin: 15px !default; .dx-recurrence-numberbox-interval { margin-right: $fluent-recurrence-number-box-margin; diff --git a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_colors.scss index c307c71d772d..edaae60d331c 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_colors.scss @@ -23,7 +23,6 @@ $scheduler-accent-border-color: $base-border-color !default; * $type color */ $scheduler-appointment-text-color: null !default; -$scheduler-appointment-focus-color: null !default; /** * $name 40. Appointment background color @@ -36,7 +35,7 @@ $scheduler-appointment-base-color: null !default; * $type color */ $scheduler-appointment-start-color: color.change($base-shadow-color, $alpha: 0.3) !default; -$scheduler-appointment-active-color: $button-default-outlined-active-bg; +$scheduler-appointment-active-color: $button-default-outlined-active-bg !default; /** * $name 60. Focused cell color @@ -87,7 +86,7 @@ $header-panel-cell-date: null !default; @if $mode == "light" { $scheduler-appointment-text-color: #000 !default; - $header-panel-cell-date: #424242; + $header-panel-cell-date: #424242 !default; @if $color == "blue" { $scheduler-appointment-base-color: color.adjust($base-accent, $lightness: 48%, $space: hsl) !default; @@ -107,7 +106,7 @@ $header-panel-cell-date: null !default; @if $mode == "dark" { $scheduler-appointment-base-color: color.adjust($base-accent, $lightness: -48%, $space: hsl) !default; $scheduler-appointment-text-color: #fff !default; - $header-panel-cell-date: #adadad; + $header-panel-cell-date: #adadad !default; $scheduler-cell-hover-bg: $base-hover-bg !default; @if $color == "blue" { @@ -122,4 +121,4 @@ $header-panel-cell-date: null !default; } $scheduler-first-month-cell-background-color: color.adjust($scheduler-appointment-base-color, $alpha: -0.85) !default; -$scheduler-appointment-focus-color: $scheduler-appointment-text-color; +$scheduler-appointment-focus-color: $scheduler-appointment-text-color !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss index b7136eab49d5..28f18745f2d9 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_index.scss @@ -55,13 +55,13 @@ @use "../recurrenceEditor" as *; @use "../scrollable"; -$fluent-scheduler-all-day-table-cell-height: $fluent-scheduler-workspace-date-table-cell-height * 1.5; +$fluent-scheduler-all-day-table-cell-height: $fluent-scheduler-workspace-date-table-cell-height * 1.5 !default; -$fluent-scheduler-month-date-text-size: 14px; +$fluent-scheduler-month-date-text-size: 14px !default; -$scheduler-first-of-month-border: 1px solid; +$scheduler-first-of-month-border: 1px solid !default; -$fluent-scheduler-agenda-time-panel-cell-padding: 8px; +$fluent-scheduler-agenda-time-panel-cell-padding: 8px !default; @include baseScheduler.scheduler-mixin(); diff --git a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_sizes.scss index 6fb2abec8776..1db3d084feb0 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scheduler/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scheduler/_sizes.scss @@ -4,8 +4,7 @@ @use "../toolbar/sizes" as *; $fluent-scheduler-workspace-date-table-cell-height: null !default; -$fluent-scheduler-toolbar-item-offset: null !default; -$fluent-scheduler-navigator-height: null !default; +$fluent-scheduler-toolbar-item-offset: 4px !default; $fluent-scheduler-header-panel-day-font-size: null !default; $fluent-scheduler-appointment-tooltip-title-font-size: null !default; $fluent-scheduler-appointment-tooltip-date-font-size: null !default; @@ -18,8 +17,8 @@ $fluent-scheduler-dropdown-button-height: null !default; $fluent-scheduler-dropdown-button-padding: null !default; $fluent-scheduler-dropdown-button-margin: null !default; $fluent-scheduler-dropdown-button-vertical-margin: null !default; -$fluent-scheduler-dropdown-button-adaptive-margin: null !default; -$fluent-scheduler-dropdown-button-adaptive-vertical-margin: null !default; +$fluent-scheduler-dropdown-button-adaptive-margin: 0 2px !default; +$fluent-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $fluent-scheduler-appointment-tooltip-content-margin-left: null !default; $fluent-tooltip-marker-dimension: null !default; $fluent-scheduler-appointment-tooltip-title-margin-top: null !default; @@ -34,19 +33,19 @@ $fluent-scheduler-appointment-popup-all-day-item-height: null !default; $fluent-scheduler-appointment-popup-days-of-week-buttons-gap: null !default; $fluent-scheduler-appointment-popup-repeat-end-item-height: null !default; -$fluent-scheduler-appointment-short-content-padding: null !default; -$fluent-scheduler-appointment-15min-height: null !default; -$fluent-scheduler-appointment-20min-height: null !default; -$fluent-scheduler-appointment-25min-height: null !default; -$fluent-scheduler-appointment-10min-recurrence-padding-right: null !default; -$fluent-scheduler-appointment-10min-title-font-size: null !default; -$fluent-scheduler-appointment-10min-title-line-height: null !default; -$fluent-scheduler-appointment-10min-icon-font-size: null !default; -$fluent-scheduler-appointment-10min-icon-right: null !default; +$fluent-scheduler-appointment-short-content-padding: 0 8px 0 12px !default; +$fluent-scheduler-appointment-15min-height: 17px !default; +$fluent-scheduler-appointment-20min-height: 24px !default; +$fluent-scheduler-appointment-25min-height: 32px !default; +$fluent-scheduler-appointment-10min-recurrence-padding-right: 14px !default; +$fluent-scheduler-appointment-10min-title-font-size: 8px !default; +$fluent-scheduler-appointment-10min-title-line-height: 12px !default; +$fluent-scheduler-appointment-10min-icon-font-size: 12px !default; +$fluent-scheduler-appointment-10min-icon-right: 2px !default; $fluent-scheduler-appointment-15min-recurrence-padding-right: null !default; -$fluent-scheduler-appointment-15min-title-font-size: null !default; +$fluent-scheduler-appointment-15min-title-font-size: 12px !default; $fluent-scheduler-appointment-15min-title-line-height: null !default; -$fluent-scheduler-appointment-15min-icon-font-size: null !default; +$fluent-scheduler-appointment-15min-icon-font-size: 12px !default; $fluent-scheduler-appointment-15min-icon-right: null !default; $fluent-scheduler-appointment-20min-recurrence-padding-right: null !default; $fluent-scheduler-appointment-20min-icon-right: null !default; @@ -60,7 +59,7 @@ $fluent-scheduler-timeline-date-table-cell-height: 50px !default; */ $scheduler-border-radius: 0 !default; -$fluent-scheduler-navigator-icon-font-size: $fluent-base-icon-size; +$fluent-scheduler-navigator-icon-font-size: $fluent-base-icon-size !default; $fluent-scheduler-appointment-month-text-size: 12px !default; $fluent-scheduler-workspace-info-text-size: 16px !default; $fluent-scheduler-all-day-title-font-size: 12px !default; @@ -90,14 +89,12 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-collapsed-all-day-table-cell-height: 32px !default; $header-panel-time-cell-padding: 12px !default; - $header-day-element-height: 26px; + $header-day-element-height: 26px !default; $fluent-scheduler-workspace-date-table-cell-height: 38px !default; - $fluent-scheduler-navigator-height: 36px !default; $fluent-scheduler-header-panel-day-font-size: 16px !default; $fluent-scheduler-header-height: 46px !default; $fluent-scheduler-header-offset: 0 12px !default; - $fluent-scheduler-toolbar-item-offset: 4px !default; $fluent-scheduler-appointment-tooltip-title-font-size: 16px !default; $fluent-scheduler-appointment-tooltip-date-font-size: 14px !default; @@ -111,8 +108,6 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-dropdown-button-padding: 4px !default; $fluent-scheduler-dropdown-button-margin: 5px 1px 3px 3px !default; $fluent-scheduler-dropdown-button-vertical-margin: 5px 3px 3px 3px !default; - $fluent-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $fluent-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $fluent-scheduler-appointment-tooltip-content-margin-left: 6px !default; $fluent-tooltip-marker-dimension: 12px !default; @@ -128,23 +123,10 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-appointment-popup-days-of-week-buttons-gap: 12px !default; $fluent-scheduler-appointment-popup-repeat-end-item-height: 32px !default; - $agenda-appointment-title-font-size: 14px; - - $fluent-scheduler-appointment-short-content-padding: 0 8px 0 12px !default; - $fluent-scheduler-appointment-15min-height: 17px !default; - $fluent-scheduler-appointment-20min-height: 24px !default; - $fluent-scheduler-appointment-25min-height: 32px !default; - - $fluent-scheduler-appointment-10min-recurrence-padding-right: 14px !default; - $fluent-scheduler-appointment-10min-title-font-size: 8px !default; - $fluent-scheduler-appointment-10min-title-line-height: 12px !default; - $fluent-scheduler-appointment-10min-icon-font-size: 12px !default; - $fluent-scheduler-appointment-10min-icon-right: 2px !default; + $agenda-appointment-title-font-size: 14px !default; $fluent-scheduler-appointment-15min-recurrence-padding-right: 14px !default; - $fluent-scheduler-appointment-15min-title-font-size: 12px !default; $fluent-scheduler-appointment-15min-title-line-height: 18px !default; - $fluent-scheduler-appointment-15min-icon-font-size: 12px !default; $fluent-scheduler-appointment-15min-icon-right: 2px !default; $fluent-scheduler-appointment-20min-icon-right: 2px !default; @@ -155,14 +137,12 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-collapsed-all-day-table-cell-height: 24px !default; $header-panel-time-cell-padding: 8px !default; - $header-day-element-height: 24px; + $header-day-element-height: 24px !default; $fluent-scheduler-workspace-date-table-cell-height: 28px !default; - $fluent-scheduler-navigator-height: 28px !default; $fluent-scheduler-header-panel-day-font-size: 14px !default; $fluent-scheduler-header-height: 34px !default; $fluent-scheduler-header-offset: 2px 8px !default; - $fluent-scheduler-toolbar-item-offset: 4px !default; $fluent-scheduler-appointment-tooltip-title-font-size: 14px !default; $fluent-scheduler-appointment-tooltip-date-font-size: 11px !default; @@ -176,8 +156,6 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-dropdown-button-padding: 2px !default; $fluent-scheduler-dropdown-button-margin: 2px 1px 2px 3px !default; $fluent-scheduler-dropdown-button-vertical-margin: 2px 1px 2px 1px !default; - $fluent-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $fluent-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $fluent-scheduler-appointment-tooltip-content-margin-left: 8px !default; $fluent-tooltip-marker-dimension: 18px !default; @@ -193,23 +171,10 @@ $header-panel-time-cell-padding: null !default; $fluent-scheduler-appointment-popup-days-of-week-buttons-gap: 8px !default; $fluent-scheduler-appointment-popup-repeat-end-item-height: 24px !default; - $agenda-appointment-title-font-size: 13px; - - $fluent-scheduler-appointment-short-content-padding: 0 8px 0 12px !default; - $fluent-scheduler-appointment-15min-height: 17px !default; - $fluent-scheduler-appointment-20min-height: 24px !default; - $fluent-scheduler-appointment-25min-height: 32px !default; - - $fluent-scheduler-appointment-10min-recurrence-padding-right: 14px !default; - $fluent-scheduler-appointment-10min-title-font-size: 8px !default; - $fluent-scheduler-appointment-10min-title-line-height: 12px !default; - $fluent-scheduler-appointment-10min-icon-font-size: 12px !default; - $fluent-scheduler-appointment-10min-icon-right: 2px !default; + $agenda-appointment-title-font-size: 13px !default; $fluent-scheduler-appointment-15min-recurrence-padding-right: 16px !default; - $fluent-scheduler-appointment-15min-title-font-size: 12px !default; $fluent-scheduler-appointment-15min-title-line-height: 12px !default; - $fluent-scheduler-appointment-15min-icon-font-size: 12px !default; $fluent-scheduler-appointment-15min-icon-right: 5px !default; $fluent-scheduler-appointment-20min-recurrence-padding-right: 20px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/scrollView/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/scrollView/_index.scss index 12a4b458fc80..fa971352b542 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scrollView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scrollView/_index.scss @@ -6,7 +6,7 @@ @use "../scrollable"; @use "../../base/scrollView"; -$scrollview-loading-indicator-size: 42px; +$scrollview-loading-indicator-size: 42px !default; .dx-scrollview-scrollbottom-text, .dx-scrollview-pull-down-text { diff --git a/packages/devextreme-scss/scss/widgets/fluent/scrollable/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/scrollable/_sizes.scss index 993a6b25a95a..0cfcdad10d93 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/scrollable/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/scrollable/_sizes.scss @@ -1,2 +1,2 @@ -$fluent-scrollbar-size-thin: 6px; -$fluent-scrollbar-size: 13px; +$fluent-scrollbar-size-thin: 6px !default; +$fluent-scrollbar-size: 13px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/slider/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/slider/_sizes.scss index 788adabaef81..b9af6d044e39 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/slider/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/slider/_sizes.scss @@ -3,31 +3,22 @@ $fluent-slider-handle-size: null !default; $fluent-slider-wrapper-height: null !default; $fluent-slider-tooltip-width: null !default; -$fluent-slider-top-tooltip-margin: null !default; $fluent-slider-height: 28px !default; // for fieldset height $fluent-slider-radius: 50% !default; $fluent-slider-track-height: null !default; $fluent-slider-track-border-radius: 2px !default; -$fluent-slider-handle-height: null !default; -$fluent-slider-handle-inner-size: null !default; @if $size == "default" { $fluent-slider-track-height: 4px !default; - $fluent-slider-handle-height: 18px !default; $fluent-slider-handle-size: 20px !default; - $fluent-slider-handle-inner-size: 18px !default; $fluent-slider-wrapper-height: 32px !default; $fluent-slider-tooltip-width: 34px !default; - $fluent-slider-top-tooltip-margin: -5px !default; } @else if $size == "compact" { $fluent-slider-track-height: 2px !default; - $fluent-slider-handle-height: 14px !default; $fluent-slider-handle-size: 16px !default; - $fluent-slider-handle-inner-size: 14px !default; $fluent-slider-wrapper-height: 26px !default; $fluent-slider-tooltip-width: 28px !default; - $fluent-slider-top-tooltip-margin: -2px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_index.scss index 4ddec0093191..d1143ea3fcea 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_index.scss @@ -5,8 +5,8 @@ @use "../button/colors" as *; @use "../overlay"; -$fluent-fa-button-label-offset: 16px; -$fluent-fa-button-label-padding: 4px 8px; +$fluent-fa-button-label-offset: 16px !default; +$fluent-fa-button-label-padding: 4px 8px !default; @include dx-fa-button-mixin( $fluent-main-fa-button-size, diff --git a/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_sizes.scss index 8862d4505a19..bfd200a0685b 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/speedDialAction/_sizes.scss @@ -8,13 +8,13 @@ $fluent-fa-button-size: null !default; @if $size == "default" { $fluent-main-fa-button-size: 44px !default; $fluent-fa-button-size: 44px !default; - $fluent-main-fa-button-horizontal-padding: 16px; - $fluent-main-fa-button-label-padding: 0 $fluent-main-fa-button-horizontal-padding 0 8px; + $fluent-main-fa-button-horizontal-padding: 16px !default; + $fluent-main-fa-button-label-padding: 0 $fluent-main-fa-button-horizontal-padding 0 8px !default; } @else if $size == "compact" { $fluent-main-fa-button-size: 32px !default; $fluent-fa-button-size: 32px !default; - $fluent-main-fa-button-horizontal-padding: 12px; - $fluent-main-fa-button-label-padding: 0 $fluent-main-fa-button-horizontal-padding 0 4px; + $fluent-main-fa-button-horizontal-padding: 12px !default; + $fluent-main-fa-button-label-padding: 0 $fluent-main-fa-button-horizontal-padding 0 4px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/splitter/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/splitter/_colors.scss index 44c8a9441194..31e5ce8db750 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/splitter/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/splitter/_colors.scss @@ -10,19 +10,19 @@ $splitter-resize-handle-color: null !default; * $name 30. Focused state icon color * $type color */ -$splitter-resize-handle-color-focused: null !default; +$splitter-resize-handle-color-focused: $base-inverted-text-color !default; /** * $name 40. Handle background color * $type color */ -$splitter-resize-handle-bg: null !default; +$splitter-resize-handle-bg: $base-border-color !default; /** * $name 50. Handle hover state background color * $type color */ -$splitter-resize-handle-bg-hover: null !default; +$splitter-resize-handle-bg-hover: $base-border-color-hover !default; /** * $name 60. Handle focused state background color @@ -32,18 +32,10 @@ $splitter-resize-handle-bg-focused: $base-accent !default; @if $mode == "light" { $splitter-resize-handle-color: $base-icon-color !default; - $splitter-resize-handle-color-focused: $base-inverted-text-color !default; - - $splitter-resize-handle-bg: $base-border-color !default; - $splitter-resize-handle-bg-hover: $base-border-color-hover !default; } @if $mode == "dark" { $splitter-resize-handle-color: $base-text-color !default; - $splitter-resize-handle-color-focused: $base-inverted-text-color !default; - - $splitter-resize-handle-bg: $base-border-color !default; - $splitter-resize-handle-bg-hover: $base-border-color-hover !default; } /** diff --git a/packages/devextreme-scss/scss/widgets/fluent/splitter/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/splitter/_sizes.scss index a0e0b973b604..94d7ce69c89a 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/splitter/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/splitter/_sizes.scss @@ -1 +1 @@ -$splitter-resize-handle-icon-size: 20px; +$splitter-resize-handle-icon-size: 20px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/switch/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/switch/_index.scss index f2dcd883f771..55c05689deca 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/switch/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/switch/_index.scss @@ -5,9 +5,9 @@ @use "../../base/switch"; @use "../../base/switch/mixins" as *; -$fluent-switch-transition: all 0.2s cubic-bezier(0.33, 0, 0.67, 1); -$fluent-switch-handle-on-shadow: 0 0 0 $fluent-switch-shadow-blur-radius color.change($switch-handle-on-bg, $alpha: 0.1); -$fluent-switch-shadow: none; +$fluent-switch-transition: all 0.2s cubic-bezier(0.33, 0, 0.67, 1) !default; +$fluent-switch-handle-on-shadow: 0 0 0 $fluent-switch-shadow-blur-radius color.change($switch-handle-on-bg, $alpha: 0.1) !default; +$fluent-switch-shadow: none !default; @include dx-switch( $fluent-switch-width, diff --git a/packages/devextreme-scss/scss/widgets/fluent/switch/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/switch/_sizes.scss index c2817e8f9714..8906ee1bef54 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/switch/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/switch/_sizes.scss @@ -3,11 +3,9 @@ $fluent-switch-width: null !default; $switch-handle-margin: null !default; $switch-padding: 8px !default; -$switch-container-vertical-margin: null !default; -$switch-container-horizontal-padding: null !default; +$switch-container-vertical-margin: 0 !default; $fluent-switch-height: null !default; $switch-handle-height: null !default; -$switch-container-height: null !default; $fluent-switch-shadow-blur-radius: 0 !default; $fluent-switch-container-adjustment: 6px !default; @@ -16,18 +14,15 @@ $fluent-switch-container-adjustment: 6px !default; $fluent-switch-width: 40px !default; $switch-handle-height: 14px !default; $switch-handle-margin: 3px !default; - $switch-container-vertical-margin: 0 !default; - $switch-container-horizontal-padding: $switch-handle-margin !default; $fluent-switch-height: 20px !default; - $switch-container-height: $fluent-switch-height !default; } @else if $size == "compact" { $fluent-switch-width: 32px !default; $switch-handle-height: 12px !default; $switch-handle-margin: 2px !default; - $switch-container-vertical-margin: 0 !default; - $switch-container-horizontal-padding: $switch-handle-margin !default; $fluent-switch-height: 16px !default; - $switch-container-height: $fluent-switch-height !default; } + +$switch-container-horizontal-padding: $switch-handle-margin !default; +$switch-container-height: $fluent-switch-height !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/tabs/layout/tab/_base.scss b/packages/devextreme-scss/scss/widgets/fluent/tabs/layout/tab/_base.scss index 43392b992139..a55f2877d88d 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/tabs/layout/tab/_base.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/tabs/layout/tab/_base.scss @@ -31,21 +31,13 @@ } .dx-tab-text .dx-tab-text-span { - visibility: visible; - - .dx-tab-text-span-pseudo { - visibility: hidden; - } + font-weight: $fluent-tab-font-weight-accent; } } &.dx-state-disabled { color: $tabs-tab-disabled-color; - .dx-tab-text .dx-tab-text-span-pseudo { - color: $tabs-tab-disabled-color; - } - .dx-icon { color: $tabs-tab-icon-disabled-color; } @@ -61,22 +53,16 @@ line-height: $fluent-line-height; .dx-tab-text-span { - position: relative; display: block; - font-weight: $fluent-tab-font-weight-accent; - visibility: hidden; + font-weight: $fluent-tab-font-weight-basic; } .dx-tab-text-span-pseudo { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; + display: block; + height: 0; pointer-events: none; - color: $tabs-tab-color; - font-weight: $fluent-tab-font-weight-basic; - visibility: visible; + font-weight: $fluent-tab-font-weight-accent; + visibility: hidden; } } diff --git a/packages/devextreme-scss/scss/widgets/fluent/tabs/variables/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/tabs/variables/_sizes.scss index 1793c8ce0a4a..0c73f52e0640 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/tabs/variables/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/tabs/variables/_sizes.scss @@ -1,22 +1,21 @@ @use "../../sizes" as *; $fluent-tabs-item-gap: 4px !default; -$fluent-tabs-item-padding: null !default; $fluent-tabs-item-padding-top-bottom: null !default; $fluent-tabs-item-padding-right-left: null !default; -$fluent-tab-height: null !default; +$fluent-tab-height: auto !default; $fluent-tab-font-weight-basic: 400 !default; $fluent-tab-font-weight-accent: 500 !default; -$fluent-base-icon-margin: null !default; +$fluent-base-icon-margin: 8px !default; $fluent-tabs-icon-size: $fluent-base-icon-size !default; $fluent-tab-nav-button-icon-width: null !default; $fluent-tab-nav-button-icon-height: null !default; $fluent-tab-nav-button-icon-font-size: null !default; -$fluent-tab-nav-button-content-padding: null !default; +$fluent-tab-nav-button-content-padding: 0 4px !default; -$fluent-tab-vertical-nav-button-content-padding: null !default; +$fluent-tab-vertical-nav-button-content-padding: 4px 0 !default; $fluent-indicator-thickness-primary: 2px !default; $fluent-indicator-thickness-secondary: 3px !default; @@ -32,27 +31,19 @@ $fluent-badge-min-size: null !default; @if $size == "default" { $fluent-tabs-item-padding-top-bottom: 12px !default; $fluent-tabs-item-padding-right-left: 10px !default; - $fluent-tabs-item-padding: $fluent-tabs-item-padding-top-bottom $fluent-tabs-item-padding-right-left !default; - $fluent-tab-height: auto !default; - $fluent-base-icon-margin: 8px !default; $fluent-tab-nav-button-icon-width: 20px !default; $fluent-tab-nav-button-icon-height: 20px !default; $fluent-tab-nav-button-icon-font-size: 20px !default; - $fluent-tab-nav-button-content-padding: 0 4px !default; - $fluent-tab-vertical-nav-button-content-padding: 4px 0 !default; $fluent-badge-min-size: 20px !default; } @else if $size == "compact" { $fluent-tabs-item-padding-top-bottom: 8px !default; $fluent-tabs-item-padding-right-left: 8px !default; - $fluent-tabs-item-padding: $fluent-tabs-item-padding-top-bottom $fluent-tabs-item-padding-right-left !default; - $fluent-tab-height: auto !default; - $fluent-base-icon-margin: 8px !default; $fluent-tab-nav-button-icon-width: 16px !default; $fluent-tab-nav-button-icon-height: 16px !default; $fluent-tab-nav-button-icon-font-size: 16px !default; - $fluent-tab-nav-button-content-padding: 0 4px !default; - $fluent-tab-vertical-nav-button-content-padding: 4px 0 !default; $fluent-badge-min-size: 16px !default; } + +$fluent-tabs-item-padding: $fluent-tabs-item-padding-top-bottom $fluent-tabs-item-padding-right-left !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/tagBox/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/tagBox/_sizes.scss index 214f072e13de..92052e567b9e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/tagBox/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/tagBox/_sizes.scss @@ -1,7 +1,7 @@ @use "../sizes" as *; $fluent-tagbox-tag-content-padding-left: null !default; -$fluent-tagbox-tag-content-padding-right: null !default; +$fluent-tagbox-tag-content-padding-right: 20px !default; $fluent-tagbox-tag-content-vertical-padding: null !default; $fluent-tagbox-tag-content-font-size: null !default; $fluent-tagbox-tag-container-padding-left: null !default; @@ -17,7 +17,6 @@ $fluent-tagbox-tag-container-gap: null !default; @if $size == "default" { $fluent-tagbox-tag-content-padding-left: 6px !default; - $fluent-tagbox-tag-content-padding-right: 20px !default; $fluent-tagbox-tag-content-vertical-padding: 2px !default; $fluent-tagbox-tag-content-font-size: 12px !default; $fluent-tagbox-tag-container-padding-left: 12px !default; @@ -31,7 +30,6 @@ $fluent-tagbox-tag-container-gap: null !default; @else if $size == "compact" { $fluent-tagbox-tag-content-padding-left: 4px !default; - $fluent-tagbox-tag-content-padding-right: 20px !default; $fluent-tagbox-tag-content-vertical-padding: 0 !default; $fluent-tagbox-tag-content-font-size: 10px !default; $fluent-tagbox-tag-container-padding-left: 8px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_colors.scss b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_colors.scss index b711bc2cce9d..40f6e1def3da 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_colors.scss @@ -87,20 +87,20 @@ $texteditor-input-border-radius: $base-border-radius !default; $texteditor-label-transition: font-size 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, transform 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, - top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms; + top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms !default; @if $mode == "light" { + $texteditor-outlined-bg: $base-element-bg !default; + @if $color == "blue" { $texteditor-color: $base-text-color !default; $texteditor-placeholder-color: color.adjust($base-text-color, $lightness: 29.80%, $space: hsl) !default; - $texteditor-outlined-bg: $base-element-bg !default; $texteditor-filled-bg: color.adjust($base-element-bg, $lightness: -3.9%, $space: hsl) !default; } @if $color == "saas" { $texteditor-color: rgba(66, 81, 118, 1) !default; $texteditor-placeholder-color: rgba(144, 158, 192, 1) !default; - $texteditor-outlined-bg: $base-element-bg !default; $texteditor-filled-bg: rgba(243, 245, 248, 1) !default; } @@ -141,8 +141,8 @@ $texteditor-filled-bg-hover: $texteditor-filled-bg !default; */ $texteditor-filled-bg-focused: $texteditor-filled-bg !default; -$texteditor-label-color: $texteditor-placeholder-color; -$texteditor-label-color-focused: $base-accent; +$texteditor-label-color: $texteditor-placeholder-color !default; +$texteditor-label-color-focused: $base-accent !default; :root { --dx-texteditor-color-text: #{$texteditor-color}; diff --git a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_index.scss b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_index.scss index 6ef8c31da61a..463672eeeafe 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_index.scss @@ -12,10 +12,10 @@ @use "../../base/textEditor/mixins" as *; @use "../colorBox/sizes" as *; -$fluent-base-standard-texteditor-input-horizontal-padding: 0; -$fluent-texteditor-invalid-badge-size: $fluent-invalid-badge-size + 2 * $fluent-invalid-badge-horizontal-padding; -$fluent-editor-custom-button-margin: 6px; -$fluent-editor-border-width: 1px; +$fluent-base-standard-texteditor-input-horizontal-padding: 0 !default; +$fluent-texteditor-invalid-badge-size: $fluent-invalid-badge-size + 2 * $fluent-invalid-badge-horizontal-padding !default; +$fluent-editor-custom-button-margin: 6px !default; +$fluent-editor-border-width: 1px !default; @mixin texteditor-outlined-styling() { background-color: $texteditor-outlined-bg; diff --git a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_sizes.scss index eabf7d1cf17a..d49693d85091 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/textEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/textEditor/_sizes.scss @@ -4,13 +4,13 @@ $fluent-texteditor-line-height: null !default; $fluent-texteditor-font-size: null !default; $fluent-texteditor-input-height: null !default; -$fluent-texteditor-label-top-offset: null !default; +$fluent-texteditor-label-top-offset: -1px !default; $fluent-texteditor-label-outside-top-offset: null !default; $fluent-filled-texteditor-input-horizontal-padding: null !default; $fluent-filled-texteditor-input-top-padding: null !default; $fluent-filled-texteditor-input-bottom-padding: null !default; $fluent-standard-texteditor-input-top-padding: null !default; -$fluent-underlined-editor-button-height: null !default; +$fluent-underlined-editor-button-height: 22px !default; $fluent-texteditor-label-font-size: null !default; $fluent-texteditor-label-outside-height: null !default; $fluent-standard-texteditor-input-bottom-padding: null !default; @@ -27,7 +27,6 @@ $texteditor-icon-container-size: $fluent-base-icon-size !default; $fluent-texteditor-label-outside-height: 20px !default; $fluent-texteditor-label-outside-top-offset: 24px !default; $fluent-texteditor-label-font-size: 12px !default; - $fluent-texteditor-label-top-offset: -1px !default; $fluent-filled-texteditor-input-horizontal-padding: 12px !default; $fluent-filled-texteditor-input-top-padding: 5px !default; $fluent-filled-texteditor-input-with-label-top-padding: 8px !default; @@ -37,7 +36,6 @@ $texteditor-icon-container-size: $fluent-base-icon-size !default; $fluent-standard-texteditor-input-bottom-padding: 5px !default; $fluent-standard-texteditor-with-label-input-top-padding: 9px !default; $fluent-standard-texteditor-with-label-input-bottom-padding: 3px !default; - $fluent-underlined-editor-button-height: 22px !default; } @else if $size == "compact" { @@ -47,7 +45,6 @@ $texteditor-icon-container-size: $fluent-base-icon-size !default; $fluent-texteditor-label-outside-height: 16px !default; $fluent-texteditor-label-outside-top-offset: 18px !default; $fluent-texteditor-label-font-size: 10px !default; - $fluent-texteditor-label-top-offset: -1px !default; $fluent-filled-texteditor-input-horizontal-padding: 8px !default; $fluent-filled-texteditor-input-top-padding: 3px !default; $fluent-filled-texteditor-input-with-label-top-padding: 6px !default; @@ -57,7 +54,6 @@ $texteditor-icon-container-size: $fluent-base-icon-size !default; $fluent-standard-texteditor-input-bottom-padding: 3px !default; $fluent-standard-texteditor-with-label-input-top-padding: 6px !default; $fluent-standard-texteditor-with-label-input-bottom-padding: 2px !default; - $fluent-underlined-editor-button-height: 22px !default; } $fluent-filled-texteditor-input-padding: diff --git a/packages/devextreme-scss/scss/widgets/fluent/tooltip/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/tooltip/_sizes.scss index 62ab9b9c048a..80bebbfef54f 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/tooltip/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/tooltip/_sizes.scss @@ -2,16 +2,14 @@ @use "../sizes" as *; $fluent-tooltip-padding: null !default; -$fluent-tooltip-font-size: null !default; +$fluent-tooltip-font-size: 12px !default; $fluent-tooltip-arrow-border-radius: 2px !default; $fluent-tooltip-shadow: 0 0 2px 0 $base-dropdown-shadow-color1 !default; @if $size == "default" { $fluent-tooltip-padding: 7px 11px !default; - $fluent-tooltip-font-size: 12px; } @else if $size == "compact" { $fluent-tooltip-padding: 3px 5px !default; - $fluent-tooltip-font-size: 12px; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/treeView/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/treeView/_sizes.scss index 8f2518ae8958..8cb50f05e334 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/treeView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/treeView/_sizes.scss @@ -1,18 +1,17 @@ @use "../sizes" as *; $fluent-treeview-toggle-item-width: null !default; -$fluent-treeview-item-height: null !default; $fluent-checkbox-container-size: null !default; -$fluent-treeview-container-with-border-offset: 8px; +$fluent-treeview-container-with-border-offset: 8px !default; $fluent-treeview-item-element-gap: 8px !default; -$fluent-treeview-item-margin: 1px 0; -$fluent-treeview-item-border-radius: 4px; -$fluent-treeview-border-padding: 1px; +$fluent-treeview-item-margin: 1px 0 !default; +$fluent-treeview-item-border-radius: 4px !default; +$fluent-treeview-border-padding: 1px !default; $fluent-treeview-container-padding: $fluent-treeview-border-padding $fluent-treeview-border-padding $fluent-treeview-border-padding - $fluent-treeview-container-with-border-offset; + $fluent-treeview-container-with-border-offset !default; $fluent-treeview-checkbox-offset: null !default; $fluent-select-all-item-padding: null !default; @@ -24,7 +23,6 @@ $fluent-treeview-item-side-padding: 12px !default; $fluent-select-all-item-border-visible-padding: 26px !default; @if $size == "default" { - $fluent-treeview-item-height: 44px !default; $fluent-treeview-toggle-item-width: 20px !default; $fluent-checkbox-container-size: 20px !default; $fluent-treeview-checkbox-offset: 42px !default; @@ -32,11 +30,10 @@ $fluent-select-all-item-border-visible-padding: 26px !default; $fluent-treeview-item-padding: 12px $fluent-treeview-item-side-padding !default; $fluent-treeview-load-indicator-size: 20px !default; $fluent-treeview-searchbox-margin-bottom: 8px !default; - $fluent-treeview-search-editor-height: 48px; + $fluent-treeview-search-editor-height: 48px !default; } @else if $size == "compact" { - $fluent-treeview-item-height: 32px !default; $fluent-treeview-toggle-item-width: 16px !default; $fluent-treeview-checkbox-offset: 36px !default; $fluent-checkbox-container-size: 16px !default; @@ -44,5 +41,5 @@ $fluent-select-all-item-border-visible-padding: 26px !default; $fluent-treeview-item-padding: 10px $fluent-treeview-item-side-padding !default; $fluent-treeview-load-indicator-size: 16px !default; $fluent-treeview-searchbox-margin-bottom: 4px !default; - $fluent-treeview-search-editor-height: 32px; + $fluent-treeview-search-editor-height: 32px !default; } diff --git a/packages/devextreme-scss/scss/widgets/fluent/typography/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/typography/_sizes.scss index 554959097d56..7a73a5756f5e 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/typography/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/typography/_sizes.scss @@ -4,7 +4,7 @@ $fluent-xl-font-size: null !default; $fluent-l-font-size: null !default; $fluent-m-font-size: null !default; $fluent-s-font-size: null !default; -$fluent-xs-font-size: null !default; +$fluent-xs-font-size: 12px !default; $fluent-h1-font-size: null !default; $fluent-h2-font-size: null !default; $fluent-h3-font-size: null !default; @@ -17,7 +17,6 @@ $fluent-h6-font-size: null !default; $fluent-l-font-size: 28px !default; $fluent-m-font-size: 20px !default; $fluent-s-font-size: 18px !default; - $fluent-xs-font-size: 12px !default; $fluent-h1-font-size: 40px !default; $fluent-h2-font-size: 36px !default; @@ -32,7 +31,6 @@ $fluent-h6-font-size: null !default; $fluent-l-font-size: 20px !default; $fluent-m-font-size: 16px !default; $fluent-s-font-size: 14px !default; - $fluent-xs-font-size: 12px !default; $fluent-h1-font-size: 32px !default; $fluent-h2-font-size: 28px !default; diff --git a/packages/devextreme-scss/scss/widgets/fluent/validation/_sizes.scss b/packages/devextreme-scss/scss/widgets/fluent/validation/_sizes.scss index 408e81922fdd..f46cec6854f2 100644 --- a/packages/devextreme-scss/scss/widgets/fluent/validation/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/fluent/validation/_sizes.scss @@ -1,6 +1,5 @@ @use "../sizes" as *; -$fluent-validation-message-padding: null !default; $fluent-validationsummary-item-margin: null !default; $fluent-validation-message-line-height: null !default; $fluent-validation-message-horizontal-padding: null !default; @@ -10,7 +9,6 @@ $fluent-validation-message-vertical-padding: 4px !default; $fluent-validation-message-font-size: 12px !default; @if $size == "default" { - $fluent-validation-message-padding: 6px !default; $fluent-validationsummary-item-margin: 6px !default; $fluent-validation-message-horizontal-padding: 8px !default; @@ -18,7 +16,6 @@ $fluent-validation-message-font-size: 12px !default; } @else if $size == "compact" { - $fluent-validation-message-padding: 4px !default; $fluent-validationsummary-item-margin: 4px !default; $fluent-validation-message-horizontal-padding: 6px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/_colors.scss index c0a20d72d71e..85a4533e8ef8 100644 --- a/packages/devextreme-scss/scss/widgets/generic/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/_colors.scss @@ -124,8 +124,6 @@ $base-invalid-color: null !default; */ $base-invalid-faded-border-color: null !default; $base-header-color: null !default; - -$scrollview-pulldown-path: null !default; $base-default: null !default; $base-info: null !default; $screen-text-color: null !default; @@ -166,8 +164,6 @@ $base-grid-selectedrow-border-color: null !default; $base-invalid-color: $base-danger !default; $base-invalid-faded-border-color: color.change($base-invalid-color, $alpha: 0.4) !default; $base-header-color: #627789 !default; - - $scrollview-pulldown-path: data-uri("images/widgets/generic/color-schemes/carmine/pulldown.png") !default; } @if $color == "contrast" { @@ -194,8 +190,6 @@ $base-grid-selectedrow-border-color: null !default; $base-invalid-faded-border-color: $base-invalid-color !default; $base-label-color: $base-text-color !default; $base-dropdown-shadow-color: $base-shadow-color !default; - - $scrollview-pulldown-path: data-uri("images/widgets/generic/color-schemes/contrast/pulldown.png") !default; } @if $color == "dark" { @@ -226,8 +220,6 @@ $base-grid-selectedrow-border-color: null !default; $base-dropdown-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; $base-invalid-color: $base-danger !default; $base-invalid-faded-border-color: color.change($base-invalid-color, $alpha: 0.4) !default; - - $scrollview-pulldown-path: data-uri("images/widgets/generic/color-schemes/dark/pulldown.png") !default; } @if $color == "darkmoon" { @@ -262,8 +254,6 @@ $base-grid-selectedrow-border-color: null !default; $base-invalid-faded-border-color: color.change($base-invalid-color, $alpha: 0.4) !default; $screen-text-color: extcolor.screen($base-bg, #686868) !default; $base-grid-selected-border-color: #45c7de !default; - - $scrollview-pulldown-path: data-uri("images/widgets/generic/color-schemes/darkmoon/pulldown.png") !default; } @if $color == "darkviolet" { @@ -298,8 +288,6 @@ $base-grid-selectedrow-border-color: null !default; $base-dropdown-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; $base-invalid-color: $base-danger !default; $base-invalid-faded-border-color: color.change($base-invalid-color, $alpha: 0.4) !default; - - $scrollview-pulldown-path: data-uri("images/widgets/generic/color-schemes/darkviolet/pulldown.png") !default; } @if $color == "greenmist" { @@ -330,8 +318,6 @@ $base-grid-selectedrow-border-color: null !default; $base-dropdown-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; $base-invalid-color: $base-danger !default; $base-invalid-faded-border-color: color.change($base-invalid-color, $alpha: 0.4) !default; - - $scrollview-pulldown-path: data-uri("images/widgets/generic/color-schemes/greenmist/pulldown.png") !default; } @if $color == "light" { @@ -362,8 +348,6 @@ $base-grid-selectedrow-border-color: null !default; $base-dropdown-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; $base-invalid-color: $base-danger !default; $base-invalid-faded-border-color: color.change($base-invalid-color, $alpha: 0.4) !default; - - $scrollview-pulldown-path: data-uri("images/widgets/generic/color-schemes/light/pulldown.png") !default; } @if $color == "softblue" { @@ -399,8 +383,6 @@ $base-grid-selectedrow-border-color: null !default; $base-dropdown-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; $base-invalid-color: $base-danger !default; $base-invalid-faded-border-color: color.change($base-invalid-color, $alpha: 0.4) !default; - - $scrollview-pulldown-path: data-uri("images/widgets/generic/color-schemes/softblue/pulldown.png") !default; } $typography-bg: $base-bg !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/_sizes.scss index bfcfe2484d0a..3ad8c9f221f2 100644 --- a/packages/devextreme-scss/scss/widgets/generic/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/_sizes.scss @@ -69,7 +69,7 @@ $generic-base-inline-borderedwidget-bottom-padding: $generic-base-inline-widget-bottom-padding - $generic-base-border-width !default; -$generic-treeview-search-editor-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding + 2 * $generic-base-border-width; +$generic-treeview-search-editor-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding + 2 * $generic-base-border-width !default; :root { --dx-component-height: #{$generic-base-inline-widget-height}; diff --git a/packages/devextreme-scss/scss/widgets/generic/button/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/button/_sizes.scss index 8c648c6e3c96..d8302f0516c3 100644 --- a/packages/devextreme-scss/scss/widgets/generic/button/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/button/_sizes.scss @@ -4,7 +4,6 @@ $generic-button-text-line-height: null !default; $generic-button-vertical-padding: null !default; $generic-button-horizontal-padding: null !default; $generic-button-icon-horizontal-padding: null !default; -$generic-button-box-shadow-size: null !default; $generic-button-border-weight: $generic-base-border-width !default; $generic-button-content-line-height: $generic-base-line-height !default; @@ -14,7 +13,6 @@ $generic-button-content-line-height: $generic-base-line-height !default; $generic-button-vertical-padding: 8px !default; $generic-button-horizontal-padding: 12px !default; $generic-button-icon-horizontal-padding: 8px !default; - $generic-button-box-shadow-size: 0 1px 3px !default; } @else if $size == "compact" { @@ -22,7 +20,6 @@ $generic-button-content-line-height: $generic-base-line-height !default; $generic-button-vertical-padding: 4px !default; $generic-button-horizontal-padding: 10px !default; $generic-button-icon-horizontal-padding: 5px !default; - $generic-button-box-shadow-size: 0 1px 2px !default; } :root { diff --git a/packages/devextreme-scss/scss/widgets/generic/cardView/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/cardView/_sizes.scss index e1f0ed77f527..561619679484 100644 --- a/packages/devextreme-scss/scss/widgets/generic/cardView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/cardView/_sizes.scss @@ -17,14 +17,14 @@ * $name 101. Border radius * $type text */ -$cardview-generic-common-border-radius: null !default; +$cardview-generic-common-border-radius: 16px !default; /** * $name 104. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-common-text-weight: null !default; +$cardview-generic-common-text-weight: 400 !default; /** * $name 105. Vertical gap between cards @@ -46,9 +46,9 @@ $cardview-generic-common-padding: null !default; // #region CARD -$cardview-generic-card-min-width: null !default; +$cardview-generic-card-min-width: 250px !default; -$cardview-generic-card-border-width: null !default; +$cardview-generic-card-border-width: 1px !default; /** * $name 202. Border radius @@ -67,7 +67,7 @@ $cardview-generic-card__header__selection-checkbox-padding-right: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-card__field-caption-weight: null !default; +$cardview-generic-card__field-caption-weight: 600 !default; $cardview-generic-card__cover-max-height: null !default; @@ -90,13 +90,13 @@ $cardview-generic-card__content__cell-padding-horizontal: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-card--selected__field-caption-weight: null !default; +$cardview-generic-card--selected__field-caption-weight: $cardview-generic-card__field-caption-weight !default; // #endregion // #region HEADER -$cardview-generic-header-gap: null !default; +$cardview-generic-header-gap: 12px !default; // #endregion @@ -110,13 +110,13 @@ $cardview-generic-header-panel--empty-line-height: null !default; // #region HEADER PANEL ITEM -$cardview-generic-header-panel__item-border-width: null !default; +$cardview-generic-header-panel__item-border-width: 1px !default; /** * $name 402. Border radius * $type text */ -$cardview-generic-header-panel__item-border-radius: null !default; +$cardview-generic-header-panel__item-border-radius: 4px !default; $cardview-generic-header-panel__item-padding-vertical: null !default; @@ -129,16 +129,16 @@ $cardview-generic-header-panel__item-line-height: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item-text-weight: null !default; +$cardview-generic-header-panel__item-text-weight: $cardview-generic-common-text-weight !default; /** * $name 408. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item__sort-index-text-weight: null !default; +$cardview-generic-header-panel__item__sort-index-text-weight: 400 !default; -$cardview-generic-header-panel__item__icon-size: null !default; +$cardview-generic-header-panel__item__icon-size: $generic-base-icon-size !default; $cardview-generic-header-item__left-padding: null !default; $cardview-generic-header-item__right-padding: null !default; @@ -152,14 +152,14 @@ $cardview-generic-header-item__right-padding: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--hovered-text-weight: null !default; +$cardview-generic-header-panel__item--hovered-text-weight: $cardview-generic-header-panel__item-text-weight !default; /** * $name 507. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--hovered__sort-index-text-weight: null !default; +$cardview-generic-header-panel__item--hovered__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; // #endregion @@ -170,14 +170,14 @@ $cardview-generic-header-panel__item--hovered__sort-index-text-weight: null !def * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--disabled-text-weight: null !default; +$cardview-generic-header-panel__item--disabled-text-weight: $cardview-generic-header-panel__item-text-weight !default; /** * $name 607. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--disabled__sort-index-text-weight: null !default; +$cardview-generic-header-panel__item--disabled__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; // #endregion @@ -188,10 +188,10 @@ $cardview-generic-header-panel__item--disabled__sort-index-text-weight: null !de * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-header-panel__item--dragged-text-weight: null !default; +$cardview-generic-header-panel__item--dragged-text-weight: $cardview-generic-header-panel__item-text-weight !default; -$cardview-generic-header-item__sort-indicator__border-size: null; -$cardview-generic-header-item__sort-indicator__border-radius: null; +$cardview-generic-header-item__sort-indicator__border-size: null !default; +$cardview-generic-header-item__sort-indicator__border-radius: null !default; // #endregion @@ -199,9 +199,9 @@ $cardview-generic-header-item__sort-indicator__border-radius: null; $cardview-generic-header-panel__dropzone-height: null !default; -$cardview-generic-header-panel__dropzone-top: null !default; +$cardview-generic-header-panel__dropzone-top: 0 !default; -$cardview-generic-header-panel__dropzone-border-width: null !default; +$cardview-generic-header-panel__dropzone-border-width: 1.5px !default; /** * $name 801. Border radius @@ -217,7 +217,7 @@ $cardview-generic-header-panel__dropzone-border-radius: null !default; * $name 901. Border radius * $type text */ -$cardview-generic-filter-panel-border-radius: null !default; +$cardview-generic-filter-panel-border-radius: 0 !default; // #endregion @@ -237,16 +237,16 @@ $cardview-generic-nodata-view__icon-size: null !default; * $name 1101. Border radius * $type text */ -$cardview-generic-card__content__field-value--highlighted-border-radius: null !default; +$cardview-generic-card__content__field-value--highlighted-border-radius: 5px !default; -$cardview-generic-card__content__field-value--highlighted-border-padding: null !default; +$cardview-generic-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; /** * $name 1103. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-generic-card__content__field-value--highlighted-text-weight: null !default; +$cardview-generic-card__content__field-value--highlighted-text-weight: 500 !default; // #endregion @@ -261,21 +261,16 @@ $cardview-generic-column-chooser__select-all-item-horizontal-padding: null !defa @if $size == "default" { // Common - $cardview-generic-common-border-radius: 16px !default; - $cardview-generic-common-text-weight: 400 !default; $cardview-generic-common-cards-row-gap: 16px !default; $cardview-generic-common-cards-column-gap: 16px !default; $cardview-generic-common-gap: 20px !default; $cardview-generic-common-padding: 24px !default; // Card - $cardview-generic-card-min-width: 250px !default; - $cardview-generic-card-border-width: 1px !default; $cardview-generic-card-border-radius: 8px !default; $cardview-generic-card__header-height: 52px !default; $cardview-generic-card__header-text-size: 16px !default; $cardview-generic-card__header__selection-checkbox-padding-right: 12px !default; - $cardview-generic-card__field-caption-weight: 600 !default; $cardview-generic-card__cover-max-height: 180px !default; $cardview-generic-card__cover--noimage__icon-size: 48px !default; $cardview-generic-card__content-padding: 12px !default; @@ -283,60 +278,30 @@ $cardview-generic-column-chooser__select-all-item-horizontal-padding: null !defa $cardview-generic-card__content__cell-padding-vertical: 6px !default; $cardview-generic-card__content__cell-padding-horizontal: 12px !default; - // Card - selected - $cardview-generic-card--selected__field-caption-weight: $cardview-generic-card__field-caption-weight !default; - - // Header - $cardview-generic-header-gap: 12px !default; - // Header panel $cardview-generic-header-panel-vertical-margin: 4px !default; $cardview-generic-header-panel--empty-line-height: 36px !default; // Header panel item - default - $cardview-generic-header-panel__item-border-width: 1px !default; - $cardview-generic-header-panel__item-border-radius: 4px !default; $cardview-generic-header-panel__item-padding-horizontal: 12px !default; $cardview-generic-header-panel__item-padding-vertical: 8px !default; $cardview-generic-header-item__left-padding: 6px !default; $cardview-generic-header-item__right-padding: 2px !default; $cardview-generic-header-panel__item-line-height: 18px !default; - $cardview-generic-header-panel__item-text-weight: $cardview-generic-common-text-weight !default; - $cardview-generic-header-panel__item__sort-index-text-weight: 400 !default; - $cardview-generic-header-panel__item__icon-size: $generic-base-icon-size !default; - - // Header panel item - hovered - $cardview-generic-header-panel__item--hovered-text-weight: $cardview-generic-header-panel__item-text-weight !default; - $cardview-generic-header-panel__item--hovered__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-generic-header-panel__item--disabled-text-weight: $cardview-generic-header-panel__item-text-weight !default; - $cardview-generic-header-panel__item--disabled__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; // Header panel item - dragged - $cardview-generic-header-panel__item--dragged-text-weight: $cardview-generic-header-panel__item-text-weight !default; $cardview-generic-header-item__sort-indicator__border-size: 2px !default; $cardview-generic-header-item__sort-indicator__border-radius: 8px !default; // Header panel Drop-zone $cardview-generic-header-panel__dropzone-height: 36px !default; - $cardview-generic-header-panel__dropzone-top: 0 !default; - $cardview-generic-header-panel__dropzone-border-width: 1.5px !default; $cardview-generic-header-panel__dropzone-border-radius: 8px !default; - // Filter panel - $cardview-generic-filter-panel-border-radius: 0 !default; - // Empty view $cardview-generic-nodata-view__icon-container-size: 60px !default; $cardview-generic-nodata-view__icon-container-margin: 12px !default; $cardview-generic-nodata-view__icon-size: 36px !default; - // Search highlight - $cardview-generic-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-generic-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-generic-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-generic-column-chooser--empty__message-horizontal-padding: 24px !default; $cardview-generic-column-chooser__select-all-item-horizontal-padding: 10px !default; @@ -344,21 +309,16 @@ $cardview-generic-column-chooser__select-all-item-horizontal-padding: null !defa @else if $size == "compact" { // Common - $cardview-generic-common-border-radius: 16px !default; - $cardview-generic-common-text-weight: 400 !default; $cardview-generic-common-cards-row-gap: 12px !default; $cardview-generic-common-cards-column-gap: 12px !default; $cardview-generic-common-gap: 16px !default; $cardview-generic-common-padding: 16px !default; // Card - $cardview-generic-card-min-width: 250px !default; - $cardview-generic-card-border-width: 1px !default; $cardview-generic-card-border-radius: 6px !default; $cardview-generic-card__header-height: 48px !default; $cardview-generic-card__header-text-size: 14px !default; $cardview-generic-card__header__selection-checkbox-padding-right: 8px !default; - $cardview-generic-card__field-caption-weight: 600 !default; $cardview-generic-card__cover-max-height: 140px !default; $cardview-generic-card__cover--noimage__icon-size: 32px !default; $cardview-generic-card__content-padding: 8px !default; @@ -366,60 +326,30 @@ $cardview-generic-column-chooser__select-all-item-horizontal-padding: null !defa $cardview-generic-card__content__cell-padding-vertical: 4px !default; $cardview-generic-card__content__cell-padding-horizontal: 8px !default; - // Card - selected - $cardview-generic-card--selected__field-caption-weight: $cardview-generic-card__field-caption-weight !default; - - // Header - $cardview-generic-header-gap: 12px !default; - // Header panel $cardview-generic-header-panel-vertical-margin: 0 !default; $cardview-generic-header-panel--empty-line-height: 24px !default; // Header panel item - default - $cardview-generic-header-panel__item-border-width: 1px !default; - $cardview-generic-header-panel__item-border-radius: 4px !default; $cardview-generic-header-panel__item-padding-horizontal: 8px !default; $cardview-generic-header-panel__item-padding-vertical: 5px !default; $cardview-generic-header-item__left-padding: 3px !default; $cardview-generic-header-item__right-padding: 1px !default; $cardview-generic-header-panel__item-line-height: 14px !default; - $cardview-generic-header-panel__item-text-weight: $cardview-generic-common-text-weight !default; - $cardview-generic-header-panel__item__sort-index-text-weight: 400 !default; - $cardview-generic-header-panel__item__icon-size: $generic-base-icon-size !default; - - // Header panel item - hovered - $cardview-generic-header-panel__item--hovered-text-weight: $cardview-generic-header-panel__item-text-weight !default; - $cardview-generic-header-panel__item--hovered__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-generic-header-panel__item--disabled-text-weight: $cardview-generic-header-panel__item-text-weight !default; - $cardview-generic-header-panel__item--disabled__sort-index-text-weight: $cardview-generic-header-panel__item__sort-index-text-weight !default; // Header panel item - dragged - $cardview-generic-header-panel__item--dragged-text-weight: $cardview-generic-header-panel__item-text-weight !default; $cardview-generic-header-item__sort-indicator__border-size: 1px !default; $cardview-generic-header-item__sort-indicator__border-radius: 4px !default; // Header panel Drop-zone $cardview-generic-header-panel__dropzone-height: 24px !default; - $cardview-generic-header-panel__dropzone-top: 0 !default; - $cardview-generic-header-panel__dropzone-border-width: 1.5px !default; $cardview-generic-header-panel__dropzone-border-radius: 6px !default; - // Filter panel - $cardview-generic-filter-panel-border-radius: 0 !default; - // Empty view $cardview-generic-nodata-view__icon-container-size: 40px !default; $cardview-generic-nodata-view__icon-container-margin: 8px !default; $cardview-generic-nodata-view__icon-size: 24px !default; - // Search highlight - $cardview-generic-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-generic-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-generic-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-generic-column-chooser--empty__message-horizontal-padding: 16px !default; $cardview-generic-column-chooser__select-all-item-horizontal-padding: 6px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/chat/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/chat/_sizes.scss index d7d7f7342931..5c6de4412cf1 100644 --- a/packages/devextreme-scss/scss/widgets/generic/chat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/chat/_sizes.scss @@ -59,7 +59,6 @@ $chat-messagelist-day-header-first-padding-top: 20px !default; $chat-messagelist-day-header-font-size: 12px !default; $chat-typingindicator-template: $chat-messagegroup-alignment-start-template !default; -$chat-typingindicator-padding: null !default; $chat-typingindicator-row-gap: $chat-messagegroup-gap !default; $chat-typingindicator-font-size: $chat-information-font-size !default; $chat-typingindicator-bubble-padding-block: null !default; @@ -119,7 +118,6 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 14) * 1em !default; $chat-typingindicator-bubble-padding-block: 12px !default; $chat-typingindicator-bubble-padding-inline: 20px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; $chat-confirmation-popup-content-padding: 0 !default; $chat-confirmation-popup-toolbar-gap: 8px !default; @@ -160,10 +158,11 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 12) * 1em !default; $chat-typingindicator-bubble-padding-block: 8px !default; $chat-typingindicator-bubble-padding-inline: 12px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; $chat-confirmation-popup-content-padding: $generic-popup-content-padding !default; $chat-confirmation-popup-toolbar-gap: 4px !default; $chat-file-icon-font-size: 14 !default; } + +$chat-typingindicator-padding: $chat-messagegroup-padding !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/checkBox/_index.scss b/packages/devextreme-scss/scss/widgets/generic/checkBox/_index.scss index 740205c5095b..9a808cc9f92a 100644 --- a/packages/devextreme-scss/scss/widgets/generic/checkBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/checkBox/_index.scss @@ -4,7 +4,7 @@ @use "../../base/checkBox"; @use "../../base/checkBox/mixins" as *; -$generic-checkbox-disable-opacity: 0.4; +$generic-checkbox-disable-opacity: 0.4 !default; .dx-checkbox { line-height: 0; diff --git a/packages/devextreme-scss/scss/widgets/generic/checkBox/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/checkBox/_sizes.scss index 4d759dc73135..bda37bf216e9 100644 --- a/packages/devextreme-scss/scss/widgets/generic/checkBox/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/checkBox/_sizes.scss @@ -5,7 +5,7 @@ $generic-checkbox-icon-font-size: null !default; $generic-checkbox-border-width: 1px !default; -$generic-checkbox-border-radius: null !default; +$generic-checkbox-border-radius: if($base-border-radius-exsmall, min($base-border-radius-exsmall, 2px), null) !default; $generic-checkbox-arrow-icon-size: 1em !default; $generic-checkbox-arrow-font-size: null !default; @@ -16,12 +16,10 @@ $generic-checkbox-indeterminate-icon-border-radius: 2px !default; $generic-checkbox-icon-font-size: 22px !default; $generic-checkbox-arrow-font-size: 0.727273em !default; $generic-checkbox-indeterminate-icon-size: 0.545455em !default; - $generic-checkbox-border-radius: if($base-border-radius-exsmall, min($base-border-radius-exsmall, 2px), null) !default; } @else if $size == "compact" { $generic-checkbox-icon-font-size: 16px !default; $generic-checkbox-arrow-font-size: 0.625em !default; $generic-checkbox-indeterminate-icon-size: 0.5em !default; - $generic-checkbox-border-radius: if($base-border-radius-exsmall, min($base-border-radius-exsmall, 2px), null) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/colorView/_index.scss b/packages/devextreme-scss/scss/widgets/generic/colorView/_index.scss index 05284c86677b..d304e8f7c8d0 100644 --- a/packages/devextreme-scss/scss/widgets/generic/colorView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/colorView/_index.scss @@ -4,8 +4,8 @@ @use "../../base/colorView"; @include dx-base-colorview-styles($colorview-handle-color, $colorview-handle-border-color); -$generic-colorview-alpha-width: $generic-colorview-palette-width + 4; -$generic-colorview-hue-scale-height: $generic-colorview-palette-height; +$generic-colorview-alpha-width: $generic-colorview-palette-width + 4 !default; +$generic-colorview-hue-scale-height: $generic-colorview-palette-height !default; .dx-colorview-container { width: $generic-colorview-container-width; diff --git a/packages/devextreme-scss/scss/widgets/generic/common/_mixins.scss b/packages/devextreme-scss/scss/widgets/generic/common/_mixins.scss index c4e2389d750a..004e85bb20cb 100644 --- a/packages/devextreme-scss/scss/widgets/generic/common/_mixins.scss +++ b/packages/devextreme-scss/scss/widgets/generic/common/_mixins.scss @@ -6,10 +6,10 @@ @use "../../base/validation" as *; @use "../list/sizes" as *; -$badge-invalid-bg: $base-invalid-color; +$badge-invalid-bg: $base-invalid-color !default; $generic-dropdownlist-min-height: $generic-base-font-size * $generic-base-line-height + - 2 * $generic-list-item-vertical-padding; + 2 * $generic-list-item-vertical-padding !default; @mixin dx-base-typography() { @include dx-base-typography-mixin( diff --git a/packages/devextreme-scss/scss/widgets/generic/contextMenu/_index.scss b/packages/devextreme-scss/scss/widgets/generic/contextMenu/_index.scss index 4fe335a1b57b..5dddf82d1e49 100644 --- a/packages/devextreme-scss/scss/widgets/generic/contextMenu/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/contextMenu/_index.scss @@ -8,7 +8,7 @@ @use "../overlay"; @use "../menuBase" as *; // need as private -$generic-menu-popup-border: 1px solid; +$generic-menu-popup-border: 1px solid !default; .dx-context-menu { color: $menu-color; diff --git a/packages/devextreme-scss/scss/widgets/generic/dateView/_index.scss b/packages/devextreme-scss/scss/widgets/generic/dateView/_index.scss index f769f5703721..9a546976aa44 100644 --- a/packages/devextreme-scss/scss/widgets/generic/dateView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/dateView/_index.scss @@ -5,7 +5,7 @@ @use "../../base/dateView"; @use "../scrollable"; -$generic-dateview-roller-padding: math.div($generic-dateview-roller-height - $generic-dateview-roller-item-height, 2); +$generic-dateview-roller-padding: math.div($generic-dateview-roller-height - $generic-dateview-roller-item-height, 2) !default; .dx-dateview-rollers { width: auto; diff --git a/packages/devextreme-scss/scss/widgets/generic/diagram/_index.scss b/packages/devextreme-scss/scss/widgets/generic/diagram/_index.scss index db120bcabaf2..f226c1eabec4 100644 --- a/packages/devextreme-scss/scss/widgets/generic/diagram/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/diagram/_index.scss @@ -14,11 +14,11 @@ @use "../fieldset/sizes" as *; @use "../menu/colors" as *; -$generic-diagram-toolbar-icon-size: $generic-base-icon-size; -$diagram-toolbar-colorbutton-border-width: 3px; -$diagram-touchbar-item-padding: 5px; +$generic-diagram-toolbar-icon-size: $generic-base-icon-size !default; +$diagram-toolbar-colorbutton-border-width: 3px !default; +$diagram-touchbar-item-padding: 5px !default; -$diagram-toolbox-title-size: 12px; +$diagram-toolbox-title-size: 12px !default; .dx-diagram { border-color: $diagram-border-color; diff --git a/packages/devextreme-scss/scss/widgets/generic/dropDownEditor/_index.scss b/packages/devextreme-scss/scss/widgets/generic/dropDownEditor/_index.scss index e9ba41d0d338..6656681ab031 100644 --- a/packages/devextreme-scss/scss/widgets/generic/dropDownEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/dropDownEditor/_index.scss @@ -8,20 +8,20 @@ @use "../../base/icon_fonts" as *; @use "../../base/dropDownEditor"; -$generic-dropdowneditor-container-button-size: $generic-base-inline-borderedwidget-inner-size; -$generic-dropdowneditor-button-offset: $generic-base-border-width; +$generic-dropdowneditor-container-button-size: $generic-base-inline-borderedwidget-inner-size !default; +$generic-dropdowneditor-button-offset: $generic-base-border-width !default; $generic-dropdowneditor-button-size: $generic-dropdowneditor-container-button-size - - $generic-dropdowneditor-button-offset * 2; + $generic-dropdowneditor-button-offset * 2 !default; $generic-dropdowneditor-invalid-badge-offset: $generic-dropdowneditor-container-button-size + - $generic-invalid-badge-halfhorizontal-padding; + $generic-invalid-badge-halfhorizontal-padding !default; -$generic-dropdowneditor-invalid-badge-with-clear-offset: $generic-dropdowneditor-container-button-size * 2; +$generic-dropdowneditor-invalid-badge-with-clear-offset: $generic-dropdowneditor-container-button-size * 2 !default; -$generic-dropdowneditor-invalid-badge-size: $generic-invalid-badge-size + $generic-invalid-badge-horizontal-padding; +$generic-dropdowneditor-invalid-badge-size: $generic-invalid-badge-size + $generic-invalid-badge-horizontal-padding !default; @mixin dx-dropdowneditor-button-icon() { width: $generic-dropdowneditor-button-size; diff --git a/packages/devextreme-scss/scss/widgets/generic/fieldset/_index.scss b/packages/devextreme-scss/scss/widgets/generic/fieldset/_index.scss index 0ea3fc18ff59..a6c5d42e5a62 100644 --- a/packages/devextreme-scss/scss/widgets/generic/fieldset/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/fieldset/_index.scss @@ -11,8 +11,8 @@ @use "../slider/sizes" as *; @use "../../base/fieldset"; -$generic-field-min-height: $generic-base-inline-widget-height; -$generic-fieldset-attention-icon-offset: $generic-base-inline-horizontal-padding + $generic-invalid-badge-size; +$generic-field-min-height: $generic-base-inline-widget-height !default; +$generic-fieldset-attention-icon-offset: $generic-base-inline-horizontal-padding + $generic-invalid-badge-size !default; @mixin dx-fieldset-styling() { .dx-field { diff --git a/packages/devextreme-scss/scss/widgets/generic/fileManager/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/fileManager/_colors.scss index e7a8071ba5b7..28c2fc9c26b0 100644 --- a/packages/devextreme-scss/scss/widgets/generic/fileManager/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/fileManager/_colors.scss @@ -14,19 +14,15 @@ $filemanager-file-item-select-bg: color.change($base-accent, $alpha: 0.8) !defau $filemanager-file-item-select-focus-color: $base-bg !default; $filemanager-file-item-select-focus-bg: color.change($base-accent, $alpha: 0.7) !default; $filemanager-file-item-hover-bg: null !default; -$filemanager-file-item-focused-bg: null !default; $filemanager-file-item-focused-border: $base-accent !default; $filemanager-item-focused-color: null !default; -$filemanager-progressbox-shadow-color: null !default; @if $color == "carmine" { $filemanager-file-toolbar-bg: color.adjust($base-bg, $lightness: -3%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $color == "contrast" { @@ -34,9 +30,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: $base-text-color !default; $filemanager-dirs-tree-item-focus-color: $base-bg !default; $filemanager-file-item-hover-bg: $base-hover-color !default; - $filemanager-file-item-focused-bg: $base-accent !default; $filemanager-item-focused-color: $base-text-color !default; - $filemanager-progressbox-shadow-color: $base-shadow-color !default; } @if $color == "dark" { @@ -44,9 +38,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-focus-bg !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; } @if $color == "darkmoon" { @@ -54,9 +46,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change(color.adjust($base-shadow-color, $lightness: -100%, $space: hsl), $alpha: 0.3) !default; } @if $color == "darkviolet" { @@ -64,9 +54,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-focus-bg !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; } @if $color == "greenmist" { @@ -74,9 +62,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $color == "light" { @@ -84,9 +70,7 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-focus-bg !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $color == "softblue" { @@ -94,7 +78,5 @@ $filemanager-progressbox-shadow-color: null !default; $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; $filemanager-dirs-tree-item-focus-color: $base-text-color !default; $filemanager-file-item-hover-bg: $base-hover-bg !default; - $filemanager-file-item-focused-bg: $base-focus-bg !default; $filemanager-item-focused-color: $base-inverted-text-color !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/fileManager/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/fileManager/_sizes.scss index 50a1d5bc883e..ff43e8aea566 100644 --- a/packages/devextreme-scss/scss/widgets/generic/fileManager/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/fileManager/_sizes.scss @@ -4,7 +4,7 @@ $filemanager-toolbar-separator-height: null !default; $filemanager-toolbar-viewmode-width: null !default; $filemanager-toolbar-viewmode-padding: null !default; -$filemanager-toolbar-height: null !default; +$filemanager-toolbar-height: $generic-toolbar-height + 12px !default; $filemanager-breadcrumbs-item-padding-top: null !default; $filemanager-breadcrumbs-item-padding-bottom: null !default; $filemanager-details-view-checkbox-column-width: null !default; @@ -21,7 +21,6 @@ $filemanager-progress-panel-padding-top: 6px !default; $filemanager-toolbar-separator-height: 36px !default; $filemanager-toolbar-viewmode-width: 150px !default; $filemanager-toolbar-viewmode-padding: 8px !default; - $filemanager-toolbar-height: $generic-toolbar-height + 12px; $filemanager-breadcrumbs-item-padding-top: 5px !default; $filemanager-breadcrumbs-item-padding-bottom: 8px !default; $filemanager-details-view-checkbox-column-width: 43px !default; @@ -37,7 +36,6 @@ $filemanager-progress-panel-padding-top: 6px !default; $filemanager-toolbar-separator-height: 26px !default; $filemanager-toolbar-viewmode-width: 130px !default; $filemanager-toolbar-viewmode-padding: 5px !default; - $filemanager-toolbar-height: $generic-toolbar-height + 12px; $filemanager-breadcrumbs-item-padding-top: 3px !default; $filemanager-breadcrumbs-item-padding-bottom: 7px !default; $filemanager-details-view-checkbox-column-width: 39px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/fileUploader/_index.scss b/packages/devextreme-scss/scss/widgets/generic/fileUploader/_index.scss index db8ac6f2827f..d9c1d0c3c4b5 100644 --- a/packages/devextreme-scss/scss/widgets/generic/fileUploader/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/fileUploader/_index.scss @@ -6,7 +6,7 @@ @use "../../base/mixins" as *; @use "../../base/fileUploader"; -$generic-fileuploader-file-wrapper-border-size: 3px; +$generic-fileuploader-file-wrapper-border-size: 3px !default; .dx-fileuploader-wrapper { padding: $generic-fileuploader-vertical-padding; diff --git a/packages/devextreme-scss/scss/widgets/generic/filterBuilder/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/filterBuilder/_colors.scss index 151208112147..238ce96d88f5 100644 --- a/packages/devextreme-scss/scss/widgets/generic/filterBuilder/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/filterBuilder/_colors.scss @@ -67,7 +67,7 @@ $filterbuilder-item-value-focus-color: null !default; * $name 10. Menu icon color * $type color */ -$filterbuilder-menu-icon-color: null !default; +$filterbuilder-menu-icon-color: $base-text-color !default; @if $color == "carmine" { $filterbuilder-text-color: $base-text-color !default; @@ -90,7 +90,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: color.change($base-border-color, $alpha: 0.5) !default; $filterbuilder-item-value-hover-color: $filterbuilder-item-value-color !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.7) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "contrast" { @@ -114,7 +113,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-focus-color: $filterbuilder-item-value-color !default; $filterbuilder-text-color: $base-text-color !default; $filterbuilder-text-focus-color: $base-text-color !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "dark" { @@ -138,13 +136,11 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: rgba(204, 204, 204, 0.8) !default; $filterbuilder-item-value-hover-color: color.adjust($filterbuilder-item-value-color, $alpha: -0.1) !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.2) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "darkmoon" { $filterbuilder-text-color: #000 !default; $filterbuilder-text-focus-color: $filterbuilder-text-color !default; - $filterbuilder-menu-icon-color: $base-text-color !default; $filterbuilder-plus-icon-color: color.change($base-success, $alpha: 0.8) !default; $filterbuilder-plus-icon-hover-color: color.adjust($filterbuilder-plus-icon-color, $alpha: -0.1) !default; $filterbuilder-plus-icon-focus-color: color.adjust($filterbuilder-plus-icon-color, $alpha: 0.2) !default; @@ -186,7 +182,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: rgba(204, 204, 204, 0.8) !default; $filterbuilder-item-value-hover-color: color.adjust($filterbuilder-item-value-color, $alpha: -0.1) !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.2) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "greenmist" { @@ -210,7 +205,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: color.change($base-border-color, $alpha: 0.5) !default; $filterbuilder-item-value-hover-color: $filterbuilder-item-value-color !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.7) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "light" { @@ -234,7 +228,6 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: color.change($base-border-color, $alpha: 0.5) !default; $filterbuilder-item-value-hover-color: $filterbuilder-item-value-color !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.7) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } @if $color == "softblue" { @@ -258,5 +251,4 @@ $filterbuilder-menu-icon-color: null !default; $filterbuilder-item-value-color: color.change($base-border-color, $alpha: 0.5) !default; $filterbuilder-item-value-hover-color: $filterbuilder-item-value-color !default; $filterbuilder-item-value-focus-color: color.adjust($filterbuilder-item-value-color, $alpha: 0.7) !default; - $filterbuilder-menu-icon-color: $base-text-color !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/gantt/_index.scss b/packages/devextreme-scss/scss/widgets/generic/gantt/_index.scss index aab84e869c3f..5ee45c681393 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gantt/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gantt/_index.scss @@ -8,7 +8,7 @@ @use "../toolbar/sizes" as *; @use "../splitterBar"; -$gantt-successor-background-color: white; +$gantt-successor-background-color: white !default; .dx-gantt { border: 1px solid; diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss index ea37ef5d4fc7..3a3ba9008d53 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/_index.scss @@ -26,15 +26,15 @@ @use "../overlay"; @use "../pagination"; -$generic-grid-base-border-hidden: 1px solid transparent; -$generic-grid-base-row-border: 1px solid transparent; -$generic-grid-base-drag-header-border: 1px solid; -$generic-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color; -$generic-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color; -$generic-grid-base-checkbox-icon-size: 16px; -$generic-grid-base-checkbox-indeterminate-icon-size: 6px; -$generic-grid-base-group-panel-message-line-height: $generic-button-text-line-height; -$generic-grid-base-cell-input-height: math.round($generic-base-line-height * $generic-base-font-size) + 2 * $generic-grid-base-cell-padding; +$generic-grid-base-border-hidden: 1px solid transparent !default; +$generic-grid-base-row-border: 1px solid transparent !default; +$generic-grid-base-drag-header-border: 1px solid !default; +$generic-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color !default; +$generic-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color !default; +$generic-grid-base-checkbox-icon-size: 16px !default; +$generic-grid-base-checkbox-indeterminate-icon-size: 6px !default; +$generic-grid-base-group-panel-message-line-height: $generic-button-text-line-height !default; +$generic-grid-base-cell-input-height: math.round($generic-base-line-height * $generic-base-font-size) + 2 * $generic-grid-base-cell-padding !default; @mixin dx-set-checkbox-border-color-as-background() { @if $generic-color-postfix == ".contrast" { diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/_sizes.scss index 3d9e0643fe47..9ab006263cd5 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/_sizes.scss @@ -16,8 +16,8 @@ $generic-grid-base-checkbox-fontsize: null !default; $generic-grid-base-adaptive-text-padding: null !default; $generic-grid-base-treeview-search-margin: null !default; $generic-command-edit-icon-size: null !default; -$generic-command-edit-icon-margin: null !default; -$generic-command-edit-text-margin: null !default; +$generic-command-edit-icon-margin: 5px !default; +$generic-command-edit-text-margin: 2px !default; $generic-grid-base-nodata-font-size: null !default; $generic-grid-base-checkbox-padding-bottom: null !default; $generic-grid-base-treeview-select-all-item-offset: null !default; @@ -26,8 +26,8 @@ $generic-grid-base-command-ai-text-content-padding: null !default; $generic-grid-base-command-ai-header-button-padding: null !default; $generic-grid-base-command-ai-header-button-size: null !default; -$datagrid-text-link-disabled-opacity: $base-disabled-opacity; -$datagrid-icon-link-disabled-opacity: 0.6; +$datagrid-text-link-disabled-opacity: $base-disabled-opacity !default; +$datagrid-icon-link-disabled-opacity: 0.6 !default; $generic-grid-base-ai-prompt-editor-progressbar-height: 4px !default; @if $size == "default" { @@ -46,11 +46,9 @@ $generic-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $generic-grid-base-adaptive-text-padding: 8px !default; $generic-grid-base-treeview-search-margin: 10px !default; $generic-command-edit-icon-size: 18px !default; - $generic-command-edit-icon-margin: 5px !default; - $generic-command-edit-text-margin: 2px !default; $generic-grid-base-nodata-font-size: 17px !default; - $generic-grid-base-checkbox-padding-bottom: 2px; - $generic-grid-base-treeview-select-all-item-offset: 34px; + $generic-grid-base-checkbox-padding-bottom: 2px !default; + $generic-grid-base-treeview-select-all-item-offset: 34px !default; $generic-grid-base-command-ai-chatsparkleoutline-icon-size: 18px !default; $generic-grid-base-command-ai-text-content-padding: $generic-grid-base-command-ai-chatsparkleoutline-icon-size + 8px !default; $generic-grid-base-command-ai-header-button-padding: 6px !default; @@ -73,11 +71,9 @@ $generic-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $generic-grid-base-adaptive-text-padding: 6px !default; $generic-grid-base-treeview-search-margin: 5px !default; $generic-command-edit-icon-size: 14px !default; - $generic-command-edit-icon-margin: 5px !default; - $generic-command-edit-text-margin: 2px !default; $generic-grid-base-nodata-font-size: 14px !default; - $generic-grid-base-checkbox-padding-bottom: 0; - $generic-grid-base-treeview-select-all-item-offset: 28px; + $generic-grid-base-checkbox-padding-bottom: 0 !default; + $generic-grid-base-treeview-select-all-item-offset: 28px !default; $generic-grid-base-command-ai-chatsparkleoutline-icon-size: 14px !default; $generic-grid-base-command-ai-text-content-padding: $generic-grid-base-command-ai-chatsparkleoutline-icon-size + 6px !default; $generic-grid-base-command-ai-header-button-padding: 5px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/_variables.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/_variables.scss index 4d9254181e0a..58d431d63c19 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/_variables.scss @@ -1 +1 @@ -$grid-masterdetail-padding: 20px; +$grid-masterdetail-padding: 20px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/gridBase/layout/aiChat/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/gridBase/layout/aiChat/_sizes.scss index fa9ad84197d8..0b43b77902d3 100644 --- a/packages/devextreme-scss/scss/widgets/generic/gridBase/layout/aiChat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/gridBase/layout/aiChat/_sizes.scss @@ -1,3 +1,3 @@ @use "../../../sizes" as *; -$ai-chat-message-icon-size: $generic-base-icon-size; +$ai-chat-message-icon-size: $generic-base-icon-size !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_colors.scss index 797aaf643d03..90e0b2e4232c 100644 --- a/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_colors.scss @@ -9,10 +9,6 @@ $htmleditor-filled-border-color: transparent !default; $htmleditor-invalid-border-color: $base-invalid-color !default; $htmleditor-invalid-faded-border-color: $base-invalid-faded-border-color !default; $htmleditor-toolbar-border-color: $base-border-color !default; -$htmleditor-normal-format-active-bg: null !default; -$htmleditor-default-format-active-bg: null !default; -$htmleditor-danger-format-active-bg: null !default; -$htmleditor-success-format-active-bg: null !default; $htmleditor-variable-bg: color.change($base-accent, $alpha: 0.15) !default; $htmleditor-resize-frame-border-color: $base-accent !default; $htmleditor-resize-handle-bg: $base-accent !default; @@ -22,73 +18,41 @@ $htmleditor-table-border-color: $base-border-color !default; $htmleditor-cells-separator-bg: color.change($base-accent, $alpha: 0.5) !default; @if $color == "carmine" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: -10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: -10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: -10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: -9%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "contrast" { - $htmleditor-normal-format-active-bg: $base-bg !default; - $htmleditor-default-format-active-bg: $base-default !default; - $htmleditor-danger-format-active-bg: $base-danger !default; - $htmleditor-success-format-active-bg: $base-success !default; $htmleditor-mention-bg: extcolor.difference(#fff, $base-bg) !default; $htmleditor-mention-color: $base-inverted-text-color !default; } @if $color == "dark" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: 10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: -10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: -10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: 13.5%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "darkmoon" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: 10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: 10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: 10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: 13.5%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "darkviolet" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: 10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: 10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: 10%, $space: hsl) !default; $htmleditor-mention-bg: $base-select-bg !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "greenmist" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: 10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: 10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: 10%, $space: hsl) !default; $htmleditor-mention-bg: $base-accent !default; $htmleditor-mention-color: color.adjust($base-text-color, $lightness: 100%, $space: hsl) !default; } @if $color == "light" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: -10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: -10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: -10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: -13.5%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } @if $color == "softblue" { - $htmleditor-normal-format-active-bg: color.adjust($base-element-bg, $lightness: -10%, $space: hsl) !default; - $htmleditor-default-format-active-bg: color.adjust($base-accent, $lightness: -10%, $space: hsl) !default; - $htmleditor-danger-format-active-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; - $htmleditor-success-format-active-bg: color.adjust($base-success, $lightness: -10%, $space: hsl) !default; $htmleditor-mention-bg: color.adjust($base-bg, $lightness: -13.5%, $space: hsl) !default; $htmleditor-mention-color: $base-text-color !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_sizes.scss index b19b96465f4e..85381b1e0bfc 100644 --- a/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/htmlEditor/_sizes.scss @@ -10,15 +10,15 @@ $generic-aidialog-content-padding: null !default; @if $size == "default" { $generic-toolbar-size-editor-width: 105px !default; $generic-html-editor-add-image-dialog-fileuploader-padding: 60px 20px !default; - $generic-aidialog-selects-gap: 10px; - $generic-aidialog-content-gap: 20px; - $generic-aidialog-content-padding: 20px; + $generic-aidialog-selects-gap: 10px !default; + $generic-aidialog-content-gap: 20px !default; + $generic-aidialog-content-padding: 20px !default; } @else if $size == "compact" { $generic-toolbar-size-editor-width: 80px !default; $generic-html-editor-add-image-dialog-fileuploader-padding: 40px 20px !default; - $generic-aidialog-selects-gap: 5px; - $generic-aidialog-content-gap: 10px; - $generic-aidialog-content-padding: 10px; + $generic-aidialog-selects-gap: 5px !default; + $generic-aidialog-content-gap: 10px !default; + $generic-aidialog-content-padding: 10px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/icons/_index.scss b/packages/devextreme-scss/scss/widgets/generic/icons/_index.scss index cce54f733c88..5d955957b919 100644 --- a/packages/devextreme-scss/scss/widgets/generic/icons/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/icons/_index.scss @@ -12,12 +12,6 @@ } } -.dx-scrollview-pulldown { - background-image: $scrollview-pulldown-path; - background-position: 0 0; - background-repeat: no-repeat; -} - .dx-loadindicator-image { background-position: center center; background-repeat: no-repeat; diff --git a/packages/devextreme-scss/scss/widgets/generic/informer/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/informer/_colors.scss index 573e09684108..20a972f1246b 100644 --- a/packages/devextreme-scss/scss/widgets/generic/informer/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/informer/_colors.scss @@ -8,57 +8,57 @@ $informer-info-color: null !default; $informer-info-background-color: null !default; @if $color == "carmine" { - $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -351deg, $saturation: -26.91%, $lightness: -5.49%); - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -347deg, $saturation: -26.69%, $lightness: 33.14%); - $informer-info-color: $base-text-color; - $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl); + $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -351deg, $saturation: -26.91%, $lightness: -5.49%) !default; + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -347deg, $saturation: -26.69%, $lightness: 33.14%) !default; + $informer-info-color: $base-text-color !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl) !default; } @if $color == "contrast" { - $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 2deg, $saturation: -16.93%, $lightness: -6.08%); - $informer-error-background-color: rgba(212, 63, 58, 0.1); - $informer-info-color: color.adjust($base-text-color, $lightness: -12.94%, $space: hsl); - $informer-info-background-color: color.adjust($base-text-color, $lightness: -68.63%, $space: hsl); + $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 2deg, $saturation: -16.93%, $lightness: -6.08%) !default; + $informer-error-background-color: rgba(212, 63, 58, 0.1) !default; + $informer-info-color: color.adjust($base-text-color, $lightness: -12.94%, $space: hsl) !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: -68.63%, $space: hsl) !default; } @if $color == "dark" { - $informer-error-color: $base-danger; - $informer-error-background-color: rgba(212, 63, 58, 0.1); - $informer-info-color: $base-text-color; - $informer-info-background-color: color.adjust($base-text-color, $lightness: -55.69%, $space: hsl); + $informer-error-color: $base-danger !default; + $informer-error-background-color: rgba(212, 63, 58, 0.1) !default; + $informer-info-color: $base-text-color !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: -55.69%, $space: hsl) !default; } @if $color == "darkmoon" { - $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -342deg, $saturation: -28.85%, $lightness: -6.67%); - $informer-error-background-color: rgba(212, 63, 58, 0.1); - $informer-info-color: color.adjust($base-text-color, $lightness: -12.94%, $space: hsl); - $informer-info-background-color: color.adjust($base-text-color, $lightness: -68.63%, $space: hsl); + $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -342deg, $saturation: -28.85%, $lightness: -6.67%) !default; + $informer-error-background-color: rgba(212, 63, 58, 0.1) !default; + $informer-info-color: color.adjust($base-text-color, $lightness: -12.94%, $space: hsl) !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: -68.63%, $space: hsl) !default; } @if $color == "darkviolet" { - $informer-error-color: $base-danger; - $informer-error-background-color: rgba(212, 63, 58, 0.1); - $informer-info-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -210deg, $saturation: -11.11%, $lightness: -9.41%); - $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -210deg, $saturation: -11.11%, $lightness: -65.10%); + $informer-error-color: $base-danger !default; + $informer-error-background-color: rgba(212, 63, 58, 0.1) !default; + $informer-info-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -210deg, $saturation: -11.11%, $lightness: -9.41%) !default; + $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -210deg, $saturation: -11.11%, $lightness: -65.10%) !default; } @if $color == "greenmist" { - $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -351deg, $saturation: -27.05%, $lightness: -4.90%); - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -348deg, $saturation: -26.83%, $lightness: 33.73%); - $informer-info-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -191deg, $saturation: -32.77%, $lightness: -3.33%); - $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -191deg, $saturation: -32.77%, $lightness: 68.82%); + $informer-error-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -351deg, $saturation: -27.05%, $lightness: -4.90%) !default; + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -348deg, $saturation: -26.83%, $lightness: 33.73%) !default; + $informer-info-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -191deg, $saturation: -32.77%, $lightness: -3.33%) !default; + $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -191deg, $saturation: -32.77%, $lightness: 68.82%) !default; } @if $color == "light" { - $informer-error-color: $base-danger; - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 4deg, $saturation: 0.22%, $lightness: 38.63%); - $informer-info-color: $base-text-color; - $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl); + $informer-error-color: $base-danger !default; + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 4deg, $saturation: 0.22%, $lightness: 38.63%) !default; + $informer-info-color: $base-text-color !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl) !default; } @if $color == "softblue" { - $informer-error-color: $base-danger; - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 4deg, $saturation: 0.22%, $lightness: 38.63%); - $informer-info-color: $base-text-color; - $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl); + $informer-error-color: $base-danger !default; + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 4deg, $saturation: 0.22%, $lightness: 38.63%) !default; + $informer-info-color: $base-text-color !default; + $informer-info-background-color: color.adjust($base-text-color, $lightness: 72.16%, $space: hsl) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/list/_index.scss b/packages/devextreme-scss/scss/widgets/generic/list/_index.scss index c1dcca0e4a38..ed6b8aed869c 100644 --- a/packages/devextreme-scss/scss/widgets/generic/list/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/list/_index.scss @@ -13,9 +13,9 @@ @use "../../base/list/mixins" as *; @use "../badge"; -$generic-list-border: $generic-list-border-width solid; -$generic-list-item-border: $generic-list-item-border-width solid; -$generic-list-search-editor-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding + 2 * $generic-base-border-width; +$generic-list-border: $generic-list-border-width solid !default; +$generic-list-item-border: $generic-list-item-border-width solid !default; +$generic-list-search-editor-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding + 2 * $generic-base-border-width !default; .dx-list-item-chevron { @include dx-icon(chevronnext); diff --git a/packages/devextreme-scss/scss/widgets/generic/loadIndicator/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/loadIndicator/_colors.scss index a5829b425501..21159495c936 100644 --- a/packages/devextreme-scss/scss/widgets/generic/loadIndicator/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/loadIndicator/_colors.scss @@ -8,14 +8,13 @@ */ $loadindicator-bg: null !default; $loadindicator-segment-shadow-color: null !default; -$load-indicator-animation-sparkle-bg-color1: null !default; +$load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: null !default; $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "carmine" { $loadindicator-bg: $base-accent !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -8.58%, $space: hsl, $lightness: 27.06%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 2deg, $saturation: -6.88%, $lightness: 33.14%) !default; } @@ -23,7 +22,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "contrast" { $loadindicator-bg: $base-inverted-bg !default; $loadindicator-segment-shadow-color: $base-shadow-color !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -25.83%, $space: hsl, $lightness: -19.61%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 1deg, $saturation: -44.18%, $lightness: -11.76%) !default; } @@ -31,7 +29,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "dark" { $loadindicator-bg: color.adjust($base-text-color, $lightness: -17.5%, $space: hsl) !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -25.83%, $space: hsl, $lightness: -19.61%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 1deg, $saturation: -44.18%, $lightness: -11.76%) !default; } @@ -39,7 +36,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "darkmoon" { $loadindicator-bg: $base-accent !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -25.83%, $space: hsl, $lightness: -19.61%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 1deg, $saturation: -44.18%, $lightness: -11.76%) !default; } @@ -47,7 +43,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "darkviolet" { $loadindicator-bg: $base-accent-highlight-color !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -25.83%, $space: hsl, $lightness: -19.61%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 1deg, $saturation: -44.18%, $lightness: -11.76%) !default; } @@ -55,7 +50,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "greenmist" { $loadindicator-bg: color.adjust($base-text-color, $lightness: 17.5%, $space: hsl) !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -8.58%, $space: hsl, $lightness: 27.06%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 2deg, $saturation: -6.88%, $lightness: 33.14%) !default; } @@ -63,7 +57,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "light" { $loadindicator-bg: color.adjust($base-text-color, $lightness: 17.5%, $space: hsl) !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -8.58%, $space: hsl, $lightness: 27.06%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 2deg, $saturation: -6.88%, $lightness: 33.14%) !default; } @@ -71,7 +64,6 @@ $load-indicator-animation-sparkle-bg-color3: null !default; @if $color == "softblue" { $loadindicator-bg: $base-accent !default; $loadindicator-segment-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $load-indicator-animation-sparkle-bg-color1: $base-accent !default; $load-indicator-animation-sparkle-bg-color2: color.adjust($base-accent, $saturation: -8.58%, $space: hsl, $lightness: 27.06%) !default; $load-indicator-animation-sparkle-bg-color3: extcolor.adjust-if-not-achromatic($color: $base-accent, $hue: 2deg, $saturation: -6.88%, $lightness: 33.14%) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/menuBase/_index.scss b/packages/devextreme-scss/scss/widgets/generic/menuBase/_index.scss index c000cb70ef14..fbfba161c754 100644 --- a/packages/devextreme-scss/scss/widgets/generic/menuBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/menuBase/_index.scss @@ -8,11 +8,11 @@ @use "../../base/mixins" as *; @use "../../base/menuBase"; -$generic-menu-item-padding-increased: $generic-menu-item-padding-base + 2px; -$generic-menu-item-padding-reduced: $generic-menu-item-padding-base - 2px; -$generic-menu-arrow-image-size: 7px; +$generic-menu-item-padding-increased: $generic-menu-item-padding-base + 2px !default; +$generic-menu-item-padding-reduced: $generic-menu-item-padding-base - 2px !default; +$generic-menu-arrow-image-size: 7px !default; -$generic-menu-popup-border: 1px solid; +$generic-menu-popup-border: 1px solid !default; .dx-menu-item { color: $menu-color; diff --git a/packages/devextreme-scss/scss/widgets/generic/pivotGrid/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/pivotGrid/_sizes.scss index a6c667cfc55b..f1c91857cdf8 100644 --- a/packages/devextreme-scss/scss/widgets/generic/pivotGrid/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/pivotGrid/_sizes.scss @@ -16,7 +16,7 @@ $pivotgrid-empty-area-text-padding: null !default; $pivotgrid-common-top-bottom-padding: 7px !default; $pivotgrid-field-indicators-offset: 6px !default; $pivotgrid-indicators-line-height: 19px !default; - $pivotgrid-button-top-padding: 12px; + $pivotgrid-button-top-padding: 12px !default; $pivotgrid-empty-area-text-padding: 7px !default; } @@ -27,6 +27,6 @@ $pivotgrid-empty-area-text-padding: null !default; $pivotgrid-common-top-bottom-padding: 5px !default; $pivotgrid-field-indicators-offset: 4px !default; $pivotgrid-indicators-line-height: 16px !default; - $pivotgrid-button-top-padding: 8px; + $pivotgrid-button-top-padding: 8px !default; $pivotgrid-empty-area-text-padding: 5px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/popup/_index.scss b/packages/devextreme-scss/scss/widgets/generic/popup/_index.scss index 584ad3d8053d..f7cfb912957a 100644 --- a/packages/devextreme-scss/scss/widgets/generic/popup/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/popup/_index.scss @@ -11,8 +11,8 @@ @use "../../base/popup"; @use "../overlay"; -$generic-popup-toolbar-height: $generic-base-inline-widget-height; -$generic-popup-toolbar-label-font-size: $generic-m-font-size; +$generic-popup-toolbar-height: $generic-base-inline-widget-height !default; +$generic-popup-toolbar-label-font-size: $generic-m-font-size !default; .dx-popup-wrapper { & > .dx-overlay-content { diff --git a/packages/devextreme-scss/scss/widgets/generic/progressBar/_index.scss b/packages/devextreme-scss/scss/widgets/generic/progressBar/_index.scss index ed0a16ca0296..0d42ade93bae 100644 --- a/packages/devextreme-scss/scss/widgets/generic/progressBar/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/progressBar/_index.scss @@ -4,11 +4,11 @@ @use "../../base/mixins" as *; @use "../../base/progressBar"; -$generic-bar-height: 6px; -$generic-background-width: 90%; -$generic-progressbar-border-radius: 2px; -$generic-progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95%; -$generic-progress-bar-border: 1px solid; +$generic-bar-height: 6px !default; +$generic-background-width: 90% !default; +$generic-progressbar-border-radius: 2px !default; +$generic-progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95% !default; +$generic-progress-bar-border: 1px solid !default; .dx-progressbar-container { height: $generic-bar-height; diff --git a/packages/devextreme-scss/scss/widgets/generic/rangeSlider/_index.scss b/packages/devextreme-scss/scss/widgets/generic/rangeSlider/_index.scss index d437e5903f72..5e412c3672ef 100644 --- a/packages/devextreme-scss/scss/widgets/generic/rangeSlider/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/rangeSlider/_index.scss @@ -2,7 +2,7 @@ @use "sizes" as *; @use "../../base/rangeSlider"; -$generic-slider-handle-margin: -7px; +$generic-slider-handle-margin: -7px !default; .dx-rangeslider-start-handle { margin-inline-start: $generic-slider-handle-margin; diff --git a/packages/devextreme-scss/scss/widgets/generic/recurrenceEditor/_index.scss b/packages/devextreme-scss/scss/widgets/generic/recurrenceEditor/_index.scss index 8ae754ace0b8..58c2cc65dbce 100644 --- a/packages/devextreme-scss/scss/widgets/generic/recurrenceEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/recurrenceEditor/_index.scss @@ -3,7 +3,7 @@ @use "../scheduler/sizes" as *; @use "../../base/recurrenceEditor" as *; -$generic-recurrence-number-box-margin-right: 11px; +$generic-recurrence-number-box-margin-right: 11px !default; .dx-recurrence-editor-container { .dx-recurrence-numberbox-interval { diff --git a/packages/devextreme-scss/scss/widgets/generic/scheduler/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/scheduler/_colors.scss index 998c9d6176d5..71c6890c436a 100644 --- a/packages/devextreme-scss/scss/widgets/generic/scheduler/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/scheduler/_colors.scss @@ -67,32 +67,27 @@ $scheduler-dd-appointment-hover-text-color: null !default; $scheduler-form-icon-color: $base-icon-color !default; $scheduler-workspace-month-text-color: null !default; $scheduler-dropdown-appointment-date-color: null !default; -$scheduler-workspace-active-cell-color: null !default; $scheduler-workspace-hovered-cell-color: null !default; $scheduler-group-header-color: null !default; $scheduler-header-bg: null !default; $scheduler-alldaypanel-bg: null !default; -$scheduler-tab-selected-bg: null !default; -$scheduler-tab-selected-before-bg: null !default; -$scheduler-tab-focused-border-color: null !default; $scheduler-other-month-cell-opacity: null !default; $scheduler-fill-focused-appointment: null !default; $scheduler-is-shadow-color-for-focused-state: true !default; $scheduler-time-indicator-background-color: null !default; $scheduler-first-month-cell-background-color: null !default; -$scheduler-time-panel-background-color: null !default; $scheduler-tooltip-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: null !default; -$month-text-color-light: #757575; -$month-text-color-dark: #9c9c9c; +$month-text-color-light: #757575 !default; +$month-text-color-dark: #9c9c9c !default; // Agenda $agenda-appointment-recurrence-icon-color: null !default; $agenda-appointment-hover-bg: $base-hover-bg !default; $agenda-appointment-active-bg: null !default; -$agenda-appointment-text-color: null !default; +$agenda-appointment-text-color: $base-text-color !default; @if $color == "carmine" { $scheduler-base-border-color: color.change($base-border-color, $alpha: 0.6) !default; @@ -112,14 +107,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-light !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: color.adjust($base-bg, $lightness: -4%, $space: hsl) !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $base-bg !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-accent !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(51, 51, 51, 0.04) !default; @@ -129,7 +120,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-inverted-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change($base-accent, $alpha: 0.15) !default; } @@ -149,14 +139,10 @@ $agenda-appointment-text-color: null !default; $scheduler-workspace-focused-cell-color: color.adjust($base-accent, $lightness: 5%, $space: hsl) !default; $scheduler-panel-text-color: $base-text-color !default; $scheduler-dropdown-appointment-date-color: $base-text-color !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: extcolor.difference(#fff, $base-text-color) !default; $scheduler-group-header-color: $base-text-color !default; $scheduler-header-bg: $base-bg !default; $scheduler-alldaypanel-bg: $base-bg !default; - $scheduler-tab-selected-bg: $base-inverted-bg !default; - $scheduler-tab-selected-before-bg: transparent !default; - $scheduler-tab-focused-border-color: $base-border-color !default; $scheduler-other-month-cell-opacity: 1 !default; $scheduler-fill-focused-appointment: true !default; $scheduler-time-indicator-background-color: color.change(#3806ff, $alpha: 0.6) !default; @@ -167,7 +153,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-inverted-bg !default; $agenda-appointment-recurrence-icon-color: $base-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: $base-accent !default; } @@ -189,14 +174,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-dark !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-focus-bg !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(222, 222, 222, 0.07) !default; @@ -206,7 +187,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#fff, $alpha: 0.15) !default; } @@ -230,14 +210,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: #fff !default; $scheduler-workspace-month-text-color: $month-text-color-dark !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-accent !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(222, 222, 222, 0.07) !default; @@ -247,7 +223,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#000, $alpha: 0.15) !default; } @@ -269,14 +244,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-dark !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-accent !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(222, 222, 222, 0.07) !default; @@ -286,7 +257,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#9e9ec5, $alpha: 0.35) !default; } @@ -308,15 +278,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-time-panel-background-color: $scheduler-workspace-background-color !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-time-panel-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-focus-bg !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(51, 51, 51, 0.04) !default; @@ -326,7 +291,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-inverted-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#000, $alpha: 0.15) !default; } @@ -349,13 +313,9 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-light !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-focus-bg !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(51, 51, 51, 0.04) !default; @@ -365,7 +325,6 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-inverted-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#000, $alpha: 0.08) !default; } @@ -387,14 +346,10 @@ $agenda-appointment-text-color: null !default; $scheduler-dd-appointment-hover-text-color: $base-inverted-text-color !default; $scheduler-workspace-month-text-color: $month-text-color-light !default; $scheduler-dropdown-appointment-date-color: color.adjust($base-text-color, $lightness: 38.5%, $space: hsl) !default; - $scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; $scheduler-workspace-hovered-cell-color: $scheduler-workspace-month-text-color !default; $scheduler-group-header-color: $scheduler-panel-text-color !default; $scheduler-header-bg: $base-hover-bg !default; $scheduler-alldaypanel-bg: $base-element-bg !default; - $scheduler-tab-selected-bg: $scheduler-workspace-background-color !default; - $scheduler-tab-selected-before-bg: $scheduler-tab-selected-bg !default; - $scheduler-tab-focused-border-color: $base-accent !default; $scheduler-other-month-cell-opacity: 0.5 !default; $scheduler-fill-focused-appointment: false !default; $scheduler-time-indicator-background-color: rgba(51, 51, 51, 0.04) !default; @@ -404,8 +359,9 @@ $agenda-appointment-text-color: null !default; $scheduler-appointment-overlay-bg: $base-bg !default; $agenda-appointment-recurrence-icon-color: $base-inverted-text-color !default; - $agenda-appointment-text-color: $base-text-color !default; $agenda-appointment-active-bg: color.change(#000, $alpha: 0.08) !default; } -$scheduler-appointment-active-color: $scheduler-appointment-start-color; +$scheduler-workspace-active-cell-color: $scheduler-accent-border-color !default; + +$scheduler-appointment-active-color: $scheduler-appointment-start-color !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss index 89156a91ae9a..1d8501d7f8bd 100644 --- a/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/scheduler/_index.scss @@ -52,21 +52,21 @@ @use "../recurrenceEditor" as *; @use "../scrollable"; -$generic-scheduler-appointment-reduced-content-padding: 40px; -$generic-scheduler-appointment-empty-transparent-border: 1px solid transparent; +$generic-scheduler-appointment-reduced-content-padding: 40px !default; +$generic-scheduler-appointment-empty-transparent-border: 1px solid transparent !default; -$generic-scheduler-all-day-table-cell-height: $generic-scheduler-workspace-date-table-cell-height * 1.5; -$generic-scheduler-collapsed-all-day-table-cell-height: $generic-scheduler-workspace-date-table-cell-height * 0.5; -$generic-scheduler-header-panel-table-cell-height: $generic-scheduler-workspace-date-table-cell-height; +$generic-scheduler-all-day-table-cell-height: $generic-scheduler-workspace-date-table-cell-height * 1.5 !default; +$generic-scheduler-collapsed-all-day-table-cell-height: $generic-scheduler-workspace-date-table-cell-height * 0.5 !default; +$generic-scheduler-header-panel-table-cell-height: $generic-scheduler-workspace-date-table-cell-height !default; -$generic-scheduler-current-time-cell-border-size: 2px; +$generic-scheduler-current-time-cell-border-size: 2px !default; -$generic-scheduler-month-date-text-size: $generic-h5-font-size; +$generic-scheduler-month-date-text-size: $generic-h5-font-size !default; -$scheduler-first-of-month-border: 1px solid; +$scheduler-first-of-month-border: 1px solid !default; -$generic-scheduler-agenda-time-cell-padding: 10px; -$generic-scheduler-agenda-group-header-padding: $generic-scheduler-agenda-time-cell-padding 0 0 10px; +$generic-scheduler-agenda-time-cell-padding: 10px !default; +$generic-scheduler-agenda-group-header-padding: $generic-scheduler-agenda-time-cell-padding 0 0 10px !default; @include baseScheduler.scheduler-mixin(); diff --git a/packages/devextreme-scss/scss/widgets/generic/scheduler/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/scheduler/_sizes.scss index 46750f3037f9..cab02338233c 100644 --- a/packages/devextreme-scss/scss/widgets/generic/scheduler/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/scheduler/_sizes.scss @@ -7,32 +7,29 @@ $generic-scheduler-timeline-date-table-cell-height: null !default; $generic-scheduler-header-height: null !default; $generic-scheduler-header-offset: null !default; $generic-scheduler-toolbar-item-offset: null !default; -$generic-scheduler-navigator-height: null !default; -$generic-scheduler-navigation-buttons-padding: null !default; $generic-scheduler-appointment-month-text-size: null !default; $generic-scheduler-workspace-info-text-size: null !default; $generic-scheduler-all-day-title-font-size: null !default; $generic-scheduler-appointment-content-padding: null !default; $generic-scheduler-appointment-recurrence-content-padding: null !default; $generic-scheduler-appointment-recurrence-content-padding-rtl: null !default; -$generic-scheduler-agenda-appointment-font-size: null !default; $generic-scheduler-group-header-table-cell-width: null !default; $generic-scheduler-appointment-transparent-left-border: null !default; $generic-scheduler-appointment-transparent-right-border: null !default; $generic-scheduler-dropdown-button-height: null !default; $generic-scheduler-dropdown-button-margin: null !default; $generic-scheduler-dropdown-button-vertical-margin: null !default; -$generic-scheduler-dropdown-button-adaptive-margin: null !default; -$generic-scheduler-dropdown-button-adaptive-vertical-margin: null !default; +$generic-scheduler-dropdown-button-adaptive-margin: 0 2px !default; +$generic-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 0 !default; $generic-scheduler-appointment-popup-toolbar-height: null !default; $generic-scheduler-appointment-popup-toolbar-label-size: null !default; $generic-scheduler-appointment-popup-icon-size: null !default; $generic-scheduler-appointment-popup-icon-container-height: null !default; -$generic-scheduler-appointment-popup-icon-padding-right: null !default; +$generic-scheduler-appointment-popup-icon-padding-right: 5px !default; $generic-scheduler-appointment-popup-icon-margin-top: null !default; $generic-scheduler-appointment-popup-icon-inner-label-margin-top: null !default; -$generic-scheduler-appointment-popup-item-padding-horizontal: null !default; +$generic-scheduler-appointment-popup-item-padding-horizontal: 5px !default; $generic-scheduler-appointment-popup-item-padding-top: null !default; $generic-scheduler-appointment-popup-all-day-item-height: null !default; $generic-scheduler-appointment-popup-days-of-week-button-size: null !default; @@ -42,22 +39,22 @@ $generic-scheduler-appointment-popup-repeat-end-item-height: null !default; $generic-scheduler-appointment-short-content-padding: null !default; $generic-scheduler-appointment-10min-height: null !default; -$generic-scheduler-appointment-15min-height: null !default; -$generic-scheduler-appointment-20min-height: null !default; -$generic-scheduler-appointment-25min-height: null !default; -$generic-scheduler-appointment-10min-recurrence-padding-right: null !default; -$generic-scheduler-appointment-10min-title-font-size: null !default; -$generic-scheduler-appointment-10min-title-line-height: null !default; -$generic-scheduler-appointment-10min-icon-font-size: null !default; -$generic-scheduler-appointment-10min-icon-right: null !default; +$generic-scheduler-appointment-15min-height: 23px !default; +$generic-scheduler-appointment-20min-height: 32px !default; +$generic-scheduler-appointment-25min-height: 41px !default; +$generic-scheduler-appointment-10min-recurrence-padding-right: 16px !default; +$generic-scheduler-appointment-10min-title-font-size: 8px !default; +$generic-scheduler-appointment-10min-title-line-height: 16px !default; +$generic-scheduler-appointment-10min-icon-font-size: 14px !default; +$generic-scheduler-appointment-10min-icon-right: 2px !default; $generic-scheduler-appointment-15min-recurrence-padding-right: null !default; -$generic-scheduler-appointment-15min-title-font-size: null !default; +$generic-scheduler-appointment-15min-title-font-size: 12px !default; $generic-scheduler-appointment-15min-title-line-height: null !default; $generic-scheduler-appointment-15min-icon-font-size: null !default; -$generic-scheduler-appointment-15min-icon-right: null !default; +$generic-scheduler-appointment-15min-icon-right: 2px !default; $generic-scheduler-appointment-20min-recurrence-padding-right: null !default; -$agenda-appointment-title-font-size: null !default; +$agenda-appointment-title-font-size: 16px !default; $scheduler-timeline-cell-height: null !default; $scheduler-left-column-width: 100px !default; @@ -70,8 +67,6 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-header-height: 36px !default; $generic-scheduler-header-offset: 9px 10px !default; $generic-scheduler-toolbar-item-offset: 2px !default; - $generic-scheduler-navigator-height: 36px !default; - $generic-scheduler-navigation-buttons-padding: 6px !default; $generic-scheduler-appointment-month-text-size: 13px !default; $generic-scheduler-workspace-info-text-size: 14px !default; @@ -81,7 +76,6 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-appointment-recurrence-content-padding: 5px 25px 5px 7px !default; $generic-scheduler-appointment-recurrence-content-padding-rtl: 5px 7px 5px 25px !default; - $generic-scheduler-agenda-appointment-font-size: 16px !default; $generic-scheduler-group-header-table-cell-width: 80px !default; $generic-scheduler-appointment-transparent-left-border: 3px solid transparent !default; $generic-scheduler-appointment-transparent-right-border: 2px solid transparent !default; @@ -89,17 +83,13 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-dropdown-button-height: 20px !default; $generic-scheduler-dropdown-button-margin: 3px 2px 3px 3px !default; $generic-scheduler-dropdown-button-vertical-margin: 3px 2px 3px 0 !default; - $generic-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $generic-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 0 !default; $generic-scheduler-appointment-popup-toolbar-height: 43px !default; $generic-scheduler-appointment-popup-toolbar-label-size: 20px !default; $generic-scheduler-appointment-popup-icon-size: 18px !default; $generic-scheduler-appointment-popup-icon-container-height: 36px !default; - $generic-scheduler-appointment-popup-icon-padding-right: 5px !default; $generic-scheduler-appointment-popup-icon-margin-top: 22px !default; $generic-scheduler-appointment-popup-icon-inner-label-margin-top: 7px !default; - $generic-scheduler-appointment-popup-item-padding-horizontal: 5px !default; $generic-scheduler-appointment-popup-item-padding-top: 20px !default; $generic-scheduler-appointment-popup-all-day-item-height: 36px !default; $generic-scheduler-appointment-popup-repeat-end-radio-margin-top: 10px !default; @@ -107,27 +97,14 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-appointment-popup-days-of-week-buttons-gap: 10px !default; $generic-scheduler-appointment-popup-repeat-end-item-height: 36px !default; - $agenda-appointment-title-font-size: 16px; - $scheduler-timeline-cell-height: 78px !default; $generic-scheduler-appointment-short-content-padding: 0 7px !default; $generic-scheduler-appointment-10min-height: 23px !default; - $generic-scheduler-appointment-15min-height: 23px !default; - $generic-scheduler-appointment-20min-height: 32px !default; - $generic-scheduler-appointment-25min-height: 41px !default; - - $generic-scheduler-appointment-10min-recurrence-padding-right: 16px !default; - $generic-scheduler-appointment-10min-title-font-size: 8px !default; - $generic-scheduler-appointment-10min-title-line-height: 16px !default; - $generic-scheduler-appointment-10min-icon-font-size: 14px !default; - $generic-scheduler-appointment-10min-icon-right: 2px !default; $generic-scheduler-appointment-15min-recurrence-padding-right: 20px !default; - $generic-scheduler-appointment-15min-title-font-size: 12px !default; $generic-scheduler-appointment-15min-title-line-height: 24px !default; $generic-scheduler-appointment-15min-icon-font-size: 18px !default; - $generic-scheduler-appointment-15min-icon-right: 2px !default; $generic-scheduler-appointment-20min-recurrence-padding-right: 24px !default; } @@ -139,8 +116,6 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-header-height: 26px !default; $generic-scheduler-header-offset: 6px 7px !default; $generic-scheduler-toolbar-item-offset: 0 !default; - $generic-scheduler-navigator-height: 25px !default; - $generic-scheduler-navigation-buttons-padding: 4px !default; $generic-scheduler-appointment-month-text-size: 11px !default; $generic-scheduler-workspace-info-text-size: 12px !default; @@ -150,7 +125,6 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-appointment-recurrence-content-padding: 2px 25px 2px 5px !default; $generic-scheduler-appointment-recurrence-content-padding-rtl: 2px 5px 2px 25px !default; - $generic-scheduler-agenda-appointment-font-size: 14px !default; $generic-scheduler-group-header-table-cell-width: 60px !default; $generic-scheduler-appointment-transparent-left-border: 2px solid transparent !default; $generic-scheduler-appointment-transparent-right-border: 1px solid transparent !default; @@ -158,17 +132,13 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-dropdown-button-height: 17px !default; $generic-scheduler-dropdown-button-margin: 3px 1px 3px 3px !default; $generic-scheduler-dropdown-button-vertical-margin: 3px 1px 3px 0 !default; - $generic-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $generic-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 0 !default; $generic-scheduler-appointment-popup-toolbar-height: 39px !default; $generic-scheduler-appointment-popup-toolbar-label-size: 18px !default; $generic-scheduler-appointment-popup-icon-size: 14px !default; $generic-scheduler-appointment-popup-icon-container-height: 26px !default; - $generic-scheduler-appointment-popup-icon-padding-right: 5px !default; $generic-scheduler-appointment-popup-icon-margin-top: 18px !default; $generic-scheduler-appointment-popup-icon-inner-label-margin-top: 6px !default; - $generic-scheduler-appointment-popup-item-padding-horizontal: 5px !default; $generic-scheduler-appointment-popup-item-padding-top: 10px !default; $generic-scheduler-appointment-popup-all-day-item-height: 24px !default; $generic-scheduler-appointment-popup-repeat-end-radio-margin-top: 6px !default; @@ -176,27 +146,14 @@ $generic-scheduler-group-header-agenda-font-size: 14px !default; $generic-scheduler-appointment-popup-days-of-week-buttons-gap: 6px !default; $generic-scheduler-appointment-popup-repeat-end-item-height: 26px !default; - $agenda-appointment-title-font-size: 16px; - $scheduler-timeline-cell-height: 50px !default; $generic-scheduler-appointment-short-content-padding: 0 5px !default; $generic-scheduler-appointment-10min-height: 14px !default; - $generic-scheduler-appointment-15min-height: 23px !default; - $generic-scheduler-appointment-20min-height: 32px !default; - $generic-scheduler-appointment-25min-height: 41px !default; - - $generic-scheduler-appointment-10min-title-font-size: 8px !default; - $generic-scheduler-appointment-10min-title-line-height: 16px !default; - $generic-scheduler-appointment-10min-recurrence-padding-right: 16px !default; - $generic-scheduler-appointment-10min-icon-font-size: 14px !default; - $generic-scheduler-appointment-10min-icon-right: 2px !default; $generic-scheduler-appointment-15min-recurrence-padding-right: 16px !default; - $generic-scheduler-appointment-15min-title-font-size: 12px !default; $generic-scheduler-appointment-15min-title-line-height: 16px !default; $generic-scheduler-appointment-15min-icon-font-size: 14px !default; - $generic-scheduler-appointment-15min-icon-right: 2px !default; $generic-scheduler-appointment-20min-recurrence-padding-right: 20px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/scrollable/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/scrollable/_sizes.scss index 721bcb419a19..7e5c45388a59 100644 --- a/packages/devextreme-scss/scss/widgets/generic/scrollable/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/scrollable/_sizes.scss @@ -1,2 +1,2 @@ -$generic-scrollbar-size-thin: 8px; -$generic-scrollbar-size: 15px; +$generic-scrollbar-size-thin: 8px !default; +$generic-scrollbar-size: 15px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/slider/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/slider/_colors.scss index 6e7b828ad034..4ffcbc4e0e48 100644 --- a/packages/devextreme-scss/scss/widgets/generic/slider/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/slider/_colors.scss @@ -18,7 +18,6 @@ $slider-handle-bg: null !default; $slider-handle-border-color: null !default; $slider-handle-active-bg: null !default; $slider-handle-hover-bg: null !default; -$slider-handle-focused-bg: null !default; $slider-invalid-handle-accent-bg: null !default; $slider-handle-border-radius: $base-border-radius - 1px !default; @@ -27,7 +26,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: -17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: -10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; } @@ -36,7 +34,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: extcolor.difference(#fff, $base-border-color) !default; $slider-handle-active-bg: $base-inverted-bg !default; $slider-handle-hover-bg: $slider-range-bg !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; } @@ -45,7 +42,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: 17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: 10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; } @@ -54,7 +50,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: 17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: 10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; } @@ -63,7 +58,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: 17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: 10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: 10%, $space: hsl) !default; } @@ -72,7 +66,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: -17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: -10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; } @@ -81,7 +74,6 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: -17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: -10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; } @@ -90,6 +82,7 @@ $slider-handle-border-radius: $base-border-radius - 1px !default; $slider-handle-border-color: $base-bg !default; $slider-handle-active-bg: color.adjust($slider-handle-bg, $lightness: -17%, $space: hsl) !default; $slider-handle-hover-bg: color.adjust($slider-handle-bg, $lightness: -10%, $space: hsl) !default; - $slider-handle-focused-bg: $slider-handle-hover-bg !default; $slider-invalid-handle-accent-bg: color.adjust($base-danger, $lightness: -10%, $space: hsl) !default; } + +$slider-handle-focused-bg: $slider-handle-hover-bg !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/slider/_index.scss b/packages/devextreme-scss/scss/widgets/generic/slider/_index.scss index b9459bab9b75..c036962f113b 100644 --- a/packages/devextreme-scss/scss/widgets/generic/slider/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/slider/_index.scss @@ -5,7 +5,7 @@ @use "sizes" as *; @use "../../base/slider"; -$generic-slider-radius: 2px; +$generic-slider-radius: 2px !default; .dx-slider { .dx-tooltip-wrapper { diff --git a/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_index.scss b/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_index.scss index 396fe10c61a2..1089f51ba2a1 100644 --- a/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_index.scss @@ -5,9 +5,9 @@ @use "../button/colors" as *; @use "../overlay"; -$generic-fa-button-size: 30px; -$generic-fa-button-label-padding: 5px 10px; -$generic-fa-button-label-offset: 10px; +$generic-fa-button-size: 30px !default; +$generic-fa-button-label-padding: 5px 10px !default; +$generic-fa-button-label-offset: 10px !default; @include dx-fa-button-mixin( $generic-main-fa-button-size, diff --git a/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_sizes.scss index 650d9654e9bd..5e8f7e17bc8e 100644 --- a/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/speedDialAction/_sizes.scss @@ -7,10 +7,10 @@ $generic-fa-button-label-font-size: 12px !default; @if $size == "default" { $generic-main-fa-button-size: 48px !default; - $generic-main-fa-button-label-padding: 0 25px 0 10px; + $generic-main-fa-button-label-padding: 0 25px 0 10px !default; } @else if $size == "compact" { $generic-main-fa-button-size: 34px !default; - $generic-main-fa-button-label-padding: 0 15px 0 5px; + $generic-main-fa-button-label-padding: 0 15px 0 5px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/splitter/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/splitter/_sizes.scss index 839b901f0580..b4d9ec2a53d7 100644 --- a/packages/devextreme-scss/scss/widgets/generic/splitter/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/splitter/_sizes.scss @@ -1 +1 @@ -$splitter-resize-handle-icon-size: 18px; +$splitter-resize-handle-icon-size: 18px !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/switch/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/switch/_colors.scss index a4e5208391da..85340cd00c40 100644 --- a/packages/devextreme-scss/scss/widgets/generic/switch/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/switch/_colors.scss @@ -35,7 +35,6 @@ $switch-active-border-color: null !default; $switch-off-color: null !default; $switch-handle-off-bg: null !default; $switch-readonly-border-color: null !default; -$switch-container-readonly-bg: null !default; $switch-border-radius: null !default; @if $color == "carmine" { @@ -51,7 +50,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: 15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: 9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -67,7 +65,6 @@ $switch-border-radius: null !default; $switch-off-color: $base-text-color !default; $switch-handle-off-bg: $base-inverted-bg !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: -50%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius - 2px !default; } @@ -83,7 +80,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: -40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: -15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: -9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -99,7 +95,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: -40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: -15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: -9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -115,7 +110,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: -40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: -15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: -9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -131,7 +125,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: 15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: 9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -147,7 +140,6 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: 15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: 9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } @@ -163,6 +155,7 @@ $switch-border-radius: null !default; $switch-off-color: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; $switch-handle-off-bg: color.adjust($switch-handle-bg, $lightness: 15%, $space: hsl) !default; $switch-readonly-border-color: color.adjust($switch-border-color, $lightness: 9%, $space: hsl) !default; - $switch-container-readonly-bg: $switch-bg !default; $switch-border-radius: $base-border-radius-exsmall !default; } + +$switch-container-readonly-bg: $switch-bg !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/switch/_index.scss b/packages/devextreme-scss/scss/widgets/generic/switch/_index.scss index 7b3bf528f688..de3aee4096fd 100644 --- a/packages/devextreme-scss/scss/widgets/generic/switch/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/switch/_index.scss @@ -4,8 +4,8 @@ @use "../../base/mixins" as *; @use "../../base/switch"; -$generic-switch-border-weight: 1px; -$generic-switch-inner-size: $generic-switch-height - $generic-switch-border-weight * 2; +$generic-switch-border-weight: 1px !default; +$generic-switch-inner-size: $generic-switch-height - $generic-switch-border-weight * 2 !default; .dx-switch { width: $generic-switch-width; diff --git a/packages/devextreme-scss/scss/widgets/generic/tabPanel/variables/_borders.scss b/packages/devextreme-scss/scss/widgets/generic/tabPanel/variables/_borders.scss index 7065d72a2709..89b71d9318f2 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabPanel/variables/_borders.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabPanel/variables/_borders.scss @@ -1,93 +1,4 @@ -@use "../../colors" as *; -@use "../variables/sizes" as *; -@use "../../button/colors" as *; @use "../../tabs/variables/sizes" as *; -@use "../../tabs/variables/colors" as *; -$generic-tabpanel-tab-border-transparent: null !default; -$generic-tabpanel-tab-border-selected-bottom: null !default; -$generic-tabpanel-tab-border-active-bottom: null !default; - -$generic-tabpanel-tab-border-focused-bottom: null !default; -$generic-tabpanel-tab-border-focused-left: null !default; -$generic-tabpanel-tab-border-focused-right: null !default; - -@if $color == "carmine" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "contrast" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "dark" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "darkmoon" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "darkviolet" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "greenmist" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "light" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} - -@if $color == "softblue" { - $generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; - $generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabpanel-tab-border-active-bottom: $generic-tabs-border-width solid $tabs-focused-border-color !default; - - $generic-tabpanel-tab-border-focused-bottom: $generic-tabs-border-width solid $tabs-tab-selected-bg !default; - $generic-tabpanel-tab-border-focused-left: $generic-tabs-border-width solid $tabs-focused-border-color !default; - $generic-tabpanel-tab-border-focused-right: $generic-tabs-border-width solid $tabs-focused-border-color !default; -} +$generic-tabpanel-tab-border-transparent: $generic-tabs-border-width solid transparent !default; +$generic-tabpanel-tab-border-selected-bottom: $generic-tabs-border-width solid !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_colors.scss index 63fc74fbffb6..99b07836830a 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_colors.scss @@ -50,8 +50,7 @@ $tabs-focused-disabled-border-color: null !default; $tabs-tab-hover-bg: null !default; $tabs-tab-active-bg: null !default; $tabs-tab-active-border-color: null !default; -$tabs-tab-hover-border-color-horizontal: null !default; -$tabs-tab-hover-border-color-vertical: null !default; +$tabs-tab-hover-border-color-horizontal: $base-border-color !default; @if $color == "carmine" { $tabs-tab-selected-color: $base-text-color !default; @@ -64,8 +63,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-hover-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: -62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.change(color.adjust($base-border-color, $lightness: -2.55%, $space: hsl)) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: color.change(color.adjust($base-accent, $lightness: 33.92%, $space: hsl)) !default; } @if $color == "contrast" { @@ -79,8 +76,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-focused-disabled-border-color: color.adjust($base-border-color, $lightness: -20%, $space: hsl) !default; $tabs-tab-active-bg: $base-inverted-bg !default; $tabs-tab-active-border-color: $base-border-color !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "dark" { @@ -94,8 +89,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: 62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.change(color.adjust($tabs-tab-bg, $lightness: 12.55%, $space: hsl)) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "darkmoon" { @@ -109,8 +102,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: 62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: transparent !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "darkviolet" { @@ -124,8 +115,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-hover-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: 62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.change(color.adjust($base-bg, $lightness: 15.49%, $space: hsl)) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: color.change(color.adjust($tabs-tab-bg, $lightness: 16.86%, $space: hsl)) !default; } @if $color == "greenmist" { @@ -139,8 +128,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: -62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.adjust($tabs-tab-bg, $lightness: -12.16%, $space: hsl) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "light" { @@ -154,8 +141,6 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: -62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.adjust($tabs-tab-bg, $lightness: -12.16%, $space: hsl) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } @if $color == "softblue" { @@ -169,6 +154,4 @@ $tabs-tab-hover-border-color-vertical: null !default; $tabs-tab-hover-bg: $base-element-bg !default; $tabs-tab-active-bg: color.change(color.adjust($tabs-tab-bg, $lightness: -62.5%, $space: hsl), $alpha: 0.2) !default; $tabs-tab-active-border-color: color.adjust($tabs-tab-bg, $lightness: -12.16%, $space: hsl) !default; - $tabs-tab-hover-border-color-horizontal: $base-border-color !default; - $tabs-tab-hover-border-color-vertical: $tabs-tab-hover-bg !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_sizes.scss index 9aa3bb5dc194..5183306a42b6 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/_sizes.scss @@ -3,11 +3,10 @@ $generic-tabs-item-gap: null !default; $generic-tabs-item-padding: null !default; -$generic-tabs-item-padding-disabled-focused: null !default; $generic-tabs-border-width: $generic-base-border-width !default; -$generic-tabs-nav-button-height: null !default; -$generic-tabs-nav-button-width: null !default; +$generic-tabs-nav-button-height: 38px !default; +$generic-tabs-nav-button-width: 25px !default; $generic-tab-text-line-height: null !default; $generic-tabs-icon-margin: null !default; $generic-tabs-icon-size: $generic-base-icon-size !default; @@ -16,10 +15,7 @@ $generic-badge-min-size: null !default; @if $size == "default" { $generic-tabs-item-gap: 4px !default; $generic-tabs-item-padding: 10px 9px !default; - $generic-tabs-item-padding-disabled-focused: 10px !default; - $generic-tabs-nav-button-width: 25px !default; $generic-tab-text-line-height: 1.285715 !default; - $generic-tabs-nav-button-height: 38px !default; $generic-tabs-icon-margin: 8px !default; $generic-badge-min-size: 18px !default; } @@ -27,10 +23,7 @@ $generic-badge-min-size: null !default; @else if $size == "compact" { $generic-tabs-item-gap: 2px !default; $generic-tabs-item-padding: 5px 4px !default; - $generic-tabs-item-padding-disabled-focused: 5px !default; - $generic-tabs-nav-button-width: 25px !default; $generic-tab-text-line-height: 1.75 !default; - $generic-tabs-nav-button-height: 38px !default; $generic-tabs-icon-margin: 7px !default; $generic-badge-min-size: 21px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-horizontal.scss b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-horizontal.scss index eed206407654..42077dcc6b05 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-horizontal.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-horizontal.scss @@ -8,243 +8,27 @@ $generic-tabs-border: $generic-tabs-border-width solid !default; $generic-tabs-border-transparent: $generic-tabs-border-width solid transparent !default; -$generic-tabs-border-hover-top: null !default; -$generic-tabs-border-hover-bottom: null !default; -$generic-tabs-border-hover-left: null !default; -$generic-tabs-border-hover-right: null !default; - -$generic-tabs-border-selected-top: null !default; -$generic-tabs-border-selected-bottom: null !default; -$generic-tabs-border-selected-left: null !default; -$generic-tabs-border-selected-right: null !default; - -$generic-tabs-border-focused-top: null !default; -$generic-tabs-border-focused-bottom: null !default; -$generic-tabs-border-focused-left: null !default; -$generic-tabs-border-focused-right: null !default; - -$generic-tabs-border-focused-disabled-top: null !default; -$generic-tabs-border-focused-disabled-bottom: null !default; -$generic-tabs-border-focused-disabled-left: null !default; -$generic-tabs-border-focused-disabled-right: null !default; - -$generic-tabs-border-active-top: null !default; -$generic-tabs-border-active-bottom: null !default; -$generic-tabs-border-active-left: null !default; -$generic-tabs-border-active-right: null !default; - -@if $color == "carmine" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "contrast" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "dark" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "darkmoon" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "darkviolet" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "greenmist" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "light" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "softblue" { - $generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-hover-left: none !default; - $generic-tabs-border-hover-right: none !default; - - $generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-border-active-right: $generic-tabs-border-width solid !default; -} +$generic-tabs-border-hover-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-hover-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-hover-left: none !default; +$generic-tabs-border-hover-right: none !default; + +$generic-tabs-border-selected-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-selected-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-selected-left: $generic-tabs-border-width solid !default; +$generic-tabs-border-selected-right: $generic-tabs-border-width solid !default; + +$generic-tabs-border-focused-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-left: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-right: $generic-tabs-border-width solid !default; + +$generic-tabs-border-focused-disabled-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-disabled-left: $generic-tabs-border-width solid !default; +$generic-tabs-border-focused-disabled-right: $generic-tabs-border-width solid !default; + +$generic-tabs-border-active-top: $generic-tabs-border-width solid !default; +$generic-tabs-border-active-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-border-active-left: $generic-tabs-border-width solid !default; +$generic-tabs-border-active-right: $generic-tabs-border-width solid !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-vertical.scss b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-vertical.scss index f9d01c04e193..04cc910391e2 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-vertical.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tabs/variables/borders/_borders-vertical.scss @@ -6,243 +6,27 @@ @use "../../variables/sizes" as *; @use "../../variables/borders/borders-horizontal"; -$generic-tabs-vertical-border-hover-top: null !default; -$generic-tabs-vertical-border-hover-bottom: null !default; -$generic-tabs-vertical-border-hover-left: null !default; -$generic-tabs-vertical-border-hover-right: null !default; - -$generic-tabs-vertical-border-selected-top: null !default; -$generic-tabs-vertical-border-selected-bottom: null !default; -$generic-tabs-vertical-border-selected-left: null !default; -$generic-tabs-vertical-border-selected-right: null !default; - -$generic-tabs-vertical-border-focused-top: null !default; -$generic-tabs-vertical-border-focused-bottom: null !default; -$generic-tabs-vertical-border-focused-left: null !default; -$generic-tabs-vertical-border-focused-right: null !default; - -$generic-tabs-vertical-border-focused-disabled-top: null !default; -$generic-tabs-vertical-border-focused-disabled-bottom: null !default; -$generic-tabs-vertical-border-focused-disabled-left: null !default; -$generic-tabs-vertical-border-focused-disabled-right: null !default; - -$generic-tabs-vertical-border-active-top: null !default; -$generic-tabs-vertical-border-active-bottom: null !default; -$generic-tabs-vertical-border-active-left: null !default; -$generic-tabs-vertical-border-active-right: null !default; - -@if $color == "carmine" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "contrast" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "dark" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "darkmoon" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "darkviolet" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "greenmist" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "light" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} - -@if $color == "softblue" { - $generic-tabs-vertical-border-hover-top: none !default; - $generic-tabs-vertical-border-hover-bottom: none !default; - $generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; - - $generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; - $generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; -} +$generic-tabs-vertical-border-hover-top: none !default; +$generic-tabs-vertical-border-hover-bottom: none !default; +$generic-tabs-vertical-border-hover-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-hover-right: $generic-tabs-border-width solid !default; + +$generic-tabs-vertical-border-selected-top: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-selected-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-selected-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-selected-right: $generic-tabs-border-width solid !default; + +$generic-tabs-vertical-border-focused-top: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-right: $generic-tabs-border-width solid !default; + +$generic-tabs-vertical-border-focused-disabled-top: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-disabled-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-disabled-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-focused-disabled-right: $generic-tabs-border-width solid !default; + +$generic-tabs-vertical-border-active-top: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-active-bottom: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-active-left: $generic-tabs-border-width solid !default; +$generic-tabs-vertical-border-active-right: $generic-tabs-border-width solid !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/tagBox/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/tagBox/_colors.scss index a342938c89a6..294bb2ffc202 100644 --- a/packages/devextreme-scss/scss/widgets/generic/tagBox/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/tagBox/_colors.scss @@ -23,7 +23,6 @@ $tagbox-tag-button-remove-bg: null !default; $tagbox-tag-button-remove-active-bg: null !default; $tagbox-tag-focused-bg: null !default; $tagbox-tag-focused-color: null !default; -$tagbox-tag-focused-button-remove-bg: null !default; $tagbox-select-all-border-color: $base-border-color !default; @if $color == "carmine" { @@ -33,7 +32,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "contrast" { @@ -43,7 +41,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: $base-focus-color !default; $tagbox-tag-focused-color: $base-text-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "dark" { @@ -53,7 +50,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: -20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7.3%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "darkmoon" { @@ -63,7 +59,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: -20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7.3%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "darkviolet" { @@ -73,7 +68,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: -20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7.3%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "greenmist" { @@ -83,7 +77,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "light" { @@ -93,7 +86,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } @if $color == "softblue" { @@ -103,5 +95,6 @@ $tagbox-select-all-border-color: $base-border-color !default; $tagbox-tag-button-remove-active-bg: color.adjust($tagbox-tag-button-remove-bg, $lightness: 20%, $space: hsl) !default; $tagbox-tag-focused-bg: color.adjust($tagbox-tag-bg, $lightness: -7%, $space: hsl) !default; $tagbox-tag-focused-color: $tagbox-tag-color !default; - $tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; } + +$tagbox-tag-focused-button-remove-bg: $tagbox-tag-button-remove-bg !default; diff --git a/packages/devextreme-scss/scss/widgets/generic/textEditor/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/textEditor/_colors.scss index f6c080b13de3..79a778f4d217 100644 --- a/packages/devextreme-scss/scss/widgets/generic/textEditor/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/textEditor/_colors.scss @@ -54,7 +54,7 @@ $texteditor-readonly-border-color: $base-border-color !default; $texteditor-label-transition: font-size 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, transform 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, - top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms; + top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms !default; @if $color == "carmine" { $texteditor-placeholder-color: $base-label-color !default; @@ -128,7 +128,7 @@ $texteditor-label-transition: $texteditor-filled-invalid-background: color.change($texteditor-invalid-faded-border-color, $alpha: 0.1) !default; } -$texteditor-label-color: $texteditor-placeholder-color; +$texteditor-label-color: $texteditor-placeholder-color !default; :root { --dx-texteditor-color-text: #{$texteditor-color}; diff --git a/packages/devextreme-scss/scss/widgets/generic/textEditor/_index.scss b/packages/devextreme-scss/scss/widgets/generic/textEditor/_index.scss index 67a56708f324..d3b4dc45bebc 100644 --- a/packages/devextreme-scss/scss/widgets/generic/textEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/textEditor/_index.scss @@ -13,21 +13,21 @@ @use "../../base/textEditor/mixins" as *; @use "../colorBox/sizes" as *; -$generic-texteditor-border-width: $generic-base-border-width; +$generic-texteditor-border-width: $generic-base-border-width !default; $generic-texteditor-input-padding: $generic-base-inline-borderedwidget-top-padding $generic-base-inline-borderedwidget-horizontal-padding - $generic-base-inline-borderedwidget-bottom-padding; + $generic-base-inline-borderedwidget-bottom-padding !default; -$generic-texteditor-input-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding; +$generic-texteditor-input-height: math.round($generic-base-line-height * $generic-base-font-size) + $generic-base-inline-borderedwidget-top-padding + $generic-base-inline-borderedwidget-bottom-padding !default; -$generic-texteditor-icon-container-size: $generic-base-inline-borderedwidget-inner-size; -$generic-texteditor-icon-size: $generic-base-icon-size; +$generic-texteditor-icon-container-size: $generic-base-inline-borderedwidget-inner-size !default; +$generic-texteditor-icon-size: $generic-base-icon-size !default; -$generic-texteditor-clear-icon-size: $generic-texteditor-icon-size + $generic-texteditor-clear-icon-oversize; +$generic-texteditor-clear-icon-size: $generic-texteditor-icon-size + $generic-texteditor-clear-icon-oversize !default; -$generic-texteditor-custom-button-margin: 1px; -$generic-texteditor-invalid-badge-size: $generic-invalid-badge-size + 2 * $generic-invalid-badge-horizontal-padding; +$generic-texteditor-custom-button-margin: 1px !default; +$generic-texteditor-invalid-badge-size: $generic-invalid-badge-size + 2 * $generic-invalid-badge-horizontal-padding !default; @mixin dx-texteditor-icon() { position: absolute; diff --git a/packages/devextreme-scss/scss/widgets/generic/textEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/textEditor/_sizes.scss index ebbe32e60ef1..6520341bb9fe 100644 --- a/packages/devextreme-scss/scss/widgets/generic/textEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/textEditor/_sizes.scss @@ -7,7 +7,7 @@ $generic-texteditor-label-outside-font-size: null !default; $generic-texteditor-label-outside-height: null !default; $generic-texteditor-label-outside-top-offset: null !default; $generic-texteditor-button-icononly-bottom-padding: null !default; -$generic-texteditor-button-icononly-horizontal-padding: null !default; +$generic-texteditor-button-icononly-horizontal-padding: $generic-button-icon-horizontal-padding - 2px !default; $generic-texteditor-button-top-padding: $generic-base-inline-widget-top-padding - $generic-button-border-weight - 2px !default; $generic-texteditor-button-bottom-padding: $generic-base-inline-widget-bottom-padding - $generic-button-border-weight - 2px !default; @@ -26,7 +26,6 @@ $generic-texteditor-underlined-input-with-label-bottom-padding: $generic-base-in $generic-texteditor-label-outside-height: 20px !default; $generic-texteditor-label-outside-top-offset: 24px !default; $generic-texteditor-button-icononly-bottom-padding: $generic-texteditor-button-bottom-padding !default; - $generic-texteditor-button-icononly-horizontal-padding: $generic-button-icon-horizontal-padding - 2px !default; } @else if $size == "compact" { @@ -36,5 +35,4 @@ $generic-texteditor-underlined-input-with-label-bottom-padding: $generic-base-in $generic-texteditor-label-outside-height: 16px !default; $generic-texteditor-label-outside-top-offset: 18px !default; $generic-texteditor-button-icononly-bottom-padding: $generic-texteditor-button-bottom-padding + 1px !default; - $generic-texteditor-button-icononly-horizontal-padding: $generic-button-icon-horizontal-padding - 2px !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/toast/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/toast/_colors.scss index 4c1601dec53a..9ffbc3ea7ffa 100644 --- a/packages/devextreme-scss/scss/widgets/generic/toast/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/toast/_colors.scss @@ -32,79 +32,48 @@ $toast-error-bg: $base-danger !default; * $type color */ $toast-success-bg: $base-success !default; -$toast-info-icon-image-bg: null !default; -$toast-warning-icon-image-bg: null !default; -$toast-error-icon-image-bg: null !default; -$toast-success-icon-image-bg: null !default; + +$toast-info-icon-image-bg: data-uri("images/widgets/generic/toast/info.png") !default; +$toast-warning-icon-image-bg: data-uri("images/widgets/generic/toast/warning.png") !default; +$toast-error-icon-image-bg: data-uri("images/widgets/generic/toast/error.png") !default; +$toast-success-icon-image-bg: data-uri("images/widgets/generic/toast/success.png") !default; @if $color == "carmine" { $toast-shadow-color: transparent !default; $toast-info-bg: $base-accent !default; - $toast-info-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/info.png") !default; - $toast-warning-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/warning.png") !default; - $toast-error-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/error.png") !default; - $toast-success-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/success.png") !default; } @if $color == "contrast" { $toast-shadow-color: $base-shadow-color !default; $toast-info-bg: $base-info !default; - $toast-info-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/info.png") !default; - $toast-warning-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/warning.png") !default; - $toast-error-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/error.png") !default; - $toast-success-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/success.png") !default; } @if $color == "dark" { $toast-shadow-color: transparent !default; $toast-info-bg: $base-accent !default; - $toast-info-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/info.png") !default; - $toast-warning-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/warning.png") !default; - $toast-error-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/error.png") !default; - $toast-success-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/success.png") !default; } @if $color == "darkmoon" { $toast-shadow-color: transparent !default; $toast-info-bg: $base-accent !default; - $toast-info-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/info.png") !default; - $toast-warning-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/warning.png") !default; - $toast-error-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/error.png") !default; - $toast-success-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/success.png") !default; } @if $color == "darkviolet" { $toast-shadow-color: transparent !default; $toast-info-bg: $base-accent !default; - $toast-info-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/info.png") !default; - $toast-warning-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/warning.png") !default; - $toast-error-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/error.png") !default; - $toast-success-icon-image-bg: data-uri("images/widgets/generic/color-schemes/dark/toast/success.png") !default; } @if $color == "greenmist" { $toast-shadow-color: transparent !default; $toast-info-bg: $base-accent !default; - $toast-info-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/info.png") !default; - $toast-warning-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/warning.png") !default; - $toast-error-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/error.png") !default; - $toast-success-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/success.png") !default; } @if $color == "light" { $toast-shadow-color: transparent !default; $toast-info-bg: $base-accent !default; - $toast-info-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/info.png") !default; - $toast-warning-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/warning.png") !default; - $toast-error-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/error.png") !default; - $toast-success-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/success.png") !default; } @if $color == "softblue" { $toast-shadow-color: transparent !default; $toast-info-bg: $base-accent !default; - $toast-info-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/info.png") !default; - $toast-warning-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/warning.png") !default; - $toast-error-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/error.png") !default; - $toast-success-icon-image-bg: data-uri("images/widgets/generic/color-schemes/light/toast/success.png") !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/toast/_index.scss b/packages/devextreme-scss/scss/widgets/generic/toast/_index.scss index 3be2e70bcbaf..8ae3fbf94cd5 100644 --- a/packages/devextreme-scss/scss/widgets/generic/toast/_index.scss +++ b/packages/devextreme-scss/scss/widgets/generic/toast/_index.scss @@ -4,7 +4,7 @@ @use "../../base/toast"; @use "../overlay"; -$generic-toast-shadow: 0 2px 3px 0 $toast-shadow-color; +$generic-toast-shadow: 0 2px 3px 0 $toast-shadow-color !default; .dx-toast-content { color: $toast-color; diff --git a/packages/devextreme-scss/scss/widgets/generic/treeList/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/treeList/_colors.scss index 86c52ae07a9f..b46d475d6eeb 100644 --- a/packages/devextreme-scss/scss/widgets/generic/treeList/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/treeList/_colors.scss @@ -2,45 +2,37 @@ @use "../colors" as *; @use "../gridBase/colors" as *; -$treelist-spin-icon-color: null !default; +$treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: null !default; @if $color == "carmine" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: color.adjust($datagrid-spin-icon-color, $lightness: 100%, $space: hsl) !default; } @if $color == "contrast" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $base-inverted-text-color !default; } @if $color == "dark" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $treelist-spin-icon-color !default; } @if $color == "darkmoon" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $base-bg !default; } @if $color == "darkviolet" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $base-icon-color !default; } @if $color == "greenmist" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: color.adjust($treelist-spin-icon-color, $lightness: 100%, $space: hsl) !default; } @if $color == "light" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $treelist-spin-icon-color !default; } @if $color == "softblue" { - $treelist-spin-icon-color: $datagrid-spin-icon-color !default; $treelist-selection-spin-icon-color: $base-icon-color !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/treeView/_colors.scss b/packages/devextreme-scss/scss/widgets/generic/treeView/_colors.scss index 1a618a1088fe..b69ae56ce488 100644 --- a/packages/devextreme-scss/scss/widgets/generic/treeView/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/generic/treeView/_colors.scss @@ -29,7 +29,6 @@ $treeview-border-color: $base-border-color !default; */ $treeview-item-selected-color: null !default; $treeview-item-selected-bg: null !default; -$treeview-item-selected-hover-bg: null !default; $treeview-item-content-disabled: null !default; /** @@ -38,7 +37,7 @@ $treeview-item-content-disabled: null !default; */ $treeview-spin-icon-color: null !default; -$generic-treeview-focused-row-load-indicator-color: #fff; +$generic-treeview-focused-row-load-indicator-color: #fff !default; @if $color == "carmine" { $treeview-color: $base-text-color !default; @@ -48,7 +47,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-hover-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change($base-accent, $alpha: 0.48) !default; - $treeview-item-selected-hover-bg: color.change($base-accent, $alpha: 0.3) !default; $treeview-spin-icon-color: $base-header-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; } @@ -60,7 +58,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-text-color !default; $treeview-item-selected-color: $base-inverted-text-color !default; $treeview-item-selected-bg: $base-inverted-bg !default; - $treeview-item-selected-hover-bg: $base-inverted-bg !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: -48.24%, $space: hsl) !default; } @@ -73,7 +70,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-hover-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#fff, $alpha: 0.12) !default; - $treeview-item-selected-hover-bg: color.change(#fff, $alpha: 0.09) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.change($base-text-color, $lightness: 51.8%, $space: hsl) !default; } @@ -86,7 +82,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $treeview-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#748fbc, $alpha: 0.42) !default; - $treeview-item-selected-hover-bg: color.change(#748fbc, $alpha: 0.3) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: -48.24%, $space: hsl) !default; } @@ -99,7 +94,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-hover-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change($base-accent, $alpha: 0.46) !default; - $treeview-item-selected-hover-bg: color.change($base-accent, $alpha: 0.6) !default; $treeview-spin-icon-color: $base-header-color !default; $treeview-item-content-disabled: color.change($base-text-color, $saturation: 0%, $lightness: 51.76%, $space: hsl) !default; } @@ -112,7 +106,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $base-hover-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#000, $alpha: 0.1) !default; - $treeview-item-selected-hover-bg: color.change(#000, $alpha: 0.2) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.change($base-text-color, $saturation: 0%, $lightness: 60%, $space: hsl) !default; } @@ -125,7 +118,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $treeview-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#000, $alpha: 0.1) !default; - $treeview-item-selected-hover-bg: color.change(#000, $alpha: 0.07) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; } @@ -138,7 +130,6 @@ $generic-treeview-focused-row-load-indicator-color: #fff; $treeview-item-hover-color: $treeview-color !default; $treeview-item-selected-color: $treeview-color !default; $treeview-item-selected-bg: color.change(#000, $alpha: 0.1) !default; - $treeview-item-selected-hover-bg: color.change(#000, $alpha: 0.15) !default; $treeview-spin-icon-color: $base-icon-color !default; $treeview-item-content-disabled: color.adjust($base-text-color, $lightness: 40%, $space: hsl) !default; } diff --git a/packages/devextreme-scss/scss/widgets/generic/treeView/_sizes.scss b/packages/devextreme-scss/scss/widgets/generic/treeView/_sizes.scss index e24765ccb446..aa86eaa7255c 100644 --- a/packages/devextreme-scss/scss/widgets/generic/treeView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/generic/treeView/_sizes.scss @@ -3,10 +3,8 @@ $generic-treeview-container-with-border-offset: null !default; $generic-treeview-base-container-size: null !default; $generic-treeview-toggle-item-width: null !default; -$generic-treeview-item-height: null !default; $generic-select-all-item-padding: null !default; $generic-treeview-item-padding: null !default; -$generic-treeview-load-indicator-size: null !default; $generic-checkbox-container-size: null !default; $generic-treeview-searchbox-margin-bottom: null !default; $generic-treeview-border-padding: 1px !default; @@ -14,29 +12,27 @@ $generic-treeview-item-element-gap: 8px !default; $generic-select-all-item-border-visible-padding: 20px !default; @if $size == "default" { - $generic-treeview-item-height: 32px; $generic-treeview-base-container-size: 22px !default; $generic-treeview-container-with-border-offset: 7px !default; $generic-checkbox-container-size: 22px !default; $generic-treeview-toggle-item-width: 22px !default; $generic-select-all-item-padding: 9px 40px 11px 40px !default; $generic-treeview-item-padding: 5px 10px !default; - $generic-treeview-load-indicator-size: $generic-treeview-toggle-item-width !default; $generic-treeview-searchbox-margin-bottom: 5px !default; } @else if $size == "compact" { - $generic-treeview-item-height: 28px; $generic-treeview-container-with-border-offset: 6px !default; $generic-treeview-base-container-size: 16px !default; $generic-checkbox-container-size: 16px !default; $generic-treeview-toggle-item-width: 16px !default; $generic-select-all-item-padding: 6px 30px 8px 30px !default; $generic-treeview-item-padding: 4px 6px !default; - $generic-treeview-load-indicator-size: $generic-treeview-toggle-item-width !default; $generic-treeview-searchbox-margin-bottom: 3px !default; } +$generic-treeview-load-indicator-size: $generic-treeview-toggle-item-width !default; + $generic-treeview-container-padding: $generic-treeview-border-padding $generic-treeview-border-padding diff --git a/packages/devextreme-scss/scss/widgets/material/_colors.scss b/packages/devextreme-scss/scss/widgets/material/_colors.scss index 6b8686ed2905..1a27a389588a 100644 --- a/packages/devextreme-scss/scss/widgets/material/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/_colors.scss @@ -31,18 +31,6 @@ $base-text-color: null !default; */ $base-label-color: null !default; -/** -* $name 40. Icon color -* $type color -*/ -$base-icon-color: null !default; - -/** -* $name 50. Arrow icon color -* $type color -*/ -$base-spin-icon-color: null !default; - /** * $name 30. Background color * $type color @@ -55,7 +43,6 @@ $base-bg: null !default; */ $base-border-color: null !default; $base-inverted-bg: null !default; -$base-element-bg: null !default; $base-shadow-color: null !default; /** @@ -76,12 +63,6 @@ $base-warning: null !default; */ $base-danger: null !default; -/** -* $name 80. Hover state text color -* $type color -*/ -$base-hover-color: null !default; - /** * $name 90. Hover state background color * $type color @@ -89,22 +70,14 @@ $base-hover-color: null !default; $base-hover-bg: null !default; $base-inverted-text-color: null !default; -/** -* $name 100. Focused state text color -* $type color -*/ -$base-focus-color: null !default; - /** * $name 110. Focused state background color * $type color */ $base-focus-bg: null !default; -$base-inverted-icon-color: null !default; $base-disabled-opacity: 0.38 !default; $base-disabled-color: null !default; $base-dropdown-shadow-color: null !default; -$scrollview-pulldown-path: null !default; @if $color == "blue" { $material-color-accent-modificator: "blue" !default; @@ -155,22 +128,14 @@ $scrollview-pulldown-path: null !default; $base-text-color: rgba(0, 0, 0, 0.87) !default; $base-label-color: rgba(0, 0, 0, 0.6) !default; $base-disabled-color: rgba(0, 0, 0, $base-disabled-opacity) !default; - $base-icon-color: color.change($base-text-color, $alpha: 0.6) !default; - $base-spin-icon-color: color.change($base-text-color, $alpha: 0.6) !default; $base-bg: #fff !default; $base-border-color: #e0e0e0 !default; $base-inverted-bg: color.adjust($base-bg, $lightness: -100%, $space: hsl) !default; - $base-element-bg: $base-bg !default; $base-shadow-color: $base-inverted-bg !default; - $base-hover-color: $base-text-color !default; $base-hover-bg: color.change(#000, $alpha: 0.04) !default; $base-inverted-text-color: #fff !default; - $base-focus-color: $base-inverted-text-color !default; $base-focus-bg: color.change(#000, $alpha: 0.04) !default; - $base-inverted-icon-color: $base-inverted-text-color !default; $base-dropdown-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - - $scrollview-pulldown-path: data-uri("images/widgets/material/color-schemes/light/pulldown.png") !default; } @if $mode == "dark" { @@ -185,24 +150,42 @@ $scrollview-pulldown-path: null !default; $base-text-color: #fff !default; $base-label-color: rgba(255, 255, 255, 0.6) !default; $base-disabled-color: rgba(255, 255, 255, $base-disabled-opacity) !default; - $base-icon-color: color.change($base-text-color, $alpha: 0.6) !default; - $base-spin-icon-color: color.change($base-text-color, $alpha: 0.6) !default; $base-bg: #292929 !default; $base-border-color: #515159 !default; $base-inverted-bg: color.adjust($base-bg, $lightness: 100%, $space: hsl) !default; - $base-element-bg: $base-bg !default; $base-shadow-color: #000 !default; - $base-hover-color: $base-text-color !default; $base-hover-bg: color.change(#fff, $alpha: 0.04) !default; $base-inverted-text-color: rgba(0, 0, 0, 0.87) !default; - $base-focus-color: $base-inverted-text-color !default; $base-focus-bg: color.change(#fff, $alpha: 0.04) !default; - $base-inverted-icon-color: $base-inverted-text-color !default; $base-dropdown-shadow-color: color.change($base-shadow-color, $alpha: 0.4) !default; - - $scrollview-pulldown-path: data-uri("images/widgets/material/color-schemes/dark/pulldown.png") !default; } +/** +* $name 40. Icon color +* $type color +*/ +$base-icon-color: color.change($base-text-color, $alpha: 0.6) !default; + +/** +* $name 50. Arrow icon color +* $type color +*/ +$base-spin-icon-color: color.change($base-text-color, $alpha: 0.6) !default; +$base-element-bg: $base-bg !default; + +/** +* $name 80. Hover state text color +* $type color +*/ +$base-hover-color: $base-text-color !default; + +/** +* $name 100. Focused state text color +* $type color +*/ +$base-focus-color: $base-inverted-text-color !default; +$base-inverted-icon-color: $base-inverted-text-color !default; + /** * $name 30. Link color * $type color diff --git a/packages/devextreme-scss/scss/widgets/material/actionSheet/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/actionSheet/_sizes.scss index 01d93773d6c3..14a898935fca 100644 --- a/packages/devextreme-scss/scss/widgets/material/actionSheet/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/actionSheet/_sizes.scss @@ -1,2 +1,2 @@ -$material-actionsheet-item-margin-value: 20px; +$material-actionsheet-item-margin-value: 20px !default; $material-actionsheet-item-margin: 0 0 $material-actionsheet-item-margin-value 0 !default; diff --git a/packages/devextreme-scss/scss/widgets/material/button/_colors.scss b/packages/devextreme-scss/scss/widgets/material/button/_colors.scss index d14a977b8059..8e2218b4706a 100644 --- a/packages/devextreme-scss/scss/widgets/material/button/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/button/_colors.scss @@ -13,7 +13,7 @@ $button-shadow-color: null !default; * $type color */ $button-normal-color: $base-text-color !default; -$button-normal-border-color: color.change($button-normal-color, $alpha: 0.24); +$button-normal-border-color: color.change($button-normal-color, $alpha: 0.24) !default; /** * $name 20. Selected state text color diff --git a/packages/devextreme-scss/scss/widgets/material/button/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/button/_sizes.scss index 7de9889ee26f..88e97b8ff4a4 100644 --- a/packages/devextreme-scss/scss/widgets/material/button/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/button/_sizes.scss @@ -2,7 +2,6 @@ $material-button-horizontal-padding: null !default; $material-button-vertical-padding: null !default; -$material-button-icon-padding: null !default; $material-button-icon-text-padding: null !default; $material-button-min-width: null !default; @@ -19,16 +18,14 @@ $material-button-active-box-shadow-size: 0 4px 6px !default; @if $size == "default" { $material-button-horizontal-padding: 16px !default; $material-button-vertical-padding: 6px !default; - $material-button-icon-padding: 8px !default; - $material-button-icon-text-padding: 12px; + $material-button-icon-text-padding: 12px !default; $material-button-min-width: 64px !default; } @else if $size == "compact" { $material-button-horizontal-padding: 12px !default; $material-button-vertical-padding: 5px !default; - $material-button-icon-padding: 5px !default; - $material-button-icon-text-padding: 8px; + $material-button-icon-text-padding: 8px !default; $material-button-min-width: 48px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/cardView/_colors.scss b/packages/devextreme-scss/scss/widgets/material/cardView/_colors.scss index f5b547710de0..3f3c19fa0907 100644 --- a/packages/devextreme-scss/scss/widgets/material/cardView/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/cardView/_colors.scss @@ -19,19 +19,19 @@ * $name 100. Background color * $type color */ -$cardview-material-common-bg-color: null !default; +$cardview-material-common-bg-color: $base-bg !default; /** * $name 102. Link color * $type color */ -$cardview-material-common-link-color: null !default; +$cardview-material-common-link-color: $base-link-color !default; /** * $name 103. Text color * $type color */ -$cardview-material-common-text-color: null !default; +$cardview-material-common-text-color: $base-text-color !default; // #endregion @@ -41,31 +41,31 @@ $cardview-material-common-text-color: null !default; * $name 200. Background color * $type color */ -$cardview-material-card-bg-color: null !default; +$cardview-material-card-bg-color: $base-bg !default; /** * $name 201. Border color * $type color */ -$cardview-material-card-border-color: null !default; +$cardview-material-card-border-color: $base-border-color !default; /** * $name 203. Divider color * $type color */ -$cardview-material-card-divider-color: null !default; +$cardview-material-card-divider-color: $cardview-material-card-border-color !default; /** * $name 204. Text color * $type color */ -$cardview-material-card-text-color: null !default; +$cardview-material-card-text-color: $cardview-material-common-text-color !default; /** * $name 206. Cover background color * $type color */ -$cardview-material-card__cover-bg-color: null !default; +$cardview-material-card__cover-bg-color: #FFF !default; /** * $name 207. "Empty cover" background color @@ -77,9 +77,9 @@ $cardview-material-card__cover--noimage-bg-color: null !default; * $name 208. "Empty cover" icon color * $type color */ -$cardview-material-card__cover--noimage__icon-color: null !default; +$cardview-material-card__cover--noimage__icon-color: color.change($cardview-material-common-text-color, $alpha: 0.38) !default; -$cardview-material-card--focused-outline-color: null !default; +$cardview-material-card--focused-outline-color: $base-accent !default; // #endregion @@ -97,35 +97,23 @@ $cardview-material-card--selected-bg-color: null !default; */ $cardview-material-card--selected-border-color: null !default; -/** -* $name 302. Divider color -* $type color -*/ -$cardview-material-card--selected-divider-color: null !default; - /** * $name 303. Text color * $type color */ -$cardview-material-card--selected-text-color: null !default; +$cardview-material-card--selected-text-color: $cardview-material-card-text-color !default; /** * $name 305. Cover background color * $type color */ -$cardview-material-card--selected__cover-bg-color: null !default; - -/** -* $name 306. "Empty cover" background color -* $type color -*/ -$cardview-material-card--selected__cover--noimage-bg-color: null !default; +$cardview-material-card--selected__cover-bg-color: $cardview-material-card__cover-bg-color !default; /** * $name 307. "Empty cover" icon color * $type color */ -$cardview-material-card--selected__cover--noimage__icon-color: null !default; +$cardview-material-card--selected__cover--noimage__icon-color: $cardview-material-card__cover--noimage__icon-color !default; // #endregion @@ -137,37 +125,31 @@ $cardview-material-card--selected__cover--noimage__icon-color: null !default; */ $cardview-material-header-panel__item-bg-color: null !default; -/** -* $name 401. Border color -* $type color -*/ -$cardview-material-header-panel__item-border-color: null !default; - /** * $name 403. Text color * $type color */ -$cardview-material-header-panel__item-text-color: null !default; +$cardview-material-header-panel__item-text-color: $cardview-material-common-text-color !default; /** * $name 405. Icon color * $type color */ -$cardview-material-header-panel__item__icon-color: null !default; +$cardview-material-header-panel__item__icon-color: $cardview-material-common-text-color !default; /** * $name 406. Active "Filter" icon color * $type color */ -$cardview-material-header-panel__item__header-filter-icon--selected-color: null !default; +$cardview-material-header-panel__item__header-filter-icon--selected-color: $base-accent !default; /** * $name 407. Sort index text color * $type color */ -$cardview-material-header-panel__item__sort-index-text-color: null !default; +$cardview-material-header-panel__item__sort-index-text-color: $cardview-material-common-text-color !default; -$cardview-material-header-panel__item--focused-outline-color: null !default; +$cardview-material-header-panel__item--focused-outline-color: $base-accent !default; // #endregion @@ -179,103 +161,73 @@ $cardview-material-header-panel__item--focused-outline-color: null !default; */ $cardview-material-header-panel__item--hovered-bg-color: null !default; -/** -* $name 501. Border color -* $type color -*/ -$cardview-material-header-panel__item--hovered-border-color: null !default; - /** * $name 502. Text color * $type color */ -$cardview-material-header-panel__item--hovered-text-color: null !default; +$cardview-material-header-panel__item--hovered-text-color: $cardview-material-header-panel__item-text-color !default; /** * $name 504. Icon color * $type color */ -$cardview-material-header-panel__item--hovered__icon-color: null !default; +$cardview-material-header-panel__item--hovered__icon-color: $cardview-material-header-panel__item__icon-color !default; /** * $name 505. Active "Filter" icon color * $type color */ -$cardview-material-header-panel__item--hovered__header-filter-icon--selected-color: null !default; +$cardview-material-header-panel__item--hovered__header-filter-icon--selected-color: $cardview-material-header-panel__item__header-filter-icon--selected-color !default; /** * $name 506. Sort index text color * $type color */ -$cardview-material-header-panel__item--hovered__sort-index-text-color: null !default; +$cardview-material-header-panel__item--hovered__sort-index-text-color: $cardview-material-header-panel__item__sort-index-text-color !default; // #endregion // #region HEADER PANEL ITEM (DISABLED) -/** -* $name 600. Background color -* $type color -*/ -$cardview-material-header-panel__item--disabled-bg-color: null !default; - -/** -* $name 601. Border color -* $type color -*/ -$cardview-material-header-panel__item--disabled-border-color: null !default; - /** * $name 602. Text color * $type color */ -$cardview-material-header-panel__item--disabled-text-color: null !default; +$cardview-material-header-panel__item--disabled-text-color: color.change($cardview-material-header-panel__item-text-color, $alpha: 0.38) !default; /** * $name 604. Icon color * $type color */ -$cardview-material-header-panel__item--disabled__icon-color: null !default; +$cardview-material-header-panel__item--disabled__icon-color: color.change($cardview-material-header-panel__item__icon-color, $alpha: 0.38) !default; /** * $name 605. Active "Filter" icon color * $type color */ -$cardview-material-header-panel__item--disabled__header-filter-icon--selected-color: null !default; +$cardview-material-header-panel__item--disabled__header-filter-icon--selected-color: color.change($cardview-material-header-panel__item__header-filter-icon--selected-color, $alpha: 0.38) !default; /** * $name 606. Sort index text color * $type color */ -$cardview-material-header-panel__item--disabled__sort-index-text-color: null !default; +$cardview-material-header-panel__item--disabled__sort-index-text-color: color.change($cardview-material-header-panel__item__sort-index-text-color, $alpha: 0.38) !default; // #endregion // #region HEADER PANEL ITEM (ON DRAG) -/** -* $name 700. Background color -* $type color -*/ -$cardview-material-header-panel__item--dragged-bg-color: null !default; - -/** -* $name 701. Border color -* $type color -*/ -$cardview-material-header-panel__item--dragged-border-color: null !default; - /** * $name 702. Text color * $type color */ -$cardview-material-header-panel__item--dragged-text-color: null !default; +$cardview-material-header-panel__item--dragged-text-color: $cardview-material-header-panel__item-text-color !default; /** * $name 704. "Drag allowed" icon color * $type color */ -$cardview-material-header-panel__item--dragged__allowance-icon-color: null !default; +$cardview-material-header-panel__item--dragged__allowance-icon-color: $cardview-material-common-text-color !default; /** * $name 705. "Drag blocked" icon color @@ -287,7 +239,7 @@ $cardview-material-header-panel__item--dragged__prohibition-icon-color: null !de * $name 706. Box shadow color * $type color */ -$cardview-material-header-panel__item--dragged-box-shadow-color: null !default; +$cardview-material-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // #endregion @@ -303,19 +255,19 @@ $cardview-material-header-panel__dropzone-bg-color: null !default; * $name 802. Border color * $type color */ -$cardview-material-header-panel__dropzone-border-color: null !default; +$cardview-material-header-panel__dropzone-border-color: $base-accent !default; /** * $name 803. Text color * $type color */ -$cardview-material-header-panel__dropzone-text-color: null !default; +$cardview-material-header-panel__dropzone-text-color: $base-accent !default; /** * $name 804. Icon color * $type color */ -$cardview-material-header-panel__dropzone__icon-color: null !default; +$cardview-material-header-panel__dropzone__icon-color: $base-accent !default; // #endregion @@ -325,25 +277,25 @@ $cardview-material-header-panel__dropzone__icon-color: null !default; * $name 900. Background color * $type color */ -$cardview-material-filter-panel-bg-color: null !default; +$cardview-material-filter-panel-bg-color: $cardview-material-common-bg-color !default; /** * $name 902. Border color * $type color */ -$cardview-material-filter-panel-border-color: null !default; +$cardview-material-filter-panel-border-color: $base-border-color !default; /** * $name 903. Text color * $type color */ -$cardview-material-filter-panel-text-color: null !default; +$cardview-material-filter-panel-text-color: $base-accent !default; /** * $name 904. Icon color * $type color */ -$cardview-material-filter-panel__icon-color: null !default; +$cardview-material-filter-panel__icon-color: $base-accent !default; // #endregion @@ -353,19 +305,19 @@ $cardview-material-filter-panel__icon-color: null !default; * $name 1000. Background color * $type color */ -$cardview-material-nodata-view-bg-color: null !default; +$cardview-material-nodata-view-bg-color: $cardview-material-common-bg-color !default; /** * $name 1001. Text color * $type color */ -$cardview-material-nodata-view-text-color: null !default; +$cardview-material-nodata-view-text-color: $base-label-color !default; /** * $name 1002. Icon color * $type color */ -$cardview-material-nodata-view__icon-color: null !default; +$cardview-material-nodata-view__icon-color: $cardview-material-nodata-view-text-color !default; /** * $name 1003. Icon background color @@ -393,190 +345,120 @@ $cardview-material-card__content__field-value--highlighted-text-color: null !def * $name 1104. Box shadow color * $type color */ -$cardview-material-card__content__field-value--highlighted-box-shadow-color: null !default; +$cardview-material-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; // #endregion // #region COLUMN CHOOSER -$cardview-material-columnchooser--empty__message-color: null !default; +$cardview-material-columnchooser--empty__message-color: color.adjust($cardview-material-common-text-color, $lightness: 26%, $space: hsl) !default; // #endregion @if $mode == "light" { - // Common - $cardview-material-common-bg-color: $base-bg !default; - $cardview-material-common-link-color: $base-link-color !default; - $cardview-material-common-text-color: $base-text-color !default; - // Card - $cardview-material-card-bg-color: $base-bg !default; - $cardview-material-card-border-color: $base-border-color !default; - $cardview-material-card-divider-color: $cardview-material-card-border-color !default; - $cardview-material-card-text-color: $cardview-material-common-text-color !default; - $cardview-material-card__cover-bg-color: #FFF !default; $cardview-material-card__cover--noimage-bg-color: color.adjust($base-bg, $lightness: -7.84%, $space: hsl) !default; - $cardview-material-card__cover--noimage__icon-color: color.change($cardview-material-common-text-color, $alpha: 0.38) !default; - - $cardview-material-card--focused-outline-color: $base-accent !default; // Card - selected $cardview-material-card--selected-bg-color: color.change($base-accent, $alpha: 0.08) !default; $cardview-material-card--selected-border-color: color.change($base-accent, $alpha: 0.32) !default; - $cardview-material-card--selected-divider-color: $cardview-material-card--selected-border-color !default; - $cardview-material-card--selected-text-color: $cardview-material-card-text-color !default; - $cardview-material-card--selected__cover-bg-color: $cardview-material-card__cover-bg-color !default; - $cardview-material-card--selected__cover--noimage-bg-color: $cardview-material-card__cover--noimage-bg-color !default; - $cardview-material-card--selected__cover--noimage__icon-color: $cardview-material-card__cover--noimage__icon-color !default; // Header panel item - default state $cardview-material-header-panel__item-bg-color: color.adjust($base-bg, $lightness: -7.84%, $space: hsl) !default; - $cardview-material-header-panel__item-border-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item-text-color: $cardview-material-common-text-color !default; - $cardview-material-header-panel__item__icon-color: $cardview-material-common-text-color !default; - $cardview-material-header-panel__item__header-filter-icon--selected-color: $base-accent !default; - $cardview-material-header-panel__item__sort-index-text-color: $cardview-material-common-text-color !default; - - $cardview-material-header-panel__item--focused-outline-color: $base-accent !default; // Header panel item - hovered $cardview-material-header-panel__item--hovered-bg-color: color.adjust($cardview-material-header-panel__item-bg-color, $lightness: -4.31%, $space: hsl) !default; - $cardview-material-header-panel__item--hovered-border-color: $cardview-material-header-panel__item--hovered-bg-color !default; - $cardview-material-header-panel__item--hovered-text-color: $cardview-material-header-panel__item-text-color !default; - $cardview-material-header-panel__item--hovered__icon-color: $cardview-material-header-panel__item__icon-color !default; - $cardview-material-header-panel__item--hovered__header-filter-icon--selected-color: $cardview-material-header-panel__item__header-filter-icon--selected-color !default; - $cardview-material-header-panel__item--hovered__sort-index-text-color: $cardview-material-header-panel__item__sort-index-text-color !default; - - // Header panel item - disabled - $cardview-material-header-panel__item--disabled-bg-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item--disabled-border-color: $cardview-material-header-panel__item--disabled-bg-color !default; - $cardview-material-header-panel__item--disabled-text-color: color.change($cardview-material-header-panel__item-text-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__icon-color: color.change($cardview-material-header-panel__item__icon-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__header-filter-icon--selected-color: color.change($cardview-material-header-panel__item__header-filter-icon--selected-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__sort-index-text-color: color.change($cardview-material-header-panel__item__sort-index-text-color, $alpha: 0.38) !default; // Header panel item - dragged - $cardview-material-header-panel__item--dragged-bg-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item--dragged-border-color: $cardview-material-header-panel__item--dragged-bg-color !default; - $cardview-material-header-panel__item--dragged-text-color: $cardview-material-header-panel__item-text-color !default; - $cardview-material-header-panel__item--dragged__allowance-icon-color: $cardview-material-common-text-color !default; $cardview-material-header-panel__item--dragged__prohibition-icon-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: -4deg, $saturation: 22.8%, $lightness: -9.61%) !default; - $cardview-material-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // Header panel Drop-zone $cardview-material-header-panel__dropzone-bg-color: color.mix($base-accent, $base-bg, 8%) !default; - $cardview-material-header-panel__dropzone-border-color: $base-accent !default; - $cardview-material-header-panel__dropzone-text-color: $base-accent !default; - $cardview-material-header-panel__dropzone__icon-color: $base-accent !default; - - // Filter panel - $cardview-material-filter-panel-bg-color: $cardview-material-common-bg-color !default; - $cardview-material-filter-panel-border-color: $base-border-color !default; - $cardview-material-filter-panel-text-color: $base-accent !default; - $cardview-material-filter-panel__icon-color: $base-accent !default; // Empty view - $cardview-material-nodata-view-bg-color: $cardview-material-common-bg-color !default; - $cardview-material-nodata-view-text-color: $base-label-color !default; - $cardview-material-nodata-view__icon-color: $cardview-material-nodata-view-text-color !default; $cardview-material-nodata-view__icon-bg-color: color.adjust($base-bg, $lightness: -7.84%, $space: hsl) !default; // Search highlight $cardview-material-card__content__field-value--highlighted-text-color: rgba(0, 0, 0, 1) !default; - $cardview-material-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; - - // Column chooser - $cardview-material-columnchooser--empty__message-color: color.adjust($cardview-material-common-text-color, $lightness: 26%, $space: hsl) !default; } @if $mode == "dark" { - // Common - $cardview-material-common-bg-color: $base-bg !default; - $cardview-material-common-link-color: $base-link-color !default; - $cardview-material-common-text-color: $base-text-color !default; - // Card - $cardview-material-card-bg-color: $base-bg !default; - $cardview-material-card-border-color: $base-border-color !default; - $cardview-material-card-divider-color: $cardview-material-card-border-color !default; - $cardview-material-card-text-color: $cardview-material-common-text-color !default; - $cardview-material-card__cover-bg-color: #FFF !default; $cardview-material-card__cover--noimage-bg-color: color.adjust($base-bg, $saturation: -0.3%, $space: hsl, $lightness: 8.04%) !default; - $cardview-material-card__cover--noimage__icon-color: color.change($cardview-material-common-text-color, $alpha: 0.38) !default; - - $cardview-material-card--focused-outline-color: $base-accent !default; // Card - selected $cardview-material-card--selected-bg-color: color.change(color.adjust($base-accent, $lightness: 19.22%, $space: hsl), $alpha: 0.08) !default; $cardview-material-card--selected-border-color: color.change(color.adjust($base-accent, $lightness: 19.22%, $space: hsl), $alpha: 0.32) !default; - $cardview-material-card--selected-divider-color: $cardview-material-card--selected-border-color !default; - $cardview-material-card--selected-text-color: $cardview-material-card-text-color !default; - $cardview-material-card--selected__cover-bg-color: $cardview-material-card__cover-bg-color !default; - $cardview-material-card--selected__cover--noimage-bg-color: $cardview-material-card__cover--noimage-bg-color !default; - $cardview-material-card--selected__cover--noimage__icon-color: $cardview-material-card__cover--noimage__icon-color !default; // Header panel item - default $cardview-material-header-panel__item-bg-color: color.adjust($base-bg, $saturation: -0.3%, $space: hsl, $lightness: 8.04%) !default; - $cardview-material-header-panel__item-border-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item-text-color: $cardview-material-common-text-color !default; - $cardview-material-header-panel__item__icon-color: $cardview-material-common-text-color !default; - $cardview-material-header-panel__item__header-filter-icon--selected-color: $base-accent !default; - $cardview-material-header-panel__item__sort-index-text-color: $cardview-material-common-text-color !default; - - $cardview-material-header-panel__item--focused-outline-color: $base-accent !default; // Header panel item - hovered $cardview-material-header-panel__item--hovered-bg-color: color.adjust($cardview-material-header-panel__item-bg-color, $saturation: -3.47%, $space: hsl, $lightness: 2.16%) !default; - $cardview-material-header-panel__item--hovered-border-color: $cardview-material-header-panel__item--hovered-bg-color !default; - $cardview-material-header-panel__item--hovered-text-color: $cardview-material-header-panel__item-text-color !default; - $cardview-material-header-panel__item--hovered__icon-color: $cardview-material-header-panel__item__icon-color !default; - $cardview-material-header-panel__item--hovered__header-filter-icon--selected-color: $cardview-material-header-panel__item__header-filter-icon--selected-color !default; - $cardview-material-header-panel__item--hovered__sort-index-text-color: $cardview-material-header-panel__item__sort-index-text-color !default; - - // Header panel item - disabled - $cardview-material-header-panel__item--disabled-bg-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item--disabled-border-color: $cardview-material-header-panel__item--disabled-bg-color !default; - $cardview-material-header-panel__item--disabled-text-color: color.change($cardview-material-header-panel__item-text-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__icon-color: color.change($cardview-material-header-panel__item__icon-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__header-filter-icon--selected-color: color.change($cardview-material-header-panel__item__header-filter-icon--selected-color, $alpha: 0.38) !default; - $cardview-material-header-panel__item--disabled__sort-index-text-color: color.change($cardview-material-header-panel__item__sort-index-text-color, $alpha: 0.38) !default; // Header panel item - dragged - $cardview-material-header-panel__item--dragged-bg-color: $cardview-material-header-panel__item-bg-color !default; - $cardview-material-header-panel__item--dragged-border-color: $cardview-material-header-panel__item--dragged-bg-color !default; - $cardview-material-header-panel__item--dragged-text-color: $cardview-material-header-panel__item-text-color !default; - $cardview-material-header-panel__item--dragged__allowance-icon-color: $cardview-material-common-text-color !default; $cardview-material-header-panel__item--dragged__prohibition-icon-color: color.adjust($base-danger, $saturation: -0.27%, $space: hsl, $lightness: -7.45%) !default; - $cardview-material-header-panel__item--dragged-box-shadow-color: rgba(0, 0, 0, 0.2) !default; // Header panel Drop-zone $cardview-material-header-panel__dropzone-bg-color: color.mix(color.adjust($base-accent, $lightness: 19.22%, $space: hsl), $base-bg, 8%) !default; - $cardview-material-header-panel__dropzone-border-color: $base-accent !default; - $cardview-material-header-panel__dropzone-text-color: $base-accent !default; - $cardview-material-header-panel__dropzone__icon-color: $base-accent !default; - - // Filter panel - $cardview-material-filter-panel-bg-color: $cardview-material-common-bg-color !default; - $cardview-material-filter-panel-border-color: $base-border-color !default; - $cardview-material-filter-panel-text-color: $base-accent !default; - $cardview-material-filter-panel__icon-color: $base-accent !default; // Empty view - $cardview-material-nodata-view-bg-color: $cardview-material-common-bg-color !default; - $cardview-material-nodata-view-text-color: $base-label-color !default; - $cardview-material-nodata-view__icon-color: $cardview-material-nodata-view-text-color !default; $cardview-material-nodata-view__icon-bg-color: color.adjust($base-bg, $saturation: -0.3%, $space: hsl, $lightness: 8.04%) !default; // Search highlight $cardview-material-card__content__field-value--highlighted-text-color: $base-inverted-text-color !default; - $cardview-material-card__content__field-value--highlighted-box-shadow-color: rgba(0, 0, 0, 0.25) !default; - - // Column chooser - $cardview-material-columnchooser--empty__message-color: color.adjust($cardview-material-common-text-color, $lightness: 26%, $space: hsl) !default; } +/** +* $name 302. Divider color +* $type color +*/ +$cardview-material-card--selected-divider-color: $cardview-material-card--selected-border-color !default; + +/** +* $name 306. "Empty cover" background color +* $type color +*/ +$cardview-material-card--selected__cover--noimage-bg-color: $cardview-material-card__cover--noimage-bg-color !default; + +/** +* $name 401. Border color +* $type color +*/ +$cardview-material-header-panel__item-border-color: $cardview-material-header-panel__item-bg-color !default; + +/** +* $name 501. Border color +* $type color +*/ +$cardview-material-header-panel__item--hovered-border-color: $cardview-material-header-panel__item--hovered-bg-color !default; + +/** +* $name 600. Background color +* $type color +*/ +$cardview-material-header-panel__item--disabled-bg-color: $cardview-material-header-panel__item-bg-color !default; + +/** +* $name 601. Border color +* $type color +*/ +$cardview-material-header-panel__item--disabled-border-color: $cardview-material-header-panel__item--disabled-bg-color !default; + +/** +* $name 700. Background color +* $type color +*/ +$cardview-material-header-panel__item--dragged-bg-color: $cardview-material-header-panel__item-bg-color !default; + +/** +* $name 701. Border color +* $type color +*/ +$cardview-material-header-panel__item--dragged-border-color: $cardview-material-header-panel__item--dragged-bg-color !default; + $cardview-bg-color: $cardview-material-common-bg-color !default; $cardview-link-color: $cardview-material-common-link-color !default; $cardview-text-color: $cardview-material-common-text-color !default; diff --git a/packages/devextreme-scss/scss/widgets/material/cardView/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/cardView/_sizes.scss index c7ef0ddf524f..e67175a11df0 100644 --- a/packages/devextreme-scss/scss/widgets/material/cardView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/cardView/_sizes.scss @@ -17,14 +17,14 @@ * $name 101. Border radius * $type text */ -$cardview-material-common-border-radius: null !default; +$cardview-material-common-border-radius: 16px !default; /** * $name 104. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-common-text-weight: null !default; +$cardview-material-common-text-weight: 400 !default; /** * $name 105. Vertical gap between cards @@ -46,9 +46,9 @@ $cardview-material-common-padding: null !default; // #region CARD -$cardview-material-card-min-width: null !default; +$cardview-material-card-min-width: 250px !default; -$cardview-material-card-border-width: null !default; +$cardview-material-card-border-width: 1px !default; /** * $name 202. Border radius @@ -73,7 +73,7 @@ $cardview-material-card__content-line-height: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-card__field-caption-weight: null !default; +$cardview-material-card__field-caption-weight: 500 !default; $cardview-material-card__cover-max-height: null !default; @@ -85,7 +85,7 @@ $cardview-material-card__content-row-gap: null !default; $cardview-material-card__content__cell-padding-vertical: null !default; -$cardview-material-card__content__cell-padding-horizontal: null !default; +$cardview-material-card__content__cell-padding-horizontal: 12px !default; // #endregion @@ -96,19 +96,19 @@ $cardview-material-card__content__cell-padding-horizontal: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-card--selected__field-caption-weight: null !default; +$cardview-material-card--selected__field-caption-weight: $cardview-material-card__field-caption-weight !default; // #endregion // #region HEADER -$cardview-material-header-gap: null !default; +$cardview-material-header-gap: 4px !default; // #endregion // #region HEADER PANEL -$cardview-material-header-panel-vertical-margin: null !default; +$cardview-material-header-panel-vertical-margin: 4px !default; $cardview-material-header-panel--empty-line-height: null !default; @@ -116,13 +116,13 @@ $cardview-material-header-panel--empty-line-height: null !default; // #region HEADER PANEL ITEM -$cardview-material-header-panel__item-border-width: null !default; +$cardview-material-header-panel__item-border-width: 1px !default; /** * $name 402. Border radius * $type text */ -$cardview-material-header-panel__item-border-radius: null !default; +$cardview-material-header-panel__item-border-radius: 99px !default; $cardview-material-header-panel__item-padding-vertical: null !default; @@ -135,14 +135,14 @@ $cardview-material-header-panel__item-line-height: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item-text-weight: null !default; +$cardview-material-header-panel__item-text-weight: $cardview-material-common-text-weight !default; /** * $name 408. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item__sort-index-text-weight: null !default; +$cardview-material-header-panel__item__sort-index-text-weight: 400 !default; $cardview-material-header-panel__item__icon-size: null !default; @@ -158,14 +158,14 @@ $cardview-material-header-item__right-padding: null !default; * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--hovered-text-weight: null !default; +$cardview-material-header-panel__item--hovered-text-weight: $cardview-material-header-panel__item-text-weight !default; /** * $name 507. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--hovered__sort-index-text-weight: null !default; +$cardview-material-header-panel__item--hovered__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; // #endregion @@ -176,14 +176,14 @@ $cardview-material-header-panel__item--hovered__sort-index-text-weight: null !de * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--disabled-text-weight: null !default; +$cardview-material-header-panel__item--disabled-text-weight: $cardview-material-header-panel__item-text-weight !default; /** * $name 607. Sort index text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--disabled__sort-index-text-weight: null !default; +$cardview-material-header-panel__item--disabled__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; // #endregion @@ -194,10 +194,10 @@ $cardview-material-header-panel__item--disabled__sort-index-text-weight: null !d * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-header-panel__item--dragged-text-weight: null !default; +$cardview-material-header-panel__item--dragged-text-weight: $cardview-material-header-panel__item-text-weight !default; -$cardview-material-header-item__sort-indicator__border-size: null; -$cardview-material-header-item__sort-indicator__border-radius: null; +$cardview-material-header-item__sort-indicator__border-size: null !default; +$cardview-material-header-item__sort-indicator__border-radius: null !default; // #endregion @@ -207,7 +207,7 @@ $cardview-material-header-panel__dropzone-height: null !default; $cardview-material-header-panel__dropzone-top: null !default; -$cardview-material-header-panel__dropzone-border-width: null !default; +$cardview-material-header-panel__dropzone-border-width: 1.5px !default; /** * $name 801. Border radius @@ -223,7 +223,7 @@ $cardview-material-header-panel__dropzone-border-radius: null !default; * $name 901. Border radius * $type text */ -$cardview-material-filter-panel-border-radius: null !default; +$cardview-material-filter-panel-border-radius: 0 !default; // #endregion @@ -243,16 +243,16 @@ $cardview-material-nodata-view__icon-size: null !default; * $name 1101. Border radius * $type text */ -$cardview-material-card__content__field-value--highlighted-border-radius: null !default; +$cardview-material-card__content__field-value--highlighted-border-radius: 5px !default; -$cardview-material-card__content__field-value--highlighted-border-padding: null !default; +$cardview-material-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; /** * $name 1103. Text weight * $type select * $type-values 100|200|300|400|500|600|700|800|900 */ -$cardview-material-card__content__field-value--highlighted-text-weight: null !default; +$cardview-material-card__content__field-value--highlighted-text-weight: 500 !default; // #endregion @@ -267,84 +267,48 @@ $cardview-material-column-chooser__select-all-item-horizontal-padding: null !def @if $size == "default" { // Common - $cardview-material-common-border-radius: 16px !default; - $cardview-material-common-text-weight: 400 !default; $cardview-material-common-cards-row-gap: 16px !default; $cardview-material-common-cards-column-gap: 16px !default; $cardview-material-common-gap: 24px !default; $cardview-material-common-padding: 24px !default; // Card - $cardview-material-card-min-width: 250px !default; - $cardview-material-card-border-width: 1px !default; $cardview-material-card-border-radius: 8px !default; $cardview-material-card__header-height: 52px !default; $cardview-material-card__header-text-size: 16px !default; $cardview-material-card__header__toolbar-override-height: 52px !default; $cardview-material-card__header__selection-checkbox-padding-right: 12px !default; - $cardview-material-card__header-toolbar-override-height: 52px !default; - $cardview-material-card__field-caption-weight: 500 !default; $cardview-material-card__cover-max-height: 180px !default; $cardview-material-card__cover--noimage__icon-size: 48px !default; $cardview-material-card__content-padding: 12px !default; $cardview-material-card__content-row-gap: 4px !default; $cardview-material-card__content__cell-padding-vertical: 6px !default; - $cardview-material-card__content__cell-padding-horizontal: 12px !default; - - // Card - selected - $cardview-material-card--selected__field-caption-weight: $cardview-material-card__field-caption-weight !default; - - // Header - $cardview-material-header-gap: 4px !default; // Header panel - $cardview-material-header-panel-vertical-margin: 4px !default; $cardview-material-header-panel--empty-line-height: 36px !default; // Header panel item - default - $cardview-material-header-panel__item-border-width: 1px !default; - $cardview-material-header-panel__item-border-radius: 99px !default; $cardview-material-header-panel__item-padding-horizontal: 16px !default; $cardview-material-header-panel__item-padding-vertical: 7px !default; $cardview-material-header-item__left-padding: 6px !default; $cardview-material-header-item__right-padding: 2px !default; $cardview-material-header-panel__item-line-height: 20px !default; - $cardview-material-header-panel__item-text-weight: $cardview-material-common-text-weight !default; - $cardview-material-header-panel__item__sort-index-text-weight: 400 !default; $cardview-material-header-panel__item__icon-size: 20px !default; - // Header panel item - hovered - $cardview-material-header-panel__item--hovered-text-weight: $cardview-material-header-panel__item-text-weight !default; - $cardview-material-header-panel__item--hovered__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-material-header-panel__item--disabled-text-weight: $cardview-material-header-panel__item-text-weight !default; - $cardview-material-header-panel__item--disabled__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; - // Header panel item - dragged - $cardview-material-header-panel__item--dragged-text-weight: $cardview-material-header-panel__item-text-weight !default; $cardview-material-header-item__sort-indicator__border-size: 2px !default; $cardview-material-header-item__sort-indicator__border-radius: 8px !default; // Header panel Drop-zone $cardview-material-header-panel__dropzone-height: 44px !default; $cardview-material-header-panel__dropzone-top: -4px !default; - $cardview-material-header-panel__dropzone-border-width: 1.5px !default; $cardview-material-header-panel__dropzone-border-radius: 8px !default; - // Filter panel - $cardview-material-filter-panel-border-radius: 0 !default; - // Empty view $cardview-material-nodata-view__icon-container-size: 64px !default; $cardview-material-nodata-view__icon-container-margin: 12px !default; $cardview-material-nodata-view__icon-size: 40px !default; - // Search highlight - $cardview-material-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-material-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-material-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-material-column-chooser--empty__message-horizontal-padding: 24px !default; $cardview-material-column-chooser__select-all-item-horizontal-padding: 19px !default; @@ -353,22 +317,16 @@ $cardview-material-column-chooser__select-all-item-horizontal-padding: null !def @else if $size == "compact" { // Common - $cardview-material-common-border-radius: 16px !default; - $cardview-material-common-text-weight: 400 !default; $cardview-material-common-cards-row-gap: 12px !default; $cardview-material-common-cards-column-gap: 12px !default; $cardview-material-common-gap: 16px !default; $cardview-material-common-padding: 16px !default; // Card - $cardview-material-card-min-width: 250px !default; - $cardview-material-card-border-width: 1px !default; $cardview-material-card-border-radius: 6px !default; $cardview-material-card__header-height: 44px !default; $cardview-material-card__header-text-size: 14px !default; $cardview-material-card__header__selection-checkbox-padding-right: 8px !default; - $cardview-material-card__header-toolbar-override-height: 44px !default; - $cardview-material-card__field-caption-weight: 500 !default; $cardview-material-card__cover-max-height: 140px !default; $cardview-material-card__cover--noimage__icon-size: 32px !default; $cardview-material-card__content-padding: 8px !default; @@ -376,62 +334,32 @@ $cardview-material-column-chooser__select-all-item-horizontal-padding: null !def $cardview-material-card__content-font-size: 12px !default; $cardview-material-card__content-line-height: 16px !default; $cardview-material-card__content__cell-padding-vertical: 4px !default; - $cardview-material-card__content__cell-padding-horizontal: 12px !default; - - // Card - selected - $cardview-material-card--selected__field-caption-weight: $cardview-material-card__field-caption-weight !default; - - // Header - $cardview-material-header-gap: 4px !default; // Header panel - $cardview-material-header-panel-vertical-margin: 4px !default; $cardview-material-header-panel--empty-line-height: 32px !default; // Header panel item - default - $cardview-material-header-panel__item-border-width: 1px !default; - $cardview-material-header-panel__item-border-radius: 99px !default; $cardview-material-header-panel__item-padding-horizontal: 12px !default; $cardview-material-header-panel__item-padding-vertical: 5px !default; $cardview-material-header-item__left-padding: 3px !default; $cardview-material-header-item__right-padding: 1px !default; $cardview-material-header-panel__item-line-height: 16px !default; - $cardview-material-header-panel__item-text-weight: $cardview-material-common-text-weight !default; - $cardview-material-header-panel__item__sort-index-text-weight: 400 !default; $cardview-material-header-panel__item__icon-size: 16px !default; - // Header panel item - hovered - $cardview-material-header-panel__item--hovered-text-weight: $cardview-material-header-panel__item-text-weight !default; - $cardview-material-header-panel__item--hovered__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; - - // Header panel item - disabled - $cardview-material-header-panel__item--disabled-text-weight: $cardview-material-header-panel__item-text-weight !default; - $cardview-material-header-panel__item--disabled__sort-index-text-weight: $cardview-material-header-panel__item__sort-index-text-weight !default; - // Header panel item - dragged - $cardview-material-header-panel__item--dragged-text-weight: $cardview-material-header-panel__item-text-weight !default; $cardview-material-header-item__sort-indicator__border-size: 1px !default; $cardview-material-header-item__sort-indicator__border-radius: 4px !default; // Header panel Drop-zone $cardview-material-header-panel__dropzone-height: 38px !default; $cardview-material-header-panel__dropzone-top: -3px !default; - $cardview-material-header-panel__dropzone-border-width: 1.5px !default; $cardview-material-header-panel__dropzone-border-radius: 6px !default; - // Filter panel - $cardview-material-filter-panel-border-radius: 0 !default; - // Empty view $cardview-material-nodata-view__icon-container-size: 48px !default; $cardview-material-nodata-view__icon-container-margin: 8px !default; $cardview-material-nodata-view__icon-size: 32px !default; - // Search highlight - $cardview-material-card__content__field-value--highlighted-border-radius: 5px !default; - $cardview-material-card__content__field-value--highlighted-border-padding: 0.125em 0.2em !default; - $cardview-material-card__content__field-value--highlighted-text-weight: 500 !default; - // Column chooser $cardview-material-column-chooser--empty__message-horizontal-padding: 16px !default; $cardview-material-column-chooser__select-all-item-horizontal-padding: 17px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/chat/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/chat/_sizes.scss index eee7e7d54d74..8bb57067f07c 100644 --- a/packages/devextreme-scss/scss/widgets/material/chat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/chat/_sizes.scss @@ -56,7 +56,6 @@ $chat-messagelist-day-header-first-padding-top: 20px !default; $chat-messagelist-day-header-font-size: 12px !default; $chat-typingindicator-template: $chat-messagegroup-alignment-start-template !default; -$chat-typingindicator-padding: null !default; $chat-typingindicator-row-gap: $chat-messagegroup-gap !default; $chat-typingindicator-font-size: $chat-information-font-size !default; $chat-typingindicator-bubble-padding-block: null !default; @@ -115,7 +114,6 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 14) * 1em !default; $chat-typingindicator-bubble-padding-block: 11px !default; $chat-typingindicator-bubble-padding-inline: 20px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; $chat-confirmation-popup-content-padding: 16px !default; $chat-confirmation-popup-toolbar-padding-block: 0 16px !default; @@ -156,7 +154,6 @@ $chat-bubble-gap: 8px !default; $chat-typingindicator-circle-size-center: math.div(12, 13) * 1em !default; $chat-typingindicator-bubble-padding-block: 8px !default; $chat-typingindicator-bubble-padding-inline: 12px !default; - $chat-typingindicator-padding: $chat-messagegroup-padding !default; $chat-confirmation-popup-content-padding: 8px !default; $chat-confirmation-popup-toolbar-padding-block: 0 8px !default; @@ -165,3 +162,5 @@ $chat-bubble-gap: 8px !default; $chat-file-icon-font-size: 16px !default; } + +$chat-typingindicator-padding: $chat-messagegroup-padding !default; diff --git a/packages/devextreme-scss/scss/widgets/material/checkBox/_index.scss b/packages/devextreme-scss/scss/widgets/material/checkBox/_index.scss index 119dfc9588ae..cb1034a3b0dd 100644 --- a/packages/devextreme-scss/scss/widgets/material/checkBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/checkBox/_index.scss @@ -7,7 +7,7 @@ @use "../../base/checkBox"; @use "../../base/checkBox/mixins" as *; -$material-checkbox-ripple-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); +$material-checkbox-ripple-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) !default; .dx-checkbox { .dx-checkbox-container { diff --git a/packages/devextreme-scss/scss/widgets/material/colorView/_index.scss b/packages/devextreme-scss/scss/widgets/material/colorView/_index.scss index 556fe5fe74c5..deb1bfcbfced 100644 --- a/packages/devextreme-scss/scss/widgets/material/colorView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/colorView/_index.scss @@ -4,8 +4,8 @@ @use "../../base/colorView"; @include dx-base-colorview-styles($colorview-handle-color, $colorview-handle-border-color); -$material-colorview-alpha-width: $material-colorview-palette-width + 4; -$material-colorview-hue-scale-height: $material-colorview-palette-height; +$material-colorview-alpha-width: $material-colorview-palette-width + 4 !default; +$material-colorview-hue-scale-height: $material-colorview-palette-height !default; .dx-colorview-container { width: $material-colorview-container-width; diff --git a/packages/devextreme-scss/scss/widgets/material/common/_mixins.scss b/packages/devextreme-scss/scss/widgets/material/common/_mixins.scss index 9d81f4600169..a4c30cea256a 100644 --- a/packages/devextreme-scss/scss/widgets/material/common/_mixins.scss +++ b/packages/devextreme-scss/scss/widgets/material/common/_mixins.scss @@ -8,7 +8,7 @@ @use "../../base/validation" as *; @use "../list/sizes" as *; -$badge-invalid-bg: $base-invalid-color; +$badge-invalid-bg: $base-invalid-color !default; @mixin dx-base-typography() { @include dx-base-typography-mixin( diff --git a/packages/devextreme-scss/scss/widgets/material/dateRangeBox/_index.scss b/packages/devextreme-scss/scss/widgets/material/dateRangeBox/_index.scss index 38c27e283940..902b0493def2 100644 --- a/packages/devextreme-scss/scss/widgets/material/dateRangeBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/dateRangeBox/_index.scss @@ -9,7 +9,7 @@ @use "../../base/icon_fonts" as *; @use "../textEditor" as *; -$material-editor-custom-button-margin: 5px; +$material-editor-custom-button-margin: 5px !default; .dx-daterangebox { @include dx-dropdowneditor-button-icon-styling(daterangepicker); diff --git a/packages/devextreme-scss/scss/widgets/material/dateView/_index.scss b/packages/devextreme-scss/scss/widgets/material/dateView/_index.scss index 936652fe63ca..003ec8b587fe 100644 --- a/packages/devextreme-scss/scss/widgets/material/dateView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/dateView/_index.scss @@ -6,7 +6,7 @@ @use "../../base/dateView"; @use "../scrollable"; -$material-dateview-roller-padding: math.div($material-dateview-roller-height - $material-dateview-roller-item-height, 2); +$material-dateview-roller-padding: math.div($material-dateview-roller-height - $material-dateview-roller-item-height, 2) !default; .dx-dateview-rollers { width: auto; diff --git a/packages/devextreme-scss/scss/widgets/material/diagram/_index.scss b/packages/devextreme-scss/scss/widgets/material/diagram/_index.scss index eb57e7b9e913..c436fcd6eebe 100644 --- a/packages/devextreme-scss/scss/widgets/material/diagram/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/diagram/_index.scss @@ -15,10 +15,10 @@ @use "../fieldset/sizes" as *; @use "../menu/colors" as *; -$material-diagram-toolbar-icon-size: $material-base-icon-size; -$diagram-toolbar-colorbutton-border-width: 3px; +$material-diagram-toolbar-icon-size: $material-base-icon-size !default; +$diagram-toolbar-colorbutton-border-width: 3px !default; -$diagram-toolbox-title-size: 18px; +$diagram-toolbox-title-size: 18px !default; .dx-diagram { border-color: $diagram-border-color; diff --git a/packages/devextreme-scss/scss/widgets/material/dropDownButton/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/dropDownButton/_sizes.scss index 57c4be674692..60ff376fd31c 100644 --- a/packages/devextreme-scss/scss/widgets/material/dropDownButton/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/dropDownButton/_sizes.scss @@ -5,10 +5,10 @@ $material-dropdownbutton-icon-spindown-size: null !default; @if $size == "default" { $material-dropdownbutton-action-horizontal-padding: 12px !default; - $material-dropdownbutton-icon-spindown-size: 24px; + $material-dropdownbutton-icon-spindown-size: 24px !default; } @else if $size == "compact" { $material-dropdownbutton-action-horizontal-padding: 8px !default; - $material-dropdownbutton-icon-spindown-size: 18px; + $material-dropdownbutton-icon-spindown-size: 18px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/dropDownEditor/_index.scss b/packages/devextreme-scss/scss/widgets/material/dropDownEditor/_index.scss index aecf0ce9c787..e71b1065dae8 100644 --- a/packages/devextreme-scss/scss/widgets/material/dropDownEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/dropDownEditor/_index.scss @@ -8,17 +8,17 @@ @use "../../base/dropDownEditor"; @use "../../base/dropDownEditor/mixins" as *; -$material-dropdowneditor-container-button-size: $material-base-icon-size; +$material-dropdowneditor-container-button-size: $material-base-icon-size !default; $material-dropdowneditor-invalid-badge-offset: $material-dropdowneditor-container-button-size + - $material-invalid-badge-halfhorizontal-padding; + $material-invalid-badge-halfhorizontal-padding !default; $material-dropdowneditor-invalid-badge-with-clear-offset: $material-dropdowneditor-container-button-size * 2 + - $material-invalid-badge-halfhorizontal-padding; + $material-invalid-badge-halfhorizontal-padding !default; -$material-dropdowneditor-invalid-badge-size: $material-invalid-badge-size + $material-invalid-badge-horizontal-padding; +$material-dropdowneditor-invalid-badge-size: $material-invalid-badge-size + $material-invalid-badge-horizontal-padding !default; @mixin dx-dropdowneditor-button-icon() { width: $material-dropdowneditor-container-button-size; diff --git a/packages/devextreme-scss/scss/widgets/material/fileManager/_colors.scss b/packages/devextreme-scss/scss/widgets/material/fileManager/_colors.scss index ed7f90e27e38..f0a6358c9177 100644 --- a/packages/devextreme-scss/scss/widgets/material/fileManager/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/fileManager/_colors.scss @@ -16,14 +16,11 @@ $filemanager-file-item-hover-bg: $base-hover-bg !default; $filemanager-file-item-focused-border: $base-accent !default; $filemanager-item-focused-color: $base-inverted-text-color !default; $filemanager-dirs-tree-item-focus-bg: null !default; -$filemanager-progressbox-shadow-color: null !default; @if $mode == "light" { $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: -8%, $space: hsl) !default; - $filemanager-progressbox-shadow-color: color.change($base-shadow-color, $alpha: 0.175) !default; } @if $mode == "dark" { $filemanager-dirs-tree-item-focus-bg: color.adjust($base-bg, $lightness: 8%, $space: hsl) !default; - $filemanager-progressbox-shadow-color: color.change(color.adjust($base-shadow-color, $lightness: -100%, $space: hsl), $alpha: 0.3) !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/fileManager/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/fileManager/_sizes.scss index 8a8635282724..710c1b09d13f 100644 --- a/packages/devextreme-scss/scss/widgets/material/fileManager/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/fileManager/_sizes.scss @@ -15,7 +15,7 @@ $filemanager-large-icon-size: null !default; $filemanager-progress-panel-title-padding: null !default; $filemanager-progress-box-button-close-margin-top: null !default; -$filemanager-toolbar-height: $material-toolbar-height; +$filemanager-toolbar-height: $material-toolbar-height !default; @if $size == "default" { $filemanager-toolbar-separator-height: 36px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/fileUploader/_index.scss b/packages/devextreme-scss/scss/widgets/material/fileUploader/_index.scss index bb9fcb08ec53..1d2218ae1772 100644 --- a/packages/devextreme-scss/scss/widgets/material/fileUploader/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/fileUploader/_index.scss @@ -6,7 +6,7 @@ @use "../../base/mixins" as *; @use "../../base/fileUploader"; -$material-fileuploader-file-wrapper-border-size: 3px; +$material-fileuploader-file-wrapper-border-size: 3px !default; .dx-fileuploader-wrapper { padding: $material-fileuploader-vertical-padding; diff --git a/packages/devextreme-scss/scss/widgets/material/fileUploader/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/fileUploader/_sizes.scss index 7956eae8fdf4..a0d2d94e429e 100644 --- a/packages/devextreme-scss/scss/widgets/material/fileUploader/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/fileUploader/_sizes.scss @@ -3,7 +3,7 @@ $material-fileuploader-file-container-padding: null !default; $material-fileuploader-file-container-text-margin: null !default; $material-fileuploader-file-container-button-margin: null !default; -$material-fileuploader-file-cancel-button-margin-left: 4px; +$material-fileuploader-file-cancel-button-margin-left: 4px !default; $material-fileuploader-file-container-button-size: null !default; $material-fileuploader-file-icon-font-size: null !default; diff --git a/packages/devextreme-scss/scss/widgets/material/gantt/_colors.scss b/packages/devextreme-scss/scss/widgets/material/gantt/_colors.scss index de5874276173..fe96fa4a1ac0 100644 --- a/packages/devextreme-scss/scss/widgets/material/gantt/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/gantt/_colors.scss @@ -12,8 +12,8 @@ $gantt-task-background-color: null !default; @if $color == "lime" { $gantt-parent-task-background-color: #f44336 !default; $gantt-task-progress-background-color: rgba(0, 0, 0, 0.2) !default; - $gantt-task-color: #fff; - $gantt-task-background-color: #999; + $gantt-task-color: #fff !default; + $gantt-task-background-color: #999 !default; } @if $color == "blue" { @@ -34,8 +34,8 @@ $gantt-task-background-color: null !default; @if $color == "orange" or $color == "purple" or $color == "teal" { $gantt-parent-task-background-color: #8bc34a !default; $gantt-task-progress-background-color: rgba(0, 0, 0, 0.2) !default; - $gantt-task-color: #fff; - $gantt-task-background-color: #999; + $gantt-task-color: #fff !default; + $gantt-task-background-color: #999 !default; } @if $mode == "light" { diff --git a/packages/devextreme-scss/scss/widgets/material/gantt/_index.scss b/packages/devextreme-scss/scss/widgets/material/gantt/_index.scss index fcdcfef389c2..4a5b51c944b4 100644 --- a/packages/devextreme-scss/scss/widgets/material/gantt/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/gantt/_index.scss @@ -9,7 +9,7 @@ @use "../form/sizes" as *; @use "../splitterBar"; -$gantt-successor-background-color: white; +$gantt-successor-background-color: white !default; .dx-gantt { border: 1px solid; diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_colors.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_colors.scss index 3c3662d4e159..31b9b07734c4 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_colors.scss @@ -35,7 +35,6 @@ $datagrid-drag-header-border-color: color.change($base-accent, $alpha: 0.5) !def $datagrid-drag-header-shadow-color: null !default; $datagrid-drag-header-second-shadow-color: null !default; $datagrid-header-drag-bg: $datagrid-base-background-color !default; -$header-filter-color: null !default; /** * $name 51. Selected row background color @@ -82,7 +81,6 @@ $datagrid-menu-icon-color: inherit !default; $datagrid-cell-modified-border-color: null !default; $datagrid-cell-removed-border-color: $datagrid-border-color !default; $datagrid-row-removed-bg: $datagrid-border-color !default; -$datagrid-cell-removed-text-color: null !default; /** * $name 45. Invalid data faded color @@ -162,12 +160,13 @@ $datagrid-summary-bg: $datagrid-base-background-color !default; $datagrid-column-separator-bg: $base-accent !default; $datagrid-filter-row-background-color: color.adjust($datagrid-base-background-color, $lightness: -2%, $space: hsl) !default; $datagrid-active-header-filter-icon-color: $base-accent !default; -$datagrid-text-stub-background-image-path: null !default; + +$datagrid-text-stub-background-image-path: data-uri("images/widgets/material/grid/text-stub.png") !default; $datagrid-filter-panel-color: $base-accent !default; $datagrid-draggable-column-text-color: color.change($base-text-color, $alpha: 0.38) !default; -$datagrid-text-link-disabled-opacity: $base-disabled-opacity; -$datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; +$datagrid-text-link-disabled-opacity: $base-disabled-opacity !default; +$datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity !default; @if $mode == "light" { $datagrid-cell-modified-border-color: color.adjust($base-success, $lightness: 51%, $space: hsl) !default; @@ -175,12 +174,9 @@ $datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; $datagrid-block-separator-bg: color.adjust($datagrid-base-background-color, $lightness: -12%, $space: hsl) !default; $datagrid-drag-header-shadow-color: color.change($base-shadow-color, $alpha: 0.1) !default; $datagrid-drag-header-second-shadow-color: color.change($base-shadow-color, $alpha: 0.2) !default; - $header-filter-color: $datagrid-columnchooser-item-color !default; $datagrid-columnchooser-item-bg: $datagrid-columnchooser-bg !default; $datagrid-row-alternation-bg: color.adjust($datagrid-base-background-color, $lightness: -4%, $space: hsl) !default; $datagrid-hover-bg: color.adjust($datagrid-base-background-color, $lightness: -4%, $space: hsl) !default; - $datagrid-text-stub-background-image-path: data-uri("images/widgets/material/color-schemes/light/grid/text-stub.png") !default; - $datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; $datagrid-row-invalid-faded-border-color: color.adjust($base-danger, $lightness: 44%, $space: hsl) !default; } @@ -190,15 +186,15 @@ $datagrid-icon-link-disabled-opacity: $button-disabled-icon-opacity; $datagrid-block-separator-bg: color.adjust($datagrid-base-background-color, $lightness: 12%, $space: hsl) !default; $datagrid-drag-header-shadow-color: color.change($base-shadow-color, $alpha: 0.3) !default; $datagrid-drag-header-second-shadow-color: color.change($base-shadow-color, $alpha: 0.4) !default; - $header-filter-color: $datagrid-columnchooser-item-color !default; $datagrid-columnchooser-item-bg: color.adjust($datagrid-columnchooser-bg, $lightness: 3%, $space: hsl) !default; $datagrid-row-alternation-bg: color.adjust($datagrid-base-background-color, $lightness: 4%, $space: hsl) !default; $datagrid-hover-bg: color.adjust($datagrid-base-background-color, $lightness: 4%, $space: hsl) !default; - $datagrid-text-stub-background-image-path: data-uri("images/widgets/material/color-schemes/dark/grid/text-stub.png") !default; - $datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; $datagrid-row-invalid-faded-border-color: color.adjust($base-danger, $lightness: -31%, $space: hsl) !default; } +$header-filter-color: $datagrid-columnchooser-item-color !default; +$datagrid-cell-removed-text-color: $datagrid-columnchooser-item-color !default; + $header-filter-color-empty: $header-filter-color !default; :root { diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss index 1afc72cc3cfb..7ea5a0c74fb6 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_index.scss @@ -28,12 +28,12 @@ @use "../overlay"; @use "../pagination"; -$material-grid-base-border-hidden: 1px solid transparent; -$material-grid-base-row-border: 1px solid transparent; -$material-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color; -$material-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color; +$material-grid-base-border-hidden: 1px solid transparent !default; +$material-grid-base-row-border: 1px solid transparent !default; +$material-grid-base-drag-header-first-shadow: 0 0 1px $datagrid-drag-header-shadow-color !default; +$material-grid-base-drag-header-second-shadow: 0 1px 3px $datagrid-drag-header-second-shadow-color !default; -$material-grid-base-group-panel-message-line-height: $material-button-text-line-height; +$material-grid-base-group-panel-message-line-height: $material-button-text-line-height !default; @mixin grid-base($widget-name) { @include material-grid-base-cell($widget-name); diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_sizes.scss index 0ea533d4e9aa..928d14b042ab 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_sizes.scss @@ -15,11 +15,10 @@ $material-grid-base-grouppanel-item-margin: null !default; $material-grid-base-command-edit-column-width: null !default; $material-grid-base-command-edit-column-with-icons-width: null !default; $material-grid-base-adaptive-column-height: null !default; -$material-grid-base-treeview-search-margin: null !default; +$material-grid-base-treeview-search-margin: 10px !default; $material-grid-base-nodata-font-size: null !default; $material-command-edit-icon-size: null !default; $material-filter-row-between-editor-padding: null !default; -$material-command-column-additional-right-margin: null !default; $material-grid-base-treeview-select-all-item-offset: null !default; $material-grid-base-command-ai-chatsparkleoutline-icon-size: null !default; $material-grid-base-command-ai-text-content-padding: null !default; @@ -51,12 +50,10 @@ $material-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $material-grid-base-command-edit-column-width: 115px !default; $material-grid-base-command-edit-column-with-icons-width: 93px !default; $material-grid-base-adaptive-column-height: 40px !default; - $material-grid-base-treeview-search-margin: 10px !default; $material-grid-base-nodata-font-size: 17px !default; $material-command-edit-icon-size: 18px !default; $material-filter-row-between-editor-padding: 12px !default; - $material-command-column-additional-right-margin: 0 !default; $material-grid-base-treeview-select-all-item-offset: 43px !default; $material-grid-base-command-ai-chatsparkleoutline-icon-size: 24px !default; @@ -83,12 +80,10 @@ $material-grid-base-ai-prompt-editor-progressbar-height: 4px !default; $material-grid-base-command-edit-column-width: 105px !default; $material-grid-base-command-edit-column-with-icons-width: 80px !default; $material-grid-base-adaptive-column-height: 26px !default; - $material-grid-base-treeview-search-margin: 10px !default; $material-grid-base-nodata-font-size: 14px !default; $material-command-edit-icon-size: 16px !default; $material-filter-row-between-editor-padding: 8px !default; - $material-command-column-additional-right-margin: 2px !default; $material-grid-base-treeview-select-all-item-offset: 35px !default; $material-grid-base-command-ai-chatsparkleoutline-icon-size: 18px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/_variables.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/_variables.scss index 9ad1220cd023..4014da737971 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/_variables.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/_variables.scss @@ -2,9 +2,9 @@ @use "colors" as *; @use "sizes" as *; -$grid-masterdetail-padding: 16px; -$material-grid-base-border-color: $datagrid-border-color; -$material-grid-base-header-cell-vertical-padding: math.round(math.div($material-grid-base-header-height - $material-grid-base-header-line-height, 2)); -$material-grid-base-command-select-column-width: $material-grid-base-checkbox-size + 2 * $material-grid-base-cell-horizontal-padding; -$material-grid-base-cell-vertical-padding: math.round(math.div($material-grid-base-cell-height - $material-grid-base-cell-line-height, 2)); -$material-grid-base-cell-padding: $material-grid-base-cell-vertical-padding $material-grid-base-cell-horizontal-padding; +$grid-masterdetail-padding: 16px !default; +$material-grid-base-border-color: $datagrid-border-color !default; +$material-grid-base-header-cell-vertical-padding: math.round(math.div($material-grid-base-header-height - $material-grid-base-header-line-height, 2)) !default; +$material-grid-base-command-select-column-width: $material-grid-base-checkbox-size + 2 * $material-grid-base-cell-horizontal-padding !default; +$material-grid-base-cell-vertical-padding: math.round(math.div($material-grid-base-cell-height - $material-grid-base-cell-line-height, 2)) !default; +$material-grid-base-cell-padding: $material-grid-base-cell-vertical-padding $material-grid-base-cell-horizontal-padding !default; diff --git a/packages/devextreme-scss/scss/widgets/material/gridBase/layout/aiChat/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/gridBase/layout/aiChat/_sizes.scss index a5cd8a835bab..c8f045a350f9 100644 --- a/packages/devextreme-scss/scss/widgets/material/gridBase/layout/aiChat/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/gridBase/layout/aiChat/_sizes.scss @@ -1,3 +1,3 @@ @use "../../../sizes" as *; -$ai-chat-message-icon-size: $material-base-icon-size - 4px; +$ai-chat-message-icon-size: $material-base-icon-size - 4px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/htmlEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/htmlEditor/_sizes.scss index 03c12ee590fa..adbf5ec48cd4 100644 --- a/packages/devextreme-scss/scss/widgets/material/htmlEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/htmlEditor/_sizes.scss @@ -4,7 +4,7 @@ $material-toolbar-size-editor-width: null !default; $material-htmleditor-toolbar-padding: null !default; $material-html-editor-add-image-dialog-fileuploader-padding: null !default; -$material-aidialog-selects-gap: null !default; +$material-aidialog-selects-gap: 8px !default; $material-aidialog-content-gap: null !default; $material-aidialog-content-padding: null !default; $material-aidialog-toolbar-padding: null !default; @@ -14,7 +14,6 @@ $material-aidialog-border-radius: $base-border-radius * 2 !default; $material-toolbar-size-editor-width: 120px !default; $material-htmleditor-toolbar-padding: 16px !default; $material-html-editor-add-image-dialog-fileuploader-padding: 50px 40px !default; - $material-aidialog-selects-gap: 8px !default; $material-aidialog-content-gap: 24px !default; $material-aidialog-content-padding: 24px !default; $material-aidialog-toolbar-padding: 0 24px 16px !default; @@ -24,7 +23,6 @@ $material-aidialog-border-radius: $base-border-radius * 2 !default; $material-toolbar-size-editor-width: 90px !default; $material-htmleditor-toolbar-padding: 11px !default; $material-html-editor-add-image-dialog-fileuploader-padding: 40px 30px !default; - $material-aidialog-selects-gap: 8px !default; $material-aidialog-content-gap: 16px !default; $material-aidialog-content-padding: 16px !default; $material-aidialog-toolbar-padding: 0 16px 8px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/icons/_index.scss b/packages/devextreme-scss/scss/widgets/material/icons/_index.scss index 4e194c685efb..1c9b359a0ede 100644 --- a/packages/devextreme-scss/scss/widgets/material/icons/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/icons/_index.scss @@ -19,12 +19,6 @@ } } -.dx-scrollview-pulldown { - background-image: $scrollview-pulldown-path; - background-position: 0 0; - background-repeat: no-repeat; -} - .dx-loadindicator-image { background-position: center center; background-repeat: no-repeat; diff --git a/packages/devextreme-scss/scss/widgets/material/informer/_colors.scss b/packages/devextreme-scss/scss/widgets/material/informer/_colors.scss index d8059cab008d..fd8e7ba5a2e7 100644 --- a/packages/devextreme-scss/scss/widgets/material/informer/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/informer/_colors.scss @@ -7,11 +7,11 @@ $informer-info-color: $base-text-color !default; $informer-info-background-color: null !default; @if $mode == "light" { - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1deg, $saturation: -24.92%, $lightness: 38.24%); - $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -240deg, $saturation: -100.00%, $lightness: 48.63%); + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1deg, $saturation: -24.92%, $lightness: 38.24%) !default; + $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: -240deg, $saturation: -100.00%, $lightness: 48.63%) !default; } @if $mode == "dark" { - $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1deg, $saturation: -41.80%, $lightness: -53.92%); - $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: 240deg, $saturation: 6.04%, $lightness: -70.78%); + $informer-error-background-color: extcolor.adjust-if-not-achromatic($color: $base-danger, $hue: 1deg, $saturation: -41.80%, $lightness: -53.92%) !default; + $informer-info-background-color: extcolor.adjust-if-not-achromatic($color: $base-text-color, $hue: 240deg, $saturation: 6.04%, $lightness: -70.78%) !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/list/_index.scss b/packages/devextreme-scss/scss/widgets/material/list/_index.scss index 0394033ee1eb..1f6f61db64c5 100644 --- a/packages/devextreme-scss/scss/widgets/material/list/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/list/_index.scss @@ -13,11 +13,11 @@ @use "../radioGroup/colors" as *; @use "../badge"; -$material-list-item-border-width: 1px; -$material-list-border-width: 1px; -$material-list-border: $material-list-border-width solid; -$material-list-item-border: $material-list-item-border-width solid; -$material-list-searchbox-padding-top: $material-list-searchbox-vertical-padding - 2px; +$material-list-item-border-width: 1px !default; +$material-list-border-width: 1px !default; +$material-list-border: $material-list-border-width solid !default; +$material-list-item-border: $material-list-item-border-width solid !default; +$material-list-searchbox-padding-top: $material-list-searchbox-vertical-padding - 2px !default; .dx-list-item-chevron { @include dx-icon(chevronnext); diff --git a/packages/devextreme-scss/scss/widgets/material/menu/_index.scss b/packages/devextreme-scss/scss/widgets/material/menu/_index.scss index 86d8eec7b186..74feb67ec42e 100644 --- a/packages/devextreme-scss/scss/widgets/material/menu/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/menu/_index.scss @@ -9,7 +9,7 @@ @use "../menuBase" as *; @use "../../base/menu"; -$material-menu-font-size: $material-base-font-size; +$material-menu-font-size: $material-base-font-size !default; .dx-context-menu-container-border { background-color: transparent; diff --git a/packages/devextreme-scss/scss/widgets/material/menuBase/_index.scss b/packages/devextreme-scss/scss/widgets/material/menuBase/_index.scss index c58fb28cc838..e9cff178ebc9 100644 --- a/packages/devextreme-scss/scss/widgets/material/menuBase/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/menuBase/_index.scss @@ -8,7 +8,7 @@ @use "../../base/mixins" as *; @use "../../base/menuBase"; -$material-menu-arrow-image-size: 7px; +$material-menu-arrow-image-size: 7px !default; .dx-menu-item { color: $menu-color; diff --git a/packages/devextreme-scss/scss/widgets/material/menuBase/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/menuBase/_sizes.scss index cbcdc4c94b66..697bd594e2e1 100644 --- a/packages/devextreme-scss/scss/widgets/material/menuBase/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/menuBase/_sizes.scss @@ -3,7 +3,7 @@ $material-menu-item-line-height: null !default; $material-menu-item-with-icon-horizontal-padding: null !default; $material-menu-horizontal-padding: null !default; -$material-menu-icon-to-text-offset: 8px; +$material-menu-icon-to-text-offset: 8px !default; @if $size == "default" { $material-menu-item-line-height: 18px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/pivotGrid/_index.scss b/packages/devextreme-scss/scss/widgets/material/pivotGrid/_index.scss index e8f0a912e74c..fedacbe636cf 100644 --- a/packages/devextreme-scss/scss/widgets/material/pivotGrid/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/pivotGrid/_index.scss @@ -32,12 +32,12 @@ $pivotgrid-button-top-padding: $pivotgrid-button-top-padding, ); -$material-pivotgrid-headers-font-size: 12px; -$material-pivotgrid-area-font-size: 13px; -$material-pivotgrid-chevron-icon-size: 20px; +$material-pivotgrid-headers-font-size: 12px !default; +$material-pivotgrid-area-font-size: 13px !default; +$material-pivotgrid-chevron-icon-size: 20px !default; -$material-pivotgrid-drag-header-shadow: 0 3px 4px color.change($base-shadow-color, $alpha: 0.15); -$material-pivotgridfieldchooser-field-min-height: 36px; +$material-pivotgrid-drag-header-shadow: 0 3px 4px color.change($base-shadow-color, $alpha: 0.15) !default; +$material-pivotgridfieldchooser-field-min-height: 36px !default; .dx-pivotgrid-fields-container { &.dx-drag { diff --git a/packages/devextreme-scss/scss/widgets/material/popup/_index.scss b/packages/devextreme-scss/scss/widgets/material/popup/_index.scss index 0082098d5238..bfbb2902eed0 100644 --- a/packages/devextreme-scss/scss/widgets/material/popup/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/popup/_index.scss @@ -8,7 +8,7 @@ @use "../../base/popup"; @use "../overlay"; -$material-popup-toolbar-label-font-size: $material-m-font-size; +$material-popup-toolbar-label-font-size: $material-m-font-size !default; .dx-popup-wrapper { & > .dx-overlay-content { diff --git a/packages/devextreme-scss/scss/widgets/material/progressBar/_index.scss b/packages/devextreme-scss/scss/widgets/material/progressBar/_index.scss index 58b93ad7bfc8..66c0e933c0fa 100644 --- a/packages/devextreme-scss/scss/widgets/material/progressBar/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/progressBar/_index.scss @@ -4,9 +4,9 @@ @use "../../base/mixins" as *; @use "../../base/progressBar"; -$material-bar-height: 4px; -$material-background-width: 90%; -$material-progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95%; +$material-bar-height: 4px !default; +$material-background-width: 90% !default; +$material-progressbar-interdeterminate-state-gradient: -90deg, transparent 5%, $progressbar-range-indeterminate-color, transparent 95% !default; .dx-progressbar-container { height: $material-bar-height; diff --git a/packages/devextreme-scss/scss/widgets/material/radioButton/_index.scss b/packages/devextreme-scss/scss/widgets/material/radioButton/_index.scss index 960a46991c2a..f42753264aab 100644 --- a/packages/devextreme-scss/scss/widgets/material/radioButton/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/radioButton/_index.scss @@ -6,7 +6,7 @@ @use "../radioGroup/colors" as *; @use "../../base/radioButton"; -$material-radiobutton-ripple-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); +$material-radiobutton-ripple-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) !default; @include dx-radiobutton-icon-mixin( $material-radiobutton-size, diff --git a/packages/devextreme-scss/scss/widgets/material/rangeSlider/_index.scss b/packages/devextreme-scss/scss/widgets/material/rangeSlider/_index.scss index 583170ede72f..d73e48a91ceb 100644 --- a/packages/devextreme-scss/scss/widgets/material/rangeSlider/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/rangeSlider/_index.scss @@ -2,7 +2,7 @@ @use "sizes" as *; @use "../../base/rangeSlider"; -$material-slider-handle-margin: -14px; +$material-slider-handle-margin: -14px !default; .dx-rangeslider-start-handle { margin-inline-start: $material-slider-handle-margin; diff --git a/packages/devextreme-scss/scss/widgets/material/recurrenceEditor/_index.scss b/packages/devextreme-scss/scss/widgets/material/recurrenceEditor/_index.scss index 2ca39d753187..48e26a618efb 100644 --- a/packages/devextreme-scss/scss/widgets/material/recurrenceEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/recurrenceEditor/_index.scss @@ -4,9 +4,9 @@ @use "../scheduler/sizes" as *; @use "../../base/recurrenceEditor" as *; -$material-recurrence-number-box-margin: 11px; -$material-recurrence-button-group-padding: 6px; -$material-recurrence-radio-group-margin: 15px; +$material-recurrence-number-box-margin: 11px !default; +$material-recurrence-button-group-padding: 6px !default; +$material-recurrence-radio-group-margin: 15px !default; .dx-recurrence-numberbox-interval { margin-right: $material-recurrence-number-box-margin; diff --git a/packages/devextreme-scss/scss/widgets/material/scheduler/_colors.scss b/packages/devextreme-scss/scss/widgets/material/scheduler/_colors.scss index 1d3349f02d9d..1468a9d68cfb 100644 --- a/packages/devextreme-scss/scss/widgets/material/scheduler/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/scheduler/_colors.scss @@ -35,7 +35,7 @@ $scheduler-appointment-base-color: $base-accent !default; * $type color */ $scheduler-appointment-start-color: color.change($base-shadow-color, $alpha: 0.3) !default; -$scheduler-appointment-active-color: $scheduler-appointment-start-color; +$scheduler-appointment-active-color: $scheduler-appointment-start-color !default; /** * $name 60. Focused cell color @@ -74,8 +74,8 @@ $scheduler-fill-focused-appointment: false !default; $scheduler-is-shadow-color-for-focused-state: true !default; $scheduler-time-indicator-background-color: rgba(0, 0, 0, 0.03) !default; $scheduler-first-month-cell-background-color: color.adjust($scheduler-appointment-base-color, $alpha: -0.85) !default; -$material-scheduler-toolbar-color: $base-accent; -$material-scheduler-toolbar-active-color: color.change($base-accent, $alpha: 0.08); +$material-scheduler-toolbar-color: $base-accent !default; +$material-scheduler-toolbar-active-color: color.change($base-accent, $alpha: 0.08) !default; $scheduler-tooltip-appointment-text-color: $base-text-color !default; $scheduler-appointment-overlay-bg: $base-bg !default; diff --git a/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss b/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss index e655da5b8b04..7b42c6fb901a 100644 --- a/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/scheduler/_index.scss @@ -53,17 +53,17 @@ @use "../recurrenceEditor" as *; @use "../scrollable"; -$scheduler-appointment-recurrence-content-padding: 5px 25px 5px 7px; -$scheduler-appointment-recurrence-content-padding-rtl: 5px 7px 5px 25px; +$scheduler-appointment-recurrence-content-padding: 5px 25px 5px 7px !default; +$scheduler-appointment-recurrence-content-padding-rtl: 5px 7px 5px 25px !default; -$material-scheduler-all-day-table-cell-height: $material-scheduler-workspace-date-table-cell-height * 1.5; -$material-scheduler-collapsed-all-day-table-cell-height: $material-scheduler-workspace-date-table-cell-height * 0.5; +$material-scheduler-all-day-table-cell-height: $material-scheduler-workspace-date-table-cell-height * 1.5 !default; +$material-scheduler-collapsed-all-day-table-cell-height: $material-scheduler-workspace-date-table-cell-height * 0.5 !default; -$material-scheduler-month-date-text-size: 14px; +$material-scheduler-month-date-text-size: 14px !default; -$scheduler-first-of-month-border: 1px solid; +$scheduler-first-of-month-border: 1px solid !default; -$material-scheduler-agenda-time-panel-cell-padding: 8px; +$material-scheduler-agenda-time-panel-cell-padding: 8px !default; @include baseScheduler.scheduler-mixin(); diff --git a/packages/devextreme-scss/scss/widgets/material/scheduler/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/scheduler/_sizes.scss index 26815ff6d53b..d8178c42da4d 100644 --- a/packages/devextreme-scss/scss/widgets/material/scheduler/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/scheduler/_sizes.scss @@ -4,45 +4,43 @@ $material-scheduler-workspace-date-table-cell-height: null !default; $material-scheduler-toolbar-item-offset: null !default; -$material-scheduler-navigator-height: null !default; $material-scheduler-header-panel-day-font-size: null !default; $material-scheduler-appointment-tooltip-title-font-size: null !default; $material-scheduler-appointment-tooltip-date-font-size: null !default; $material-scheduler-workspace-month-cell-left-offset: null !default; $material-scheduler-header-panel-table-cell-height: null !default; $material-scheduler-appointment-content-padding: null !default; -$material-scheduler-dropdown-appointment-margin-top: null !default; $material-scheduler-dropdown-button-height: null !default; $material-scheduler-dropdown-button-padding: null !default; $material-scheduler-dropdown-button-margin: null !default; $material-scheduler-dropdown-button-vertical-margin: null !default; -$material-scheduler-dropdown-button-adaptive-margin: null !default; -$material-scheduler-dropdown-button-adaptive-vertical-margin: null !default; +$material-scheduler-dropdown-button-adaptive-margin: 0 2px !default; +$material-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $material-scheduler-appointment-tooltip-content-margin-left: null !default; $material-tooltip-marker-dimension: null !default; $material-scheduler-appointment-tooltip-title-margin-top: null !default; $material-scheduler-appointment-tooltip-remove-padding-top: null !default; -$material-scheduler-appointment-short-content-padding: null !default; -$material-scheduler-appointment-15min-height: null !default; -$material-scheduler-appointment-20min-height: null !default; -$material-scheduler-appointment-25min-height: null !default; -$material-scheduler-appointment-10min-recurrence-padding-right: null !default; -$material-scheduler-appointment-10min-title-font-size: null !default; -$material-scheduler-appointment-10min-title-line-height: null !default; -$material-scheduler-appointment-10min-icon-font-size: null !default; -$material-scheduler-appointment-10min-icon-right: null !default; +$material-scheduler-appointment-short-content-padding: 0 7px !default; +$material-scheduler-appointment-15min-height: 17px !default; +$material-scheduler-appointment-20min-height: 24px !default; +$material-scheduler-appointment-25min-height: 32px !default; +$material-scheduler-appointment-10min-recurrence-padding-right: 14px !default; +$material-scheduler-appointment-10min-title-font-size: 8px !default; +$material-scheduler-appointment-10min-title-line-height: 12px !default; +$material-scheduler-appointment-10min-icon-font-size: 12px !default; +$material-scheduler-appointment-10min-icon-right: 2px !default; $material-scheduler-appointment-15min-recurrence-padding-right: null !default; -$material-scheduler-appointment-15min-title-font-size: null !default; +$material-scheduler-appointment-15min-title-font-size: 12px !default; $material-scheduler-appointment-15min-title-line-height: null !default; $material-scheduler-appointment-15min-icon-font-size: null !default; -$material-scheduler-appointment-15min-icon-right: null !default; -$material-scheduler-appointment-20min-recurrence-padding-right: null !default; -$material-scheduler-appointment-20min-icon-right: null !default; +$material-scheduler-appointment-15min-icon-right: 2px !default; +$material-scheduler-appointment-20min-recurrence-padding-right: 25px !default; +$material-scheduler-appointment-20min-icon-right: 4px !default; $scheduler-left-column-width: 65px !default; $material-scheduler-timeline-date-table-cell-height: 50px !default; -$material-scheduler-navigator-icon-font-size: $material-base-icon-size; +$material-scheduler-navigator-icon-font-size: $material-base-icon-size !default; $material-scheduler-appointment-month-text-size: 12px !default; $material-scheduler-workspace-info-text-size: 16px !default; $material-scheduler-all-day-title-font-size: 12px !default; @@ -80,7 +78,6 @@ $scheduler-timeline-cell-height: 50px !default; @if $size == "default" { $material-scheduler-workspace-date-table-cell-height: 38px !default; - $material-scheduler-navigator-height: 36px !default; $material-scheduler-header-panel-day-font-size: 30px !default; $material-scheduler-header-height: 54px !default; $material-scheduler-header-offset: 2px 16px !default; @@ -92,13 +89,10 @@ $scheduler-timeline-cell-height: 50px !default; $material-scheduler-header-panel-table-cell-height: 56px !default; $material-scheduler-appointment-content-padding: 5px 7px !default; - $material-scheduler-dropdown-appointment-margin-top: 7px !default; $material-scheduler-dropdown-button-height: 22px !default; $material-scheduler-dropdown-button-padding: 4px !default; $material-scheduler-dropdown-button-margin: 5px 1px 3px 3px !default; $material-scheduler-dropdown-button-vertical-margin: 5px 2px 3px 1px !default; - $material-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $material-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $material-scheduler-appointment-tooltip-content-margin-left: 10px !default; $material-tooltip-marker-dimension: 24px !default; @@ -120,33 +114,16 @@ $scheduler-timeline-cell-height: 50px !default; $material-scheduler-appointment-popup-days-of-week-buttons-gap: 8px !default; $material-scheduler-appointment-popup-repeat-end-item-height: 48px !default; - $agenda-appointment-title-font-size: 14px; - - $material-scheduler-appointment-short-content-padding: 0 7px !default; - $material-scheduler-appointment-15min-height: 17px !default; - $material-scheduler-appointment-20min-height: 24px !default; - $material-scheduler-appointment-25min-height: 32px !default; - - $material-scheduler-appointment-10min-recurrence-padding-right: 14px !default; - $material-scheduler-appointment-10min-title-font-size: 8px !default; - $material-scheduler-appointment-10min-title-line-height: 12px !default; - $material-scheduler-appointment-10min-icon-font-size: 12px !default; - $material-scheduler-appointment-10min-icon-right: 2px !default; + $agenda-appointment-title-font-size: 14px !default; $material-scheduler-appointment-15min-recurrence-padding-right: 18px !default; - $material-scheduler-appointment-15min-title-font-size: 12px !default; $material-scheduler-appointment-15min-title-line-height: 18px !default; $material-scheduler-appointment-15min-icon-font-size: 16px !default; - $material-scheduler-appointment-15min-icon-right: 2px !default; - - $material-scheduler-appointment-20min-recurrence-padding-right: 25px !default; - $material-scheduler-appointment-20min-icon-right: 4px !default; } @else if $size == "compact" { $material-scheduler-workspace-date-table-cell-height: 28px !default; - $material-scheduler-navigator-height: 28px !default; $material-scheduler-header-panel-day-font-size: 20px !default; $material-scheduler-header-height: 38px !default; $material-scheduler-header-offset: 0 8px !default; @@ -158,13 +135,10 @@ $scheduler-timeline-cell-height: 50px !default; $material-scheduler-header-panel-table-cell-height: 40px !default; $material-scheduler-appointment-content-padding: 2px 4px !default; - $material-scheduler-dropdown-appointment-margin-top: 3px !default; $material-scheduler-dropdown-button-height: 18px !default; $material-scheduler-dropdown-button-padding: 2px !default; $material-scheduler-dropdown-button-margin: 2px 1px 2px 2px !default; $material-scheduler-dropdown-button-vertical-margin: 2px 1px 2px 1px !default; - $material-scheduler-dropdown-button-adaptive-margin: 0 2px !default; - $material-scheduler-dropdown-button-adaptive-vertical-margin: 0 2px 0 1px !default; $material-scheduler-appointment-tooltip-content-margin-left: 8px !default; $material-tooltip-marker-dimension: 18px !default; @@ -186,25 +160,9 @@ $scheduler-timeline-cell-height: 50px !default; $material-scheduler-appointment-popup-days-of-week-buttons-gap: 4px !default; $material-scheduler-appointment-popup-repeat-end-item-height: 32px !default; - $agenda-appointment-title-font-size: 13px; - - $material-scheduler-appointment-short-content-padding: 0 7px !default; - $material-scheduler-appointment-15min-height: 17px !default; - $material-scheduler-appointment-20min-height: 24px !default; - $material-scheduler-appointment-25min-height: 32px !default; - - $material-scheduler-appointment-10min-recurrence-padding-right: 14px !default; - $material-scheduler-appointment-10min-title-font-size: 8px !default; - $material-scheduler-appointment-10min-title-line-height: 12px !default; - $material-scheduler-appointment-10min-icon-font-size: 12px !default; - $material-scheduler-appointment-10min-icon-right: 2px !default; + $agenda-appointment-title-font-size: 13px !default; $material-scheduler-appointment-15min-recurrence-padding-right: 14px !default; - $material-scheduler-appointment-15min-title-font-size: 12px !default; $material-scheduler-appointment-15min-title-line-height: 12px !default; $material-scheduler-appointment-15min-icon-font-size: 12px !default; - $material-scheduler-appointment-15min-icon-right: 2px !default; - - $material-scheduler-appointment-20min-recurrence-padding-right: 25px !default; - $material-scheduler-appointment-20min-icon-right: 4px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/scrollView/_index.scss b/packages/devextreme-scss/scss/widgets/material/scrollView/_index.scss index b91875142119..ea4f3b80cd11 100644 --- a/packages/devextreme-scss/scss/widgets/material/scrollView/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/scrollView/_index.scss @@ -6,7 +6,7 @@ @use "../scrollable"; @use "../../base/scrollView"; -$scrollview-loading-indicator-size: 42px; +$scrollview-loading-indicator-size: 42px !default; .dx-scrollview-scrollbottom-text, .dx-scrollview-pull-down-text { diff --git a/packages/devextreme-scss/scss/widgets/material/scrollable/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/scrollable/_sizes.scss index 28567ffc4f8a..08d5cbb4b37b 100644 --- a/packages/devextreme-scss/scss/widgets/material/scrollable/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/scrollable/_sizes.scss @@ -1,2 +1,2 @@ -$material-scrollbar-size-thin: 6px; -$material-scrollbar-size: 13px; +$material-scrollbar-size-thin: 6px !default; +$material-scrollbar-size: 13px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/slider/_index.scss b/packages/devextreme-scss/scss/widgets/material/slider/_index.scss index 36bf203145da..9c819f8ec0b2 100644 --- a/packages/devextreme-scss/scss/widgets/material/slider/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/slider/_index.scss @@ -6,11 +6,11 @@ @use "../../material/tooltip/colors" as *; @use "../../base/slider"; -$material-slider-radius: 50%; -$material-slider-handle-active-border-width: 2px; -$material-slider-handle-inner-size: 12px; -$material-slider-handle-inner-border-width: 1px; -$material-slider-handle-inner-disabled-size: 2px; +$material-slider-radius: 50% !default; +$material-slider-handle-active-border-width: 2px !default; +$material-slider-handle-inner-size: 12px !default; +$material-slider-handle-inner-border-width: 1px !default; +$material-slider-handle-inner-disabled-size: 2px !default; .dx-slider-wrapper { height: $material-slider-wrapper-height; diff --git a/packages/devextreme-scss/scss/widgets/material/slider/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/slider/_sizes.scss index 6a7fa38a0fdd..9b1763a9f246 100644 --- a/packages/devextreme-scss/scss/widgets/material/slider/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/slider/_sizes.scss @@ -3,7 +3,6 @@ $material-slider-handle-size: null !default; $material-slider-wrapper-height: null !default; $material-slider-tooltip-width: null !default; -$material-slider-top-tooltip-margin: null !default; $material-slider-tooltip-popup-content-padding: null !default; $material-slider-track-height: 2px !default; @@ -13,7 +12,6 @@ $material-slider-height: 28px !default; $material-slider-handle-size: 32px !default; $material-slider-wrapper-height: 32px !default; $material-slider-tooltip-width: 34px !default; - $material-slider-top-tooltip-margin: -5px !default; $material-slider-tooltip-popup-content-padding: 5px 7px !default; } @@ -21,6 +19,5 @@ $material-slider-height: 28px !default; $material-slider-handle-size: 26px !default; $material-slider-wrapper-height: 26px !default; $material-slider-tooltip-width: 28px !default; - $material-slider-top-tooltip-margin: -2px !default; $material-slider-tooltip-popup-content-padding: 3px 5px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/speedDialAction/_index.scss b/packages/devextreme-scss/scss/widgets/material/speedDialAction/_index.scss index ac08160d72be..615c8df0cf78 100644 --- a/packages/devextreme-scss/scss/widgets/material/speedDialAction/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/speedDialAction/_index.scss @@ -5,9 +5,9 @@ @use "../button/colors" as *; @use "../overlay"; -$material-fa-button-size: 40px; -$material-fa-button-label-offset: 16px; -$material-fa-button-label-padding: 4px 8px; +$material-fa-button-size: 40px !default; +$material-fa-button-label-offset: 16px !default; +$material-fa-button-label-padding: 4px 8px !default; @include dx-fa-button-mixin( $material-main-fa-button-size, diff --git a/packages/devextreme-scss/scss/widgets/material/speedDialAction/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/speedDialAction/_sizes.scss index bc68610c12b7..ffdd306ceace 100644 --- a/packages/devextreme-scss/scss/widgets/material/speedDialAction/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/speedDialAction/_sizes.scss @@ -3,16 +3,12 @@ $material-main-fa-button-label-padding: null !default; $material-main-fa-button-size: null !default; -$material-fa-button-label-font-size: null !default; - @if $size == "default" { $material-main-fa-button-size: 56px !default; - $material-main-fa-button-label-padding: 0 24px 0 8px; - $material-fa-button-label-font-size: 14px; + $material-main-fa-button-label-padding: 0 24px 0 8px !default; } @else if $size == "compact" { $material-main-fa-button-size: 42px !default; - $material-main-fa-button-label-padding: 0 20px 0 8px; - $material-fa-button-label-font-size: 12px; + $material-main-fa-button-label-padding: 0 20px 0 8px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/splitter/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/splitter/_sizes.scss index 3af001d1096d..a97016b18725 100644 --- a/packages/devextreme-scss/scss/widgets/material/splitter/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/splitter/_sizes.scss @@ -1 +1 @@ -$splitter-resize-handle-icon-size: 24px; +$splitter-resize-handle-icon-size: 24px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/switch/_index.scss b/packages/devextreme-scss/scss/widgets/material/switch/_index.scss index 9b7f631c40ef..59f2347d69ed 100644 --- a/packages/devextreme-scss/scss/widgets/material/switch/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/switch/_index.scss @@ -6,10 +6,10 @@ @use "../../base/switch"; @use "../../base/switch/mixins" as *; -$material-switch-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); -$material-switch-handle-on-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($switch-handle-on-bg, $alpha: 0.1); -$material-switch-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($base-text-color, $alpha: 0.1); -$material-invalid-switch-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($switch-invalid-handle-bg, $alpha: 0.1); +$material-switch-transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) !default; +$material-switch-handle-on-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($switch-handle-on-bg, $alpha: 0.1) !default; +$material-switch-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($base-text-color, $alpha: 0.1) !default; +$material-invalid-switch-shadow: 0 0 0 $material-switch-shadow-blur-radius color.change($switch-invalid-handle-bg, $alpha: 0.1) !default; @include dx-switch( $material-switch-width, diff --git a/packages/devextreme-scss/scss/widgets/material/switch/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/switch/_sizes.scss index edc230fb821e..5688f2501bf0 100644 --- a/packages/devextreme-scss/scss/widgets/material/switch/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/switch/_sizes.scss @@ -3,28 +3,25 @@ $material-switch-width: null !default; $switch-padding: 10px !default; $switch-container-vertical-margin: null !default; -$switch-container-horizontal-padding: null !default; $material-switch-height: null !default; -$switch-handle-height: null !default; $switch-container-height: 14px !default; $switch-handle-margin: 0 !default; $material-switch-shadow-blur-radius: 10px !default; $material-switch-handle-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2) !default; $material-switch-container-adjustment: 6px !default; +$switch-container-horizontal-padding: $switch-handle-margin !default; @if $size == "default" { $material-switch-width: 36px !default; $switch-container-vertical-margin: 3px !default; - $switch-container-horizontal-padding: $switch-handle-margin !default; $material-switch-height: 20px !default; - $switch-handle-height: $material-switch-height !default; } @else if $size == "compact" { $material-switch-width: 32px !default; $switch-container-vertical-margin: 2px !default; - $switch-container-horizontal-padding: $switch-handle-margin !default; $material-switch-height: 18px !default; - $switch-handle-height: $material-switch-height !default; } + +$switch-handle-height: $material-switch-height !default; diff --git a/packages/devextreme-scss/scss/widgets/material/tabs/variables/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/tabs/variables/_sizes.scss index 97c92422d3a9..c50cef3909cf 100644 --- a/packages/devextreme-scss/scss/widgets/material/tabs/variables/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/tabs/variables/_sizes.scss @@ -1,21 +1,20 @@ @use "../../sizes" as *; $material-tabs-item-gap: 4px !default; -$material-tabs-item-padding: null !default; $material-tabs-item-padding-horizontal: null !default; $material-tabs-item-padding-vertical: null !default; -$material-tab-height: null !default; +$material-tab-height: auto !default; $material-tab-min-width: null !default; $material-tab-text-line-height: 1.714286 !default; $material-base-icon-margin: null !default; -$material-tab-nav-button-icon-width: null !default; -$material-tab-nav-button-icon-height: null !default; -$material-tab-nav-button-icon-font-size: null !default; -$material-tab-nav-button-content-padding: null !default; +$material-tab-nav-button-icon-width: 24px !default; +$material-tab-nav-button-icon-height: 24px !default; +$material-tab-nav-button-icon-font-size: 24px !default; +$material-tab-nav-button-content-padding: 0 4px !default; -$material-tab-vertical-nav-button-content-padding: null !default; +$material-tab-vertical-nav-button-content-padding: 4px 0 !default; $material-indicator-thickness-primary: 2px !default; $material-indicator-thickness-secondary: 3px !default; @@ -30,27 +29,15 @@ $material-tabs-icon-size: $material-base-icon-size !default; @if $size == "default" { $material-tabs-item-padding-horizontal: 12px !default; $material-tabs-item-padding-vertical: 16px !default; - $material-tabs-item-padding: $material-tabs-item-padding-horizontal $material-tabs-item-padding-vertical !default; - $material-tab-height: auto !default; $material-tab-min-width: 90px !default; $material-base-icon-margin: 8px !default; - $material-tab-nav-button-icon-width: 24px !default; - $material-tab-nav-button-icon-height: 24px !default; - $material-tab-nav-button-icon-font-size: 24px !default; - $material-tab-nav-button-content-padding: 0 4px !default; - $material-tab-vertical-nav-button-content-padding: 4px 0 !default; } @else if $size == "compact" { $material-tabs-item-padding-horizontal: 6px !default; $material-tabs-item-padding-vertical: 12px !default; - $material-tabs-item-padding: $material-tabs-item-padding-horizontal $material-tabs-item-padding-vertical !default; - $material-tab-height: auto !default; $material-tab-min-width: 82px !default; $material-base-icon-margin: 7px !default; - $material-tab-nav-button-icon-width: 24px !default; - $material-tab-nav-button-icon-height: 24px !default; - $material-tab-nav-button-icon-font-size: 24px !default; - $material-tab-nav-button-content-padding: 0 4px !default; - $material-tab-vertical-nav-button-content-padding: 4px 0 !default; } + +$material-tabs-item-padding: $material-tabs-item-padding-horizontal $material-tabs-item-padding-vertical !default; diff --git a/packages/devextreme-scss/scss/widgets/material/tagBox/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/tagBox/_sizes.scss index af20f502f158..0a55098aa46e 100644 --- a/packages/devextreme-scss/scss/widgets/material/tagBox/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/tagBox/_sizes.scss @@ -4,7 +4,6 @@ $material-tagbox-tag-content-padding-left: null !default; $material-tagbox-tag-content-padding-right: null !default; $material-tagbox-tag-content-vertical-padding: null !default; $material-tagbox-tag-content-font-size: null !default; -$material-tagbox-tag-container-padding: null !default; $material-tagbox-tag-container-padding-left: null !default; $material-tagbox-tag-container-vertical-padding: null !default; $material-tagbox-tag-height: null !default; @@ -15,21 +14,19 @@ $material-tagbox-select-all-padding-bottom: 12px !default; $material-tagbox-select-all-margin-bottom: 3px !default; $tagbox-tag-border-radius: 2em !default; $material-tagbox-filled-with-label-top-padding: null !default; -$material-tagbox-filled-with-label-bottom-padding: null !default; +$material-tagbox-filled-with-label-bottom-padding: 4px !default; @if $size == "default" { $material-tagbox-tag-content-padding-left: 8px !default; $material-tagbox-tag-content-padding-right: 30px !default; $material-tagbox-tag-content-vertical-padding: 4px !default; $material-tagbox-tag-content-font-size: 14px !default; - $material-tagbox-tag-container-padding: 0 12px 11px 16px !default; $material-tagbox-tag-container-padding-left: 16px !default; $material-tagbox-tag-container-vertical-padding: 11px !default; $material-tagbox-tag-height: 26px !default; $material-tagbox-remove-button-width: 26px !default; $material-tagbox-remove-button-icon-size: 16px !default; $material-tagbox-filled-with-label-top-padding: 18px !default; - $material-tagbox-filled-with-label-bottom-padding: 4px !default; } @else if $size == "compact" { @@ -37,12 +34,10 @@ $material-tagbox-filled-with-label-bottom-padding: null !default; $material-tagbox-tag-content-padding-right: 20px !default; $material-tagbox-tag-content-vertical-padding: 2px !default; $material-tagbox-tag-content-font-size: 12px !default; - $material-tagbox-tag-container-padding: 0 8px 3px 11px !default; $material-tagbox-tag-container-padding-left: 12px !default; $material-tagbox-tag-container-vertical-padding: 7px !default; $material-tagbox-tag-height: 18px !default; $material-tagbox-remove-button-width: 18px !default; $material-tagbox-remove-button-icon-size: 12px !default; $material-tagbox-filled-with-label-top-padding: 10px !default; - $material-tagbox-filled-with-label-bottom-padding: 4px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/textBox/_index.scss b/packages/devextreme-scss/scss/widgets/material/textBox/_index.scss index dacb0d90e04e..da0905c94c67 100644 --- a/packages/devextreme-scss/scss/widgets/material/textBox/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/textBox/_index.scss @@ -8,15 +8,15 @@ @use "../../base/textBox"; @use "../textEditor" as *; -$textbox-search-icon-right-padding: math.div($material-filled-texteditor-input-horizontal-padding, 2); +$textbox-search-icon-right-padding: math.div($material-filled-texteditor-input-horizontal-padding, 2) !default; $textbox-standard-search-icon-size: $material-texteditor-icon-container-size + $material-base-standard-texteditor-input-horizontal-padding + - $textbox-search-icon-right-padding; + $textbox-search-icon-right-padding !default; $textbox-filled-search-icon-size: $material-texteditor-icon-container-size + $material-filled-texteditor-input-horizontal-padding + - $textbox-search-icon-right-padding; + $textbox-search-icon-right-padding !default; @mixin dx-texteditor-search-icon($texteditor-icon-size, $texteditor-padding) { position: absolute; diff --git a/packages/devextreme-scss/scss/widgets/material/textEditor/_colors.scss b/packages/devextreme-scss/scss/widgets/material/textEditor/_colors.scss index e05f1ab8f71e..e7495f66bedd 100644 --- a/packages/devextreme-scss/scss/widgets/material/textEditor/_colors.scss +++ b/packages/devextreme-scss/scss/widgets/material/textEditor/_colors.scss @@ -13,7 +13,7 @@ $texteditor-color: $base-text-color !default; * $type color */ $texteditor-placeholder-color: $base-label-color !default; -$texteditor-label-color: $texteditor-placeholder-color; +$texteditor-label-color: $texteditor-placeholder-color !default; /** * $name 30. Background color @@ -59,7 +59,7 @@ $texteditor-input-border-radius: $base-border-radius !default; $texteditor-label-transition: font-size 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, transform 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms, - top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms; + top 0.2s cubic-bezier(0, 0, 0.2, 1) 0ms !default; @if $mode == "light" { $texteditor-disabled-color: rgba(0, 0, 0, 0.6) !default; diff --git a/packages/devextreme-scss/scss/widgets/material/textEditor/_index.scss b/packages/devextreme-scss/scss/widgets/material/textEditor/_index.scss index ae152575c305..c9e40f7baa5b 100644 --- a/packages/devextreme-scss/scss/widgets/material/textEditor/_index.scss +++ b/packages/devextreme-scss/scss/widgets/material/textEditor/_index.scss @@ -13,10 +13,10 @@ @use "../../base/textEditor/mixins" as *; @use "../colorBox/sizes" as *; -$material-base-standard-texteditor-input-horizontal-padding: 0; -$material-texteditor-icon-container-size: $material-base-icon-size - 2px; -$material-texteditor-invalid-badge-size: $material-invalid-badge-size + 2 * $material-invalid-badge-horizontal-padding; -$material-editor-custom-button-margin: 5px; +$material-base-standard-texteditor-input-horizontal-padding: 0 !default; +$material-texteditor-icon-container-size: $material-base-icon-size - 2px !default; +$material-texteditor-invalid-badge-size: $material-invalid-badge-size + 2 * $material-invalid-badge-horizontal-padding !default; +$material-editor-custom-button-margin: 5px !default; @mixin dx-texteditor-icon() { position: absolute; diff --git a/packages/devextreme-scss/scss/widgets/material/textEditor/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/textEditor/_sizes.scss index 8082b872260d..1e206df56fc9 100644 --- a/packages/devextreme-scss/scss/widgets/material/textEditor/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/textEditor/_sizes.scss @@ -28,9 +28,9 @@ $material-filled-texteditor-input-with-label-bottom-padding: null !default; $material-texteditor-label-outside-top-offset: 24px !default; $material-filled-texteditor-input-horizontal-padding: 16px !default; $material-filled-texteditor-input-top-padding: 15px !default; - $material-filled-texteditor-input-with-label-top-padding: 21px; + $material-filled-texteditor-input-with-label-top-padding: 21px !default; $material-filled-texteditor-input-bottom-padding: 14px !default; - $material-filled-texteditor-input-with-label-bottom-padding: 8px; + $material-filled-texteditor-input-with-label-bottom-padding: 8px !default; $material-standard-texteditor-input-top-padding: 15px !default; $material-standard-texteditor-input-bottom-padding: 14px !default; $material-standard-texteditor-with-label-input-top-padding: 21px !default; @@ -47,10 +47,10 @@ $material-filled-texteditor-input-with-label-bottom-padding: null !default; $material-texteditor-label-outside-height: 16px !default; $material-texteditor-label-outside-top-offset: 18px !default; $material-filled-texteditor-input-horizontal-padding: 12px !default; - $material-filled-texteditor-input-top-padding: 9px; - $material-filled-texteditor-input-with-label-top-padding: 15px; - $material-filled-texteditor-input-bottom-padding: 9px; - $material-filled-texteditor-input-with-label-bottom-padding: 3px; + $material-filled-texteditor-input-top-padding: 9px !default; + $material-filled-texteditor-input-with-label-top-padding: 15px !default; + $material-filled-texteditor-input-bottom-padding: 9px !default; + $material-filled-texteditor-input-with-label-bottom-padding: 3px !default; $material-standard-texteditor-input-top-padding: 9px !default; $material-standard-texteditor-input-bottom-padding: 9px !default; $material-standard-texteditor-with-label-input-top-padding: 14px !default; @@ -68,5 +68,5 @@ $material-standard-texteditor-input-padding: $material-base-standard-texteditor-input-horizontal-padding $material-standard-texteditor-input-bottom-padding !default; -$material-filled-texteditor-input-button-horizontal-padding: $material-filled-texteditor-input-horizontal-padding - 4px; -$material-filled-texteditor-spin-button-horizontal-padding: $material-filled-texteditor-input-horizontal-padding - 8px; +$material-filled-texteditor-input-button-horizontal-padding: $material-filled-texteditor-input-horizontal-padding - 4px !default; +$material-filled-texteditor-spin-button-horizontal-padding: $material-filled-texteditor-input-horizontal-padding - 8px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/treeView/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/treeView/_sizes.scss index a9a01c5e3df1..b1bdbb525bb6 100644 --- a/packages/devextreme-scss/scss/widgets/material/treeView/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/treeView/_sizes.scss @@ -4,11 +4,10 @@ $material-treeview-toggle-item-width: null !default; $material-treeview-checkbox-offset: null !default; $material-treeview-item-padding: null !default; $material-select-all-item-padding: null !default; -$material-treeview-item-height: null !default; $material-checkbox-container-size: null !default; $material-treeview-load-indicator-size: null !default; -$material-treeview-item-element-gap: null !default; +$material-treeview-item-element-gap: 12px !default; $material-treeview-searchbox-margin-bottom: null !default; $material-treeview-search-editor-height: null !default; @@ -20,30 +19,26 @@ $material-treeview-container-padding: $material-treeview-border-padding $material-treeview-border-padding $material-treeview-border-padding - $material-treeview-container-with-border-offset; + $material-treeview-container-with-border-offset !default; @if $size == "default" { - $material-treeview-item-element-gap: 12px !default; $material-treeview-toggle-item-width: 24px !default; $material-treeview-checkbox-offset: 55px !default; $material-select-all-item-padding: 15px $material-treeview-checkbox-offset !default; $material-checkbox-container-size: 24px !default; $material-treeview-item-padding: 12px $material-treeview-item-side-padding !default; - $material-treeview-item-height: 48px !default; $material-treeview-load-indicator-size: 24px !default; $material-treeview-searchbox-margin-bottom: 8px !default; - $material-treeview-search-editor-height: 48px; + $material-treeview-search-editor-height: 48px !default; } @else if $size == "compact" { - $material-treeview-item-element-gap: 12px !default; $material-treeview-toggle-item-width: 18px !default; $material-treeview-checkbox-offset: 47px !default; $material-select-all-item-padding: 10px $material-treeview-checkbox-offset !default; $material-treeview-item-padding: 9px $material-treeview-item-side-padding !default; - $material-treeview-item-height: 36px !default; $material-treeview-load-indicator-size: 18px !default; - $material-checkbox-container-size: 18px; + $material-checkbox-container-size: 18px !default; $material-treeview-searchbox-margin-bottom: 4px !default; - $material-treeview-search-editor-height: 32px; + $material-treeview-search-editor-height: 32px !default; } diff --git a/packages/devextreme-scss/scss/widgets/material/typography/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/typography/_sizes.scss index c9ab6f8f1224..8cf4574b658d 100644 --- a/packages/devextreme-scss/scss/widgets/material/typography/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/typography/_sizes.scss @@ -4,7 +4,7 @@ $material-xl-font-size: null !default; $material-l-font-size: null !default; $material-m-font-size: null !default; $material-s-font-size: null !default; -$material-xs-font-size: null !default; +$material-xs-font-size: 12px !default; $material-h1-font-size: null !default; $material-h2-font-size: null !default; $material-h3-font-size: null !default; @@ -17,7 +17,6 @@ $material-h6-font-size: null !default; $material-l-font-size: 28px !default; $material-m-font-size: 20px !default; $material-s-font-size: 18px !default; - $material-xs-font-size: 12px !default; $material-h1-font-size: 96px !default; $material-h2-font-size: 60px !default; @@ -32,7 +31,6 @@ $material-h6-font-size: null !default; $material-l-font-size: 20px !default; $material-m-font-size: 16px !default; $material-s-font-size: 14px !default; - $material-xs-font-size: 12px !default; $material-h1-font-size: 64px !default; $material-h2-font-size: 42px !default; diff --git a/packages/devextreme-scss/scss/widgets/material/validation/_sizes.scss b/packages/devextreme-scss/scss/widgets/material/validation/_sizes.scss index ba97716f3337..5fec857c6d92 100644 --- a/packages/devextreme-scss/scss/widgets/material/validation/_sizes.scss +++ b/packages/devextreme-scss/scss/widgets/material/validation/_sizes.scss @@ -1,6 +1,5 @@ @use "../sizes" as *; -$material-validation-message-padding: null !default; $material-validationsummary-item-margin: null !default; $material-validation-message-line-height: null !default; @@ -10,7 +9,6 @@ $material-validation-overlay-border-radius: 4px !default; $material-validation-message-font-size: 12px !default; @if $size == "default" { - $material-validation-message-padding: 6px !default; $material-validationsummary-item-margin: 10px !default; $material-validation-message-horizontal-padding: 8px !default; @@ -18,7 +16,6 @@ $material-validation-message-font-size: 12px !default; } @else if $size == "compact" { - $material-validation-message-padding: 4px !default; $material-validationsummary-item-margin: 6px !default; $material-validation-message-horizontal-padding: 6px !default; diff --git a/packages/devextreme-scss/tests/module-variable-default.test.ts b/packages/devextreme-scss/tests/module-variable-default.test.ts new file mode 100644 index 000000000000..92edb88505f2 --- /dev/null +++ b/packages/devextreme-scss/tests/module-variable-default.test.ts @@ -0,0 +1,90 @@ +import { createRunner, scss } from './stylelint-rule'; + +const ruleName = 'dx/module-variable-default'; +const { lint, fix } = createRunner('module-variable-default.mjs', ruleName); + +test('a module-level variable without the flag is reported, at the top level and inside @if branches', () => { + const { warnings } = lint('missing.scss', scss( + '$top: 7px;', + '$flagged: 8px !default;', + '$global: 9px !global;', + '', + '@if $size == "default" {', + ' $branch: 1px;', + '}', + '', + '@else {', + ' $branch: 2px;', + '}', + )); + + expect(warnings).toEqual([ + { line: 1, rule: ruleName, text: `Expected the !default flag on "$top": without it a value passed through @use … with() is ignored (${ruleName})` }, + { line: 6, rule: ruleName, text: `Expected the !default flag on "$branch": without it a value passed through @use … with() is ignored (${ruleName})` }, + { line: 10, rule: ruleName, text: `Expected the !default flag on "$branch": without it a value passed through @use … with() is ignored (${ruleName})` }, + ]); +}); + +test('variables local to a mixin, function or rule are not module variables', () => { + const { warnings } = lint('local.scss', scss( + '@mixin size($base) {', + ' $double: $base * 2;', + ' width: $double;', + '}', + '', + '@function half($value) {', + ' $result: $value / 2;', + ' @return $result;', + '}', + '', + '.dx-widget {', + ' $gap: 4px;', + ' margin: $gap;', + '', + ' @if $size == "compact" {', + ' $gap: 2px;', + ' }', + '}', + )); + + expect(warnings).toEqual([]); +}); + +test('reassigning a variable that already holds a value is not a default', () => { + const { warnings } = lint('reassign.scss', scss( + '$offset: 4px !default;', + '$offset: $offset + 1px;', + '', + '$late: null !default;', + '$late: 2px;', + )); + + expect(warnings.map((warning) => warning.line)).toEqual([5]); +}); + +test('--fix appends the flag, also to a multi-line value', () => { + const { warnings, output } = fix('fix.scss', scss( + '$top: 7px;', + '$map: (', + ' "a": 1,', + ' "b": 2,', + ');', + '', + '@if $size == "default" {', + ' $branch: 1px;', + '}', + )); + + expect(warnings).toEqual([]); + expect(output).toBe(scss( + '$top: 7px !default;', + '$map: (', + ' "a": 1,', + ' "b": 2,', + ') !default;', + '', + '@if $size == "default" {', + ' $branch: 1px !default;', + '}', + )); +}); diff --git a/packages/devextreme-scss/tests/no-identical-branch-declarations.test.ts b/packages/devextreme-scss/tests/no-identical-branch-declarations.test.ts new file mode 100644 index 000000000000..1c70a11fe2f9 --- /dev/null +++ b/packages/devextreme-scss/tests/no-identical-branch-declarations.test.ts @@ -0,0 +1,392 @@ +import { createRunner, scss } from './stylelint-rule'; + +const ruleName = 'dx/no-identical-branch-declarations'; +const { lint, fix } = createRunner('no-identical-branch-declarations.mjs', ruleName); + +test('a variable set to one value in every @else branch is reported once, on the first branch', () => { + const { warnings } = lint('else-chain.scss', scss( + '$same: null !default;', + '$differs: null !default;', + '', + '@if $size == "default" {', + ' $same: 8px !default;', + ' $differs: 8px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $same: 8px !default;', + ' $differs: 4px !default;', + '}', + )); + + expect(warnings).toEqual([{ + line: 5, + rule: ruleName, + text: `"$same" has the same value in all 2 branches of the @if chain; declare it once outside the chain (${ruleName})`, + }]); +}); + +test('consecutive @if blocks testing one variable against different constants form a chain', () => { + const { warnings } = lint('if-if.scss', scss( + '@if $size == "default" {', + ' $same: 8px !default;', + '}', + '', + '@if $size == "compact" {', + ' $same: 8px !default;', + '}', + '', + '@if $mode == "dark" {', + ' $same: 8px !default;', + '}', + )); + + expect(warnings.map((warning) => warning.line)).toEqual([2]); +}); + +test('nothing is reported for a value that differs, a variable missing from a branch, or a repeated constant', () => { + const { warnings } = lint('clean.scss', scss( + '@if $size == "default" {', + ' $a: 8px !default;', + ' $only-here: 1px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $a: 4px !default;', + '}', + '', + '@if $size == "default" {', + ' $b: 8px !default;', + '}', + '', + '@if $size == "default" {', + ' $b: 8px !default;', + '}', + )); + + expect(warnings).toEqual([]); +}); + +test('--fix replaces the null pre-declaration with a literal and removes the emptied chain', () => { + const { warnings, output } = fix('fix-literal.scss', scss( + '$x: null !default;', + '$keep: 1px !default;', + '', + '@if $size == "default" {', + ' $x: ds.$spacing-480 !default;', + '}', + '', + '@else if $size == "compact" {', + ' $x: ds.$spacing-480 !default;', + '}', + )); + + expect(warnings).toEqual([]); + expect(output).toBe(scss( + '$x: ds.$spacing-480 !default;', + '$keep: 1px !default;', + )); +}); + +test('--fix puts a value that reads a branch-bound variable after the chain', () => { + const { output } = fix('fix-after.scss', scss( + '$height: null !default;', + '$handle: null !default;', + '', + '@if $size == "default" {', + ' $height: 20px !default;', + ' $handle: $height !default;', + '}', + '', + '@else if $size == "compact" {', + ' $height: 18px !default;', + ' $handle: $height !default;', + '}', + )); + + expect(output).toBe(scss( + '$height: null !default;', + '', + '@if $size == "default" {', + ' $height: 20px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $height: 18px !default;', + '}', + '', + '$handle: $height !default;', + )); +}); + +test('--fix leaves no blank first line when the removed declaration opened its group', () => { + const { output } = fix('fix-first-line.scss', scss( + '$same: null !default;', + '', + '@if $size == "default" {', + ' $same: 1px !default;', + '', + ' $width: 36px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $same: 1px !default;', + '', + ' $width: 32px !default;', + '}', + )); + + expect(output).toBe(scss( + '$same: 1px !default;', + '', + '@if $size == "default" {', + ' $width: 36px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $width: 32px !default;', + '}', + )); +}); + +test('--fix moves a value that reads a variable declared below its pre-declaration to just before the chain', () => { + const { output } = fix('fix-before.scss', scss( + '$padding: null !default;', + '$margin: 0 !default;', + '', + '@if $size == "default" {', + ' $padding: $margin !default;', + ' $width: 36px !default;', + '}', + '', + '@if $size == "compact" {', + ' $padding: $margin !default;', + ' $width: 32px !default;', + '}', + )); + + expect(output).toBe(scss( + '$margin: 0 !default;', + '$padding: $margin !default;', + '', + '@if $size == "default" {', + ' $width: 36px !default;', + '}', + '', + '@if $size == "compact" {', + ' $width: 32px !default;', + '}', + )); +}); + +test('--fix only removes branch declarations that a non-null default above the chain already overrides', () => { + const { output } = fix('fix-dead.scss', scss( + '$x: 10px !default;', + '', + '@if $size == "default" {', + ' $x: 12px !default;', + '}', + '', + '@else if $size == "compact" {', + ' $x: 12px !default;', + '}', + )); + + expect(output).toBe(scss('$x: 10px !default;')); +}); + +test('--fix settles a duplicate that only appears in the outer chain after the inner one is hoisted', () => { + const { warnings, output } = fix('fix-nested.scss', scss( + '$focused: null !default;', + '', + '@if $mode == "light" {', + ' @if $color == "blue" {', + ' $hover: #00f !default;', + ' $focused: $hover !default;', + ' }', + '', + ' @if $color == "saas" {', + ' $hover: #0ff !default;', + ' $focused: $hover !default;', + ' }', + '}', + '', + '@if $mode == "dark" {', + ' @if $color == "blue" {', + ' $hover: #008 !default;', + ' $focused: $hover !default;', + ' }', + '', + ' @if $color == "saas" {', + ' $hover: #088 !default;', + ' $focused: $hover !default;', + ' }', + '}', + )); + + expect(warnings).toEqual([]); + expect(output).toBe(scss( + '@if $mode == "light" {', + ' @if $color == "blue" {', + ' $hover: #00f !default;', + ' }', + '', + ' @if $color == "saas" {', + ' $hover: #0ff !default;', + ' }', + '}', + '', + '@if $mode == "dark" {', + ' @if $color == "blue" {', + ' $hover: #008 !default;', + ' }', + '', + ' @if $color == "saas" {', + ' $hover: #088 !default;', + ' }', + '}', + '', + '$focused: $hover !default;', + )); +}); + +test('--fix keeps a declaration behind the chain ahead of the declarations that read it', () => { + const { warnings, output } = fix('fix-order.scss', scss( + '$item-bg: null !default;', + '$disabled-bg: null !default;', + '$disabled-border: null !default;', + '', + '@if $mode == "light" {', + ' $item-bg: #eee !default;', + ' $disabled-bg: $item-bg !default;', + ' $disabled-border: $disabled-bg !default;', + '}', + '', + '@if $mode == "dark" {', + ' $item-bg: #333 !default;', + ' $disabled-bg: $item-bg !default;', + ' $disabled-border: $disabled-bg !default;', + '}', + )); + + expect(warnings).toEqual([]); + expect(output).toBe(scss( + '$item-bg: null !default;', + '', + '@if $mode == "light" {', + ' $item-bg: #eee !default;', + '}', + '', + '@if $mode == "dark" {', + ' $item-bg: #333 !default;', + '}', + '', + '$disabled-bg: $item-bg !default;', + '$disabled-border: $disabled-bg !default;', + )); +}); + +test('a duplicate that a branch-bound declaration still reads is reported but not moved', () => { + const source = scss( + '$item-bg: null !default;', + '$disabled-bg: null !default;', + '$hovered-bg: null !default;', + '', + '@if $mode == "light" {', + ' $item-bg: #eee !default;', + ' $disabled-bg: $item-bg !default;', + ' $hovered-bg: color.adjust($disabled-bg, $lightness: -4%) !default;', + '}', + '', + '@if $mode == "dark" {', + ' $item-bg: #333 !default;', + ' $disabled-bg: $item-bg !default;', + ' $hovered-bg: color.adjust($disabled-bg, $lightness: 4%) !default;', + '}', + ); + + const { warnings, output } = fix('fix-blocked.scss', source); + + expect(warnings.map((warning) => warning.line)).toEqual([7]); + expect(output).toBe(source); +}); + +test('--fix keeps a declaration under its doc comment when what it reads is not reassigned in between', () => { + const { output } = fix('fix-doc-in-place.scss', scss( + '@use "../colors" as *;', + '', + '/**', + '* $name 100. Background color', + '* $type color', + '*/', + '$common-bg: null !default;', + '', + '@if $mode == "light" {', + ' $common-bg: $base-bg !default;', + ' $accent: #00f !default;', + '}', + '', + '@if $mode == "dark" {', + ' $common-bg: $base-bg !default;', + ' $accent: #0ff !default;', + '}', + )); + + expect(output).toBe(scss( + '@use "../colors" as *;', + '', + '/**', + '* $name 100. Background color', + '* $type color', + '*/', + '$common-bg: $base-bg !default;', + '', + '@if $mode == "light" {', + ' $accent: #00f !default;', + '}', + '', + '@if $mode == "dark" {', + ' $accent: #0ff !default;', + '}', + )); +}); + +test('--fix takes the doc comment along when the declaration has to move', () => { + const { output } = fix('fix-doc-moves.scss', scss( + '/**', + '* $name 302. Divider color', + '* $type color', + '*/', + '$divider: null !default;', + '', + '$border: null !default;', + '', + '@if $mode == "light" {', + ' $border: #ccc !default;', + ' $divider: $border !default;', + '}', + '', + '@if $mode == "dark" {', + ' $border: #444 !default;', + ' $divider: $border !default;', + '}', + )); + + expect(output).toBe(scss( + '$border: null !default;', + '', + '@if $mode == "light" {', + ' $border: #ccc !default;', + '}', + '', + '@if $mode == "dark" {', + ' $border: #444 !default;', + '}', + '', + '/**', + '* $name 302. Divider color', + '* $type color', + '*/', + '$divider: $border !default;', + )); +}); diff --git a/packages/devextreme-scss/tests/stylelint-rule.ts b/packages/devextreme-scss/tests/stylelint-rule.ts new file mode 100644 index 000000000000..c8e8d5bafbf7 --- /dev/null +++ b/packages/devextreme-scss/tests/stylelint-rule.ts @@ -0,0 +1,49 @@ +import { execFileSync } from 'child_process'; +import { mkdtempSync, readFileSync, writeFileSync } from 'fs'; +import { tmpdir } from 'os'; +import { basename, join } from 'path'; + +export type Warning = { line: number; rule: string; text: string }; +export type Result = { warnings: Warning[]; output: string }; +export type Runner = { + lint: (name: string, source: string) => Result; + fix: (name: string, source: string) => Result; +}; + +const packageRoot = process.cwd(); +const stylelintBin = join(packageRoot, 'node_modules', '.bin', 'stylelint'); + +export const scss = (...rows: string[]): string => `${rows.join('\n')}\n`; + +export const createRunner = (pluginFile: string, ruleName: string): Runner => { + const fixture = mkdtempSync(join(tmpdir(), `${basename(pluginFile, '.mjs')}-`)); + const configPath = join(fixture, 'config.json'); + writeFileSync(configPath, JSON.stringify({ + customSyntax: require.resolve('postcss-scss', { paths: [require.resolve('stylelint-scss')] }), + plugins: [join(packageRoot, 'tools', 'stylelint', pluginFile)], + rules: { [ruleName]: true }, + })); + + const run = (name: string, source: string, fix: boolean): Result => { + const file = join(fixture, name); + const reportFile = join(fixture, `${name}.report.json`); + writeFileSync(file, source); + writeFileSync(reportFile, ''); + const args = [file, '--config', configPath, '--formatter', 'json', '--output-file', reportFile, ...(fix ? ['--fix'] : [])]; + try { + execFileSync(stylelintBin, args, { stdio: 'ignore' }); + } catch (error) { + if ((error as { status?: number }).status !== 2) throw error; + } + const [{ warnings }] = JSON.parse(readFileSync(reportFile, 'utf8') || '[{"warnings":[]}]'); + return { + warnings: warnings.map(({ line, rule, text }: Warning) => ({ line, rule, text })), + output: readFileSync(file, 'utf8'), + }; + }; + + return { + lint: (name, source) => run(name, source, false), + fix: (name, source) => run(name, source, true), + }; +}; diff --git a/packages/devextreme-scss/tests/unused-elements.test.ts b/packages/devextreme-scss/tests/unused-elements.test.ts index 559096b709b3..52180125fabf 100644 --- a/packages/devextreme-scss/tests/unused-elements.test.ts +++ b/packages/devextreme-scss/tests/unused-elements.test.ts @@ -47,24 +47,41 @@ const removeAllCommentsFromContent = (content: string): string => content .filter((_, index) => index % 2 === 0) .join(''); -const extractVariables = (filePath: string): string[] => { +const variableRegex = new RegExp(`\\$[${VAR_NAME_CHARS}]+`, 'y'); + +type VariableUsage = { declared: Set; read: Set }; + +const collectVariableUsage = (filePath: string, usage: VariableUsage): void => { const content = removeAllCommentsFromContent(readFileSync(filePath, 'utf8')); - const regex = new RegExp(`\\$[${VAR_NAME_CHARS}]+`, 'g'); - return content.match(regex) ?? []; + let depth = 0; + let index = 0; + while (index < content.length) { + const char = content[index]; + if (char === '(') depth += 1; + if (char === ')') depth -= 1; + if (char === '$') { + variableRegex.lastIndex = index; + const [variable] = variableRegex.exec(content) ?? []; + if (!variable) { + index += 1; + continue; + } + const assigned = /^\s*:/.test(content.slice(index + variable.length)); + if (assigned && depth === 0) usage.declared.add(variable); + if (!assigned) usage.read.add(variable); + index += variable.length; + continue; + } + index += 1; + } }; -const findUniqueVariables = (variables: string[]): string[] => { - const variableCounts = new Map(); - variables.forEach((variable) => { - variableCounts.set(variable, (variableCounts.get(variable) ?? 0) + 1); - }); - return Array.from(variableCounts.entries()) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // @ts-ignore - .filter(([variable, count]) => count === 1) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - // @ts-ignore - .map(([variable, count]) => variable); +const variableUsageOf = (directories: string[]): VariableUsage => { + const usage: VariableUsage = { declared: new Set(), read: new Set() }; + directories + .flatMap((directory) => getFilesFromDirectory(join('scss', 'widgets', directory), ['.scss'])) + .forEach((filePath) => collectVariableUsage(filePath, usage)); + return usage; }; test('There are no unused images in repository', () => { @@ -85,29 +102,14 @@ test('There are no unused images in repository', () => { expect(fullImagesFileList).toEqual(usedImagesFileList); }); -['generic', 'material', 'fluent'].forEach((themeName) => { - test(`There are no unused variables in ${themeName} SCSS files`, () => { - const baseScssFiles = getFilesFromDirectory(join('scss', 'widgets', 'base'), ['.scss']) - .map((fileName) => resolve(fileName)); - - const genericScssFiles = getFilesFromDirectory(join('scss', 'widgets', themeName), ['.scss']) - .map((fileName) => resolve(fileName)); - - const scssFiles = [...baseScssFiles, ...genericScssFiles]; - - let variables: string[] = []; - scssFiles.forEach((filePath) => { - variables = variables.concat(extractVariables(getFilePath(filePath.substring(filePath.indexOf('/scss'))))); - }); - - const uniqueVariables = findUniqueVariables(variables); +const themes = ['generic', 'material', 'fluent']; +const readAnywhere = variableUsageOf(['base', ...themes]).read; - const exclusions: { generic: string[]; material: string[]; fluent: string[] } = { - generic: [], - material: [], - fluent: [], - }; +themes.forEach((themeName) => { + test(`There are no unused variables in ${themeName} SCSS files`, () => { + const { declared } = variableUsageOf(['base', themeName]); + const unused = [...declared].filter((variable) => !readAnywhere.has(variable)).sort(); - expect(uniqueVariables).toEqual(exclusions[themeName]); + expect(unused).toEqual([]); }); }); diff --git a/packages/devextreme-scss/tools/stylelint/module-variable-default.mjs b/packages/devextreme-scss/tools/stylelint/module-variable-default.mjs new file mode 100644 index 000000000000..1bf97bfbf8ce --- /dev/null +++ b/packages/devextreme-scss/tools/stylelint/module-variable-default.mjs @@ -0,0 +1,55 @@ +import stylelint from 'stylelint'; + +const { + createPlugin, + utils: { report, ruleMessages, validateOptions }, +} = stylelint; + +const ruleName = 'dx/module-variable-default'; + +const messages = ruleMessages(ruleName, { + expected: (name) => `Expected the !default flag on "${name}": without it a value passed through @use … with() is ignored`, +}); + +const meta = { fixable: true }; + +const isConditional = (node) => node.type === 'atrule' && ['if', 'else'].includes(node.name); +const isModuleLevel = (decl) => { + let node = decl.parent; + while (isConditional(node)) node = node.parent; + return node.type === 'root'; +}; +const hasFlag = (decl, flag) => new RegExp(`!${flag}\\b`, 'i').test(decl.value); +const isNull = (decl) => /^null(\s+!default)?$/.test(decl.value.trim()); + +const ruleFunction = (primary) => (root, result) => { + if (!validateOptions(result, ruleName, { actual: primary, possible: [true] })) return; + + const assigned = new Set(); + + root.walkDecls((decl) => { + if (!decl.prop.startsWith('$') || !isModuleLevel(decl)) return; + + const reassignment = assigned.has(decl.prop); + if (decl.parent.type === 'root' && !isNull(decl)) assigned.add(decl.prop); + if (reassignment || hasFlag(decl, 'default') || hasFlag(decl, 'global')) return; + + report({ + message: messages.expected, + messageArgs: [decl.prop], + node: decl, + result, + ruleName, + fix: () => { + decl.value = `${decl.value} !default`; + if (decl.raws.value) decl.raws.value = { value: decl.value, raw: `${decl.raws.value.raw} !default` }; + }, + }); + }); +}; + +ruleFunction.ruleName = ruleName; +ruleFunction.messages = messages; +ruleFunction.meta = meta; + +export default createPlugin(ruleName, ruleFunction); diff --git a/packages/devextreme-scss/tools/stylelint/no-identical-branch-declarations.mjs b/packages/devextreme-scss/tools/stylelint/no-identical-branch-declarations.mjs new file mode 100644 index 000000000000..088b41e742bd --- /dev/null +++ b/packages/devextreme-scss/tools/stylelint/no-identical-branch-declarations.mjs @@ -0,0 +1,238 @@ +import stylelint from 'stylelint'; + +const { + createPlugin, + utils: { report, ruleMessages, validateOptions }, +} = stylelint; + +const ruleName = 'dx/no-identical-branch-declarations'; + +const messages = ruleMessages(ruleName, { + rejected: (name, count) => `"${name}" has the same value in all ${count} branches of the @if chain; declare it once outside the chain`, +}); + +const meta = { fixable: true }; + +const isElse = (node) => node.type === 'atrule' && node.name === 'else'; +const isIf = (node) => node.type === 'atrule' && node.name === 'if'; +const isVariable = (node) => node.type === 'decl' && node.prop.startsWith('$'); +const isEmpty = (block) => (block.nodes ?? []).length === 0; +const normalise = (value) => value.replace(/\s+/g, ' ').trim(); +const isNull = (value) => /^null(\s+!default)?$/.test(normalise(value)); +const hasDefaultFlag = (value) => /!default\b/.test(value); +const indentOf = (node) => (node.raws.before ?? '').match(/[ \t]*$/)[0]; + +const localReferences = (value) => [...value.matchAll(/(^|[^\w.-])(\$[\w-]+)/g)].map((match) => match[2]); + +const equalityTest = (atRule) => { + const match = atRule.params.trim().match(/^(\$[\w-]+)\s*==\s*("[^"]*"|'[^']*'|[\w.-]+)$/); + return match && { variable: match[1], literal: match[2] }; +}; + +const chainOf = (ifRule) => { + const branches = [ifRule]; + let next = ifRule.next(); + + while (next && (next.type === 'comment' || isElse(next))) { + if (isElse(next)) branches.push(next); + next = next.next(); + } + if (branches.length > 1) return { branches, independent: false }; + + const test = equalityTest(ifRule); + const literals = new Set([test?.literal]); + + while (test && next && (next.type === 'comment' || isIf(next))) { + if (isIf(next)) { + const other = equalityTest(next); + if (other?.variable !== test.variable || literals.has(other.literal)) break; + literals.add(other.literal); + branches.push(next); + } + next = next.next(); + } + return { branches, independent: true }; +}; + +const collectChains = (root) => { + const chains = []; + const seen = new WeakSet(); + + root.walkAtRules('if', (ifRule) => { + if (seen.has(ifRule)) return; + const chain = chainOf(ifRule); + chain.branches.forEach((branch) => seen.add(branch)); + if (chain.branches.length > 1) chains.push(chain); + }); + return chains.reverse(); +}; + +const directDeclarations = (block) => { + const byName = new Map(); + (block.nodes ?? []).filter(isVariable).forEach((decl) => byName.set(decl.prop, decl)); + return byName; +}; + +const identicalDeclarations = (branches) => { + const [first, ...rest] = branches.map(directDeclarations); + const identical = new Map(); + + for (const [name, decl] of first) { + const declarations = [decl, ...rest.map((byName) => byName.get(name))]; + const value = normalise(decl.value); + if (declarations.every((other) => other && normalise(other.value) === value)) identical.set(name, declarations); + } + return identical; +}; + +const walkFinds = (branches, predicate) => branches.some((branch) => { + let found = false; + branch.walkDecls((decl) => { + if (!predicate(decl, branch)) return undefined; + found = true; + return false; + }); + return found; +}); + +const assignedInside = (branches, name) => walkFinds(branches, (decl) => decl.prop === name); + +const planMoves = (branches, identical) => { + const movable = new Set(identical.keys()); + const placement = new Map(); + + const settle = () => { + placement.clear(); + for (const name of movable) { + const behind = localReferences(identical.get(name)[0].value).some((ref) => ( + movable.has(ref) ? placement.get(ref) === 'after' : assignedInside(branches, ref) + )); + placement.set(name, behind ? 'after' : 'before'); + } + }; + const readByStaying = (name) => walkFinds(branches, (decl, branch) => ( + !(movable.has(decl.prop) && decl.parent === branch) && localReferences(decl.value).includes(name) + )); + + for (let changed = true; changed;) { + settle(); + changed = false; + for (const name of movable) { + if (placement.get(name) === 'after' && readByStaying(name)) { + movable.delete(name); + changed = true; + } + } + } + return { movable, placement }; +}; + +const earlierDeclaration = (parent, ifRule, name) => { + let found; + for (const sibling of parent.nodes) { + if (sibling === ifRule) break; + if (isVariable(sibling) && sibling.prop === name) found = sibling; + } + return found; +}; + +const declaredBetween = (parent, after, before, name) => parent.nodes + .slice(parent.index(after) + 1, parent.index(before)) + .some((node) => isVariable(node) && node.prop === name); + +const attachedComment = (decl) => { + const previous = decl.prev(); + const attached = previous?.type === 'comment' && !/\n[ \t]*\n/.test(decl.raws.before ?? ''); + return attached ? previous : undefined; +}; + +const dropEmptyBranches = (branches, independent) => { + if (independent) { + branches.filter(isEmpty).forEach((branch) => branch.remove()); + return; + } + while (branches.length && isEmpty(branches[branches.length - 1])) { + branches.pop().remove(); + } +}; + +const ruleFunction = (primary) => (root, result) => { + if (!validateOptions(result, ruleName, { actual: primary, possible: [true] })) return; + + const lastHoisted = new WeakMap(); + + for (const { branches, independent } of collectChains(root)) { + const ifRule = branches[0]; + const { parent } = ifRule; + const identical = identicalDeclarations(branches); + const { movable, placement } = planMoves(branches, identical); + + for (const [name, declarations] of identical) { + const [first] = declarations; + const earlier = earlierDeclaration(parent, ifRule, name); + const overridden = earlier !== undefined && !isNull(earlier.value); + const fixable = overridden ? declarations.every((decl) => hasDefaultFlag(decl.value)) : movable.has(name); + + const fix = () => { + const live = branches.filter((branch) => branch.parent); + const tidy = () => { + dropEmptyBranches(live, independent); + live.forEach((branch) => { + if (branch.parent && branch.first?.type === 'decl') branch.first.raws.before = branch.first.raws.before.replace(/^\s*\n/, '\n'); + }); + parent.raws.semicolon = true; + if (parent.type === 'root' && parent.first) parent.first.raws.before = ''; + }; + + if (overridden) { + declarations.forEach((decl) => decl.remove()); + tidy(); + return; + } + + const hoisted = first.clone(); + const references = localReferences(first.value); + const comment = earlier && attachedComment(earlier); + declarations.forEach((decl) => decl.remove()); + + const relocate = (insert, gap) => { + const indent = indentOf(ifRule); + const moved = comment?.clone({ raws: { ...comment.raws, before: `${gap}${indent}` } }); + hoisted.raws.before = moved ? `\n${indent}` : `${gap}${indent}`; + if (moved) insert(moved); + insert(hoisted, moved); + comment?.remove(); + earlier?.remove(); + }; + + if (placement.get(name) === 'after') { + const anchor = lastHoisted.get(ifRule) ?? live[live.length - 1]; + relocate((node, previous) => parent.insertAfter(previous ?? anchor, node), lastHoisted.has(ifRule) ? '\n' : '\n\n'); + lastHoisted.set(ifRule, hoisted); + } else if (earlier && !references.some((ref) => declaredBetween(parent, earlier, ifRule, ref))) { + hoisted.raws.before = earlier.raws.before; + earlier.replaceWith(hoisted); + } else { + relocate((node, previous) => (previous ? parent.insertAfter(previous, node) : parent.insertBefore(ifRule, node)), '\n'); + } + + tidy(); + }; + + report({ + message: messages.rejected, + messageArgs: [name, branches.length], + node: first, + result, + ruleName, + ...(fixable && { fix: { apply: fix, node: parent } }), + }); + } + } +}; + +ruleFunction.ruleName = ruleName; +ruleFunction.messages = messages; +ruleFunction.meta = meta; + +export default createPlugin(ruleName, ruleFunction); diff --git a/packages/devextreme-themebuilder/src/metadata/generator.ts b/packages/devextreme-themebuilder/src/metadata/generator.ts index 2a328f6a1451..446da5b8fb70 100644 --- a/packages/devextreme-themebuilder/src/metadata/generator.ts +++ b/packages/devextreme-themebuilder/src/metadata/generator.ts @@ -112,7 +112,7 @@ export default class MetadataGenerator { }); const overriddenVariables = MetadataGenerator.getOverriddenVariables(metaItems); - const collector = `$never-used: collector(${MetadataGenerator.getMapFromMeta(metaItems)});\n`; + const collector = `$_never-used: collector(${MetadataGenerator.getMapFromMeta(metaItems)});\n`; modifiedContent = overriddenVariables + '\n\n' + modifiedContent + collector; return modifiedContent; diff --git a/packages/devextreme-themebuilder/src/modules/post-compiler.ts b/packages/devextreme-themebuilder/src/modules/post-compiler.ts index b0357609a572..a88720fdc708 100644 --- a/packages/devextreme-themebuilder/src/modules/post-compiler.ts +++ b/packages/devextreme-themebuilder/src/modules/post-compiler.ts @@ -68,8 +68,10 @@ export function fixSwatchCss(css: string | Buffer, swatchClass: string, colorSch const swatchOrderRegex = new RegExp(`([ \\t]*)([\\w\\.#:\\*][\\w\\.#:\\*\\->()\\s]*)(${escapedSelector}\\s)([^,{+~]*)`, 'gm'); const changeTypographyRulesOrderRegex = /(\.dx-swatch-.*?)\s(\.dx-theme-.*?-typography)(.*?)(\s{|,)/g; const themeMarkerRegex = /(\.dx-theme-marker\s*{\s*font-family:\s*['"]dx\..*?\.)(.*)(['"])/g; + const rootSelectorRegex = new RegExp(`${escapedSelector}\\s+:root\\b`, 'g'); result = result + .replace(rootSelectorRegex, swatchClass) .replace(swatchOrderRegex, '$1$3$2$4') .replace(changeTypographyRulesOrderRegex, '$2 $1$3,$2$1$3$4') .replace(themeMarkerRegex, `$1${colorScheme}$3`); diff --git a/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/_colors.scss b/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/_colors.scss index e15f6dbe91cd..7d115ebee381 100644 --- a/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/_colors.scss +++ b/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/_colors.scss @@ -18,7 +18,7 @@ $float-channel-color: null !default; } -$never-used: collector( +$_never-used: collector( ( "$base-font-family": $base-font-family, "$base-accent": $base-accent, diff --git a/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/accordion/_colors.scss b/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/accordion/_colors.scss index d671f1015680..e08a47d95169 100644 --- a/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/accordion/_colors.scss +++ b/packages/devextreme-themebuilder/tests/data/scss/widgets/generic/accordion/_colors.scss @@ -5,8 +5,6 @@ $accordion-item-title-opened-bg: getCustomVar(("$accordion-item-title-opened-bg" @use "../sizes" as *; @use "../colors" as *; -// adduse - $accordion-title-color: null !default; $accordion-item-title-opened-bg: null !default; @@ -15,7 +13,7 @@ $accordion-item-title-opened-bg: null !default; $accordion-item-title-opened-bg: transparent !default; } -$never-used: collector( +$_never-used: collector( ( "$accordion-title-color": $accordion-title-color, "$accordion-item-title-opened-bg": $accordion-item-title-opened-bg, diff --git a/packages/devextreme-themebuilder/tests/metadata/generator.test.ts b/packages/devextreme-themebuilder/tests/metadata/generator.test.ts index ee6b3aa83eb0..4b0cb63f446f 100644 --- a/packages/devextreme-themebuilder/tests/metadata/generator.test.ts +++ b/packages/devextreme-themebuilder/tests/metadata/generator.test.ts @@ -200,7 +200,7 @@ $slideout-background: #000 !default; * $type color */ $slideout-background: #000 !default; -$never-used: collector(( +$_never-used: collector(( "$slideout-background": $slideout-background, )); `; diff --git a/packages/devextreme-themebuilder/tests/modules/builder.test.ts b/packages/devextreme-themebuilder/tests/modules/builder.test.ts index b4f0f8ac7620..8adeb9deb6d3 100644 --- a/packages/devextreme-themebuilder/tests/modules/builder.test.ts +++ b/packages/devextreme-themebuilder/tests/modules/builder.test.ts @@ -38,6 +38,8 @@ describe('Builder integration tests', () => { return buildTheme(config).then((result) => { expect(result.css).not.toBe(''); expect(result.swatchSelector).toBe('.dx-swatch-custom-scheme'); + expect(result.css).not.toMatch(/\.dx-swatch-custom-scheme\s*:root/); + expect(result.css).toMatch(/\.dx-swatch-custom-scheme\s*\{[^}]*--dx-color-primary:/); }); }, buildTimeout); @@ -122,6 +124,22 @@ describe('Builder integration tests', () => { }); }, buildTimeout); + test('Theme built without parameters is the same that in distribution (fluent)', async () => { + const config: ConfigSettings = { + command: commands.BUILD_THEME, + outputColorScheme: 'custom-scheme', + baseTheme: 'fluent.blue.light', + items: [], + }; + + return buildTheme(config).then((result) => { + const themeBuilderCss = normalizeCss(result.css); + const cssPath = path.resolve(__dirname, '../../../devextreme/artifacts/css/dx.fluent.blue.light.css'); + const distributionCss = normalizeCss(readFileSync(cssPath, 'utf8')); + expect(themeBuilderCss).toBe(distributionCss); + }); + }, buildTimeout); + test('Theme built without parameters is the same that in distribution (material)', async () => { const config: ConfigSettings = { command: commands.BUILD_THEME, diff --git a/packages/devextreme-themebuilder/tests/modules/post-compiler.test.ts b/packages/devextreme-themebuilder/tests/modules/post-compiler.test.ts index 41717ff21294..de51a0368c62 100644 --- a/packages/devextreme-themebuilder/tests/modules/post-compiler.test.ts +++ b/packages/devextreme-themebuilder/tests/modules/post-compiler.test.ts @@ -163,6 +163,96 @@ describe('PostCompiler - swatch features (fixSwatchCss)', () => { expect(result).toBe(expectedCss); }); + test('fixSwatchCss - apply custom properties to the swatch class instead of :root (T1334644)', () => { + const compilerCss = ` +.dx-swatch-c :root { + --dx-color-primary: #fff; + --dx-color-text: #000; +}`; + + const expectedCss = ` +.dx-swatch-c { + --dx-color-primary: #fff; + --dx-color-text: #000; +}`; + + const result = fixSwatchCss(compilerCss, '.dx-swatch-c', 'c'); + expect(result).toBe(expectedCss); + }); + + test('fixSwatchCss - do not change the order of cascade\'s classes in rules next to :root', () => { + const compilerCss = ` +.dx-swatch-c .dx-widget { + color: #fff; +} +.dx-swatch-c :root { + --dx-color-primary: #fff; +} +.dx-swatch-c .dx-checkbox-checked.dx-checkbox-icon { + background-color: #fff; +}`; + + const expectedCss = ` +.dx-swatch-c .dx-widget { + color: #fff; +} +.dx-swatch-c { + --dx-color-primary: #fff; +} +.dx-swatch-c .dx-checkbox-checked.dx-checkbox-icon { + background-color: #fff; +}`; + + const result = fixSwatchCss(compilerCss, '.dx-swatch-c', 'c'); + expect(result).toBe(expectedCss); + }); + + test('fixSwatchCss - apply compound :root selectors to the swatch class itself', () => { + const compilerCss = ` +.dx-swatch-c :root.dx-foo { + --dx-color-primary: #fff; +} +.dx-swatch-c :root:not(.dx-bar) { + --dx-color-text: #000; +} +.dx-swatch-c :root .dx-widget { + color: #fff; +}`; + + const expectedCss = ` +.dx-swatch-c.dx-foo { + --dx-color-primary: #fff; +} +.dx-swatch-c:not(.dx-bar) { + --dx-color-text: #000; +} +.dx-swatch-c .dx-widget { + color: #fff; +}`; + + const result = fixSwatchCss(compilerCss, '.dx-swatch-c', 'c'); + expect(result).toBe(expectedCss); + }); + + test('fixSwatchCss - apply custom properties to the swatch class inside media queries', () => { + const compilerCss = ` +@media (max-width: 600px) { + .dx-swatch-c :root { + --dx-color-primary: #fff; + } +}`; + + const expectedCss = ` +@media (max-width: 600px) { + .dx-swatch-c { + --dx-color-primary: #fff; + } +}`; + + const result = fixSwatchCss(compilerCss, '.dx-swatch-c', 'c'); + expect(result).toBe(expectedCss); + }); + test('fixSwatchCss - fix theme marker in swatch css', () => { const compilerCss = '.dx-swatch-c .dx-theme-marker { font-family: \'dx.generic.light\'; }'; diff --git a/packages/devextreme/docker-ci.sh b/packages/devextreme/docker-ci.sh index 28f51484ee3c..4944b701684c 100755 --- a/packages/devextreme/docker-ci.sh +++ b/packages/devextreme/docker-ci.sh @@ -40,7 +40,8 @@ function run_test { function run_test_impl { local port=`node -e "console.log(require('./ports.json').qunit)"` - local url="http://0.0.0.0:$port/run?notimers=true" + # Use 127.0.0.1, not 0.0.0.0 — Chrome cannot fetch modules from 0.0.0.0. + local url="http://127.0.0.1:$port/run?notimers=true" local runner_pid local runner_result=0 diff --git a/packages/devextreme/js/__internal/common/core/animation/frame.ts b/packages/devextreme/js/__internal/common/core/animation/frame.ts index e3c7f7023bb2..2ab76ff12122 100644 --- a/packages/devextreme/js/__internal/common/core/animation/frame.ts +++ b/packages/devextreme/js/__internal/common/core/animation/frame.ts @@ -17,11 +17,13 @@ const window: ExtendedWindow = (hasWindow() ? getWindow() : {}) as ExtendedWindo const FRAME_ANIMATION_STEP_TIME = 1000 / 60; +// eslint-disable-next-line func-names -- description seam for tests let request = function (callback: FrameRequestCallback): number { /* eslint-disable no-restricted-globals */ return setTimeout(callback, FRAME_ANIMATION_STEP_TIME); }; +// eslint-disable-next-line func-names -- description seam for tests let cancel = function (requestID: number): void { clearTimeout(requestID); }; @@ -45,15 +47,37 @@ const setAnimationFrameMethods = callOnce(() => { } }); -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function requestAnimationFrame(...args): number { +/* eslint-disable-next-line + @typescript-eslint/explicit-module-boundary-types, import/no-mutable-exports + -- description seam for tests */ +export let requestAnimationFrame = function (...args): number { setAnimationFrameMethods(); // @ts-ignore return request.apply(window, args); -} +}; -export function cancelAnimationFrame(requestID: number): void { +/* eslint-disable-next-line + import/no-mutable-exports + -- description seam for tests */ +export let cancelAnimationFrame = function (requestID: number): void { setAnimationFrameMethods(); cancel.apply(window, [requestID]); +}; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_requestAnimationFrame( + value: typeof requestAnimationFrame, +): void { + requestAnimationFrame = value; +} +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_cancelAnimationFrame( + value: typeof cancelAnimationFrame, +): void { + cancelAnimationFrame = value; } +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/common/core/animation/m_position.ts b/packages/devextreme/js/__internal/common/core/animation/m_position.ts index 0c4089d10c8d..ce6207053ab0 100644 --- a/packages/devextreme/js/__internal/common/core/animation/m_position.ts +++ b/packages/devextreme/js/__internal/common/core/animation/m_position.ts @@ -2,7 +2,6 @@ import { move, resetPosition } from '@js/common/core/animation/translator'; import devices from '@js/core/devices'; import domAdapter from '@js/core/dom_adapter'; import $ from '@js/core/renderer'; -import browser from '@js/core/utils/browser'; import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { getBoundingRect } from '@js/core/utils/position'; @@ -21,7 +20,6 @@ const horzRe = /left|right/; const vertRe = /top|bottom/; const collisionRe = /fit|flip|none/; const scaleRe = /scale\(.+?\)/; -const IS_SAFARI = browser.safari; const normalizeAlign = function (raw) { const result = { @@ -258,14 +256,18 @@ const calculatePosition = function (what, options) { if (isWindow(of[0])) { h.atLocation = of.scrollLeft(); v.atLocation = of.scrollTop(); - if (devices.real().deviceType === 'phone' && of[0].visualViewport) { + const device = devices.real(); + + if (device.deviceType === 'phone' && of[0].visualViewport) { h.atLocation = Math.max(h.atLocation, of[0].visualViewport.offsetLeft); v.atLocation = Math.max(v.atLocation, of[0].visualViewport.offsetTop); h.atSize = of[0].visualViewport.width; v.atSize = of[0].visualViewport.height; } else { - h.atSize = of[0].innerWidth > of[0].outerWidth ? of[0].innerWidth : getWidth(of); - v.atSize = of[0].innerHeight > of[0].outerHeight || IS_SAFARI ? of[0].innerHeight : getHeight(of); + const isIos = device.platform === 'ios'; + + h.atSize = getWidth(of); + v.atSize = isIos ? of[0].innerHeight : getHeight(of); } } else if (of[0].nodeType === 9) { h.atLocation = 0; diff --git a/packages/devextreme/js/__internal/common/core/animation/translator.ts b/packages/devextreme/js/__internal/common/core/animation/translator.ts index 001375a94dc6..306152f9b687 100644 --- a/packages/devextreme/js/__internal/common/core/animation/translator.ts +++ b/packages/devextreme/js/__internal/common/core/animation/translator.ts @@ -124,7 +124,8 @@ export const move = function ( } }; -export const resetPosition = function ( +/* eslint-disable import/no-mutable-exports -- description seam for tests */ +export let resetPosition = function ( $element: dxElementWrapper | Element | undefined, finishTransition?: boolean, ): void { @@ -170,3 +171,11 @@ export const parseTranslate = function (translateString: string): TranslateVecto z: parseFloat(result[2]), }; }; + +/// #DEBUG +/* eslint-disable @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_resetPosition(value: typeof resetPosition): void { + resetPosition = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/core/events_strategy.ts b/packages/devextreme/js/__internal/core/events_strategy.ts new file mode 100644 index 000000000000..fc3ef62ca88d --- /dev/null +++ b/packages/devextreme/js/__internal/core/events_strategy.ts @@ -0,0 +1,104 @@ +import { each } from '@js/core/utils/iterator'; +import { isFunction, isPlainObject } from '@js/core/utils/type'; +import { Callbacks } from '@ts/core/utils/m_callbacks'; + +type EventHandler = Function; + +type EventHandlers = Record; + +interface EventCallbacks { + add: (fn?: EventHandler) => void; + originalAdd?: (fn?: EventHandler) => void; + remove: (fn?: EventHandler) => void; + has: () => boolean; + empty: () => void; + fireWith: (context: unknown, args?: ArrayLike) => void; +} + +export interface EventsStrategyOptions { + syncStrategy?: boolean; +} + +export interface EventsStrategyInterface { + on: (eventName: string | EventHandlers, eventHandler?: EventHandler) => void; + off: (eventName: string, eventHandler?: EventHandler) => void; + fireEvent: (eventName: string, eventArgs?: ArrayLike) => unknown; + hasEvent: (eventName: string) => boolean; + dispose: () => void; +} + +export class EventsStrategy implements EventsStrategyInterface { + _events: Record; + + _owner: unknown; + + _options: EventsStrategyOptions; + + constructor(owner: unknown, options: EventsStrategyOptions = {}) { + this._events = {}; + this._owner = owner; + this._options = options; + } + + static create( + owner: unknown, + strategy?: EventsStrategyInterface | ((owner: unknown) => EventsStrategyInterface), + ): EventsStrategyInterface { + if (strategy) { + return isFunction(strategy) ? strategy(owner) : strategy; + } + return new EventsStrategy(owner); + } + + hasEvent(eventName: string): boolean { + const callbacks = this._events[eventName]; + return callbacks ? callbacks.has() : false; + } + + fireEvent(eventName: string, eventArgs?: ArrayLike): unknown { + const callbacks = this._events[eventName]; + if (callbacks) { + callbacks.fireWith(this._owner, eventArgs); + } + return this._owner; + } + + on(eventName: string | EventHandlers, eventHandler?: EventHandler): void { + if (isPlainObject(eventName)) { + each(eventName, (e, h) => { + this.on(e, h); + }); + } else { + let callbacks = this._events[eventName]; + + if (!callbacks) { + callbacks = Callbacks({ + syncStrategy: this._options.syncStrategy, + }); + this._events[eventName] = callbacks; + } + + const addFn = callbacks.originalAdd || callbacks.add; + addFn.call(callbacks, eventHandler); + } + } + + off(eventName: string, eventHandler?: EventHandler): void { + const callbacks = this._events[eventName]; + if (callbacks) { + if (isFunction(eventHandler)) { + callbacks.remove(eventHandler); + } else { + callbacks.empty(); + } + } + } + + dispose(): void { + each(this._events, (eventName, event) => { + event.empty(); + }); + + this._owner = null; + } +} diff --git a/packages/devextreme/js/__internal/core/localization/ldml/date.parser.ts b/packages/devextreme/js/__internal/core/localization/ldml/date.parser.ts index 941d4ee6970e..43752f07fbba 100644 --- a/packages/devextreme/js/__internal/core/localization/ldml/date.parser.ts +++ b/packages/devextreme/js/__internal/core/localization/ldml/date.parser.ts @@ -235,7 +235,8 @@ export const isPossibleForParsingFormat = (patterns: string[]): boolean => { }); }; -export const getRegExpInfo = ( +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let getRegExpInfo = ( format: string, dateParts: LdlmDateLocalization, ): { @@ -381,3 +382,11 @@ export const getParser = (format: string, dateParts: LdlmDateLocalization) => { return null; }; }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_getRegExpInfo(value: typeof getRegExpInfo): void { + getRegExpInfo = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/core/localization/number.ts b/packages/devextreme/js/__internal/core/localization/number.ts index 53631e00f316..67c8f5bbede1 100644 --- a/packages/devextreme/js/__internal/core/localization/number.ts +++ b/packages/devextreme/js/__internal/core/localization/number.ts @@ -286,7 +286,7 @@ const numberLocalizationBase = { return value.replace(regExp, (char) => String.fromCharCode(char.charCodeAt(0) + (toFirstDigit.charCodeAt(0) - fromFirstDigit.charCodeAt(0)))) as TValue; }, - getNegativeEtalonRegExp(format: FormatConfig | string): RegExp { + getNegativeEtalonRegExp(format: LocalizationFormat): RegExp { const separators: FormatterConfig = this._getSeparators(); const digitalRegExp = new RegExp(`[0-9${escapeRegExp(separators.decimalSeparator + separators.thousandsSeparator)}]+`, 'g'); const specialCharacters = ['\\', '(', ')', '[', ']', '*', '+', '$', '^', '?', '|', '{', '}']; @@ -301,7 +301,7 @@ const numberLocalizationBase = { return new RegExp(negativeEtalon, 'g'); }, - getSign(text: string, format?: FormatConfig | string): 1 | -1 { + getSign(text: string, format?: LocalizationFormat): 1 | -1 { if (!format) { if (text.replace(/[^0-9-]/g, '').startsWith('-')) { return -1; diff --git a/packages/devextreme/js/__internal/core/m_events_strategy.ts b/packages/devextreme/js/__internal/core/m_events_strategy.ts deleted file mode 100644 index 8a5e59ab5192..000000000000 --- a/packages/devextreme/js/__internal/core/m_events_strategy.ts +++ /dev/null @@ -1,76 +0,0 @@ -import Callbacks from '@js/core/utils/callbacks'; -import { each } from '@js/core/utils/iterator'; -import { isFunction, isPlainObject } from '@js/core/utils/type'; - -export class EventsStrategy { - _events: any; - - _owner: any; - - _options: any; - - constructor(owner, options = {}) { - this._events = {}; - this._owner = owner; - this._options = options; - } - - static create(owner, strategy) { - if (strategy) { - return isFunction(strategy) ? strategy(owner) : strategy; - } - return new EventsStrategy(owner); - } - - hasEvent(eventName) { - const callbacks = this._events[eventName]; - return callbacks ? callbacks.has() : false; - } - - fireEvent(eventName, eventArgs) { - const callbacks = this._events[eventName]; - if (callbacks) { - callbacks.fireWith(this._owner, eventArgs); - } - return this._owner; - } - - on(eventName, eventHandler) { - if (isPlainObject(eventName)) { - each(eventName, (e, h) => { - this.on(e, h); - }); - } else { - let callbacks = this._events[eventName]; - - if (!callbacks) { - callbacks = Callbacks({ - syncStrategy: this._options.syncStrategy, - }); - this._events[eventName] = callbacks; - } - - const addFn = callbacks.originalAdd || callbacks.add; - addFn.call(callbacks, eventHandler); - } - } - - off(eventName, eventHandler) { - const callbacks = this._events[eventName]; - if (callbacks) { - if (isFunction(eventHandler)) { - callbacks.remove(eventHandler); - } else { - callbacks.empty(); - } - } - } - - dispose() { - each(this._events, (eventName, event) => { - event.empty(); - }); - - this._owner = null; - } -} diff --git a/packages/devextreme/js/__internal/core/utils/__tests__/document_size_callbacks.test.ts b/packages/devextreme/js/__internal/core/utils/__tests__/document_size_callbacks.test.ts new file mode 100644 index 000000000000..2b5f4d05e1cf --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/__tests__/document_size_callbacks.test.ts @@ -0,0 +1,161 @@ +import { + afterEach, describe, expect, it, jest, +} from '@jest/globals'; +import domAdapter from '@js/core/dom_adapter'; +import documentSizeCallbacks from '@ts/core/utils/document_size_callbacks'; + +type DocumentElement = ReturnType; + +interface Environment { + documentElement: { clientWidth: number; clientHeight: number }; + resizeVisualViewport: () => void; + addEventListenerSpy: jest.SpiedFunction; + removeEventListenerSpy: jest.SpiedFunction; +} + +function setup(): Environment { + const documentElement = { clientWidth: 1000, clientHeight: 800 }; + const visualViewport = new EventTarget(); + + jest.spyOn(domAdapter, 'getDocumentElement') + .mockImplementation(() => documentElement as unknown as DocumentElement); + Object.defineProperty(window, 'visualViewport', { value: visualViewport, configurable: true }); + window.innerWidth = 1024; + window.innerHeight = 768; + + return { + documentElement, + resizeVisualViewport: (): void => { visualViewport.dispatchEvent(new Event('resize')); }, + addEventListenerSpy: jest.spyOn(visualViewport, 'addEventListener'), + removeEventListenerSpy: jest.spyOn(visualViewport, 'removeEventListener'), + }; +} + +describe('documentSizeCallbacks', () => { + const addedHandlers: (() => void)[] = []; + + function addHandler(): jest.Mock { + const handler = jest.fn(); + + addedHandlers.push(handler); + documentSizeCallbacks.add(handler); + + return handler; + } + + afterEach(() => { + addedHandlers.splice(0).forEach((handler) => documentSizeCallbacks.remove(handler)); + delete (window as { visualViewport?: VisualViewport }).visualViewport; + jest.restoreAllMocks(); + }); + + it('should listen to the visual viewport resize once, no matter how many handlers are added', () => { + const { addEventListenerSpy } = setup(); + + addHandler(); + addHandler(); + + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + expect(addEventListenerSpy.mock.calls[0][0]).toBe('resize'); + }); + + it('should not call handlers when the document size has not changed', () => { + const { resizeVisualViewport } = setup(); + const handler = addHandler(); + + resizeVisualViewport(); + + expect(handler).not.toHaveBeenCalled(); + }); + + it('should call handlers when a scrollbar changes the client width', () => { + const { documentElement, resizeVisualViewport } = setup(); + const handler = addHandler(); + + documentElement.clientWidth = 985; + resizeVisualViewport(); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('should call handlers when a scrollbar changes the client height', () => { + const { documentElement, resizeVisualViewport } = setup(); + const handler = addHandler(); + + documentElement.clientHeight = 785; + resizeVisualViewport(); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('should leave a window resize to the window resize callbacks', () => { + const { documentElement, resizeVisualViewport } = setup(); + const handler = addHandler(); + + window.innerWidth = 1224; + documentElement.clientWidth = 1200; + resizeVisualViewport(); + + expect(handler).not.toHaveBeenCalled(); + + documentElement.clientWidth = 1185; + resizeVisualViewport(); + + expect(handler).toHaveBeenCalledTimes(1); + }); + + it('should hold a handler once even when it is added twice', () => { + const { documentElement, resizeVisualViewport, removeEventListenerSpy } = setup(); + const handler = addHandler(); + + documentSizeCallbacks.add(handler); + + documentElement.clientWidth = 985; + resizeVisualViewport(); + + expect(handler).toHaveBeenCalledTimes(1); + + documentSizeCallbacks.remove(handler); + + expect(removeEventListenerSpy).toHaveBeenCalledTimes(1); + }); + + it('should stop listening once the last handler is removed', () => { + const { removeEventListenerSpy } = setup(); + const first = addHandler(); + + addHandler(); + documentSizeCallbacks.remove(first); + + expect(removeEventListenerSpy).not.toHaveBeenCalled(); + + documentSizeCallbacks.remove(addedHandlers[1]); + + expect(removeEventListenerSpy).toHaveBeenCalledTimes(1); + }); + + it('should measure the document anew when the first handler is added again', () => { + const { documentElement, resizeVisualViewport } = setup(); + const first = addHandler(); + + documentSizeCallbacks.remove(first); + documentElement.clientWidth = 985; + + const second = addHandler(); + + resizeVisualViewport(); + + expect(second).not.toHaveBeenCalled(); + }); + + it('should do nothing when the browser has no visual viewport', () => { + setup(); + delete (window as { visualViewport?: VisualViewport }).visualViewport; + + const listenSpy = jest.spyOn(domAdapter, 'listen'); + const handler = addHandler(); + + expect(listenSpy).not.toHaveBeenCalled(); + expect(() => documentSizeCallbacks.remove(handler)).not.toThrow(); + }); +}); diff --git a/packages/devextreme/js/__internal/core/utils/document_size_callbacks.ts b/packages/devextreme/js/__internal/core/utils/document_size_callbacks.ts new file mode 100644 index 000000000000..a9fcb7f7015d --- /dev/null +++ b/packages/devextreme/js/__internal/core/utils/document_size_callbacks.ts @@ -0,0 +1,99 @@ +import domAdapter from '@js/core/dom_adapter'; +import { Callbacks } from '@ts/core/utils/m_callbacks'; +import windowUtils from '@ts/core/utils/m_window'; + +interface Size { + width: number; + height: number; +} + +type DocumentSizeHandler = () => void; + +interface HandlerList { + add: (handler: DocumentSizeHandler) => void; + remove: (handler: DocumentSizeHandler) => void; + has: () => boolean; + fire: () => void; +} + +const callbacks = Callbacks({ unique: true }) as HandlerList; + +let removeListener: (() => void) | null = null; +let previousDocumentSize: Size | null = null; +let previousWindowSize: Size | null = null; + +function getDocumentSize(): Size { + const documentElement = domAdapter.getDocumentElement(); + + return { + width: documentElement.clientWidth, + height: documentElement.clientHeight, + }; +} + +function getWindowSize(): Size { + const window: Window = windowUtils.getWindow(); + + return { + width: window.innerWidth, + height: window.innerHeight, + }; +} + +function isSameSize(previous: Size | null, current: Size): boolean { + return previous?.width === current.width && previous?.height === current.height; +} + +function handleVisualViewportResize(): void { + const documentSize = getDocumentSize(); + const windowSize = getWindowSize(); + const isDocumentSizeChanged = !isSameSize(previousDocumentSize, documentSize); + const isWindowSizeChanged = !isSameSize(previousWindowSize, windowSize); + + previousDocumentSize = documentSize; + previousWindowSize = windowSize; + + if (isDocumentSizeChanged && !isWindowSizeChanged) { + callbacks.fire(); + } +} + +function getVisualViewport(): VisualViewport | null { + if (!windowUtils.hasWindow()) { + return null; + } + + const window: Window = windowUtils.getWindow(); + + return window.visualViewport; +} + +function add(handler: DocumentSizeHandler): void { + const visualViewport = getVisualViewport(); + + if (!visualViewport) { + return; + } + + callbacks.add(handler); + + if (!removeListener) { + previousDocumentSize = getDocumentSize(); + previousWindowSize = getWindowSize(); + removeListener = domAdapter.listen(visualViewport, 'resize', handleVisualViewportResize); + } +} + +function remove(handler: DocumentSizeHandler): void { + callbacks.remove(handler); + + if (removeListener && !callbacks.has()) { + removeListener(); + removeListener = null; + previousDocumentSize = null; + previousWindowSize = null; + } +} + +export const documentSizeCallbacks = { add, remove }; +export default documentSizeCallbacks; diff --git a/packages/devextreme/js/__internal/core/utils/m_icon.ts b/packages/devextreme/js/__internal/core/utils/m_icon.ts index 41e144fa31e9..9a41ae01a46b 100644 --- a/packages/devextreme/js/__internal/core/utils/m_icon.ts +++ b/packages/devextreme/js/__internal/core/utils/m_icon.ts @@ -1,8 +1,14 @@ +import messageLocalization from '@js/common/core/localization/message'; import $ from '@js/core/renderer'; +import { camelize } from '@js/core/utils/inflector'; export const ICON_CLASS = 'dx-icon'; const SVG_ICON_CLASS = 'dx-svg-icon'; +const NOT_URL_REGEXP = /^(?!(?:https?:\/\/)|(?:ftp:\/\/)|(?:www\.))[^\s]+$/; +const FILE_NAME_REGEXP = /.+\/([^.]+)\..+$/; +const SVG_TITLE_REGEXP = /(.+?)<\/title>/; + export const getImageSourceType = (source) => { if (!source || typeof source !== 'string') { return false; @@ -41,3 +47,21 @@ export const getImageContainer = (source) => { return null; } }; + +export const getImageAriaLabel = (source: string): string | undefined => { + switch (getImageSourceType(source)) { + case 'image': { + const isPathToImage = !source.includes('base64') && NOT_URL_REGEXP.test(source); + + return isPathToImage ? source.replace(FILE_NAME_REGEXP, '$1') : undefined; + } + case 'dxIcon': + return messageLocalization.format(camelize(source, true)) || source; + case 'fontIcon': + return source; + case 'svg': + return SVG_TITLE_REGEXP.exec(source)?.[1]; + default: + return undefined; + } +}; diff --git a/packages/devextreme/js/__internal/core/widget/widget.ts b/packages/devextreme/js/__internal/core/widget/widget.ts index 7a9b950fecbd..479d82c583cb 100644 --- a/packages/devextreme/js/__internal/core/widget/widget.ts +++ b/packages/devextreme/js/__internal/core/widget/widget.ts @@ -33,6 +33,7 @@ export const DISABLED_STATE_CLASS = 'dx-state-disabled'; export const ACTIVE_STATE_CLASS = 'dx-state-active'; export const FOCUSED_STATE_CLASS = 'dx-state-focused'; export const HOVER_STATE_CLASS = 'dx-state-hover'; +export const SCREEN_READER_ONLY_CLASS = 'dx-screen-reader-only'; const INVISIBLE_STATE_CLASS = 'dx-state-invisible'; export const EMPTY_ACTIVE_STATE_UNIT = ''; diff --git a/packages/devextreme/js/__internal/data/abstract_store.ts b/packages/devextreme/js/__internal/data/abstract_store.ts index 8987913c2ed7..21bfe4fd6764 100644 --- a/packages/devextreme/js/__internal/data/abstract_store.ts +++ b/packages/devextreme/js/__internal/data/abstract_store.ts @@ -156,6 +156,7 @@ class Store { loadOptions._langParams = { ...this._langParams, ...loadOptions._langParams }; } + // @ts-expect-error public Query.enumerate() is a Promise, the query resolves a Deferred const result: DeferredObj<unknown[]> = queryByOptions( this.createQuery(loadOptions), loadOptions, @@ -193,6 +194,7 @@ class Store { } _totalCountImpl(options?: StoreLoadOptions): DeferredObj<number> { + // @ts-expect-error public Query.count() is a Promise, the query resolves a Deferred const result: DeferredObj<number> = queryByOptions( this.createQuery(options), options, diff --git a/packages/devextreme/js/__internal/data/array_store.ts b/packages/devextreme/js/__internal/data/array_store.ts index 7ae9ef524ffc..814e5a272bb9 100644 --- a/packages/devextreme/js/__internal/data/array_store.ts +++ b/packages/devextreme/js/__internal/data/array_store.ts @@ -47,27 +47,23 @@ class ArrayStore extends Store { const index = indexByKey(this, this._array, key); if (index === -1) { - // @ts-expect-error data/utils is untyped: `rejectedPromise` reads its arguments object const rejected: DeferredObj<unknown> = rejectedPromise(errors.Error('E4009')); return rejected; } - // @ts-expect-error data/utils is untyped: `trivialPromise` reads its arguments object const resolved: DeferredObj<unknown> = trivialPromise(this._array[index]); return resolved; } _insertImpl(values: unknown): DeferredObj<unknown> { - // @ts-expect-error array_utils is untyped: `insert` declares every argument as required const result: DeferredObj<unknown> = insert(this, this._array, values); return result; } _pushImpl(changes: StoreChange[]): void { - // @ts-expect-error array_utils is untyped: `applyBatch` destructures every option as required applyBatch({ keyInfo: this, data: this._array, @@ -76,14 +72,12 @@ class ArrayStore extends Store { } _updateImpl(key: unknown, values: unknown): DeferredObj<unknown> { - // @ts-expect-error array_utils is untyped: `update` declares every argument as required const result: DeferredObj<unknown> = update(this, this._array, key, values); return result; } _removeImpl(key: unknown): DeferredObj<unknown> { - // @ts-expect-error array_utils is untyped: `remove` declares every argument as required const result: DeferredObj<unknown> = remove(this, this._array, key); return result; diff --git a/packages/devextreme/js/__internal/data/array_utils.ts b/packages/devextreme/js/__internal/data/array_utils.ts new file mode 100644 index 000000000000..2e8dd20a73fd --- /dev/null +++ b/packages/devextreme/js/__internal/data/array_utils.ts @@ -0,0 +1,427 @@ +import { errors } from '@js/common/data/errors'; +import { keysEqual, rejectedPromise, trivialPromise } from '@js/common/data/utils'; +import config from '@js/core/config'; +import Guid from '@js/core/guid'; +import { compileGetter } from '@js/core/utils/data'; +import type { DeferredObj } from '@js/core/utils/deferred'; +import { extend } from '@js/core/utils/extend'; +import { deepExtendArraySafe } from '@js/core/utils/object'; +import { + isDefined, isEmptyObject, isObject, isPlainObject, isString, +} from '@js/core/utils/type'; +import type { StoreChange } from '@js/data/store'; +import { isCollectionLike } from '@ts/core/utils/m_object'; +import type { StoreKey } from '@ts/data/abstract_store'; + +export type KeyExpr = StoreKey | Function; + +export interface KeyInfo { + /* eslint-disable @typescript-eslint/method-signature-style */ + key(): KeyExpr | undefined; + keyOf(obj: unknown): unknown; + /* eslint-enable @typescript-eslint/method-signature-style */ +} + +interface CachedArray extends Array<unknown> { + _dataByKeyMap?: Record<string, unknown>; + _dataByKeyMapLength?: number; +} + +interface GroupItem { + items?: unknown[]; + collapsedItems?: unknown[]; +} + +export interface ApplyBatchOptions { + keyInfo: KeyInfo; + data: unknown[]; + changes: StoreChange[]; + groupCount?: number; + useInsertIndex?: boolean; + immutable?: boolean; + disableCache?: boolean; + logError?: boolean; + skipCopying?: boolean; +} + +export interface ApplyChangesOptions { + keyExpr?: StoreKey; + immutable?: boolean; +} + +function hasKey(target: unknown, keyOrKeys: KeyExpr): boolean { + let keys: string[] = []; + if (isString(keyOrKeys)) { + keys = [keyOrKeys]; + } else if (Array.isArray(keyOrKeys)) { + keys = keyOrKeys.slice(); + } + + while (keys.length) { + const key = keys.shift(); + if (isDefined(key) && isObject(target) && key in target) { + return true; + } + } + + return false; +} + +function generateDataByKeyMap(keyInfo: KeyInfo, array: CachedArray): void { + if (keyInfo.key() && (!array._dataByKeyMap || array._dataByKeyMapLength !== array.length)) { + const dataByKeyMap: Record<string, unknown> = {}; + const arrayLength = array.length; + for (let i = 0; i < arrayLength; i += 1) { + dataByKeyMap[JSON.stringify(keyInfo.keyOf(array[i]))] = array[i]; + } + + array._dataByKeyMap = dataByKeyMap; + array._dataByKeyMapLength = arrayLength; + } +} + +function getCacheValue(array: CachedArray, key: unknown): unknown { + return array._dataByKeyMap?.[JSON.stringify(key)]; +} + +function getHasKeyCacheValue(array: CachedArray, key: unknown): unknown { + if (array._dataByKeyMap) { + return array._dataByKeyMap[JSON.stringify(key)]; + } + + return true; +} + +function setDataByKeyMapValue(array: CachedArray, key: unknown, data: unknown): void { + if (array._dataByKeyMap) { + array._dataByKeyMap[JSON.stringify(key)] = data; + array._dataByKeyMapLength = (array._dataByKeyMapLength ?? 0) + (data ? 1 : -1); + } +} + +function indexByKey(keyInfo: KeyInfo, array: unknown[], key: unknown): number { + const keyExpr = keyInfo.key(); + + if (!getHasKeyCacheValue(array, key)) { + return -1; + } + + for (let i = 0, arrayLength = array.length; i < arrayLength; i += 1) { + if (keysEqual(keyExpr, keyInfo.keyOf(array[i]), key)) { + return i; + } + } + return -1; +} + +function findItems( + keyInfo: KeyInfo, + items: unknown[], + key: unknown, + groupCount: number, +): unknown[] | undefined { + if (groupCount) { + for (const item of items) { + const group: GroupItem = isObject(item) ? item : {}; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const childItems = group.items || group.collapsedItems || []; + const result = findItems(keyInfo, childItems, key, groupCount - 1); + if (result) { + return result; + } + } + } else if (indexByKey(keyInfo, items, key) >= 0) { + return items; + } + + return undefined; +} + +function getItems( + keyInfo: KeyInfo, + items: unknown[], + key: unknown, + groupCount: number | undefined, +): unknown[] { + if (groupCount) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + return findItems(keyInfo, items, key, groupCount) || []; + } + + return items; +} + +function cloneInstanceWithChangedPaths( + instance: unknown, + changes: unknown, + clonedInstances: WeakMap<object, unknown> = new WeakMap<object, unknown>(), +): unknown { + if (isCollectionLike(instance)) { + return instance; + } + + const source: object = isObject(instance) ? instance : {}; + const result: object = isObject(instance) + ? Object.create(Object.getPrototypeOf(instance)) + : {}; + + if (isObject(instance)) { + clonedInstances.set(instance, result); + } + + const instanceWithoutPrototype = { ...source }; + deepExtendArraySafe(result, instanceWithoutPrototype, true, true, true); + // eslint-disable-next-line no-restricted-syntax, guard-for-in + for (const name in instanceWithoutPrototype) { + const value: unknown = instanceWithoutPrototype[name]; + const change: unknown = isObject(changes) ? changes[name] : undefined; + + if (isObject(value) && !isPlainObject(value) + && isObject(change) && !clonedInstances.has(value)) { + result[name] = cloneInstanceWithChangedPaths(value, change, clonedInstances); + } + } + // eslint-disable-next-line no-restricted-syntax, guard-for-in + for (const name in result) { + const prop: unknown = result[name]; + + if (isObject(prop) && clonedInstances.has(prop)) { + result[name] = clonedInstances.get(prop); + } + } + + return result; +} + +function createObjectWithChanges( + target: unknown, + changes?: unknown, +): Record<string, unknown> { + const result = cloneInstanceWithChangedPaths(target, changes); + const extended: Record<string, unknown> = deepExtendArraySafe(result, changes, true, true, true); + + return extended; +} + +function getErrorResult( + isBatch: boolean | undefined, + logError: boolean | undefined, + errorCode: string, +): DeferredObj<unknown> | undefined { + if (!isBatch) { + const error: unknown = errors.Error(errorCode); + return rejectedPromise(error); + } + + if (logError) { + errors.log(errorCode); + } + + return undefined; +} + +function update( + keyInfo: KeyInfo, array: unknown[], key: unknown, data: unknown, +): DeferredObj<unknown>; +function update( + keyInfo: KeyInfo, array: unknown[], key: unknown, data: unknown, + isBatch: true, immutable?: boolean, logError?: boolean, +): undefined; +function update( + keyInfo: KeyInfo, + array: unknown[], + key: unknown, + data: unknown, + isBatch?: boolean, + immutable?: boolean, + logError?: boolean, +): DeferredObj<unknown> | undefined { + // eslint-disable-next-line @typescript-eslint/init-declarations + let target: unknown; + const extendComplexObject = true; + const keyExpr = keyInfo.key(); + + if (keyExpr) { + if (hasKey(data, keyExpr) && !keysEqual(keyExpr, key, keyInfo.keyOf(data))) { + return getErrorResult(isBatch, logError, 'E4017'); + } + + target = getCacheValue(array, key); + if (!target) { + const index = indexByKey(keyInfo, array, key); + if (index < 0) { + return getErrorResult(isBatch, logError, 'E4009'); + } + + target = array[index]; + + if (immutable === true && isDefined(target)) { + const newTarget = createObjectWithChanges(target, data); + array[index] = newTarget; + return isBatch ? undefined : trivialPromise(newTarget, key); + } + } + } else { + target = key; + } + + deepExtendArraySafe(target, data, extendComplexObject, false, true, true); + if (!isBatch) { + if (config().useLegacyStoreResult) { + return trivialPromise(key, data); + } + return trivialPromise(target, key); + } + + return undefined; +} + +function insert( + keyInfo: KeyInfo, array: unknown[], data: unknown, index?: number, +): DeferredObj<unknown>; +function insert( + keyInfo: KeyInfo, array: unknown[], data: unknown, index: number | undefined, + isBatch: true, logError?: boolean, skipCopying?: boolean, +): undefined; +function insert( + keyInfo: KeyInfo, + array: unknown[], + data: unknown, + index?: number, + isBatch?: boolean, + logError?: boolean, + skipCopying?: boolean, +): DeferredObj<unknown> | undefined { + // eslint-disable-next-line @typescript-eslint/init-declarations + let keyValue: unknown; + const keyExpr = keyInfo.key(); + + const obj: unknown = isPlainObject(data) && !skipCopying ? extend({}, data) : data; + + if (keyExpr) { + keyValue = keyInfo.keyOf(obj); + if (keyValue === undefined || (typeof keyValue === 'object' && isEmptyObject(keyValue))) { + if (Array.isArray(keyExpr)) { + throw errors.Error('E4007'); + } + + const generatedKey = String(new Guid()); + + if (isString(keyExpr) && isObject(obj)) { + obj[keyExpr] = generatedKey; + } + keyValue = generatedKey; + } else if (array[indexByKey(keyInfo, array, keyValue)] !== undefined) { + return getErrorResult(isBatch, logError, 'E4008'); + } + } else { + keyValue = obj; + } + if (isDefined(index) && index >= 0) { + array.splice(index, 0, obj); + } else { + array.push(obj); + } + + setDataByKeyMapValue(array, keyValue, obj); + + if (!isBatch) { + return trivialPromise(config().useLegacyStoreResult ? data : obj, keyValue); + } + + return undefined; +} + +function remove( + keyInfo: KeyInfo, array: unknown[], key: unknown, +): DeferredObj<unknown>; +function remove( + keyInfo: KeyInfo, array: unknown[], key: unknown, isBatch: true, logError?: boolean, +): undefined; +function remove( + keyInfo: KeyInfo, + array: unknown[], + key: unknown, + isBatch?: boolean, + logError?: boolean, +): DeferredObj<unknown> | undefined { + const index = indexByKey(keyInfo, array, key); + if (index > -1) { + array.splice(index, 1); + setDataByKeyMapValue(array, key, null); + } + if (!isBatch) { + return trivialPromise(key); + } + if (index < 0) { + return getErrorResult(isBatch, logError, 'E4009'); + } + + return undefined; +} + +function applyBatch({ + keyInfo, data, changes, groupCount, useInsertIndex, immutable, + disableCache, logError, skipCopying, +}: ApplyBatchOptions): unknown[] { + const resultItems = immutable === true ? [...data] : data; + + changes.forEach((item) => { + const items = item.type === 'insert' + ? resultItems + : getItems(keyInfo, resultItems, item.key, groupCount); + + if (!disableCache) { + generateDataByKeyMap(keyInfo, items); + } + + const insertIndex = useInsertIndex && isDefined(item.index) ? item.index : -1; + + // eslint-disable-next-line default-case + switch (item.type) { + case 'update': + update(keyInfo, items, item.key, item.data, true, immutable, logError); + break; + case 'insert': + insert(keyInfo, items, item.data, insertIndex, true, logError, skipCopying); + break; + case 'remove': + remove(keyInfo, items, item.key, true, logError); + break; + } + }); + return resultItems; +} + +function applyChanges( + data: unknown[], + changes: StoreChange[], + options: ApplyChangesOptions = {}, +): unknown[] { + const { keyExpr = 'id', immutable = true } = options; + // @ts-expect-error compileGetter() is declared as (expr: string) => unknown + const keyGetter: Function = compileGetter(keyExpr); + const keyInfo: KeyInfo = { + key: () => keyExpr, + keyOf: (obj: unknown): unknown => keyGetter(obj), + }; + + return applyBatch({ + keyInfo, + data, + changes, + immutable, + disableCache: true, + logError: true, + }); +} + +export { + applyBatch, + applyChanges, + createObjectWithChanges, + indexByKey, + insert, + remove, + update, +}; diff --git a/packages/devextreme/js/__internal/data/custom_store.ts b/packages/devextreme/js/__internal/data/custom_store.ts index 90e720d4056c..77d22482a4c7 100644 --- a/packages/devextreme/js/__internal/data/custom_store.ts +++ b/packages/devextreme/js/__internal/data/custom_store.ts @@ -209,7 +209,7 @@ function runRawLoadWithQuery( const rawDataQuery = arrayQuery(rawData, { errorHandler: store._errorHandler }); const waitList: DeferredObj<unknown>[] = []; - const result: { items?: unknown[]; totalCount?: unknown } = {}; + const result: { items?: unknown; totalCount?: unknown } = {}; if (!countOnly) { const itemsQuery = storeHelper.queryByOptions(rawDataQuery, loadOptions, false); @@ -217,7 +217,7 @@ function runRawLoadWithQuery( result.items = rawData.slice(0); } else { const itemsPromise: DeferredObj<unknown> = itemsQuery.enumerate() - .done((asyncResult: unknown[]) => { + .done((asyncResult: unknown) => { result.items = asyncResult; }); waitList.push(itemsPromise); @@ -367,7 +367,6 @@ class CustomStore extends Store { _pushImpl(changes: StoreChange[]): void { if (this.__rawData) { - // @ts-expect-error array_utils is untyped: `applyBatch` destructures every option as required applyBatch({ keyInfo: this, data: this.__rawData, diff --git a/packages/devextreme/js/__internal/data/data_controller/data_controller.ts b/packages/devextreme/js/__internal/data/data_controller/data_controller.ts index 840781fd8302..3b4d5b4bf14f 100644 --- a/packages/devextreme/js/__internal/data/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/data/data_controller/data_controller.ts @@ -65,7 +65,6 @@ class DataController { this._isSharedDataSource = true; this._dataSource = dataSourceOptions; } else { - // @ts-expect-error const normalizedDataSourceOptions = normalizeDataSourceOptions(dataSourceOptions); this._dataSource = new DataSource( extend(true, {}, {}, normalizedDataSourceOptions), diff --git a/packages/devextreme/js/__internal/data/data_source/data_source.ts b/packages/devextreme/js/__internal/data/data_source/data_source.ts index 72aae3df3fab..1f1cc8ee8fec 100644 --- a/packages/devextreme/js/__internal/data/data_source/data_source.ts +++ b/packages/devextreme/js/__internal/data/data_source/data_source.ts @@ -28,6 +28,7 @@ import type { ChangedEvent, DataSourceEventName, EventOptionName, LoadOperation, LoadResult, NormalizedDataSourceOptions, StoreLoadOptions, } from './types'; +import type { Mapper } from './utils'; // Mirrors the coercion the global `isFinite` applies to non-numeric values. const isFiniteValue = (value: unknown): value is number => isFinite(Number(value)); @@ -58,11 +59,11 @@ export class DataSource { _onPushHandler: Function; - _aggregationTimeoutId?: number; + _aggregationTimeoutId?: ReturnType<typeof setTimeout>; _storeLoadOptions: StoreLoadOptions; - _mapFunc?: Function; + _mapFunc?: Mapper; _postProcessFunc?: Function; @@ -567,7 +568,6 @@ export class DataSource { dataSourceChanges = changingArgs.postProcessChanges(dataSourceChanges); } - // @ts-expect-error array_utils is untyped: `applyBatch` destructures every option as required applyBatch({ keyInfo: this.store(), data: items, diff --git a/packages/devextreme/js/__internal/data/data_source/m_utils.ts b/packages/devextreme/js/__internal/data/data_source/m_utils.ts deleted file mode 100644 index 40192c669e20..000000000000 --- a/packages/devextreme/js/__internal/data/data_source/m_utils.ts +++ /dev/null @@ -1,121 +0,0 @@ -import ArrayStore from '@js/common/data/array_store'; -import { CustomStore } from '@js/common/data/custom_store'; -import { normalizeSortingInfo } from '@js/common/data/utils'; -import ajaxUtils from '@js/core/utils/ajax'; -import { extend } from '@js/core/utils/extend'; -import { each, map } from '@js/core/utils/iterator'; -import { isPlainObject } from '@js/core/utils/type'; -import Store from '@js/data/abstract_store'; - -export const CANCELED_TOKEN = 'canceled'; - -export const isPending = (deferred) => deferred.state() === 'pending'; - -export const normalizeStoreLoadOptionAccessorArguments = (originalArguments) => { - // eslint-disable-next-line default-case - switch (originalArguments.length) { - case 0: - return undefined; - case 1: - return originalArguments[0]; - } - return [].slice.call(originalArguments); -}; - -const mapGroup = (group, level, mapper) => map(group, (item) => { - const { items, ...restItem } = item; - return { - ...restItem, - items: mapRecursive(item.items, level - 1, mapper), - }; -}); - -const mapRecursive = (items, level, mapper) => { - if (!Array.isArray(items)) return items; - return level ? mapGroup(items, level, mapper) : map(items, mapper); -}; - -export const mapDataRespectingGrouping = (items, mapper, groupInfo) => { - const level = groupInfo ? normalizeSortingInfo(groupInfo).length : 0; - - return mapRecursive(items, level, mapper); -}; - -export const normalizeLoadResult = (data, extra) => { - if (data?.data) { - extra = data; - data = data.data; - } - - if (!Array.isArray(data)) { - data = [data]; - } - - return { - data, - extra, - }; -}; - -const createCustomStoreFromLoadFunc = (options) => { - const storeConfig = {}; - - each(['useDefaultSearch', 'key', 'load', 'loadMode', 'cacheRawData', 'byKey', 'lookup', 'totalCount', 'insert', 'update', 'remove'], function () { - storeConfig[this] = options[this]; - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete options[this]; - }); - return new CustomStore(storeConfig); -}; - -const createStoreFromConfig = (storeConfig) => { - const alias = storeConfig.type; - - delete storeConfig.type; - // @ts-expect-error - return Store.create(alias, storeConfig); -}; - -const createCustomStoreFromUrl = (url, normalizationOptions) => new CustomStore({ - load: () => ajaxUtils.sendRequest({ url, dataType: 'json' }), - loadMode: normalizationOptions?.fromUrlLoadMode, -}); - -export const normalizeDataSourceOptions = (options, normalizationOptions) => { - let store; - - if (typeof options === 'string') { - options = { - paginate: false, - store: createCustomStoreFromUrl(options, normalizationOptions), - }; - } - - if (options === undefined) { - options = []; - } - - if (Array.isArray(options) || options instanceof Store) { - options = { store: options }; - } else { - options = extend({}, options); - } - - if (options.store === undefined) { - options.store = []; - } - - store = options.store; - - if ('load' in options) { - store = createCustomStoreFromLoadFunc(options); - } else if (Array.isArray(store)) { - store = new ArrayStore(store); - } else if (isPlainObject(store)) { - store = createStoreFromConfig(extend({}, store)); - } - - options.store = store; - - return options; -}; diff --git a/packages/devextreme/js/__internal/data/data_source/m_operation_manager.ts b/packages/devextreme/js/__internal/data/data_source/operation_manager.ts similarity index 53% rename from packages/devextreme/js/__internal/data/data_source/m_operation_manager.ts rename to packages/devextreme/js/__internal/data/data_source/operation_manager.ts index cfde35fffb6f..4cb21cb79b92 100644 --- a/packages/devextreme/js/__internal/data/data_source/m_operation_manager.ts +++ b/packages/devextreme/js/__internal/data/data_source/operation_manager.ts @@ -1,32 +1,24 @@ import { CANCELED_TOKEN } from '@js/common/data/data_source/utils'; +import type { DeferredObj } from '@js/core/utils/deferred'; export default class OperationManager { - constructor() { - // @ts-expect-error - this._counter = -1; - // @ts-expect-error - this._deferreds = {}; - } + _counter = -1; + + _deferreds: Record<number, DeferredObj<unknown>> = {}; - add(deferred) { - // @ts-expect-error - this._counter++; - // @ts-expect-error + add(deferred: DeferredObj<unknown>): number { + this._counter += 1; this._deferreds[this._counter] = deferred; - // @ts-expect-error return this._counter; } - remove(operationId) { - // @ts-expect-error + remove(operationId: number): boolean { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete return delete this._deferreds[operationId]; } - cancel(operationId) { - // @ts-expect-error + cancel(operationId: number): boolean { if (operationId in this._deferreds) { - // @ts-expect-error this._deferreds[operationId].reject(CANCELED_TOKEN); return true; } @@ -34,13 +26,10 @@ export default class OperationManager { return false; } - cancelAll() { - // @ts-expect-error + cancelAll(): void { while (this._counter > -1) { - // @ts-expect-error this.cancel(this._counter); - // @ts-expect-error - this._counter--; + this._counter -= 1; } } } diff --git a/packages/devextreme/js/__internal/data/data_source/utils.ts b/packages/devextreme/js/__internal/data/data_source/utils.ts new file mode 100644 index 000000000000..c6aed87cfcc6 --- /dev/null +++ b/packages/devextreme/js/__internal/data/data_source/utils.ts @@ -0,0 +1,174 @@ +import ArrayStore from '@js/common/data/array_store'; +import { CustomStore } from '@js/common/data/custom_store'; +import { normalizeSortingInfo } from '@js/common/data/utils'; +import ajaxUtils from '@js/core/utils/ajax'; +import type { DeferredObj } from '@js/core/utils/deferred'; +import { extend } from '@js/core/utils/extend'; +import { isObject, isPlainObject } from '@js/core/utils/type'; +import Store from '@ts/data/abstract_store'; + +import type { NormalizedDataSourceOptions } from './types'; + +export const CANCELED_TOKEN = 'canceled'; + +export type Mapper = (item: unknown) => unknown; + +interface GroupItem { + items?: unknown[]; +} + +export interface NormalizationOptions { + fromUrlLoadMode?: string; +} + +interface DataSourceOptionsInput extends Record<string, unknown> { + store?: unknown; +} + +export const isPending = (deferred: DeferredObj<unknown>): boolean => deferred.state() === 'pending'; + +export const normalizeStoreLoadOptionAccessorArguments = ( + originalArguments: unknown[], +): unknown => { + switch (originalArguments.length) { + case 0: + return undefined; + case 1: + return originalArguments[0]; + default: + return originalArguments.slice(); + } +}; + +const mapRecursive = (items: unknown, level: number, mapper: Mapper): unknown => { + if (!Array.isArray(items)) return items; + + if (!level) { + return items.map(mapper); + } + + return items.map((item) => { + const groupItem: GroupItem = isObject(item) ? item : {}; + + return { + ...groupItem, + items: mapRecursive(groupItem.items, level - 1, mapper), + }; + }); +}; + +export const mapDataRespectingGrouping = ( + items: unknown[], + mapper: Mapper, + groupInfo?: unknown, +): unknown[] => { + const level = groupInfo ? normalizeSortingInfo(groupInfo).length : 0; + const mapped = mapRecursive(items, level, mapper); + + return Array.isArray(mapped) ? mapped : []; +}; + +export interface NormalizedLoadResult { + data: unknown[]; + extra: unknown; +} + +export const normalizeLoadResult = (data: unknown, extra?: unknown): NormalizedLoadResult => { + const loadResult: { data?: unknown } = isObject(data) ? data : {}; + + const resultData: unknown = loadResult.data ? loadResult.data : data; + const resultExtra: unknown = loadResult.data ? data : extra; + + return { + data: Array.isArray(resultData) ? resultData : [resultData], + extra: resultExtra, + }; +}; + +const CUSTOM_STORE_OPTION_NAMES = [ + 'useDefaultSearch', 'key', 'load', 'loadMode', 'cacheRawData', 'byKey', + 'lookup', 'totalCount', 'insert', 'update', 'remove', +]; + +const createCustomStoreFromLoadFunc = (options: DataSourceOptionsInput): CustomStore => { + const storeConfig: Record<string, unknown> = {}; + + CUSTOM_STORE_OPTION_NAMES.forEach((optionName) => { + storeConfig[optionName] = options[optionName]; + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete options[optionName]; + }); + + return new CustomStore(storeConfig); +}; + +const createStoreFromConfig = (storeConfig: Record<string, unknown>): Store => { + const alias = String(storeConfig.type); + + delete storeConfig.type; + + const store: Store = Store.create(alias, storeConfig); + + return store; +}; + +const createCustomStoreFromUrl = ( + url: string, + normalizationOptions?: NormalizationOptions, +): CustomStore => new CustomStore({ + load: (): unknown => ajaxUtils.sendRequest({ url, dataType: 'json' }), + loadMode: normalizationOptions?.fromUrlLoadMode, +}); + +const resolveStore = (options: DataSourceOptionsInput): Store => { + if ('load' in options) { + return createCustomStoreFromLoadFunc(options); + } + + const { store } = options; + + if (Array.isArray(store)) { + return new ArrayStore(store); + } + if (isPlainObject(store)) { + return createStoreFromConfig(extend({}, store)); + } + + // @ts-expect-error the `store` option is user-provided and is not necessarily a Store + const passedThrough: Store = store; + + return passedThrough; +}; + +export const normalizeDataSourceOptions = ( + options: unknown, + normalizationOptions?: NormalizationOptions, +): NormalizedDataSourceOptions => { + let source: unknown = options; + + if (typeof source === 'string') { + source = { + paginate: false, + store: createCustomStoreFromUrl(source, normalizationOptions), + }; + } + + if (source === undefined) { + source = []; + } + + const normalized: DataSourceOptionsInput = Array.isArray(source) || source instanceof Store + ? { store: source } + : extend({}, source); + + if (normalized.store === undefined) { + normalized.store = []; + } + + const store = resolveStore(normalized); + + return { + ...normalized, + store, + }; +}; diff --git a/packages/devextreme/js/__internal/data/endpoint_selector.ts b/packages/devextreme/js/__internal/data/endpoint_selector.ts new file mode 100644 index 000000000000..7726e93a3db4 --- /dev/null +++ b/packages/devextreme/js/__internal/data/endpoint_selector.ts @@ -0,0 +1,47 @@ +/* global Debug */ +import errors from '@js/core/errors'; +import { getWindow } from '@js/core/utils/window'; + +const window = getWindow(); + +interface Endpoint { + local: string; + production?: string; +} + +type EndpointConfig = Record<string, Endpoint | undefined>; + +let isWinJsOrigin = false; +let isLocalOrigin = false; + +function isLocalHostName(url: string): boolean { + return /^(localhost$|127\.)/i.test(url); // TODO more precise check for 127.x.x.x IP +} + +class EndpointSelector { + config: EndpointConfig; + + constructor(config: EndpointConfig) { + this.config = config; + isWinJsOrigin = window.location.protocol === 'ms-appx:'; + isLocalOrigin = isLocalHostName(window.location.hostname); + } + + urlFor(key: string): string { + const bag = this.config[key]; + if (!bag) { + throw errors.Error('E0006'); + } + + if (bag.production) { + // @ts-expect-error `Debug` is a WinJS global that has no ambient declaration here + if ((isWinJsOrigin && !Debug.debuggerEnabled) || (!isWinJsOrigin && !isLocalOrigin)) { + return bag.production; + } + } + + return bag.local; + } +} + +export default EndpointSelector; diff --git a/packages/devextreme/js/__internal/data/m_errors.ts b/packages/devextreme/js/__internal/data/errors.ts similarity index 84% rename from packages/devextreme/js/__internal/data/m_errors.ts rename to packages/devextreme/js/__internal/data/errors.ts index 0268bd78af3d..076821979898 100644 --- a/packages/devextreme/js/__internal/data/m_errors.ts +++ b/packages/devextreme/js/__internal/data/errors.ts @@ -1,5 +1,6 @@ import coreErrors from '@js/core/errors'; import errorUtils from '@js/core/utils/error'; +import { isObject } from '@js/core/utils/type'; export const errors = errorUtils(coreErrors.ERROR_MESSAGES, { @@ -59,15 +60,20 @@ export const errors = errorUtils(coreErrors.ERROR_MESSAGES, { W4002: 'Data loading has failed for some cells due to the following error: {0}', }); + +export type DataErrorHandler = (error: unknown) => void; + // eslint-disable-next-line import/no-mutable-exports -export let errorHandler = null; -export const handleError = function (error) { +export let errorHandler: DataErrorHandler | null = null; + +export const handleError = function (error: unknown): void { /// #DEBUG - const id = error && '__id' in error ? error.__id : 'E4000'; + const id = isObject(error) && '__id' in error ? error.__id : 'E4000'; errors.log(id, error); /// #ENDDEBUG - // @ts-expect-error errorHandler?.(error); }; -// eslint-disable-next-line no-return-assign -export const setErrorHandler = (handler) => errorHandler = handler; + +export const setErrorHandler = (handler: DataErrorHandler | null): void => { + errorHandler = handler; +}; diff --git a/packages/devextreme/js/__internal/data/m_local_store.ts b/packages/devextreme/js/__internal/data/local_store.ts similarity index 61% rename from packages/devextreme/js/__internal/data/m_local_store.ts rename to packages/devextreme/js/__internal/data/local_store.ts index 14c956b01cd9..7bb8e4121ab8 100644 --- a/packages/devextreme/js/__internal/data/m_local_store.ts +++ b/packages/devextreme/js/__internal/data/local_store.ts @@ -3,14 +3,26 @@ import eventsEngine from '@js/common/core/events/core/events_engine'; import ArrayStore from '@js/common/data/array_store'; import { errors } from '@js/common/data/errors'; import domAdapter from '@js/core/dom_adapter'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { getWindow } from '@js/core/utils/window'; +import type { ArrayStoreOptions } from '@ts/data/array_store'; import Store from './abstract_store'; const window = getWindow(); +export interface LocalStoreOptions extends ArrayStoreOptions { + name?: string; + immediate?: boolean; + flushInterval?: number; +} + +interface LocalStoreData { + _array: unknown[]; +} + class LocalStoreBackend { - _store: any; + _store: LocalStoreData; _dirty: boolean; @@ -18,7 +30,7 @@ class LocalStoreBackend { _key: string; - constructor(store, storeOptions) { + constructor(store: LocalStoreData, storeOptions: LocalStoreOptions) { this._store = store; this._dirty = !!storeOptions.data; @@ -31,14 +43,16 @@ class LocalStoreBackend { this.save(); - const immediate = this._immediate = storeOptions.immediate; + const immediate = storeOptions.immediate ?? false; + this._immediate = immediate; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const flushInterval = Math.max(100, storeOptions.flushInterval || 10 * 1000); if (!immediate) { const saveProxy = this.save.bind(this); setInterval(saveProxy, flushInterval); eventsEngine.on(window, 'beforeunload', saveProxy); - // @ts-expect-error + // @ts-expect-error `cordova` is injected by the Cordova container, `Window` has no such field if (window.cordova) { domAdapter.listen(domAdapter.getDocument(), 'pause', saveProxy, false); } @@ -66,16 +80,17 @@ class LocalStoreBackend { this._dirty = false; } - _loadImpl(): any { + _loadImpl(): unknown[] { const raw = window.localStorage.getItem(this._key); if (raw) { - return JSON.parse(raw); + const stored: unknown = JSON.parse(raw); + return Array.isArray(stored) ? stored : []; } return []; } - _saveImpl(array): void { + _saveImpl(array: unknown[]): void { if (!array.length) { window.localStorage.removeItem(this._key); } else { @@ -83,22 +98,21 @@ class LocalStoreBackend { } } } + class LocalStore extends ArrayStore { _backend: LocalStoreBackend; - _array: any; - - constructor(options) { - if (typeof options === 'string') { - options = { name: options }; - } else { - options = options || {}; - } + constructor(options?: LocalStoreOptions | string) { + const storeOptions: LocalStoreOptions = typeof options === 'string' + ? { name: options } + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + : options || {}; - super(options); - this._array = options.data || []; + super(storeOptions); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this._array = storeOptions.data || []; - this._backend = new LocalStoreBackend(this, options); + this._backend = new LocalStoreBackend(this, storeOptions); this._backend.load(); } @@ -111,17 +125,17 @@ class LocalStore extends ArrayStore { this._backend.notifyChanged(); } - _insertImpl(values): any { + _insertImpl(values: unknown): DeferredObj<unknown> { const b = this._backend; return super._insertImpl(values).done(b.notifyChanged.bind(b)); } - _updateImpl(key, values): any { + _updateImpl(key: unknown, values: unknown): DeferredObj<unknown> { const b = this._backend; return super._updateImpl(key, values).done(b.notifyChanged.bind(b)); } - _removeImpl(key): any { + _removeImpl(key: unknown): DeferredObj<unknown> { const b = this._backend; return super._removeImpl(key).done(b.notifyChanged.bind(b)); } diff --git a/packages/devextreme/js/__internal/data/m_array_utils.ts b/packages/devextreme/js/__internal/data/m_array_utils.ts deleted file mode 100644 index 22d11b7c9c69..000000000000 --- a/packages/devextreme/js/__internal/data/m_array_utils.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { errors } from '@js/common/data/errors'; -import { keysEqual, rejectedPromise, trivialPromise } from '@js/common/data/utils'; -import config from '@js/core/config'; -import Guid from '@js/core/guid'; -import { compileGetter } from '@js/core/utils/data'; -import { extend } from '@js/core/utils/extend'; -import { deepExtendArraySafe } from '@js/core/utils/object'; -import { - isDefined, isEmptyObject, isObject, isPlainObject, -} from '@js/core/utils/type'; -import { isCollectionLike } from '@ts/core/utils/m_object'; - -function hasKey(target, keyOrKeys) { - let key; - // @ts-expect-error - const keys = typeof keyOrKeys === 'string' ? keyOrKeys.split() : keyOrKeys.slice(); - - while (keys.length) { - key = keys.shift(); - if (key in target) { - return true; - } - } - - return false; -} - -function findItems(keyInfo, items, key, groupCount) { - let childItems; - let result; - - if (groupCount) { - for (let i = 0; i < items.length; i++) { - childItems = items[i].items || items[i].collapsedItems || []; - result = findItems(keyInfo, childItems || [], key, groupCount - 1); - if (result) { - return result; - } - } - } else if (indexByKey(keyInfo, items, key) >= 0) { - return items; - } -} - -function getItems(keyInfo, items, key, groupCount) { - if (groupCount) { - return findItems(keyInfo, items, key, groupCount) || []; - } - - return items; -} - -function generateDataByKeyMap(keyInfo, array) { - if (keyInfo.key() && (!array._dataByKeyMap || array._dataByKeyMapLength !== array.length)) { - const dataByKeyMap = {}; - const arrayLength = array.length; - for (let i = 0; i < arrayLength; i++) { - dataByKeyMap[JSON.stringify(keyInfo.keyOf(array[i]))] = array[i]; - } - - array._dataByKeyMap = dataByKeyMap; - array._dataByKeyMapLength = arrayLength; - } -} - -function getCacheValue(array, key) { - if (array._dataByKeyMap) { - return array._dataByKeyMap[JSON.stringify(key)]; - } -} - -function getHasKeyCacheValue(array, key) { - if (array._dataByKeyMap) { - return array._dataByKeyMap[JSON.stringify(key)]; - } - - return true; -} - -function setDataByKeyMapValue(array, key, data) { - if (array._dataByKeyMap) { - array._dataByKeyMap[JSON.stringify(key)] = data; - array._dataByKeyMapLength += data ? 1 : -1; - } -} - -function cloneInstanceWithChangedPaths(instance, changes, clonedInstances) { - if (isCollectionLike(instance)) { - return instance; - } - - clonedInstances = clonedInstances || new WeakMap(); - - const result = instance ? Object.create(Object.getPrototypeOf(instance)) : {}; - if (instance) { - clonedInstances.set(instance, result); - } - - const instanceWithoutPrototype = { ...instance }; - deepExtendArraySafe(result, instanceWithoutPrototype, true, true, true); - // eslint-disable-next-line no-restricted-syntax, guard-for-in - for (const name in instanceWithoutPrototype) { - const value = instanceWithoutPrototype[name]; - const change = changes?.[name]; - - if (isObject(value) && !isPlainObject(value) && isObject(change) && !clonedInstances.has(value)) { - result[name] = cloneInstanceWithChangedPaths(value, change, clonedInstances); - } - } - // eslint-disable-next-line no-restricted-syntax, guard-for-in - for (const name in result) { - const prop = result[name]; - - if (isObject(prop) && clonedInstances.has(prop)) { - result[name] = clonedInstances.get(prop); - } - } - - return result; -} - -function createObjectWithChanges(target, changes) { - // @ts-expect-error - const result = cloneInstanceWithChangedPaths(target, changes); - - return deepExtendArraySafe(result, changes, true, true, true); -} - -function applyBatch({ - keyInfo, data, changes, groupCount, useInsertIndex, immutable, disableCache, logError, skipCopying, -}) { - const resultItems = immutable === true ? [...data] : data; - - changes.forEach((item) => { - const items = item.type === 'insert' ? resultItems : getItems(keyInfo, resultItems, item.key, groupCount); - - !disableCache && generateDataByKeyMap(keyInfo, items); - // eslint-disable-next-line default-case - switch (item.type) { - case 'update': update(keyInfo, items, item.key, item.data, true, immutable, logError); break; - case 'insert': insert(keyInfo, items, item.data, useInsertIndex && isDefined(item.index) ? item.index : -1, true, logError, skipCopying); break; - case 'remove': remove(keyInfo, items, item.key, true, logError); break; - } - }); - return resultItems; -} - -function getErrorResult(isBatch, logError, errorCode) { - // @ts-expect-error - return !isBatch ? rejectedPromise(errors.Error(errorCode)) : logError && errors.log(errorCode); -} - -function applyChanges(data, changes, options = {}) { - // @ts-expect-error - const { keyExpr = 'id', immutable = true } = options; - const keyGetter = compileGetter(keyExpr); - const keyInfo = { - key: () => keyExpr, - // @ts-expect-error - keyOf: (obj) => keyGetter(obj), - }; - // @ts-expect-error - return applyBatch({ - keyInfo, - data, - changes, - immutable, - disableCache: true, - logError: true, - }); -} - -function update(keyInfo, array, key, data, isBatch, immutable, logError) { - let target; - const extendComplexObject = true; - const keyExpr = keyInfo.key(); - - if (keyExpr) { - if (hasKey(data, keyExpr) && !keysEqual(keyExpr, key, keyInfo.keyOf(data))) { - return getErrorResult(isBatch, logError, 'E4017'); - } - - target = getCacheValue(array, key); - if (!target) { - const index = indexByKey(keyInfo, array, key); - if (index < 0) { - return getErrorResult(isBatch, logError, 'E4009'); - } - - target = array[index]; - - if (immutable === true && isDefined(target)) { - const newTarget = createObjectWithChanges(target, data); - array[index] = newTarget; - // @ts-expect-error - return !isBatch && trivialPromise(newTarget, key); - } - } - } else { - target = key; - } - - deepExtendArraySafe(target, data, extendComplexObject, false, true, true); - if (!isBatch) { - if (config().useLegacyStoreResult) { - // @ts-expect-error - return trivialPromise(key, data); - } - // @ts-expect-error - return trivialPromise(target, key); - } -} - -function insert(keyInfo, array, data, index, isBatch, logError, skipCopying) { - let keyValue; - const keyExpr = keyInfo.key(); - - const obj = isPlainObject(data) && !skipCopying ? extend({}, data) : data; - - if (keyExpr) { - keyValue = keyInfo.keyOf(obj); - if (keyValue === undefined || typeof keyValue === 'object' && isEmptyObject(keyValue)) { - if (Array.isArray(keyExpr)) { - throw errors.Error('E4007'); - } - keyValue = obj[keyExpr] = String(new Guid()); - } else if (array[indexByKey(keyInfo, array, keyValue)] !== undefined) { - return getErrorResult(isBatch, logError, 'E4008'); - } - } else { - keyValue = obj; - } - if (index >= 0) { - array.splice(index, 0, obj); - } else { - array.push(obj); - } - - setDataByKeyMapValue(array, keyValue, obj); - - if (!isBatch) { - // @ts-expect-error - return trivialPromise(config().useLegacyStoreResult ? data : obj, keyValue); - } -} - -function remove(keyInfo, array, key, isBatch, logError) { - const index = indexByKey(keyInfo, array, key); - if (index > -1) { - array.splice(index, 1); - setDataByKeyMapValue(array, key, null); - } - if (!isBatch) { - // @ts-expect-error - return trivialPromise(key); - } if (index < 0) { - return getErrorResult(isBatch, logError, 'E4009'); - } -} - -function indexByKey(keyInfo, array, key) { - const keyExpr = keyInfo.key(); - - if (!getHasKeyCacheValue(array, key)) { - return -1; - } - - for (let i = 0, arrayLength = array.length; i < arrayLength; i++) { - if (keysEqual(keyExpr, keyInfo.keyOf(array[i]), key)) { - return i; - } - } - return -1; -} - -export { - applyBatch, - applyChanges, - createObjectWithChanges, - indexByKey, - insert, - remove, - update, -}; diff --git a/packages/devextreme/js/__internal/data/m_endpoint_selector.ts b/packages/devextreme/js/__internal/data/m_endpoint_selector.ts deleted file mode 100644 index 22f200d13ddd..000000000000 --- a/packages/devextreme/js/__internal/data/m_endpoint_selector.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* global Debug */ -import errors from '@js/core/errors'; -import { getWindow } from '@js/core/utils/window'; - -const window = getWindow(); - -let IS_WINJS_ORIGIN; -let IS_LOCAL_ORIGIN; - -function isLocalHostName(url) { - return /^(localhost$|127\.)/i.test(url); // TODO more precise check for 127.x.x.x IP -} - -const EndpointSelector = function (config) { - this.config = config; - IS_WINJS_ORIGIN = window.location.protocol === 'ms-appx:'; - IS_LOCAL_ORIGIN = isLocalHostName(window.location.hostname); -}; - -EndpointSelector.prototype = { - urlFor(key) { - const bag = this.config[key]; - if (!bag) { - throw errors.Error('E0006'); - } - - if (bag.production) { - // @ts-expect-error - if (IS_WINJS_ORIGIN && !Debug.debuggerEnabled || !IS_WINJS_ORIGIN && !IS_LOCAL_ORIGIN) { - return bag.production; - } - } - - return bag.local; - }, - -}; - -export default EndpointSelector; diff --git a/packages/devextreme/js/__internal/data/m_query.ts b/packages/devextreme/js/__internal/data/m_query.ts deleted file mode 100644 index c35f346ae08c..000000000000 --- a/packages/devextreme/js/__internal/data/m_query.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { queryImpl } from '@js/common/data/query_implementation'; - -const query = function () { - const impl = Array.isArray(arguments[0]) ? 'array' : 'remote'; - // @ts-expect-error - return queryImpl[impl].apply(this, arguments); -}; - -export default query; diff --git a/packages/devextreme/js/__internal/data/m_remote_query.ts b/packages/devextreme/js/__internal/data/m_remote_query.ts deleted file mode 100644 index 53fb79c54c50..000000000000 --- a/packages/devextreme/js/__internal/data/m_remote_query.ts +++ /dev/null @@ -1,155 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import arrayQueryImpl from '@js/common/data/array_query'; -import { errors, handleError } from '@js/common/data/errors'; -import queryAdapters from '@js/common/data/query_adapters'; -import { Deferred } from '@js/core/utils/deferred'; -import { each } from '@js/core/utils/iterator'; -import { isFunction } from '@js/core/utils/type'; - -const remoteQueryImpl = function (url, queryOptions, tasks) { - tasks = tasks || []; - queryOptions = queryOptions || {}; - - const createTask = function (name, args) { - return { name, args }; - }; - - const exec = function (executorTask) { - // @ts-expect-error - const d = new Deferred(); - let _adapterFactory; - let _adapter; - let _taskQueue; - let _currentTask; - let _mergedSortArgs; - - const rejectWithNotify = function (error) { - const handler = queryOptions.errorHandler; - if (handler) { - handler(error); - } - - handleError(error); - d.reject(error); - }; - - function mergeSortTask(task) { - // eslint-disable-next-line default-case - switch (task.name) { - case 'sortBy': - _mergedSortArgs = [task.args]; - return true; - - case 'thenBy': - if (!_mergedSortArgs) { - throw errors.Error('E4004'); - } - - _mergedSortArgs.push(task.args); - return true; - } - - return false; - } - - function unmergeSortTasks() { - const head = _taskQueue[0]; - const unmergedTasks = []; - - if (head && head.name === 'multiSort') { - _taskQueue.shift(); - each(head.args[0], function () { - // @ts-expect-error - unmergedTasks.push(createTask(unmergedTasks.length ? 'thenBy' : 'sortBy', this)); - }); - } - - _taskQueue = unmergedTasks.concat(_taskQueue); - } - - try { - _adapterFactory = queryOptions.adapter; - if (!isFunction(_adapterFactory)) { - _adapterFactory = queryAdapters[_adapterFactory]; - } - - _adapter = _adapterFactory(queryOptions); - - _taskQueue = [].concat(tasks).concat(executorTask); - - const { optimize } = _adapter; - if (optimize) optimize(_taskQueue); - - while (_taskQueue.length) { - /* eslint-disable-next-line prefer-destructuring */ - _currentTask = _taskQueue[0]; - - if (!mergeSortTask(_currentTask)) { - if (_mergedSortArgs) { - _taskQueue.unshift(createTask('multiSort', [_mergedSortArgs])); - _mergedSortArgs = null; - continue; - } - - if (String(_currentTask.name) !== 'enumerate') { - if (!_adapter[_currentTask.name] || _adapter[_currentTask.name].apply(_adapter, _currentTask.args) === false) { - break; - } - } - } - _taskQueue.shift(); - } - - unmergeSortTasks(); - - _adapter.exec(url) - .done((result, extra) => { - if (!_taskQueue.length) { - d.resolve(result, extra); - } else { - let clientChain = arrayQueryImpl(result, { - errorHandler: queryOptions.errorHandler, - }); - each(_taskQueue, function () { - clientChain = clientChain[this.name].apply(clientChain, this.args); - }); - clientChain - // @ts-expect-error - .done(d.resolve) - .fail(d.reject); - } - }) - .fail(rejectWithNotify); - } catch (x) { - rejectWithNotify(x); - } - - return d.promise(); - }; - - const query = {}; - - each( - ['sortBy', 'thenBy', 'filter', 'slice', 'select', 'groupBy'], - function () { - const name = String(this); - query[name] = function () { - return remoteQueryImpl(url, queryOptions, tasks.concat(createTask(name, arguments))); - }; - }, - ); - - each( - ['count', 'min', 'max', 'sum', 'avg', 'aggregate', 'enumerate'], - function () { - const name = String(this); - query[name] = function () { - return exec.call(this, createTask(name, arguments)); - }; - }, - ); - - return query; -}; - -export default remoteQueryImpl; diff --git a/packages/devextreme/js/__internal/data/m_store_helper.ts b/packages/devextreme/js/__internal/data/m_store_helper.ts deleted file mode 100644 index 9c30c9243f73..000000000000 --- a/packages/devextreme/js/__internal/data/m_store_helper.ts +++ /dev/null @@ -1,89 +0,0 @@ -import arrayQuery from '@js/common/data/array_query'; -import { normalizeSortingInfo } from '@js/common/data/utils'; -// @ts-expect-error -import { grep } from '@js/core/utils/common'; -import { extend } from '@js/core/utils/extend'; -import { each } from '@js/core/utils/iterator'; - -function multiLevelGroup(query, groupInfo) { - query = query.groupBy(groupInfo[0].selector); - - if (groupInfo.length > 1) { - query = query.select((g) => extend({}, g, { - items: multiLevelGroup(arrayQuery(g.items), groupInfo.slice(1)).toArray(), - })); - } - - return query; -} - -function arrangeSortingInfo(groupInfo, sortInfo) { - const filteredGroup = []; - each(groupInfo, (_, group) => { - const collision = grep(sortInfo, (sort) => group.selector === sort.selector); - - if (collision.length < 1) { - // @ts-expect-error - filteredGroup.push(group); - } - }); - return filteredGroup.concat(sortInfo); -} - -function queryByOptions(query, options, isCountQuery) { - options = options || {}; - - const { filter } = options; - - if (options?.langParams) { - query.setLangParams?.(options.langParams); - } - - if (filter) { - query = query.filter(filter); - } - - if (isCountQuery) { - return query; - } - - let { sort } = options; - const { select } = options; - let { group } = options; - const { skip } = options; - const { take } = options; - - if (group) { - group = normalizeSortingInfo(group); - group.keepInitialKeyOrder = !!options.group.keepInitialKeyOrder; - } - if (sort || group) { - sort = normalizeSortingInfo(sort || []); - if (group && !group.keepInitialKeyOrder) { - sort = arrangeSortingInfo(group, sort); - } - each(sort, function (index) { - query = query[index ? 'thenBy' : 'sortBy'](this.selector, this.desc, this.compare); - }); - } - - if (select) { - query = query.select(select); - } - - if (group) { - query = multiLevelGroup(query, group); - } - - if (take || skip) { - query = query.slice(skip || 0, take); - } - - return query; -} - -export default { - multiLevelGroup, - arrangeSortingInfo, - queryByOptions, -}; diff --git a/packages/devextreme/js/__internal/data/m_utils.ts b/packages/devextreme/js/__internal/data/m_utils.ts deleted file mode 100644 index ea0a65a494d8..000000000000 --- a/packages/devextreme/js/__internal/data/m_utils.ts +++ /dev/null @@ -1,304 +0,0 @@ -/* eslint-disable spellcheck/spell-checker */ -import domAdapter from '@js/core/dom_adapter'; -import { equalByValue } from '@js/core/utils/common'; -import { Deferred } from '@js/core/utils/deferred'; -import { map } from '@js/core/utils/iterator'; -import readyCallbacks from '@js/core/utils/ready_callbacks'; -import { isFunction } from '@js/core/utils/type'; -import { getWindow } from '@js/core/utils/window'; - -const ready = readyCallbacks.add; - -export const XHR_ERROR_UNLOAD = 'DEVEXTREME_XHR_ERROR_UNLOAD'; - -export const normalizeBinaryCriterion = function (crit) { - return [ - crit[0], - crit.length < 3 ? '=' : String(crit[1]).toLowerCase(), - crit.length < 2 ? true : crit[crit.length - 1], - ]; -}; - -export const normalizeSortingInfo = function (info) { - if (!Array.isArray(info)) { - info = [info]; - } - - return map(info, (i) => { - const result = { - selector: isFunction(i) || typeof i === 'string' ? i : i.getter || i.field || i.selector, - desc: !!(i.desc || String(i.dir).charAt(0).toLowerCase() === 'd'), - }; - if (i.compare) { - // @ts-expect-error - result.compare = i.compare; - } - return result; - }); -}; - -export const errorMessageFromXhr = (function () { - const textStatusMessages = { - timeout: 'Network connection timeout', - error: 'Unspecified network error', - parsererror: 'Unexpected server response', - }; - - /// #DEBUG - const textStatusDetails = { - timeout: 'possible causes: the remote host is not accessible, overloaded or is not included into the domain white-list when being run in the native container', - error: 'if the remote host is located on another domain, make sure it properly supports cross-origin resource sharing (CORS), or use the JSONP approach instead', - parsererror: 'the remote host did not respond with valid JSON data', - }; - /// #ENDDEBUG - - const explainTextStatus = function (textStatus) { - let result = textStatusMessages[textStatus]; - - if (!result) { - return textStatus; - } - - /// #DEBUG - result += ` (${textStatusDetails[textStatus]})`; - /// #ENDDEBUG - - return result; - }; - - // T542570, https://stackoverflow.com/a/18170879 - let unloading; - ready(() => { - const window = getWindow(); - domAdapter.listen(window, 'beforeunload', () => { unloading = true; }); - }); - - return function (xhr, textStatus) { - if (unloading) { - return XHR_ERROR_UNLOAD; - } - if (xhr.status < 400) { - return explainTextStatus(textStatus); - } - return xhr.statusText; - }; -}()); - -export const aggregators = { - count: { - seed: 0, - step(count) { return 1 + count; }, - }, - sum: { - seed: 0, - step(sum, item) { return sum + item; }, - }, - min: { - step(min, item) { return item < min ? item : min; }, - }, - max: { - step(max, item) { return item > max ? item : max; }, - }, - avg: { - seed: [0, 0], - step(pair, value) { - return [pair[0] + value, pair[1] + 1]; - }, - finalize(pair) { - return pair[1] ? pair[0] / pair[1] : NaN; - }, - }, -}; - -export const processRequestResultLock = (function () { - let lockCount = 0; - let lockDeferred; - - const obtain = function () { - if (lockCount === 0) { - // @ts-expect-error - lockDeferred = new Deferred(); - } - lockCount++; - }; - - const release = function () { - lockCount--; - if (lockCount < 1) { - lockDeferred.resolve(); - } - }; - - const promise = function () { - // @ts-expect-error - const deferred = lockCount === 0 ? new Deferred().resolve() : lockDeferred; - return deferred.promise(); - }; - - const reset = function () { - lockCount = 0; - if (lockDeferred) { - lockDeferred.resolve(); - } - }; - - return { - obtain, - release, - promise, - reset, - }; -}()); - -export function isDisjunctiveOperator(condition) { - return /^(or|\|\||\|)$/i.test(condition); -} - -export function isConjunctiveOperator(condition) { - return /^(and|&&|&)$/i.test(condition); -} - -export const keysEqual = function (keyExpr, key1, key2) { - if (Array.isArray(keyExpr)) { - const names = map(key1, (v, k) => k); - let name; - for (let i = 0; i < names.length; i++) { - name = names[i]; - if (!equalByValue(key1[name], key2[name], { strict: false })) { - return false; - } - } - return true; - } - - return equalByValue(key1, key2, { strict: false }); -}; - -const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; - -// eslint-disable-next-line @typescript-eslint/naming-convention -export const base64_encode = function (input) { - if (!Array.isArray(input)) { - input = stringToByteArray(String(input)); - } - - let result = ''; - - function getBase64Char(index) { - return BASE64_CHARS.charAt(index); - } - - for (let i = 0; i < input.length; i += 3) { - const octet1 = input[i]; - const octet2 = input[i + 1]; - const octet3 = input[i + 2]; - - result += map( - [ - octet1 >> 2, - ((octet1 & 3) << 4) | octet2 >> 4, - isNaN(octet2) ? 64 : ((octet2 & 15) << 2) | octet3 >> 6, - isNaN(octet3) ? 64 : octet3 & 63, - ], - getBase64Char, - ).join(''); - } - - return result; -}; - -function stringToByteArray(str) { - const bytes: number[] = []; - let code: number; - let i; - - for (i = 0; i < str.length; i++) { - code = str.charCodeAt(i); - - if (code < 128) { - bytes.push(code); - } else if (code < 2048) { - bytes.push(192 + (code >> 6), 128 + (code & 63)); - } else if (code < 65536) { - bytes.push(224 + (code >> 12), 128 + ((code >> 6) & 63), 128 + (code & 63)); - } else if (code < 2097152) { - bytes.push(240 + (code >> 18), 128 + ((code >> 12) & 63), 128 + ((code >> 6) & 63), 128 + (code & 63)); - } - } - return bytes; -} - -export const isUnaryOperation = function (crit) { - return crit[0] === '!' && Array.isArray(crit[1]); -}; - -const isGroupOperator = function (value) { - return value === 'and' || value === 'or'; -}; - -export const isUniformEqualsByOr = function (crit) { - if (crit.length > 2 && Array.isArray(crit[0]) && crit[1] === 'or' && typeof crit[0][0] === 'string' && crit[0][1] === '=') { - const [prop] = crit[0]; - return !crit.find((el, i) => (i % 2 !== 0 ? el !== 'or' - : !Array.isArray(el) || el.length !== 3 || el[0] !== prop || el[1] !== '=')); - } - return false; -}; - -export const isGroupCriterion = function (crit) { - const first = crit[0]; - const second = crit[1]; - - if (Array.isArray(first)) { - return true; - } - if (isFunction(first)) { - if (Array.isArray(second) || isFunction(second) || isGroupOperator(second)) { - return true; - } - } - - return false; -}; - -export const trivialPromise = function () { - // @ts-expect-error - const d = new Deferred(); - return d.resolve.apply(d, arguments).promise(); -}; - -export const rejectedPromise = function () { - // @ts-expect-error - const d = new Deferred(); - return d.reject.apply(d, arguments).promise(); -}; - -function throttle(func, timeout) { - let timeoutId; - return function () { - if (!timeoutId) { - timeoutId = setTimeout(() => { - timeoutId = undefined; - func.call(this); - }, isFunction(timeout) ? timeout() : timeout); - } - return timeoutId; - }; -} - -export function throttleChanges(func, timeout) { - let cache = []; - const throttled = throttle(function () { - func.call(this, cache); - cache = []; - }, timeout); - - return function (changes) { - if (Array.isArray(changes)) { - // @ts-expect-error - cache.push(...changes); - } - // @ts-expect-error - return throttled.call(this, cache); - }; -} diff --git a/packages/devextreme/js/__internal/data/odata/context.ts b/packages/devextreme/js/__internal/data/odata/context.ts index 6e070b4c937c..dbd3dadbde42 100644 --- a/packages/devextreme/js/__internal/data/odata/context.ts +++ b/packages/devextreme/js/__internal/data/odata/context.ts @@ -9,7 +9,7 @@ import { each } from '@js/core/utils/iterator'; import { isDefined, isPlainObject } from '@js/core/utils/type'; import type { StoreErrorHandler } from '@ts/data/abstract_store'; -import { errors, handleError } from '../m_errors'; +import { errors, handleError } from '../errors'; import { escapeServiceOperationParams, formatFunctionInvocationUrl } from './utils'; export type ServiceOperationParams = Record<string, unknown>; diff --git a/packages/devextreme/js/__internal/data/odata/m_query_adapter.ts b/packages/devextreme/js/__internal/data/odata/m_query_adapter.ts deleted file mode 100644 index 6205484aa0a9..000000000000 --- a/packages/devextreme/js/__internal/data/odata/m_query_adapter.ts +++ /dev/null @@ -1,363 +0,0 @@ -import queryAdapters from '@js/common/data/query_adapters'; -import config from '@js/core/config'; -import { extend } from '@js/core/utils/extend'; -import { each } from '@js/core/utils/iterator'; -import { isFunction } from '@js/core/utils/type'; - -import { errors } from '../m_errors'; -import { - isConjunctiveOperator, - isUnaryOperation, - normalizeBinaryCriterion, -} from '../m_utils'; -import { - convertPrimitiveValue, - generateExpand, - generateSelect, - sendRequest, - serializePropName, - serializeValue, -} from './utils'; - -const DEFAULT_PROTOCOL_VERSION = 4; -const STRING_FUNCTIONS = ['contains', 'notcontains', 'startswith', 'endswith']; - -const compileCriteria = (() => { - let protocolVersion; - let forceLowerCase; - let fieldTypes; - - const createBinaryOperationFormatter = (op) => (prop, val) => `${prop} ${op} ${val}`; - - const createStringFuncFormatter = (op, reverse) => (prop, val) => { - const bag = [op, '(']; - - if (forceLowerCase) { - prop = prop.indexOf('tolower(') === -1 ? `tolower(${prop})` : prop; - val = val.toLowerCase(); - } - - if (reverse) { - bag.push(val, ',', prop); - } else { - bag.push(prop, ',', val); - } - - bag.push(')'); - return bag.join(''); - }; - - const isStringFunction = function (name) { - return STRING_FUNCTIONS.some((funcName) => funcName === name); - }; - - const formatters = { - '=': createBinaryOperationFormatter('eq'), - '<>': createBinaryOperationFormatter('ne'), - '>': createBinaryOperationFormatter('gt'), - '>=': createBinaryOperationFormatter('ge'), - '<': createBinaryOperationFormatter('lt'), - '<=': createBinaryOperationFormatter('le'), - // @ts-expect-error - startswith: createStringFuncFormatter('startswith'), - // @ts-expect-error - endswith: createStringFuncFormatter('endswith'), - }; - - const formattersV2 = extend({}, formatters, { - /* eslint-disable spellcheck/spell-checker */ - contains: createStringFuncFormatter('substringof', true), - notcontains: createStringFuncFormatter('not substringof', true), - }); - - const formattersV4 = extend({}, formatters, { - // @ts-expect-error - contains: createStringFuncFormatter('contains'), - // @ts-expect-error - notcontains: createStringFuncFormatter('not contains'), - }); - - const compileBinary = (criteria) => { - criteria = normalizeBinaryCriterion(criteria); - - const op = criteria[1]; - const fieldName = criteria[0]; - const fieldType = fieldTypes && fieldTypes[fieldName]; - - if (fieldType && isStringFunction(op) && fieldType !== 'String') { - // @ts-expect-error - throw new errors.Error('E4024', op, fieldName, fieldType); - } - - const formatters = protocolVersion === 4 - ? formattersV4 - : formattersV2; - const formatter = formatters[op.toLowerCase()]; - - if (!formatter) { - throw errors.Error('E4003', op); - } - - let value = criteria[2]; - - if (fieldTypes?.[fieldName]) { - value = convertPrimitiveValue(fieldTypes[fieldName], value); - } - - return formatter( - serializePropName(fieldName), - serializeValue(value, protocolVersion, fieldTypes?.[fieldName]), - ); - }; - - const compileUnary = (criteria) => { - const op = criteria[0]; - const crit = compileCore(criteria[1]); - - if (op === '!') { - return `not (${crit})`; - } - - throw errors.Error('E4003', op); - }; - - const compileGroup = (criteria) => { - const bag = []; - let groupOperator; - let nextGroupOperator; - - each(criteria, function (index, criterion) { - if (Array.isArray(criterion)) { - if (bag.length > 1 && groupOperator !== nextGroupOperator) { - // @ts-expect-error - throw new errors.Error('E4019'); - } - // @ts-expect-error - bag.push(`(${compileCore(criterion)})`); - - groupOperator = nextGroupOperator; - nextGroupOperator = 'and'; - } else { - nextGroupOperator = isConjunctiveOperator(this) ? 'and' : 'or'; - } - }); - - return bag.join(` ${groupOperator} `); - }; - - const compileCore = (criteria) => { - if (Array.isArray(criteria[0])) { - return compileGroup(criteria); - } - - if (isUnaryOperation(criteria)) { - return compileUnary(criteria); - } - - return compileBinary(criteria); - }; - - return (criteria, version, types, filterToLower) => { - fieldTypes = types; - forceLowerCase = filterToLower ?? config().oDataFilterToLower; - protocolVersion = version; - - return compileCore(criteria); - }; -})(); - -const createODataQueryAdapter = (queryOptions) => { - /* eslint-disable @typescript-eslint/naming-convention */ - let _sorting = []; - const _criteria = []; - const _expand = queryOptions.expand; - let _select; - let _skip; - let _take; - let _countQuery; - - const _oDataVersion = queryOptions.version || DEFAULT_PROTOCOL_VERSION; - - const hasSlice = () => _skip || _take !== undefined; - - const hasFunction = (criterion) => { - for (let i = 0; i < criterion.length; i++) { - if (isFunction(criterion[i])) { - return true; - } - - if (Array.isArray(criterion[i]) && hasFunction(criterion[i])) { - return true; - } - } - return false; - }; - - const requestData = () => { - const result = {}; - - if (!_countQuery) { - if (_sorting.length) { - // @ts-expect-error - result.$orderby = _sorting.join(','); - } - if (_skip) { - // @ts-expect-error - result.$skip = _skip; - } - if (_take !== undefined) { - // @ts-expect-error - result.$top = _take; - } - // @ts-expect-error - result.$select = generateSelect(_oDataVersion, _select) || undefined; - // @ts-expect-error - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - result.$expand = generateExpand(_oDataVersion, _expand, _select) || undefined; - } - - if (_criteria.length) { - const criteria = _criteria.length < 2 ? _criteria[0] : _criteria; - const fieldTypes = queryOptions?.fieldTypes; - const filterToLower = queryOptions?.filterToLower; - // @ts-expect-error - result.$filter = compileCriteria(criteria, _oDataVersion, fieldTypes, filterToLower); - } - - if (_countQuery) { - // @ts-expect-error - result.$top = 0; - } - - if (queryOptions.requireTotalCount || _countQuery) { - // todo: tests!!! - if (_oDataVersion !== 4) { - // @ts-expect-error - result.$inlinecount = 'allpages'; - } else { - // @ts-expect-error - result.$count = 'true'; - } - } - - return result; - }; - - const tryLiftSelect = (tasks) => { - let selectIndex = -1; - for (let i = 0; i < tasks.length; i++) { - if (tasks[i].name === 'select') { - selectIndex = i; - break; - } - } - - if (selectIndex < 0 || !isFunction(tasks[selectIndex].args[0])) return; - - const nextTask = tasks[1 + selectIndex]; - if (!nextTask || nextTask.name !== 'slice') return; - - tasks[1 + selectIndex] = tasks[selectIndex]; - tasks[selectIndex] = nextTask; - }; - - return { - - optimize: tryLiftSelect, - - exec(url) { - return sendRequest( - _oDataVersion, - { - url, - params: extend(requestData(), queryOptions?.params), - }, - { - beforeSend: queryOptions.beforeSend, - jsonp: queryOptions.jsonp, - withCredentials: queryOptions.withCredentials, - countOnly: _countQuery, - processDatesAsUtc: queryOptions.processDatesAsUtc, - fieldTypes: queryOptions.fieldTypes, - isPaged: isFinite(_take), - }, - ); - }, - /* eslint-disable @typescript-eslint/no-invalid-void-type */ - multiSort(args): boolean | void { - let rules; - - if (hasSlice()) { - return false; - } - - for (let i = 0; i < args.length; i++) { - const getter = args[i][0]; - const desc = !!args[i][1]; - let rule; - - if (typeof getter !== 'string') { - return false; - } - - rule = serializePropName(getter); - if (desc) { - rule += ' desc'; - } - - rules = rules || []; - rules.push(rule); - } - - _sorting = rules; - }, - - slice(skipCount, takeCount): boolean | void { - if (hasSlice()) { - return false; - } - - _skip = skipCount; - _take = takeCount; - }, - - filter(criterion): boolean | void { - if (hasSlice()) { - return false; - } - - if (!Array.isArray(criterion)) { - criterion = [].slice.call(arguments); - } - - if (hasFunction(criterion)) { - return false; - } - - if (_criteria.length) { - // @ts-expect-error - _criteria.push('and'); - } - // @ts-expect-error - _criteria.push(criterion); - }, - - select(expr): boolean | void { - if (_select || isFunction(expr)) { - return false; - } - - if (!Array.isArray(expr)) { - expr = [].slice.call(arguments); - } - - _select = expr; - }, - // eslint-disable-next-line no-return-assign - count: () => _countQuery = true, - }; -}; - -queryAdapters.odata = createODataQueryAdapter; - -export const odata = createODataQueryAdapter; diff --git a/packages/devextreme/js/__internal/data/odata/m_request_dispatcher.ts b/packages/devextreme/js/__internal/data/odata/m_request_dispatcher.ts deleted file mode 100644 index e37f10217123..000000000000 --- a/packages/devextreme/js/__internal/data/odata/m_request_dispatcher.ts +++ /dev/null @@ -1,72 +0,0 @@ -import '@js/common/data/odata/query_adapter'; - -import { sendRequest } from '@js/common/data/odata/utils'; - -const DEFAULT_PROTOCOL_VERSION = 4; - -export default class RequestDispatcher { - constructor(options) { - options = options || {}; - // @ts-expect-error - this._url = String(options.url).replace(/\/+$/, ''); - // @ts-expect-error - this._beforeSend = options.beforeSend; - // @ts-expect-error - this._jsonp = options.jsonp; - // @ts-expect-error - this._version = options.version || DEFAULT_PROTOCOL_VERSION; - // @ts-expect-error - this._withCredentials = options.withCredentials; - // @ts-expect-error - this._processDatesAsUtc = options.processDatesAsUtc ?? options.deserializeDates ?? false; - // @ts-expect-error - this._filterToLower = options.filterToLower; - } - - sendRequest(url, method, params, payload) { - return sendRequest( - this.version, - { - url, - method, - params: params || {}, - payload, - }, - { - // @ts-expect-error - beforeSend: this._beforeSend, - // @ts-expect-error - jsonp: this._jsonp, - // @ts-expect-error - withCredentials: this._withCredentials, - // @ts-expect-error - processDatesAsUtc: this._processDatesAsUtc, - }, - ); - } - - get version() { - // @ts-expect-error - return this._version; - } - - get beforeSend() { - // @ts-expect-error - return this._beforeSend; - } - - get url() { - // @ts-expect-error - return this._url; - } - - get jsonp() { - // @ts-expect-error - return this._jsonp; - } - - get filterToLower() { - // @ts-expect-error - return this._filterToLower; - } -} diff --git a/packages/devextreme/js/__internal/data/odata/query_adapter.ts b/packages/devextreme/js/__internal/data/odata/query_adapter.ts new file mode 100644 index 000000000000..932de8a1bef3 --- /dev/null +++ b/packages/devextreme/js/__internal/data/odata/query_adapter.ts @@ -0,0 +1,367 @@ +import queryAdapters from '@js/common/data/query_adapters'; +import config from '@js/core/config'; +import type { DeferredObj } from '@js/core/utils/deferred'; +import { extend } from '@js/core/utils/extend'; +import { isFunction, isString } from '@js/core/utils/type'; +import type { QueryAdapter, RemoteQueryOptions, RemoteTask } from '@ts/data/remote_query'; + +import { errors } from '../errors'; +import { + isConjunctiveOperator, + isUnaryOperation, + normalizeBinaryCriterion, +} from '../utils'; +import { + convertPrimitiveValue, + EdmLiteral, + generateExpand, + generateSelect, + sendRequest, + serializePropName, + serializeValue, +} from './utils'; + +const DEFAULT_PROTOCOL_VERSION = 4; +const STRING_FUNCTIONS = ['contains', 'notcontains', 'startswith', 'endswith']; + +type Formatter = (prop: string, val: string) => string; + +type FieldTypes = Record<string, string>; + +interface ODataRequestParams { + [param: string]: unknown; + $orderby?: string; + $skip?: number; + $top?: number; + $select?: string; + $expand?: string; + $filter?: string; + // eslint-disable-next-line spellcheck/spell-checker + $inlinecount?: string; + $count?: string; +} + +const compileCriteria = (() => { + // eslint-disable-next-line @typescript-eslint/init-declarations + let protocolVersion: number; + // eslint-disable-next-line @typescript-eslint/init-declarations + let forceLowerCase: boolean | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations + let fieldTypes: FieldTypes | undefined; + + const createBinaryOperationFormatter = (op: string): Formatter => (prop, val) => `${prop} ${op} ${val}`; + + const createStringFuncFormatter = (op: string, reverse?: boolean): Formatter => (prop, val) => { + const bag = [op, '(']; + const propName = forceLowerCase && !prop.includes('tolower(') ? `tolower(${prop})` : prop; + const value = forceLowerCase ? val.toLowerCase() : val; + + if (reverse) { + bag.push(value, ',', propName); + } else { + bag.push(propName, ',', value); + } + + bag.push(')'); + return bag.join(''); + }; + + const isStringFunction = function (name: string): boolean { + return STRING_FUNCTIONS.some((funcName) => funcName === name); + }; + + const formatters: Record<string, Formatter> = { + '=': createBinaryOperationFormatter('eq'), + '<>': createBinaryOperationFormatter('ne'), + '>': createBinaryOperationFormatter('gt'), + '>=': createBinaryOperationFormatter('ge'), + '<': createBinaryOperationFormatter('lt'), + '<=': createBinaryOperationFormatter('le'), + startswith: createStringFuncFormatter('startswith'), + endswith: createStringFuncFormatter('endswith'), + }; + + /* eslint-disable spellcheck/spell-checker */ + const formattersV2: Record<string, Formatter> = { + ...formatters, + contains: createStringFuncFormatter('substringof', true), + notcontains: createStringFuncFormatter('not substringof', true), + }; + /* eslint-enable spellcheck/spell-checker */ + + /* eslint-disable spellcheck/spell-checker */ + const formattersV4: Record<string, Formatter> = { + ...formatters, + contains: createStringFuncFormatter('contains'), + notcontains: createStringFuncFormatter('not contains'), + }; + /* eslint-enable spellcheck/spell-checker */ + + const compileBinary = (criteria: unknown[]): string => { + const crit = normalizeBinaryCriterion(criteria); + + const [rawFieldName, op] = crit; + const fieldName = isString(rawFieldName) || rawFieldName instanceof EdmLiteral + ? rawFieldName + : String(rawFieldName); + const fieldType = fieldTypes?.[String(fieldName)]; + + if (fieldType && isStringFunction(op) && fieldType !== 'String') { + throw errors.Error('E4024', op, fieldName, fieldType); + } + + const criterionFormatters = protocolVersion === 4 + ? formattersV4 + : formattersV2; + const formatter = criterionFormatters[op.toLowerCase()]; + + if (!formatter) { + throw errors.Error('E4003', op); + } + + const value = fieldType ? convertPrimitiveValue(fieldType, crit[2]) : crit[2]; + + return formatter( + serializePropName(fieldName), + serializeValue(value, protocolVersion, fieldType), + ); + }; + + const compileUnary = (criteria: unknown[]): string => { + const op = criteria[0]; + // eslint-disable-next-line @typescript-eslint/no-use-before-define + const crit = compileCore(criteria[1]); + + if (op === '!') { + return `not (${crit})`; + } + + throw errors.Error('E4003', op); + }; + + const compileGroup = (criteria: unknown[]): string => { + const bag: string[] = []; + // eslint-disable-next-line @typescript-eslint/init-declarations + let groupOperator: string | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations + let nextGroupOperator: string | undefined; + + criteria.forEach((criterion) => { + if (Array.isArray(criterion)) { + if (bag.length > 1 && groupOperator !== nextGroupOperator) { + throw errors.Error('E4019'); + } + // eslint-disable-next-line @typescript-eslint/no-use-before-define + bag.push(`(${compileCore(criterion)})`); + + groupOperator = nextGroupOperator; + nextGroupOperator = 'and'; + } else { + nextGroupOperator = isConjunctiveOperator(criterion) ? 'and' : 'or'; + } + }); + + return bag.join(` ${groupOperator} `); + }; + + const compileCore = (criteria: unknown): string => { + const criterion: unknown[] = Array.isArray(criteria) ? criteria : [criteria]; + + if (Array.isArray(criterion[0])) { + return compileGroup(criterion); + } + + if (isUnaryOperation(criterion)) { + return compileUnary(criterion); + } + + return compileBinary(criterion); + }; + + return ( + criteria: unknown, + version: number, + types: FieldTypes | undefined, + filterToLower: boolean | undefined, + ): string => { + fieldTypes = types; + forceLowerCase = filterToLower ?? config().oDataFilterToLower; + protocolVersion = version; + + return compileCore(criteria); + }; +})(); + +const createODataQueryAdapter = (queryOptions: RemoteQueryOptions): QueryAdapter => { + let sorting: string[] = []; + const criteria: unknown[] = []; + const { expand } = queryOptions; + // eslint-disable-next-line @typescript-eslint/init-declarations + let select: string[] | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations + let skip: number | undefined; + // eslint-disable-next-line @typescript-eslint/init-declarations + let take: number | undefined; + let countQuery = false; + + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const oDataVersion = queryOptions.version || DEFAULT_PROTOCOL_VERSION; + + const hasSlice = (): boolean => !!skip || take !== undefined; + + const hasFunction = (criterion: unknown[]): boolean => criterion.some( + (item) => isFunction(item) || (Array.isArray(item) && hasFunction(item)), + ); + + const requestData = (): ODataRequestParams => { + const result: ODataRequestParams = {}; + + if (!countQuery) { + if (sorting.length) { + result.$orderby = sorting.join(','); + } + if (skip) { + result.$skip = skip; + } + if (take !== undefined) { + result.$top = take; + } + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + result.$select = generateSelect(oDataVersion, select) || undefined; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + result.$expand = generateExpand(oDataVersion, expand, select) || undefined; + } + + if (criteria.length) { + const filterCriteria: unknown = criteria.length < 2 ? criteria[0] : criteria; + const { fieldTypes, filterToLower } = queryOptions; + result.$filter = compileCriteria(filterCriteria, oDataVersion, fieldTypes, filterToLower); + } + + if (countQuery) { + result.$top = 0; + } + + if (queryOptions.requireTotalCount || countQuery) { + // todo: tests!!! + if (oDataVersion !== 4) { + // eslint-disable-next-line spellcheck/spell-checker + result.$inlinecount = 'allpages'; + } else { + result.$count = 'true'; + } + } + + return result; + }; + + const tryLiftSelect = (tasks: RemoteTask[]): void => { + const selectIndex = tasks.findIndex((task) => task.name === 'select'); + + if (selectIndex < 0 || !isFunction(tasks[selectIndex].args[0])) return; + + const nextTask = tasks[1 + selectIndex]; + if (nextTask?.name !== 'slice') return; + + tasks[1 + selectIndex] = tasks[selectIndex]; + tasks[selectIndex] = nextTask; + }; + + return { + + optimize: tryLiftSelect, + + exec(url: string): DeferredObj<unknown> { + return sendRequest( + oDataVersion, + { + url, + params: extend(requestData(), queryOptions.params), + }, + { + beforeSend: queryOptions.beforeSend, + jsonp: queryOptions.jsonp, + withCredentials: queryOptions.withCredentials, + countOnly: countQuery, + processDatesAsUtc: queryOptions.processDatesAsUtc, + fieldTypes: queryOptions.fieldTypes, + isPaged: isFinite(Number(take)), + }, + ); + }, + + multiSort(args: unknown[]): boolean | undefined { + const rules: string[] = []; + + if (hasSlice()) { + return false; + } + + for (const arg of args) { + const [getter, desc] = Array.isArray(arg) ? arg : []; + + if (typeof getter !== 'string') { + return false; + } + + rules.push(desc ? `${serializePropName(getter)} desc` : serializePropName(getter)); + } + + sorting = rules; + + return undefined; + }, + + slice(skipCount: number, takeCount: number): boolean | undefined { + if (hasSlice()) { + return false; + } + + skip = skipCount; + take = takeCount; + + return undefined; + }, + + filter(...args: unknown[]): boolean | undefined { + if (hasSlice()) { + return false; + } + + const [first] = args; + const criterion: unknown[] = Array.isArray(first) ? first : args.slice(); + + if (hasFunction(criterion)) { + return false; + } + + if (criteria.length) { + criteria.push('and'); + } + criteria.push(criterion); + + return undefined; + }, + + select(...args: unknown[]): boolean | undefined { + const [expr] = args; + + if (select || isFunction(expr)) { + return false; + } + + select = (Array.isArray(expr) ? expr : args.slice()).map(String); + + return undefined; + }, + + count(): boolean { + countQuery = true; + return countQuery; + }, + }; +}; + +queryAdapters.odata = createODataQueryAdapter; + +export const odata = createODataQueryAdapter; diff --git a/packages/devextreme/js/__internal/data/odata/request_dispatcher.ts b/packages/devextreme/js/__internal/data/odata/request_dispatcher.ts new file mode 100644 index 000000000000..36acb68ebdde --- /dev/null +++ b/packages/devextreme/js/__internal/data/odata/request_dispatcher.ts @@ -0,0 +1,91 @@ +import '@js/common/data/odata/query_adapter'; + +import { sendRequest } from '@js/common/data/odata/utils'; +import type { DeferredObj } from '@js/core/utils/deferred'; + +const DEFAULT_PROTOCOL_VERSION = 4; + +export interface RequestDispatcherOptions { + url?: string; + beforeSend?: Function; + jsonp?: boolean; + version?: number; + withCredentials?: boolean; + processDatesAsUtc?: boolean; + deserializeDates?: boolean; + filterToLower?: boolean; +} + +export default class RequestDispatcher { + _url: string; + + _beforeSend?: Function; + + _jsonp?: boolean; + + _version: number; + + _withCredentials?: boolean; + + _processDatesAsUtc: boolean; + + _filterToLower?: boolean; + + constructor(options?: RequestDispatcherOptions) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const dispatcherOptions: RequestDispatcherOptions = options || {}; + + this._url = String(dispatcherOptions.url).replace(/\/+$/, ''); + this._beforeSend = dispatcherOptions.beforeSend; + this._jsonp = dispatcherOptions.jsonp; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this._version = dispatcherOptions.version || DEFAULT_PROTOCOL_VERSION; + this._withCredentials = dispatcherOptions.withCredentials; + this._processDatesAsUtc = dispatcherOptions.processDatesAsUtc + ?? dispatcherOptions.deserializeDates ?? false; + this._filterToLower = dispatcherOptions.filterToLower; + } + + sendRequest( + url: string, + method?: string, + params?: Record<string, unknown> | null, + payload?: unknown, + ): DeferredObj<unknown> { + return sendRequest( + this.version, + { + url, + method, + params: params ?? {}, + payload, + }, + { + beforeSend: this._beforeSend, + jsonp: this._jsonp, + withCredentials: this._withCredentials, + processDatesAsUtc: this._processDatesAsUtc, + }, + ); + } + + get version(): number { + return this._version; + } + + get beforeSend(): Function | undefined { + return this._beforeSend; + } + + get url(): string { + return this._url; + } + + get jsonp(): boolean | undefined { + return this._jsonp; + } + + get filterToLower(): boolean | undefined { + return this._filterToLower; + } +} diff --git a/packages/devextreme/js/__internal/data/odata/store.ts b/packages/devextreme/js/__internal/data/odata/store.ts index 4464dc43d888..d02f3cf322fe 100644 --- a/packages/devextreme/js/__internal/data/odata/store.ts +++ b/packages/devextreme/js/__internal/data/odata/store.ts @@ -35,13 +35,6 @@ export interface ODataLoadOptions extends StoreLoadOptions { customQueryParams?: Record<string, unknown>; } -// `RequestDispatcher` does not declare its own fields yet, so the members the store reads -// directly are described here until that class is typed. -type StoreRequestDispatcher = RequestDispatcher & { - _withCredentials?: unknown; - _processDatesAsUtc?: unknown; -}; - const expandKeyType = (key: StoreKey, keyType: string): FieldTypes => ({ [String(key)]: keyType }); const getProperty = (source: unknown, name: string): unknown => ( @@ -72,7 +65,7 @@ const mergeFieldTypesWithKeyType = ( }; class ODataStore extends Store { - _requestDispatcher: StoreRequestDispatcher; + _requestDispatcher: RequestDispatcher; _fieldTypes: FieldTypes; diff --git a/packages/devextreme/js/__internal/data/query.ts b/packages/devextreme/js/__internal/data/query.ts new file mode 100644 index 000000000000..f25f87f4734f --- /dev/null +++ b/packages/devextreme/js/__internal/data/query.ts @@ -0,0 +1,16 @@ +import { queryImpl } from '@js/common/data/query_implementation'; +import type { ArrayQuery, QueryOptions } from '@ts/data/array_query'; +import type { RemoteQuery, RemoteQueryOptions } from '@ts/data/remote_query'; + +function query(array: unknown[], queryOptions?: QueryOptions): ArrayQuery; +function query(url: string, queryOptions?: RemoteQueryOptions): RemoteQuery; +function query( + source: unknown[] | string, + queryOptions?: QueryOptions & RemoteQueryOptions, +): ArrayQuery | RemoteQuery { + return Array.isArray(source) + ? queryImpl.array(source, queryOptions) + : queryImpl.remote(source, queryOptions); +} + +export default query; diff --git a/packages/devextreme/js/__internal/data/query_implementation.ts b/packages/devextreme/js/__internal/data/query_implementation.ts index 2654903ad637..d7185d49d29e 100644 --- a/packages/devextreme/js/__internal/data/query_implementation.ts +++ b/packages/devextreme/js/__internal/data/query_implementation.ts @@ -1,5 +1,5 @@ import arrayQueryImpl from './array_query'; -import remoteQueryImpl from './m_remote_query'; +import remoteQueryImpl from './remote_query'; export const queryImpl = { array: arrayQueryImpl, diff --git a/packages/devextreme/js/__internal/data/remote_query.ts b/packages/devextreme/js/__internal/data/remote_query.ts new file mode 100644 index 000000000000..d7ed4b829649 --- /dev/null +++ b/packages/devextreme/js/__internal/data/remote_query.ts @@ -0,0 +1,217 @@ +import arrayQueryImpl from '@js/common/data/array_query'; +import { errors, handleError } from '@js/common/data/errors'; +import queryAdapters from '@js/common/data/query_adapters'; +import type { DeferredObj } from '@js/core/utils/deferred'; +import { Deferred } from '@js/core/utils/deferred'; +import { isFunction } from '@js/core/utils/type'; +import type { ArrayQuery, LangParams } from '@ts/data/array_query'; + +export interface RemoteTask { + name: string; + args: unknown[]; +} + +export interface QueryAdapter { + [taskName: string]: unknown; + optimize?: (tasks: RemoteTask[]) => void; + exec: (url: string) => DeferredObj<unknown>; +} + +export interface RemoteQueryOptions { + adapter?: string | ((options: RemoteQueryOptions) => QueryAdapter); + errorHandler?: (error: unknown) => void; + langParams?: LangParams; + version?: number; + expand?: string | string[] | Function; + fieldTypes?: Record<string, string>; + filterToLower?: boolean; + requireTotalCount?: boolean; + params?: Record<string, unknown>; + beforeSend?: Function; + jsonp?: boolean; + withCredentials?: boolean; + processDatesAsUtc?: boolean; + deserializeDates?: boolean; +} + +export interface RemoteQuery { + sortBy: (...args: unknown[]) => RemoteQuery; + thenBy: (...args: unknown[]) => RemoteQuery; + filter: (...args: unknown[]) => RemoteQuery; + slice: (...args: unknown[]) => RemoteQuery; + select: (...args: unknown[]) => RemoteQuery; + groupBy: (...args: unknown[]) => RemoteQuery; + count: (...args: unknown[]) => DeferredObj<unknown>; + min: (...args: unknown[]) => DeferredObj<unknown>; + max: (...args: unknown[]) => DeferredObj<unknown>; + sum: (...args: unknown[]) => DeferredObj<unknown>; + avg: (...args: unknown[]) => DeferredObj<unknown>; + aggregate: (...args: unknown[]) => DeferredObj<unknown>; + enumerate: (...args: unknown[]) => DeferredObj<unknown>; +} + +const createTask = function (name: string, args: unknown[]): RemoteTask { + return { name, args }; +}; + +const remoteQueryImpl = function ( + url: string, + options?: RemoteQueryOptions, + previousTasks?: RemoteTask[], +): RemoteQuery { + const tasks: RemoteTask[] = previousTasks ?? []; + const queryOptions: RemoteQueryOptions = options ?? {}; + + const exec = function (executorTask: RemoteTask): DeferredObj<unknown> { + const d = Deferred<unknown>(); + // eslint-disable-next-line @typescript-eslint/init-declarations + let mergedSortArgs: unknown[][] | undefined; + + const rejectWithNotify = function (error: unknown): void { + const handler = queryOptions.errorHandler; + if (handler) { + handler(error); + } + + handleError(error); + d.reject(error); + }; + + function mergeSortTask(task: RemoteTask): boolean { + switch (task.name) { + case 'sortBy': + mergedSortArgs = [task.args]; + return true; + + case 'thenBy': + if (!mergedSortArgs) { + throw errors.Error('E4004'); + } + + mergedSortArgs.push(task.args); + return true; + + default: + return false; + } + } + + function unmergeSortTasks(queue: RemoteTask[]): RemoteTask[] { + const head = queue[0]; + const unmergedTasks: RemoteTask[] = []; + + if (head?.name === 'multiSort') { + queue.shift(); + const [sortArgsList] = head.args; + if (Array.isArray(sortArgsList)) { + sortArgsList.forEach((sortArgs: unknown) => { + unmergedTasks.push(createTask( + unmergedTasks.length ? 'thenBy' : 'sortBy', + Array.isArray(sortArgs) ? sortArgs : [sortArgs], + )); + }); + } + } + + return unmergedTasks.concat(queue); + } + + function passTaskToAdapter(adapter: QueryAdapter, task: RemoteTask): boolean { + if (String(task.name) === 'enumerate') { + return true; + } + + const taskHandler = adapter[task.name]; + + return isFunction(taskHandler) && taskHandler.apply(adapter, task.args) !== false; + } + + function collectAdapterTasks(adapter: QueryAdapter, queue: RemoteTask[]): void { + while (queue.length) { + const currentTask = queue[0]; + + if (!mergeSortTask(currentTask)) { + if (mergedSortArgs) { + queue.unshift(createTask('multiSort', [mergedSortArgs])); + mergedSortArgs = undefined; + // eslint-disable-next-line no-continue + continue; + } + + if (!passTaskToAdapter(adapter, currentTask)) { + break; + } + } + queue.shift(); + } + } + + try { + const adapterFactory = isFunction(queryOptions.adapter) + ? queryOptions.adapter + : queryAdapters[String(queryOptions.adapter)]; + + const adapter: QueryAdapter = adapterFactory(queryOptions); + + let taskQueue: RemoteTask[] = [...tasks, executorTask]; + + const { optimize } = adapter; + if (optimize) optimize(taskQueue); + + collectAdapterTasks(adapter, taskQueue); + + taskQueue = unmergeSortTasks(taskQueue); + + adapter.exec(url) + .done((result: unknown, extra: unknown) => { + if (!taskQueue.length) { + d.resolve(result, extra); + } else { + // @ts-expect-error the adapter resolves with whatever the service returned + let clientChain: ArrayQuery = arrayQueryImpl(result, { + errorHandler: queryOptions.errorHandler, + }); + taskQueue.forEach((task) => { + const method = clientChain[task.name]; + if (isFunction(method)) { + clientChain = method.apply(clientChain, task.args); + } + }); + // @ts-expect-error the queue ends with enumerate, so the chain ends with a Deferred + clientChain.done(d.resolve).fail(d.reject); + } + }) + .fail(rejectWithNotify); + } catch (x) { + rejectWithNotify(x); + } + + // @ts-expect-error DeferredObj typings: promise() is declared as a plain Promise + return d.promise(); + }; + + const chain = (name: string, args: unknown[]): RemoteQuery => remoteQueryImpl( + url, + queryOptions, + tasks.concat(createTask(name, args)), + ); + + return { + sortBy: (...args: unknown[]): RemoteQuery => chain('sortBy', args), + thenBy: (...args: unknown[]): RemoteQuery => chain('thenBy', args), + filter: (...args: unknown[]): RemoteQuery => chain('filter', args), + slice: (...args: unknown[]): RemoteQuery => chain('slice', args), + select: (...args: unknown[]): RemoteQuery => chain('select', args), + groupBy: (...args: unknown[]): RemoteQuery => chain('groupBy', args), + + count: (...args: unknown[]): DeferredObj<unknown> => exec(createTask('count', args)), + min: (...args: unknown[]): DeferredObj<unknown> => exec(createTask('min', args)), + max: (...args: unknown[]): DeferredObj<unknown> => exec(createTask('max', args)), + sum: (...args: unknown[]): DeferredObj<unknown> => exec(createTask('sum', args)), + avg: (...args: unknown[]): DeferredObj<unknown> => exec(createTask('avg', args)), + aggregate: (...args: unknown[]): DeferredObj<unknown> => exec(createTask('aggregate', args)), + enumerate: (...args: unknown[]): DeferredObj<unknown> => exec(createTask('enumerate', args)), + }; +}; + +export default remoteQueryImpl; diff --git a/packages/devextreme/js/__internal/data/store_helper.ts b/packages/devextreme/js/__internal/data/store_helper.ts new file mode 100644 index 000000000000..798d00fd5bee --- /dev/null +++ b/packages/devextreme/js/__internal/data/store_helper.ts @@ -0,0 +1,138 @@ +import arrayQuery from '@js/common/data/array_query'; +import type { SortingInfo } from '@js/common/data/utils'; +import { normalizeSortingInfo } from '@js/common/data/utils'; +import { extend } from '@js/core/utils/extend'; +import { isObject } from '@js/core/utils/type'; +import type { LangParams } from '@ts/data/array_query'; + +export interface DataQuery { + /* eslint-disable @typescript-eslint/method-signature-style */ + setLangParams?(langParams: LangParams): void; + filter(criteria: unknown): this; + sortBy(getter: unknown, desc?: unknown, compare?: unknown): this; + thenBy(getter: unknown, desc?: unknown, compare?: unknown): this; + select(getter: unknown): this; + slice(skip: number, take?: number): this; + groupBy(getter: unknown): this; + toArray(): unknown[]; + /* eslint-enable @typescript-eslint/method-signature-style */ +} + +interface GroupingInfo extends Array<SortingInfo> { + keepInitialKeyOrder?: boolean; +} + +export interface QueryByOptions { + filter?: unknown; + sort?: unknown; + select?: unknown; + group?: unknown; + skip?: number; + take?: number; + langParams?: LangParams; +} + +interface GroupResult { + items?: unknown[]; +} + +function multiLevelGroup<TQuery extends DataQuery>( + query: TQuery, + groupInfo: SortingInfo[], +): TQuery { + let result = query.groupBy(groupInfo[0].selector); + + if (groupInfo.length > 1) { + result = result.select((group: unknown) => { + const { items }: GroupResult = isObject(group) ? group : {}; + + const merged: unknown = extend({}, group, { + items: multiLevelGroup(arrayQuery(items ?? []), groupInfo.slice(1)).toArray(), + }); + + return merged; + }); + } + + return result; +} + +function arrangeSortingInfo(groupInfo: SortingInfo[], sortInfo: SortingInfo[]): SortingInfo[] { + const filteredGroup = groupInfo.filter( + (group) => !sortInfo.some((sort) => group.selector === sort.selector), + ); + + return filteredGroup.concat(sortInfo); +} + +function queryByOptions<TQuery extends DataQuery>( + query: TQuery, + options?: QueryByOptions, + isCountQuery?: boolean, +): TQuery { + const queryOptions: QueryByOptions = options ?? {}; + let result = query; + + const { filter } = queryOptions; + + if (queryOptions.langParams) { + result.setLangParams?.(queryOptions.langParams); + } + + if (filter) { + result = result.filter(filter); + } + + if (isCountQuery) { + return result; + } + + const { + sort, select, skip, take, + } = queryOptions; + + // eslint-disable-next-line @typescript-eslint/init-declarations + let group: GroupingInfo | undefined; + if (queryOptions.group) { + const groupOption: { keepInitialKeyOrder?: unknown } = isObject(queryOptions.group) + ? queryOptions.group + : {}; + + group = normalizeSortingInfo(queryOptions.group); + group.keepInitialKeyOrder = !!groupOption.keepInitialKeyOrder; + } + + if (sort || group) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const sortInfo = normalizeSortingInfo(sort || []); + const sortRules = group && !group.keepInitialKeyOrder + ? arrangeSortingInfo(group, sortInfo) + : sortInfo; + + sortRules.forEach((rule, index) => { + result = index + ? result.thenBy(rule.selector, rule.desc, rule.compare) + : result.sortBy(rule.selector, rule.desc, rule.compare); + }); + } + + if (select) { + result = result.select(select); + } + + if (group) { + result = multiLevelGroup(result, group); + } + + if (take || skip) { + result = result.slice(skip ?? 0, take); + } + + return result; +} + +export default { + multiLevelGroup, + arrangeSortingInfo, + queryByOptions, +}; diff --git a/packages/devextreme/js/__internal/data/utils.ts b/packages/devextreme/js/__internal/data/utils.ts new file mode 100644 index 000000000000..373235940504 --- /dev/null +++ b/packages/devextreme/js/__internal/data/utils.ts @@ -0,0 +1,366 @@ +/* eslint-disable spellcheck/spell-checker */ +import domAdapter from '@js/core/dom_adapter'; +import { equalByValue } from '@js/core/utils/common'; +import type { DeferredObj } from '@js/core/utils/deferred'; +import { Deferred } from '@js/core/utils/deferred'; +import { map } from '@js/core/utils/iterator'; +import readyCallbacks from '@js/core/utils/ready_callbacks'; +import { isFunction, isObject, isString } from '@js/core/utils/type'; +import { getWindow } from '@js/core/utils/window'; + +const ready = readyCallbacks.add; + +export const XHR_ERROR_UNLOAD = 'DEVEXTREME_XHR_ERROR_UNLOAD'; + +export type BinaryCriterion = [unknown, string, unknown]; + +export const normalizeBinaryCriterion = function (crit: unknown[]): BinaryCriterion { + return [ + crit[0], + crit.length < 3 ? '=' : String(crit[1]).toLowerCase(), + crit.length < 2 ? true : crit[crit.length - 1], + ]; +}; + +export type SortingSelector = string | Function; + +export interface SortingInfo { + selector: SortingSelector | undefined; + desc: boolean; + compare?: unknown; + isExpanded?: boolean; + groupInterval?: unknown; +} + +interface SortingDescriptor { + getter?: SortingSelector; + field?: SortingSelector; + selector?: SortingSelector; + desc?: unknown; + dir?: unknown; + compare?: unknown; +} + +export const normalizeSortingInfo = function (info: unknown): SortingInfo[] { + const infoArray: unknown[] = Array.isArray(info) ? info : [info]; + + return infoArray.map((item: unknown): SortingInfo => { + const descriptor: SortingDescriptor = isObject(item) ? item : {}; + const result: SortingInfo = { + selector: isFunction(item) || isString(item) + ? item + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + : descriptor.getter || descriptor.field || descriptor.selector, + desc: !!(descriptor.desc || String(descriptor.dir).charAt(0).toLowerCase() === 'd'), + }; + if (descriptor.compare) { + result.compare = descriptor.compare; + } + return result; + }); +}; + +export const errorMessageFromXhr = (function () { + const textStatusMessages: Record<string, string> = { + timeout: 'Network connection timeout', + error: 'Unspecified network error', + parsererror: 'Unexpected server response', + }; + + /// #DEBUG + const textStatusDetails: Record<string, string> = { + timeout: 'possible causes: the remote host is not accessible, overloaded or is not included into the domain white-list when being run in the native container', + error: 'if the remote host is located on another domain, make sure it properly supports cross-origin resource sharing (CORS), or use the JSONP approach instead', + parsererror: 'the remote host did not respond with valid JSON data', + }; + /// #ENDDEBUG + + const explainTextStatus = function (textStatus: string): string { + let result = textStatusMessages[textStatus]; + + if (!result) { + return textStatus; + } + + /// #DEBUG + result += ` (${textStatusDetails[textStatus]})`; + /// #ENDDEBUG + + return result; + }; + + // T542570, https://stackoverflow.com/a/18170879 + let unloading = false; + ready(() => { + const window = getWindow(); + domAdapter.listen(window, 'beforeunload', () => { unloading = true; }); + }); + + return function (xhr: unknown, textStatus: unknown): string { + if (unloading) { + return XHR_ERROR_UNLOAD; + } + + const status = isObject(xhr) && 'status' in xhr ? Number(xhr.status) : NaN; + + if (status < 400) { + return explainTextStatus(String(textStatus)); + } + + const statusText = isObject(xhr) && 'statusText' in xhr ? xhr.statusText : undefined; + + return isString(statusText) ? statusText : ''; + }; +}()); + +type AggregatableValue = number | string | Date; + +export const aggregators = { + count: { + seed: 0, + step(count: number): number { return 1 + count; }, + }, + sum: { + seed: 0, + step(sum: number, item: number): number { return sum + item; }, + }, + min: { + step(min: AggregatableValue, item: AggregatableValue): AggregatableValue { + return item < min ? item : min; + }, + }, + max: { + step(max: AggregatableValue, item: AggregatableValue): AggregatableValue { + return item > max ? item : max; + }, + }, + avg: { + seed: [0, 0], + step(pair: [number, number], value: number): [number, number] { + return [pair[0] + value, pair[1] + 1]; + }, + finalize(pair: [number, number]): number { + return pair[1] ? pair[0] / pair[1] : NaN; + }, + }, +}; + +interface RequestResultLock { + obtain: () => void; + release: () => void; + promise: () => DeferredObj<unknown>; + reset: () => void; +} + +export const processRequestResultLock: RequestResultLock = (function (): RequestResultLock { + let lockCount = 0; + // eslint-disable-next-line @typescript-eslint/init-declarations + let lockDeferred: DeferredObj<unknown> | undefined; + + const obtain = function (): void { + if (lockCount === 0) { + lockDeferred = Deferred<unknown>(); + } + lockCount += 1; + }; + + const release = function (): void { + lockCount -= 1; + if (lockCount < 1) { + lockDeferred?.resolve(); + } + }; + + const promise = function (): DeferredObj<unknown> { + const deferred = lockCount === 0 || !lockDeferred + ? Deferred<unknown>().resolve() + : lockDeferred; + + // @ts-expect-error DeferredObj typings: promise() is declared as a plain Promise + return deferred.promise(); + }; + + const reset = function (): void { + lockCount = 0; + lockDeferred?.resolve(); + }; + + return { + obtain, + release, + promise, + reset, + }; +}()); + +export function isDisjunctiveOperator(condition: unknown): boolean { + return /^(or|\|\||\|)$/i.test(String(condition)); +} + +export function isConjunctiveOperator(condition: unknown): boolean { + return /^(and|&&|&)$/i.test(String(condition)); +} + +const isRecord = (value: unknown): value is Record<string, unknown> => isObject(value) + || Array.isArray(value); + +export const keysEqual = function (keyExpr: unknown, key1: unknown, key2: unknown): boolean { + if (Array.isArray(keyExpr)) { + const names: string[] = map(key1, (value: unknown, name: string): string => name); + const values1: Record<string, unknown> = isRecord(key1) ? key1 : {}; + const values2: Record<string, unknown> = isRecord(key2) ? key2 : {}; + + for (const name of names) { + if (!equalByValue(values1[name], values2[name], { strict: false })) { + return false; + } + } + return true; + } + + return equalByValue(key1, key2, { strict: false }); +}; + +const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + +/* eslint-disable no-bitwise */ + +function stringToByteArray(str: string): number[] { + const bytes: number[] = []; + + for (let i = 0; i < str.length; i += 1) { + const code = str.charCodeAt(i); + + if (code < 128) { + bytes.push(code); + } else if (code < 2048) { + bytes.push(192 + (code >> 6), 128 + (code & 63)); + } else if (code < 65536) { + bytes.push(224 + (code >> 12), 128 + ((code >> 6) & 63), 128 + (code & 63)); + } else if (code < 2097152) { + bytes.push( + 240 + (code >> 18), + 128 + ((code >> 12) & 63), + 128 + ((code >> 6) & 63), + 128 + (code & 63), + ); + } + } + return bytes; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const base64_encode = function (input: string | number[]): string { + const bytes: number[] = Array.isArray(input) ? input : stringToByteArray(String(input)); + + let result = ''; + + function getBase64Char(index: number): string { + return BASE64_CHARS.charAt(index); + } + + for (let i = 0; i < bytes.length; i += 3) { + const octet1 = bytes[i]; + const octet2 = bytes[i + 1]; + const octet3 = bytes[i + 2]; + + result += [ + octet1 >> 2, + ((octet1 & 3) << 4) | (octet2 >> 4), + isNaN(octet2) ? 64 : ((octet2 & 15) << 2) | (octet3 >> 6), + isNaN(octet3) ? 64 : octet3 & 63, + ].map(getBase64Char).join(''); + } + + return result; +}; + +/* eslint-enable no-bitwise */ + +export const isUnaryOperation = function (crit: unknown): boolean { + return Array.isArray(crit) && crit[0] === '!' && Array.isArray(crit[1]); +}; + +const isGroupOperator = function (value: unknown): boolean { + return value === 'and' || value === 'or'; +}; + +export const isUniformEqualsByOr = function (crit: unknown[]): boolean { + if (crit.length > 2 && Array.isArray(crit[0]) && crit[1] === 'or' && typeof crit[0][0] === 'string' && crit[0][1] === '=') { + const [prop] = crit[0]; + return !crit.find((el, i) => (i % 2 !== 0 ? el !== 'or' + : !Array.isArray(el) || el.length !== 3 || el[0] !== prop || el[1] !== '=')); + } + return false; +}; + +export const isGroupCriterion = function (crit: unknown): boolean { + if (!Array.isArray(crit)) { + return false; + } + + const first = crit[0]; + const second = crit[1]; + + if (Array.isArray(first)) { + return true; + } + if (isFunction(first)) { + if (Array.isArray(second) || isFunction(second) || isGroupOperator(second)) { + return true; + } + } + + return false; +}; + +export const trivialPromise = function <T>(...args: T[]): DeferredObj<T> { + const d = Deferred<T>(); + // @ts-expect-error DeferredObj typings: promise() is declared as a plain Promise + return d.resolve(...args).promise(); +}; + +export const rejectedPromise = function <T>(...args: T[]): DeferredObj<T> { + const d = Deferred<T>(); + // @ts-expect-error DeferredObj typings: promise() is declared as a plain Promise + return d.reject(...args).promise(); +}; + +type ThrottleTimeout = number | (() => number); + +type TimeoutId = ReturnType<typeof setTimeout>; + +function throttle( + func: (this: unknown) => void, + timeout: ThrottleTimeout, +): (this: unknown) => TimeoutId | undefined { + // eslint-disable-next-line @typescript-eslint/init-declarations + let timeoutId: TimeoutId | undefined; + return function (this: unknown): TimeoutId | undefined { + if (!timeoutId) { + timeoutId = setTimeout(() => { + timeoutId = undefined; + func.call(this); + }, isFunction(timeout) ? timeout() : timeout); + } + return timeoutId; + }; +} + +export function throttleChanges<T>( + func: (this: unknown, changes: T[]) => void, + timeout: ThrottleTimeout, +): (this: unknown, changes: T[]) => TimeoutId | undefined { + let cache: T[] = []; + const throttled = throttle(function (this: unknown): void { + func.call(this, cache); + cache = []; + }, timeout); + + return function (this: unknown, changes: T[]): TimeoutId | undefined { + if (Array.isArray(changes)) { + cache.push(...changes); + } + return throttled.call(this); + }; +} diff --git a/packages/devextreme/js/__internal/events/__tests__/click.test.ts b/packages/devextreme/js/__internal/events/__tests__/click.test.ts new file mode 100644 index 000000000000..86a52d657434 --- /dev/null +++ b/packages/devextreme/js/__internal/events/__tests__/click.test.ts @@ -0,0 +1,84 @@ +import { + afterEach, describe, expect, it, jest, +} from '@jest/globals'; +import { removeEvent } from '@js/common/core/events/remove'; +import eventsEngine from '@ts/events/core/events_engine'; + +import { name as clickEventName } from '../click'; + +const noop = (): void => {}; + +const getRemoveHandlersCount = (element: Element): number => { + const elementData = eventsEngine.elementDataMap?.get(element); + + return elementData?.[removeEvent]?.handleObjects.length ?? 0; +}; + +describe('dxclick nodes disposing (5025)', () => { + const clickableNodes: HTMLElement[] = []; + + const createClickableNode = (): HTMLElement => { + const node = document.createElement('div'); + + document.body.appendChild(node); + eventsEngine.on(node, clickEventName, noop); + clickableNodes.push(node); + + return node; + }; + + afterEach(() => { + clickableNodes.forEach((node) => { + eventsEngine.off(node); + node.remove(); + }); + clickableNodes.length = 0; + }); + + it('keeps a foreign dxremove handler on the previously clicked node', () => { + const clicked = createClickableNode(); + const other = createClickableNode(); + + clicked.click(); + + const foreignHandler = jest.fn(); + eventsEngine.on(clicked, removeEvent, foreignHandler); + + other.click(); + eventsEngine.triggerHandler(clicked, { type: removeEvent }); + + expect(foreignHandler).toHaveBeenCalledTimes(1); + }); + + it('keeps a foreign dxremove handler when the same node is clicked twice', () => { + const clicked = createClickableNode(); + + clicked.click(); + expect(getRemoveHandlersCount(clicked)).toBe(1); + + const foreignHandler = jest.fn(); + eventsEngine.on(clicked, removeEvent, foreignHandler); + + clicked.click(); + expect(getRemoveHandlersCount(clicked)).toBe(2); + + eventsEngine.triggerHandler(clicked, { type: removeEvent }); + + expect(foreignHandler).toHaveBeenCalledTimes(1); + }); + + it('removes only its own dxremove handler from the previously clicked node', () => { + const clicked = createClickableNode(); + const other = createClickableNode(); + + clicked.click(); + expect(getRemoveHandlersCount(clicked)).toBe(1); + + eventsEngine.on(clicked, removeEvent, noop); + expect(getRemoveHandlersCount(clicked)).toBe(2); + + other.click(); + + expect(getRemoveHandlersCount(clicked)).toBe(1); + }); +}); diff --git a/packages/devextreme/js/__internal/events/click.ts b/packages/devextreme/js/__internal/events/click.ts index 29194146f916..d6f497463d3f 100644 --- a/packages/devextreme/js/__internal/events/click.ts +++ b/packages/devextreme/js/__internal/events/click.ts @@ -24,10 +24,11 @@ type NativeClickEvent = Event & { let prevented: boolean | null = null; let lastFiredEvent: NativeClickEvent | null = null; -const subscriptions = new Map<NativeClickEvent, NodesDisposingSubscription>(); +let lastSubscription: NodesDisposingSubscription | null = null; const onNodeRemove = (): void => { lastFiredEvent = null; + lastSubscription = null; }; const clickHandler = function (e: EmitterEvent & { originalEvent: NativeClickEvent }): void { @@ -41,25 +42,16 @@ const clickHandler = function (e: EmitterEvent & { originalEvent: NativeClickEve originalEvent.DXCLICK_FIRED = true; } - if (lastFiredEvent && subscriptions.has(lastFiredEvent)) { - // @ts-expect-error the subscription stores onceCallback, not callback, so this - // destructured callback is always undefined and off() drops every dxremove - // handler from the nodes - const { nodes, callback } = subscriptions.get(lastFiredEvent) as NodesDisposingSubscription; + if (lastSubscription) { + const { nodes, onceCallback } = lastSubscription; - unsubscribeNodesDisposing(lastFiredEvent, callback, nodes); - - subscriptions.delete(lastFiredEvent); + unsubscribeNodesDisposing(lastFiredEvent, onceCallback, nodes); } lastFiredEvent = originalEvent; - - const subscriptionData: NodesDisposingSubscription = subscribeNodesDisposing( - lastFiredEvent, - onNodeRemove, - ); - - subscriptions.set(lastFiredEvent, subscriptionData); + lastSubscription = originalEvent + ? subscribeNodesDisposing(originalEvent, onNodeRemove) + : null; fireEvent({ type: CLICK_EVENT_NAME, diff --git a/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_controller.ts new file mode 100644 index 000000000000..c978b138692e --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_controller.ts @@ -0,0 +1,20 @@ +import errors from '@js/ui/widget/ui.errors'; +import dataSourceAdapterProvider from '@ts/grids/data_grid/m_data_source_adapter'; +import { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; +import type { DataSourceAdapterProvider } from '@ts/grids/grid_core/data_source_adapter/types'; + +export class DataGridDataSourceController extends DataSourceController { + protected getAdapterProvider(): DataSourceAdapterProvider { + return dataSourceAdapterProvider; + } + + protected getSpecificDataSourceOption(): unknown { + const dataSource = this.option('dataSource'); + + if (dataSource && !Array.isArray(dataSource) && this.option('keyExpr')) { + errors.log('W1011'); + } + + return super.getSpecificDataSourceOption(); + } +} diff --git a/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_module.ts b/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_module.ts new file mode 100644 index 000000000000..87d5621798c0 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/data_source/data_source_module.ts @@ -0,0 +1,9 @@ +import gridCore from '@ts/grids/data_grid/m_core'; + +import { DataGridDataSourceController } from './data_source_controller'; + +gridCore.registerModule('dataSource', { + controllers: { + dataSource: DataGridDataSourceController, + }, +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/export/__tests__/export.integration.test.ts b/packages/devextreme/js/__internal/grids/data_grid/export/__tests__/export.integration.test.ts new file mode 100644 index 000000000000..95e088cca601 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/export/__tests__/export.integration.test.ts @@ -0,0 +1,119 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import { Deferred } from '@js/core/utils/deferred'; +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; + +import { + afterTest, + beforeTest, + createDataGrid, + flushAsync, +} from '../../../grid_core/__tests__/__mock__/helpers/utils'; + +const DATA = [ + { id: 1, value: 'a' }, + { id: 2, value: 'b' }, +]; + +const SELECTED_ROWS_DATA = [DATA[0]]; + +const setup = async (options: DataGridProperties) => { + const { instance } = await createDataGrid({ + dataSource: DATA, + keyExpr: 'id', + selectedRowKeys: [1], + ...options, + }); + + await flushAsync(); + + const exportController = instance.getController('export'); + const selectionController = instance.getController('selection'); + + const filteredItems = Deferred().resolve(SELECTED_ROWS_DATA); + + return { + exportController, + filteredItems, + loadSelectedItemsWithFilter: jest + .spyOn(selectionController, 'loadSelectedItemsWithFilter') + .mockReturnValue(filteredItems), + getSelectedRowsData: jest + .spyOn(selectionController, 'getSelectedRowsData') + .mockReturnValue(SELECTED_ROWS_DATA), + getAllItems: jest.spyOn(exportController, '_getAllItems'), + }; +}; + +describe('ExportController', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('_getSelectedItems', () => { + it('loads the selected items with a filter when loadItemsOnExportingSelectedItems is enabled', async () => { + const { + exportController, + loadSelectedItemsWithFilter, + getSelectedRowsData, + getAllItems, + filteredItems, + } = await setup({ loadItemsOnExportingSelectedItems: true } as DataGridProperties); + + exportController._getSelectedItems(); + + expect(loadSelectedItemsWithFilter).toHaveBeenCalledTimes(1); + expect(getSelectedRowsData).not.toHaveBeenCalled(); + expect(getAllItems).toHaveBeenCalledWith(filteredItems, true); + }); + + it('loads the selected items with a filter when filtering is a remote operation', async () => { + const { + exportController, + loadSelectedItemsWithFilter, + getSelectedRowsData, + getAllItems, + filteredItems, + } = await setup({ remoteOperations: { filtering: true } }); + + exportController._getSelectedItems(); + + expect(loadSelectedItemsWithFilter).toHaveBeenCalledTimes(1); + expect(getSelectedRowsData).not.toHaveBeenCalled(); + expect(getAllItems).toHaveBeenCalledWith(filteredItems, true); + }); + + it('uses the already selected rows when every operation is local', async () => { + const { + exportController, + loadSelectedItemsWithFilter, + getSelectedRowsData, + getAllItems, + } = await setup({}); + + exportController._getSelectedItems(); + + expect(getSelectedRowsData).toHaveBeenCalledTimes(1); + expect(loadSelectedItemsWithFilter).not.toHaveBeenCalled(); + expect(getAllItems).toHaveBeenCalledWith(SELECTED_ROWS_DATA); + }); + + it('uses the already selected rows when loadItemsOnExportingSelectedItems is disabled explicitly, even with remote filtering', async () => { + const { + exportController, + loadSelectedItemsWithFilter, + getSelectedRowsData, + getAllItems, + } = await setup({ + loadItemsOnExportingSelectedItems: false, + remoteOperations: { filtering: true }, + } as DataGridProperties); + + exportController._getSelectedItems(); + + expect(getSelectedRowsData).toHaveBeenCalledTimes(1); + expect(loadSelectedItemsWithFilter).not.toHaveBeenCalled(); + expect(getAllItems).toHaveBeenCalledWith(SELECTED_ROWS_DATA); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts b/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts index e94c768bf7e8..624c83309f76 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/export/m_export.ts @@ -16,6 +16,7 @@ import List from '@ts/ui/list/list.edit.search'; import type { ColumnHeadersView } from '../../grid_core/column_headers/m_column_headers'; import type { ColumnsController } from '../../grid_core/columns_controller/m_columns_controller'; import type { DataController } from '../../grid_core/data_controller/data_controller'; +import type { DataSourceController } from '../../grid_core/data_source/data_source_controller'; import type { EditingController } from '../../grid_core/editing/m_editing'; import type { HeaderPanel } from '../../grid_core/header_panel/m_header_panel'; import type { ModuleType } from '../../grid_core/m_types'; @@ -337,6 +338,8 @@ export class ExportController extends dataGridCore.ViewController { private _dataController!: DataController; + private dataSourceController!: DataSourceController; + private _selectionController!: SelectionController; private _headersView!: ColumnHeadersView; @@ -353,6 +356,7 @@ export class ExportController extends dataGridCore.ViewController { this.throwWarningIfNoOnExportingEvent(); this._columnsController = this.getController('columns'); this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._selectionController = this.getController('selection'); this._rowsView = this.getView('rowsView'); this._headersView = this.getView('columnHeadersView'); @@ -701,7 +705,7 @@ export class ExportController extends dataGridCore.ViewController { private needLoadItemsOnExportingSelectedItems(): boolean { return this.option('loadItemsOnExportingSelectedItems') - ?? (this._dataController._dataSource?.remoteOperations().filtering ?? false); + ?? (this.dataSourceController.remoteOperations().filtering ?? false); } } diff --git a/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts b/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts index e03e6b1753d2..9318b076e998 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/focus/m_focus.ts @@ -2,7 +2,7 @@ import { equalByValue } from '@js/core/utils/common'; import { compileGetter } from '@js/core/utils/data'; import { Deferred } from '@js/core/utils/deferred'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; -import { focusModule } from '@ts/grids/grid_core/focus/m_focus'; +import { focusModule } from '@ts/grids/grid_core/focus/focus_module'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; import type { GroupingDataControllerExtension, GroupingDataSourceAdapter } from '../grouping/m_grouping'; diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts index ff48341edb38..4bf12e9d36b8 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/__tests__/utils.test.ts @@ -5,7 +5,7 @@ import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import { - isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, + getGroupColumnIndices, isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, } from '../utils'; const groupRow = (partial: Partial<ProcessedItem> = {}): ProcessedItem => ({ @@ -136,3 +136,39 @@ describe('isSameContinuationState', () => { )).toBe(true); }); }); + +describe('getGroupColumnIndices', () => { + const expandedGroupRow = (partial: Partial<ProcessedItem> = {}): ProcessedItem => groupRow({ + isExpanded: true, + data: { isContinuation: false, isContinuationOnNextPage: false }, + ...partial, + }); + + it('should skip the group expand cell', () => { + const oldItem = expandedGroupRow({ + cells: [{ column: { type: 'groupExpand' } }, {}, { column: { dataField: 'name' } }], + }); + + expect(getGroupColumnIndices(oldItem, expandedGroupRow())).toEqual([1, 2]); + }); + + it('should return undefined when the old row has no cells', () => { + expect(getGroupColumnIndices(expandedGroupRow(), expandedGroupRow())).toBeUndefined(); + }); + + it('should return undefined when the expanded state has changed', () => { + const oldItem = expandedGroupRow({ cells: [{}] }); + + expect(getGroupColumnIndices(oldItem, expandedGroupRow({ isExpanded: false }))) + .toBeUndefined(); + }); + + it('should return undefined when the continuation state has changed', () => { + const oldItem = expandedGroupRow({ cells: [{}] }); + const newItem = expandedGroupRow({ + data: { isContinuation: true, isContinuationOnNextPage: false }, + }); + + expect(getGroupColumnIndices(oldItem, newItem)).toBeUndefined(); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts new file mode 100644 index 000000000000..6e1d64269b75 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.data_row_index.test.ts @@ -0,0 +1,81 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import { + afterTest, + beforeTest, + createDataGrid, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; + +declare class ExposedDataController extends DataController { + public _items: ProcessedItem[]; + + public adjustInsertRowIndex: (visibleRowIndex: number) => number; +} + +const row = (rowType: ProcessedItem['rowType']): ProcessedItem => ({ + rowType, + key: rowType, + data: {}, + values: [], +}); + +const withVisibleRows = async ( + rows: ProcessedItem[], +): Promise<(visibleRowIndex: number) => number> => { + const { instance } = await createDataGrid({ + dataSource: [], + columns: ['name', 'age'], + }); + const dataController = instance.getController('data') as unknown as ExposedDataController; + + dataController._items = rows; + + return (visibleRowIndex: number): number => dataController.adjustInsertRowIndex(visibleRowIndex); +}; + +describe('Grouping data controller data row index', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should count the group rows along with the data rows', async () => { + const dataRowIndex = await withVisibleRows([ + row('data'), row('group'), row('detail'), row('data'), + ]); + + expect(dataRowIndex(3)).toBe(2); + expect(dataRowIndex(4)).toBe(3); + }); + + it('should not count the group footer rows', async () => { + const dataRowIndex = await withVisibleRows([row('data'), row('groupFooter'), row('data')]); + + expect(dataRowIndex(3)).toBe(2); + }); + + it('should not count the adaptive detail rows', async () => { + const dataRowIndex = await withVisibleRows([row('data'), row('detailAdaptive'), row('data')]); + + expect(dataRowIndex(3)).toBe(2); + }); + + it('should count only the data rows when there are no group rows', async () => { + const dataRowIndex = await withVisibleRows([row('data'), row('detail'), row('data')]); + + expect(dataRowIndex(3)).toBe(2); + }); + + it('should return zero for the first visible index', async () => { + const dataRowIndex = await withVisibleRows([row('group'), row('data')]); + + expect(dataRowIndex(0)).toBe(0); + }); + + it('should count the rows that are there when the index is out of range', async () => { + const dataRowIndex = await withVisibleRows([row('group'), row('data')]); + + expect(dataRowIndex(10)).toBe(2); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts index 4a0559e43a5c..3c7bea04f2e4 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/__tests__/grouping_data_controller.row_changes.test.ts @@ -12,20 +12,31 @@ interface GroupRowState { isExpanded?: boolean; isContinuation?: boolean; isContinuationOnNextPage?: boolean; + values?: unknown[]; + cells?: ProcessedItem['cells']; } const groupRow = ({ isExpanded = true, isContinuation = false, isContinuationOnNextPage = false, + values = [], + cells, }: GroupRowState = {}): ProcessedItem => ({ rowType: 'group', key: [1], data: { isContinuation, isContinuationOnNextPage }, - values: [], + values, isExpanded, + cells, }); +const renderedCells: ProcessedItem['cells'] = [ + { column: { type: 'groupExpand' } }, + {}, + { column: { dataField: 'name' } }, +]; + const dataRow = (partial: Partial<ProcessedItem> = {}): ProcessedItem => ({ rowType: 'data', key: 1, @@ -46,12 +57,13 @@ describe('Grouping data controller row changes', () => { it('should report a group row when isExpanded changed', async () => { const change = await refreshRow( - groupRow({ isExpanded: true }), + groupRow({ isExpanded: true, cells: renderedCells }), groupRow({ isExpanded: false }), ); expect(change.rowIndices).toEqual([0]); expect(change.changeTypes).toEqual(['update']); + expect(change.columnIndices).toEqual([undefined]); }); it('should report a group row when isContinuation changed', async () => { @@ -68,6 +80,26 @@ describe('Grouping data controller row changes', () => { expect(change.changeTypes).toEqual(['update']); }); + it('should diff every group cell but the expand one when values changed', async () => { + const change = await refreshRow( + groupRow({ values: ['Alex'], cells: renderedCells }), + groupRow({ values: ['Bob'] }), + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([[1, 2]]); + }); + + it('should repaint the whole group row when it was never rendered', async () => { + const change = await refreshRow( + groupRow({ values: ['Alex'] }), + groupRow({ values: ['Bob'] }), + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([undefined]); + }); + it('should not report a data row when isExpanded changed (master detail row)', async () => { const change = await refreshRow( dataRow({ isExpanded: false }), diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts index 440fb56af6d1..769d0aa1590f 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/extenders/grouping_data_controller.ts @@ -3,6 +3,7 @@ import { Deferred, when } from '@js/core/utils/deferred'; import type { Properties } from '@js/ui/data_grid'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { ItemProcessingOptions, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; +import { countRowsBefore } from '@ts/grids/grid_core/data_controller/utils/row_changes'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import type { ModuleType, @@ -16,7 +17,7 @@ import type { ChangeRowExpandArgs, GroupItem, ProcessGroupItemsOptions, } from '../types'; import { - isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, + getGroupColumnIndices, isGroupNode, isGroupRow, isSameContinuationState, isSameExpandedState, } from '../utils'; export const groupingDataControllerExtender = ( @@ -134,6 +135,25 @@ export const groupingDataControllerExtender = ( return resultItems; } + protected adjustInsertRowIndex(visibleRowIndex: number): number { + const groupRowCount = countRowsBefore(this.getVisibleRows(), visibleRowIndex, 'group'); + + return super.adjustInsertRowIndex(visibleRowIndex) + groupRowCount; + } + + protected getChangedColumnIndices( + oldItem: ProcessedItem, + newItem: ProcessedItem, + visibleRowIndex: number, + isLiveUpdate?: boolean, + ): number[] | undefined { + if (oldItem.rowType === 'group' && newItem.rowType === 'group') { + return getGroupColumnIndices(oldItem, newItem); + } + + return super.getChangedColumnIndices(oldItem, newItem, visibleRowIndex, isLiveUpdate); + } + protected isSameRowState(item1: ProcessedItem, item2: ProcessedItem): boolean { if (item1.rowType === 'group' && (!isSameExpandedState(item1, item2) || !isSameContinuationState(item1, item2))) { diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts index 608c733636ba..e8886c114650 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping.ts @@ -105,6 +105,7 @@ const dataSourceAdapterExtender = (Base: ModuleType<DataSourceAdapter>) => class groups[i].isExpanded = group[i].isExpanded; } } + // @ts-expect-error normalizeSortingInfo() types selector wider than public KeySelector dataSource.group(groups); that._grouping.foreachGroups((groupInfo, parents) => { if (groupIndex === undefined || groupIndex === parents.length - 1) { diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts index 023624dca8b4..f153e81939df 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/m_grouping_expanded.ts @@ -343,7 +343,6 @@ export class GroupingHelper extends GroupingHelperCore { if (groupCount) { let { data } = options; - // @ts-expect-error const query = dataQuery(data); storeHelper.multiLevelGroup(query, groups).enumerate().done((groupedData) => { data = groupedData; diff --git a/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts b/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts index 52a05945ae26..e7e8a71a566e 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/grouping/utils.ts @@ -25,3 +25,19 @@ export function isSameContinuationState(item1: ProcessedItem, item2: ProcessedIt return item1.data?.isContinuation === item2.data?.isContinuation && item1.data?.isContinuationOnNextPage === item2.data?.isContinuationOnNextPage; } + +export function getGroupColumnIndices( + oldItem: ProcessedItem, + newItem: ProcessedItem, +): number[] | undefined { + const isSameState = isSameExpandedState(oldItem, newItem) + && isSameContinuationState(oldItem, newItem); + + if (!oldItem.cells || !isSameState) { + return undefined; + } + + return oldItem.cells + .map((cell, index) => (cell.column?.type !== 'groupExpand' ? index : -1)) + .filter((index) => index >= 0); +} diff --git a/packages/devextreme/js/__internal/grids/data_grid/m_data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/m_data_controller.ts deleted file mode 100644 index e6632d7df28f..000000000000 --- a/packages/devextreme/js/__internal/grids/data_grid/m_data_controller.ts +++ /dev/null @@ -1,31 +0,0 @@ -import errors from '@js/ui/widget/ui.errors'; -import { DataController, dataControllerModule } from '@ts/grids/grid_core/data_controller/data_controller'; - -import type { DataSourceAdapterProvider } from '../grid_core/data_source_adapter/types'; -import gridCore from './m_core'; -import dataSourceAdapterProvider from './m_data_source_adapter'; - -class DataGridDataController extends DataController { - protected _getDataSourceAdapterProvider(): DataSourceAdapterProvider { - return dataSourceAdapterProvider; - } - - protected _getSpecificDataSourceOption() { - const dataSource = this.option('dataSource'); - - if (dataSource && !Array.isArray(dataSource) && this.option('keyExpr')) { - errors.log('W1011'); - } - - return super._getSpecificDataSourceOption(); - } -} - -export { DataGridDataController as DataController }; - -gridCore.registerModule('data', { - defaultOptions: dataControllerModule.defaultOptions, - controllers: { - data: DataGridDataController, - }, -}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts b/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts index 1710af383abd..fa9cba25a7fe 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/m_widget_base.ts @@ -1,6 +1,8 @@ import './module_not_extended/column_headers'; import './m_columns_controller'; -import './m_data_controller'; +import './data_source/data_source_module'; +import './module_not_extended/filter'; +import './module_not_extended/data_controller'; import './module_not_extended/sorting'; import './module_not_extended/rows'; import './module_not_extended/context_menu'; @@ -24,6 +26,7 @@ import gridCore from './m_core'; const DATAGRID_DEPRECATED_TEMPLATE_WARNING = 'Specifying grid templates with the jQuery selector name is now deprecated. Use the DOM Node or the jQuery object that references this selector instead.'; gridCore.registerModulesOrder([ + 'dataSource', 'stateStoring', 'columns', 'selection', @@ -37,6 +40,7 @@ gridCore.registerModulesOrder([ 'masterDetail', 'validating', 'adaptivity', + 'filter', 'data', 'virtualScrolling', 'aiColumn', diff --git a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/data_controller.ts new file mode 100644 index 000000000000..5965240a4e70 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/data_controller.ts @@ -0,0 +1,5 @@ +import { dataControllerModule } from '@ts/grids/grid_core/data_controller/data_controller'; + +import gridCore from '../m_core'; + +gridCore.registerModule('data', dataControllerModule); diff --git a/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/filter.ts b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/filter.ts new file mode 100644 index 000000000000..842edf76f69d --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/module_not_extended/filter.ts @@ -0,0 +1,5 @@ +import { filterModule } from '@ts/grids/grid_core/filter/filter_module'; + +import gridCore from '../m_core'; + +gridCore.registerModule('filter', filterModule); diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.load_all_items.test.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.load_all_items.test.ts new file mode 100644 index 000000000000..ebbef09ad345 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.load_all_items.test.ts @@ -0,0 +1,46 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; +import { + afterTest, + beforeTest, + createDataGrid, + flushAsync, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; + +const DATA = [{ id: 1, value: 10 }, { id: 2, value: 20 }]; + +const loadAllSummary = async ( + options: DataGridProperties = {}, +): Promise<unknown> => { + const { instance } = await createDataGrid({ dataSource: DATA, ...options }); + const dataController = instance.getController('data'); + + let resolvedSummary: unknown = null; + dataController.loadAllItems().done((_items, summary) => { + resolvedSummary = summary; + }); + await flushAsync(); + + return resolvedSummary; +}; + +describe('Summary data controller loadAllItems', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should resolve the total summary as the second argument', async () => { + const summary = await loadAllSummary({ + summary: { totalItems: [{ column: 'value', summaryType: 'sum' }] }, + }); + + expect(summary).toEqual([30]); + }); + + it('should resolve no second argument when no summary is configured', async () => { + const summary = await loadAllSummary(); + + expect(summary).toBeUndefined(); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts index 08455315c79f..fbfd87bbc4ea 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/__tests__/summary_data_controller.row_changes.test.ts @@ -55,6 +55,7 @@ describe('Summary data controller row changes', () => { expect(change.rowIndices).toEqual([0]); expect(change.changeTypes).toEqual(['update']); + expect(change.columnIndices).toEqual([undefined]); }); it('should report a group footer when isContinuation changed', async () => { diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts index a32233d8861f..fa06dc0332d0 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/extenders/summary_data_controller.ts @@ -6,7 +6,10 @@ import type { Format } from '@js/localization'; import type { SummaryGroupItem as SummaryGroupItemOption } from '@js/ui/data_grid'; import type { Column } from '@ts/grids/data_grid/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; -import type { DataChange, ItemProcessingOptions, ProcessedItem } from '@ts/grids/grid_core/data_controller/types'; +import type { + DataChange, ItemProcessingOptions, LoadAllItemsDeferred, ProcessedItem, +} from '@ts/grids/grid_core/data_controller/types'; +import type { CustomLoadResult } from '@ts/grids/grid_core/data_source_adapter/custom_loader'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import type { ModuleType, OptionChanged } from '@ts/grids/grid_core/m_types'; @@ -288,6 +291,26 @@ export const summaryDataControllerExtender = ( return super.isSameRowState(item1, item2); } + protected getChangedColumnIndices( + oldItem: ProcessedItem, + newItem: ProcessedItem, + visibleRowIndex: number, + isLiveUpdate?: boolean, + ): number[] | undefined { + if (newItem.rowType === DATAGRID_GROUP_FOOTER_ROW_TYPE) { + return undefined; + } + + return super.getChangedColumnIndices(oldItem, newItem, visibleRowIndex, isLiveUpdate); + } + + protected resolveLoadAllItems( + d: LoadAllItemsDeferred, + loadResult: CustomLoadResult, + ): void { + d.resolve(this.processLoadAllItems(loadResult), loadResult.extra?.summary); + } + protected _updateItemsCore(change: DataChange): void { const dataSource = this._dataSource; const summaryTotalItems = this.option('summary.totalItems'); diff --git a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts index baeb374a1b7b..b6d8e21f13df 100644 --- a/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts +++ b/packages/devextreme/js/__internal/grids/data_grid/summary/m_summary.ts @@ -117,7 +117,6 @@ const sortGroupsBySummaryCore = function (items, groups, sortByGroups) { let query; if (group && sorts && sorts.length) { - // @ts-expect-error query = dataQuery(items); each(sorts, function (index) { if (index === 0) { @@ -307,7 +306,6 @@ export const summaryDataSourceAdapterExtender = ( } private sortLastLevelGroupItems(items, groups, paths) { - // @ts-expect-error const groupedItems = storeHelper.multiLevelGroup(dataQuery(items), groups).toArray(); let result = []; diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts index 67df1f4eda7f..a93f657a28d5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/row_changes.ts @@ -1,3 +1,4 @@ +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; import { createDataGrid } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { @@ -10,26 +11,37 @@ declare class ExposedDataController extends DataController { public applyChangesOnly: (change: DataChange) => void; } -const createRefreshChange = (items: ProcessedItem[]): DataChange => ({ +export interface RefreshRowOptions { + gridOptions?: DataGridProperties; + isLiveUpdate?: boolean; +} + +const createRefreshChange = ( + items: ProcessedItem[], + isLiveUpdate?: boolean, +): DataChange => ({ changeType: 'refresh', repaintChangesOnly: true, items, + isLiveUpdate, }); export const refreshRow = async ( oldItem: ProcessedItem, newItem: ProcessedItem, + { gridOptions, isLiveUpdate }: RefreshRowOptions = {}, ): Promise<UpdateChange> => { const { instance } = await createDataGrid({ dataSource: [], columns: ['name', 'age'], repaintChangesOnly: true, + ...gridOptions, }); const dataController = instance.getController('data') as unknown as ExposedDataController; dataController._items = [oldItem]; - const change = createRefreshChange([newItem]); + const change = createRefreshChange([newItem], isLiveUpdate); dataController.applyChangesOnly(change); diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/utils.ts index 3acad5bf9f84..d040c7d964aa 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/__mock__/helpers/utils.ts @@ -20,6 +20,12 @@ export type OptionSpy = jest.Mock<(optionName: string, optionValue?: unknown) => export const spyOnOption = (instance: InternalGrid): OptionSpy => jest .spyOn(instance, 'option') as unknown as OptionSpy; +// Reads the DataController._dataSource mirror. Assertions that pin the mirror have to read the +// protected field itself rather than a delegating method. Goes away with the field, in Task B4. +export const getMirroredAdapter = ( + instance: { getController: (name: 'data') => unknown }, +): unknown => (instance.getController('data') as { _dataSource?: unknown })._dataSource; + export const SELECTORS = { gridContainer: '#gridContainer', }; @@ -78,3 +84,7 @@ export const flushAsync = async (): Promise<void> => { jest.runAllTimers(); await Promise.resolve(); }; + +export const toPlainFilter = (value: unknown): unknown => (Array.isArray(value) + ? Array.from(value, toPlainFilter) + : value); diff --git a/packages/devextreme/js/__internal/grids/grid_core/__tests__/m_utils.combine_filters.test.ts b/packages/devextreme/js/__internal/grids/grid_core/__tests__/m_utils.combine_filters.test.ts new file mode 100644 index 000000000000..0496144016dd --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/__tests__/m_utils.combine_filters.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, it } from '@jest/globals'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; +import gridCoreUtils from '@ts/grids/grid_core/m_utils'; + +const FIRST = ['a', '=', 1]; +const SECOND = ['b', '=', 2]; +const THIRD = ['c', '=', 3]; +const MATCH_NOTHING = ['!']; + +const combineFilters = ( + filters: unknown[], + operation?: 'and' | 'or', +): DataFilter => gridCoreUtils.combineFilters(filters as DataFilter[], operation); + +describe('combineFilters', () => { + describe('when the list is empty', () => { + it('should return undefined', () => { + expect(combineFilters([])).toBeUndefined(); + }); + }); + + describe('when every entry is empty', () => { + it('should return undefined', () => { + expect(combineFilters([null, undefined])).toBeUndefined(); + }); + }); + + describe('when a single expression is given', () => { + it('should unwrap it', () => { + expect(combineFilters([FIRST])).toEqual(FIRST); + }); + }); + + describe('when several expressions are given', () => { + it('should join them with and by default', () => { + expect(combineFilters([FIRST, SECOND])).toEqual([FIRST, 'and', SECOND]); + }); + + it('should join them with the requested operation', () => { + expect(combineFilters([FIRST, SECOND], 'or')).toEqual([FIRST, 'or', SECOND]); + }); + + it('should skip the empty entries', () => { + expect(combineFilters([null, FIRST, undefined, SECOND])) + .toEqual([FIRST, 'and', SECOND]); + }); + }); + + describe('when the same expressions are folded pairwise and combined in one call', () => { + it('should nest to the left while folding pairwise', () => { + expect(combineFilters([combineFilters([FIRST, SECOND]), THIRD])) + .toEqual([[FIRST, 'and', SECOND], 'and', THIRD]); + }); + + it('should stay flat while combining in one call', () => { + expect(combineFilters([FIRST, SECOND, THIRD])) + .toEqual([FIRST, 'and', SECOND, 'and', THIRD]); + }); + + it('should produce different structures for the two ways', () => { + expect(combineFilters([combineFilters([FIRST, SECOND]), THIRD])) + .not.toEqual(combineFilters([FIRST, SECOND, THIRD])); + }); + }); + + describe('when a match-nothing filter is among the entries', () => { + it('should collapse an and group', () => { + expect(combineFilters([FIRST, MATCH_NOTHING])).toEqual(MATCH_NOTHING); + }); + + it('should be skipped in an or group', () => { + expect(combineFilters([FIRST, MATCH_NOTHING], 'or')).toEqual(FIRST); + }); + + it('should survive on its own', () => { + expect(combineFilters([MATCH_NOTHING])).toEqual(MATCH_NOTHING); + }); + }); + + describe('when a predicate function is among the entries', () => { + const predicate = (item: unknown): boolean => Boolean(item); + + it('should pass it through on its own', () => { + expect(combineFilters([predicate])).toBe(predicate); + }); + + it('should make it a member of the group next to an expression', () => { + const combined = combineFilters([predicate, FIRST]) as unknown as unknown[]; + + expect(combined).toHaveLength(3); + expect(combined[0]).toBe(predicate); + expect(combined.slice(1)).toEqual(['and', FIRST]); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_data_controller.ts index ed851fd89be5..67486143bdda 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/adaptivity/extenders/adaptivity_data_controller.ts @@ -4,6 +4,7 @@ import type { DataChange, ProcessedItem } from '@ts/grids/grid_core/data_control import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import type { ModuleType, RowKey } from '@ts/grids/grid_core/m_types'; import gridCoreUtils from '@ts/grids/grid_core/m_utils'; +import type { MasterDetailDataControllerExtension } from '@ts/grids/grid_core/master_detail/m_master_detail'; import { ADAPTIVE_ROW_TYPE, @@ -15,8 +16,10 @@ import type { AdaptiveColumnsController } from '../m_adaptivity'; import type { AdaptivityDataController } from '../types'; import { getAdaptiveDetailRowIndex, resolveAdaptiveDetailRowTarget } from '../utils'; +type AdaptivityDataControllerBase = DataController & MasterDetailDataControllerExtension; + export const adaptivityDataControllerExtender = ( - Base: ModuleType<DataController>, + Base: ModuleType<AdaptivityDataControllerBase>, ): ModuleType<AdaptivityDataController> => class AdaptivityDataControllerExtender extends Base { private adaptiveExpandedKey?: RowKey; @@ -59,7 +62,7 @@ export const adaptivityDataControllerExtender = ( return processedItems; } - protected getRowIndicesForExpand(key: RowKey): number[] { + public override getRowIndicesForExpand(key: RowKey): number[] { const rowIndices = super.getRowIndicesForExpand(key); if (this.adaptiveColumnsController.isAdaptiveDetailRowExpanded(key)) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/ai_assistant_integration_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/ai_assistant_integration_controller.ts index 816a27943382..3cb822bb81d2 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/ai_assistant_integration_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/ai_assistant_integration_controller.ts @@ -7,6 +7,7 @@ import errors from '@js/ui/widget/ui.errors'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import { Controller } from '../m_modules'; import type { @@ -22,6 +23,8 @@ export class AIAssistantIntegrationController extends Controller { private dataController!: DataController; + private dataSourceController!: DataSourceController; + private getAICommandCallbacks( callbacks?: RequestCallbacks<ExecuteGridAssistantCommandResult>, ): RequestCallbacks<ExecuteGridAssistantCommandResult> { @@ -46,7 +49,7 @@ export class AIAssistantIntegrationController extends Controller { protected buildContext(): GridContext { return { - keyExpr: this.option('keyExpr') ?? this.dataController.getDataSource()?.store()?.key(), + keyExpr: this.option('keyExpr') ?? this.dataSourceController.store()?.key(), columns: this.buildColumnsContext(), filtering: { filterValue: this.option('filterValue'), @@ -54,7 +57,7 @@ export class AIAssistantIntegrationController extends Controller { paging: { pageIndex: this.dataController.pageIndex(), pageSize: this.dataController.pageSize(), - totalCount: this.dataController.totalCount(), + totalCount: this.dataSourceController.totalCount(), visibleRowCount: this.dataController .getVisibleRows() .filter((row) => row.rowType === 'data') @@ -97,6 +100,7 @@ export class AIAssistantIntegrationController extends Controller { public init(): void { this.createAction('onAIAssistantRequestCreating'); this.dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this.columnsController = this.getController('columns'); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/paging.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/paging.ts index d5a31a1df4d5..395fde83ec86 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/paging.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/paging.ts @@ -64,10 +64,10 @@ export const pageIndexCommand = defineGridCommand({ schema: pageIndexCommandSchema, execute: (component, { success, failure }) => async (args): Promise<CommandResult> => { const paging = component.option('paging'); - const dataController = component.getController('data'); + const dataSourceController = component.getController('dataSource'); const defaultMessage = `Switch the view to page number ${args.pageIndex + 1}.`; - const isIndexValid = args.pageIndex < dataController.pageCount(); + const isIndexValid = args.pageIndex < dataSourceController.pageCount(); if (paging?.enabled === false || !isIndexValid) { return failure(defaultMessage); diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/selection.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/selection.ts index 76105e91357c..2cd56c186cb9 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/selection.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_assistant/commands/selection.ts @@ -187,8 +187,7 @@ const resolveKeysFromAllPages = ( component: InternalGrid, indexes: number[], ): Promise<RowKey[] | null> => { - const dataController = component.getController('data'); - const isRemotePaging = !!dataController.dataSource()?.remoteOperations()?.paging; + const isRemotePaging = !!component.getController('dataSource').remoteOperations().paging; return isRemotePaging ? resolveKeysFromAllPagesRemote(component, indexes) diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts index 94ac23fecd5f..3825239cf26e 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_controller.ts @@ -5,6 +5,7 @@ import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ColumnsController } from '../../columns_controller/m_columns_controller'; import type { DataController } from '../../data_controller/data_controller'; +import type { DataSourceController } from '../../data_source/data_source_controller'; import type { ChangedEvent, RawItemData } from '../../data_source_adapter/types'; import { Controller } from '../../m_modules'; import type { RowKey } from '../../m_types'; @@ -16,6 +17,8 @@ import { AIColumnIntegrationController } from './m_ai_column_integration_control export class AIColumnController extends Controller { private dataController!: DataController; + private dataSourceController!: DataSourceController; + private columnsController!: ColumnsController; private aiColumnIntegrationController!: AIColumnIntegrationController; @@ -56,12 +59,12 @@ export class AIColumnController extends Controller { private addAICommandColumn(): void { const that = this; - const { dataController, aiColumnIntegrationController } = this; + const { dataSourceController, aiColumnIntegrationController } = this; this.columnsController.addCommandColumn({ ...getAICommandColumnDefaultOptions(), calculateCellValue(data: RawItemData) { - const key = dataController.keyOf(data); + const key = dataSourceController.keyOf(data); const cellValue = aiColumnIntegrationController.getAIColumnText(this.name, key); const defaultValue = that.getDefaultCellValue(this, cellValue); @@ -73,7 +76,7 @@ export class AIColumnController extends Controller { private subscribeToDataSourceChanged(): void { this.dataSourceChangedHandler = this.handleDataSourceChanged.bind(this); - this.dataController.dataSource()?.changed.add(this.dataSourceChangedHandler); + this.dataSourceController.getAdapter()?.changed.add(this.dataSourceChangedHandler); } private unsubscribeFromDataControllerChanged(): void { @@ -102,7 +105,7 @@ export class AIColumnController extends Controller { } private unsubscribeFromStoreEvents(): void { - const store = this.dataController.store(); + const store = this.dataSourceController.store(); if (this.storeUpdatedHandler) { store?.off('updated', this.storeUpdatedHandler); @@ -116,7 +119,7 @@ export class AIColumnController extends Controller { } private subscribeToStoreEvents(): void { - const store = this.dataController.store(); + const store = this.dataSourceController.store(); if (!store) { return; @@ -156,7 +159,7 @@ export class AIColumnController extends Controller { } private checkStoreKey(): boolean { - const store = this.dataController.store(); + const store = this.dataSourceController.store(); if (store && !store.key()) { this.dataController.fireError('E1042', 'AI Column'); @@ -204,6 +207,7 @@ export class AIColumnController extends Controller { public init(): void { this.columnsController = this.getController('columns'); this.dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this.aiColumnIntegrationController = new AIColumnIntegrationController(this.component); this.aiColumnIntegrationController.init(); @@ -326,7 +330,7 @@ export class AIColumnController extends Controller { public dispose(): void { super.dispose(); - this.dataController.dataSource()?.changed.remove(this.dataSourceChangedHandler); + this.dataSourceController.getAdapter()?.changed.remove(this.dataSourceChangedHandler); this.unsubscribeFromStoreEvents(); this.unsubscribeFromDataControllerChanged(); } diff --git a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_integration_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_integration_controller.ts index 7654f0f132e5..a82985bf1c18 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_integration_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/ai_column/controllers/m_ai_column_integration_controller.ts @@ -8,6 +8,7 @@ import errors from '@js/ui/widget/ui.errors'; import type { ColumnsController } from '../../columns_controller/m_columns_controller'; import type { DataController } from '../../data_controller/data_controller'; +import type { DataSourceController } from '../../data_source/data_source_controller'; import type { RawItemData } from '../../data_source_adapter/types'; import type { ErrorHandlingViewController } from '../../error_handling/error_handling_view_controller'; import { Controller } from '../../m_modules'; @@ -23,6 +24,8 @@ export class AIColumnIntegrationController extends Controller { private dataController!: DataController; + private dataSourceController!: DataSourceController; + private errorHandlingController!: ErrorHandlingViewController; private aiColumnCacheController!: AIColumnCacheController; @@ -78,12 +81,13 @@ export class AIColumnIntegrationController extends Controller { } private getRowKeyHash(item: RawItemData): PropertyKey { - return getKeyHash(this.dataController.keyOf(item)) as PropertyKey; + return getKeyHash(this.dataSourceController.keyOf(item)) as PropertyKey; } public init(): void { this.columnsController = this.getController('columns'); this.dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this.errorHandlingController = this.getController('errorHandling'); this.aiColumnCacheController = new AIColumnCacheController(this.component); @@ -136,7 +140,7 @@ export class AIColumnIntegrationController extends Controller { return; } - const keyField = this.dataController.key(); + const keyField = this.dataSourceController.key(); if (keyField === undefined || isKeyMissingInData(args.data, keyField)) { this.dataController.fireError('E1046', keyField); return; diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/column_has_value.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/column_has_value.test.ts new file mode 100644 index 000000000000..a1fe900b3125 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/column_has_value.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from '@jest/globals'; +import { AI_COLUMN_NAME } from '@ts/grids/grid_core/ai_column/const'; +import { columnHasValue } from '@ts/grids/grid_core/columns_controller/m_columns_controller_utils'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; + +const column = (partial: Partial<Column>): Column => partial as Column; + +describe('columnHasValue', () => { + it('should be true for a data column', () => { + expect(columnHasValue(column({ dataField: 'name' }))).toBe(true); + }); + + it('should be false for a command column', () => { + expect(columnHasValue(column({ command: 'select' }))).toBe(false); + }); + + it('should be true for the AI column even though it carries a command', () => { + expect(columnHasValue(column({ command: AI_COLUMN_NAME, type: AI_COLUMN_NAME }))).toBe(true); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.update_filter.test.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.update_filter.test.ts new file mode 100644 index 000000000000..2f0122ced85b --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/__tests__/columns_controller.update_filter.test.ts @@ -0,0 +1,130 @@ +import { + afterEach, + beforeEach, + describe, + expect, + it, +} from '@jest/globals'; +import type { dxElementWrapper } from '@js/core/renderer'; +import { extend } from '@js/core/utils/extend'; +import type { DataGridModel } from '@ts/grids/data_grid/__tests__/__mock__/model/data_grid'; +import type { DataGridInstance } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import { + afterTest, + beforeTest, + createDataGrid, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; + +const DATA = [ + { id: 1, name: 'Alex', age: 15 }, + { id: 2, name: 'Dan', age: 20 }, +]; + +interface TaggedSelector { + columnIndex?: number; + filterValue?: unknown; + selectedFilterOperation?: unknown; +} + +const createGrid = (): Promise<{ + $container: dxElementWrapper; + component: DataGridModel; + instance: DataGridInstance; +}> => createDataGrid({ + dataSource: DATA, + columns: ['name', 'age'], +}); + +const updateFilter = ( + instance: DataGridInstance, + filter: unknown, + remoteFiltering: boolean, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): any => instance.getController('columns').updateFilter(filter, remoteFiltering); + +describe('ColumnsController.updateFilter', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('when filtering locally', () => { + it('should replace the data field with the column selector itself', async () => { + const { instance } = await createGrid(); + const nameColumn = instance.getController('columns').getVisibleColumns()[0]; + + const result = updateFilter(instance, ['name', '=', 'Alex'], false); + + expect(result[0]).toBe(nameColumn.selector); + }); + + it('should tag the column selector with its column index', async () => { + const { instance } = await createGrid(); + const columns = instance.getController('columns').getVisibleColumns(); + + updateFilter(instance, ['age', '=', 15], false); + + expect((columns[1].selector as TaggedSelector).columnIndex).toBe(columns[1].index); + }); + + it('should walk nested groups', async () => { + const { instance } = await createGrid(); + const columns = instance.getController('columns').getVisibleColumns(); + + const result = updateFilter( + instance, + [['name', '=', 'Alex'], 'and', ['age', '=', 15]], + false, + ); + + expect(result[0][0]).toBe(columns[0].selector); + expect(result[1]).toBe('and'); + expect(result[2][0]).toBe(columns[1].selector); + }); + + it('should leave an unknown field alone', async () => { + const { instance } = await createGrid(); + + expect(updateFilter(instance, ['unknown', '=', 1], false)).toEqual(['unknown', '=', 1]); + }); + }); + + describe('when filtering remotely', () => { + it('should keep the data fields', async () => { + const { instance } = await createGrid(); + + expect(updateFilter(instance, ['name', '=', 'Alex'], true)).toEqual(['name', '=', 'Alex']); + }); + }); + + describe('when the filter array carries its own properties', () => { + it('should carry them over to the result', async () => { + const { instance } = await createGrid(); + const filter = extend([], [['name', '=', 'Alex'], 'and', ['age', '=', 15]]); + + filter.columnIndex = 7; + filter.filterValue = 'ZZ'; + filter.selectedFilterOperation = 'between'; + + const result = updateFilter(instance, filter, false); + + expect(result.columnIndex).toBe(7); + expect(result.filterValue).toBe('ZZ'); + expect(result.selectedFilterOperation).toBe('between'); + }); + + it('should pass columnIndex and filterValue down but not selectedFilterOperation', async () => { + const { instance } = await createGrid(); + const customSelector = (): number => 1; + const filter = extend([], [[customSelector, '=', 'Alex']]); + + filter.columnIndex = 3; + filter.filterValue = 'inherited'; + filter.selectedFilterOperation = 'between'; + + updateFilter(instance, filter, false); + + expect((customSelector as TaggedSelector).columnIndex).toBe(3); + expect((customSelector as TaggedSelector).filterValue).toBe('inherited'); + expect((customSelector as TaggedSelector).selectedFilterOperation).toBeUndefined(); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts index aeab55d20f08..859b58d185bc 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller.ts @@ -1823,7 +1823,6 @@ export class ColumnsController extends modules.Controller { } if (isPlainObject(dataSource) || (dataSource instanceof Store) || Array.isArray(dataSource)) { if (that.valueExpr) { - // @ts-expect-error const dataSourceOptions = normalizeDataSourceOptions(dataSource); dataSourceOptions.paginate = false; dataSource = new DataSource(dataSourceOptions); diff --git a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts index d3dcd37385d2..c09139a4facb 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/columns_controller/m_columns_controller_utils.ts @@ -1097,4 +1097,8 @@ export const isColumnNameRequired = function ({ type = '' }: Column): boolean { return COMMAND_COLUMNS_WITH_REQUIRED_NAMES.includes(type); }; +export const columnHasValue = (column: Column): boolean => ( + !column.command || column.type === AI_COLUMN_NAME +); + export const getColumnHeaderCellSelector = (visibleIndex: number): string => `.dx-header-row td[aria-colindex="${visibleIndex + 1}"]`; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.combined_filter.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.combined_filter.test.ts new file mode 100644 index 000000000000..82ab7a5ba0ce --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.combined_filter.test.ts @@ -0,0 +1,375 @@ +import { + afterEach, + beforeEach, + describe, + expect, + it, +} from '@jest/globals'; +import type { dxElementWrapper } from '@js/core/renderer'; +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; +import type { DataGridModel } from '@ts/grids/data_grid/__tests__/__mock__/model/data_grid'; +import type { DataGridInstance } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import { + afterTest, + beforeTest, + createDataGrid, + toPlainFilter, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; + +type ColumnWithSelector = Column & { selector?: unknown }; + +const DATA = [ + { + id: 1, name: 'Alex', age: 15, city: 'Berlin', + }, + { + id: 2, name: 'Dan', age: 20, city: 'Munich', + }, +]; + +const COLUMNS_WITH_ROW_AND_HEADER_FILTERS: DataGridProperties['columns'] = [ + { dataField: 'name', filterValue: 'Alex' }, + { dataField: 'age', filterValues: [15] }, + 'city', +]; + +const createGrid = (options: DataGridProperties): Promise<{ + $container: dxElementWrapper; + component: DataGridModel; + instance: DataGridInstance; +}> => createDataGrid({ dataSource: DATA, ...options }); + +const getDataFieldFilter = ( + instance: DataGridInstance, +): unknown => toPlainFilter(instance.getCombinedFilter(true)); + +const getVisibleColumns = (instance: DataGridInstance): ColumnWithSelector[] => instance + .getController('columns') + .getVisibleColumns() as ColumnWithSelector[]; + +const getFilterWithoutColumn = ( + instance: DataGridInstance, + column: Column, +): unknown => toPlainFilter( + instance.getController('data').getCombinedFilterWithExcludedColumn(column, true), +); + +describe('DataController combined filter', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('when only the filter row has values', () => { + it('should take the value of a single column', async () => { + const { instance } = await createGrid({ + columns: [{ dataField: 'name', filterValue: 'Alex' }, 'age'], + }); + + expect(getDataFieldFilter(instance)).toEqual(['name', 'contains', 'Alex']); + }); + + it('should combine the values of several columns flatly', async () => { + const { instance } = await createGrid({ + columns: [ + { dataField: 'name', filterValue: 'Alex' }, + { dataField: 'age', filterValue: 15 }, + ], + }); + + expect(getDataFieldFilter(instance)).toEqual([ + ['name', 'contains', 'Alex'], 'and', ['age', '=', 15], + ]); + }); + }); + + describe('when only the header filter has values', () => { + it('should take a single value', async () => { + const { instance } = await createGrid({ + columns: [{ dataField: 'age', filterValues: [15] }, 'name'], + }); + + expect(getDataFieldFilter(instance)).toEqual(['age', '=', 15]); + }); + + it('should combine several values with or', async () => { + const { instance } = await createGrid({ + columns: [{ dataField: 'age', filterValues: [15, 20] }, 'name'], + }); + + expect(getDataFieldFilter(instance)).toEqual([ + ['age', '=', 15], 'or', ['age', '=', 20], + ]); + }); + + it('should negate the group for the exclude filter type', async () => { + const { instance } = await createGrid({ + columns: [{ dataField: 'age', filterValues: [15], filterType: 'exclude' }, 'name'], + }); + + expect(getDataFieldFilter(instance)).toEqual(['!', ['age', '=', 15]]); + }); + }); + + describe('when only the search panel has text', () => { + it('should match every searchable column with or', async () => { + const { instance } = await createGrid({ + searchPanel: { text: 'Al' }, + columns: ['name', 'city'], + }); + + expect(getDataFieldFilter(instance)).toEqual([ + ['name', 'contains', 'Al'], 'or', ['city', 'contains', 'Al'], + ]); + }); + + it('should skip hidden columns while searchVisibleColumnsOnly is on', async () => { + const { instance } = await createGrid({ + searchPanel: { text: 'Al', searchVisibleColumnsOnly: true }, + columns: [{ dataField: 'name', visible: false }, 'city'], + }); + + expect(getDataFieldFilter(instance)).toEqual(['city', 'contains', 'Al']); + }); + }); + + describe('when only the filterValue option is set', () => { + it('should take the option as is', async () => { + const { instance } = await createGrid({ + filterValue: ['city', '=', 'Berlin'], + columns: ['name', 'age', 'city'], + }); + + expect(getDataFieldFilter(instance)).toEqual(['city', '=', 'Berlin']); + }); + }); + + describe('when no source contributes', () => { + it('should produce no filter', async () => { + const { instance } = await createGrid({ columns: ['name', 'age'] }); + + expect(getDataFieldFilter(instance)).toBeUndefined(); + }); + }); + + describe('when all four sources contribute', () => { + // The chain nests to the left: every layer passes the result of `super` as the first + // element of its own array. A flat combination carries the same meaning but a different + // structure, and the QUnit suites compare structures whole. + it('should nest them left to right in the source order', async () => { + const { instance } = await createGrid({ + filterSyncEnabled: false, + filterValue: ['city', '=', 'Berlin'], + searchPanel: { text: 'Al' }, + columns: [ + { dataField: 'name', filterValue: 'A', selectedFilterOperation: 'contains' }, + { dataField: 'age', filterValues: [15, 20] }, + 'city', + ], + }); + + const filterRow = ['name', 'contains', 'A']; + const headerFilter = [['age', '=', 15], 'or', ['age', '=', 20]]; + const search = [['name', 'contains', 'Al'], 'or', ['city', 'contains', 'Al']]; + const filterValue = ['city', '=', 'Berlin']; + + expect(getDataFieldFilter(instance)).toEqual([ + [[filterRow, 'and', headerFilter], 'and', search], 'and', filterValue, + ]); + }); + + it('should keep the source order when the middle sources are empty', async () => { + const { instance } = await createGrid({ + filterSyncEnabled: false, + filterValue: ['city', '=', 'Berlin'], + columns: [{ dataField: 'name', filterValue: 'Alex' }, 'age', 'city'], + }); + + expect(getDataFieldFilter(instance)).toEqual([ + ['name', 'contains', 'Alex'], 'and', ['city', '=', 'Berlin'], + ]); + }); + }); + + describe('when filter sync is off', () => { + it('should keep the column filters next to filterValue', async () => { + const { instance } = await createGrid({ + filterSyncEnabled: false, + filterValue: ['city', '=', 'Berlin'], + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + expect(getDataFieldFilter(instance)).toEqual([ + [['name', 'contains', 'Alex'], 'and', ['age', '=', 15]], 'and', ['city', '=', 'Berlin'], + ]); + }); + }); + + describe('when filter sync is on', () => { + it('should drop the column filters while filterValue is set', async () => { + const { instance } = await createGrid({ + filterSyncEnabled: true, + filterValue: ['city', '=', 'Berlin'], + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + expect(getDataFieldFilter(instance)).toEqual(['city', '=', 'Berlin']); + }); + + it('should keep the column filters while filterValue is not set', async () => { + const { instance } = await createGrid({ + filterSyncEnabled: true, + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + expect(getDataFieldFilter(instance)).toEqual([ + ['name', 'contains', 'Alex'], 'and', ['age', '=', 15], + ]); + }); + }); + + describe('when filterSyncEnabled is auto', () => { + it('should keep the column filters while the filter panel is hidden', async () => { + const { instance } = await createGrid({ + filterValue: ['city', '=', 'Berlin'], + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + expect(getDataFieldFilter(instance)).toEqual([ + [['name', 'contains', 'Alex'], 'and', ['age', '=', 15]], 'and', ['city', '=', 'Berlin'], + ]); + }); + + it('should drop the column filters while the filter panel is visible', async () => { + const { instance } = await createGrid({ + filterPanel: { visible: true }, + filterValue: ['city', '=', 'Berlin'], + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + expect(getDataFieldFilter(instance)).toEqual(['city', '=', 'Berlin']); + }); + }); + + describe('when filterPanel.filterEnabled is false', () => { + it('should drop filterValue and keep the column filters', async () => { + const { instance } = await createGrid({ + filterSyncEnabled: false, + filterPanel: { visible: true, filterEnabled: false }, + filterValue: ['city', '=', 'Berlin'], + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + expect(getDataFieldFilter(instance)).toEqual([ + ['name', 'contains', 'Alex'], 'and', ['age', '=', 15], + ]); + }); + }); + + describe('when no column allows filtering', () => { + it('should skip filterValue', async () => { + const { instance } = await createGrid({ + filterSyncEnabled: false, + filterValue: ['name', '=', 'Alex'], + columns: [ + { dataField: 'name', allowFiltering: false, allowHeaderFiltering: false }, + { dataField: 'age', allowFiltering: false, allowHeaderFiltering: false }, + ], + }); + + expect(instance.getController('columns').getFilteringColumns()).toHaveLength(0); + expect(getDataFieldFilter(instance)).toBeUndefined(); + }); + }); + + describe('when a column is excluded', () => { + it('should drop its filter row condition', async () => { + const { instance } = await createGrid({ + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + const filter = getFilterWithoutColumn(instance, getVisibleColumns(instance)[0]); + + expect(filter).toEqual(['age', '=', 15]); + }); + + it('should drop its header filter condition', async () => { + const { instance } = await createGrid({ + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + const filter = getFilterWithoutColumn(instance, getVisibleColumns(instance)[1]); + + expect(filter).toEqual(['name', 'contains', 'Alex']); + }); + + it('should change nothing when the column has no conditions', async () => { + const { instance } = await createGrid({ + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + const filter = getFilterWithoutColumn(instance, getVisibleColumns(instance)[2]); + + expect(filter).toEqual([ + ['name', 'contains', 'Alex'], 'and', ['age', '=', 15], + ]); + }); + + it('should drop its filterValue conditions while filter sync is on', async () => { + const { instance } = await createGrid({ + filterPanel: { visible: true }, + filterSyncEnabled: true, + filterValue: [['city', '=', 'Berlin'], 'and', ['name', '=', 'Alex']], + columns: ['name', 'age', 'city'], + }); + const columns = getVisibleColumns(instance); + + expect(getFilterWithoutColumn(instance, columns[2])).toEqual(['name', '=', 'Alex']); + expect(getFilterWithoutColumn(instance, columns[0])).toEqual(['city', '=', 'Berlin']); + }); + + it('should not remember the excluded column after the call', async () => { + const { instance } = await createGrid({ + columns: COLUMNS_WITH_ROW_AND_HEADER_FILTERS, + }); + + getFilterWithoutColumn(instance, getVisibleColumns(instance)[0]); + + expect(getDataFieldFilter(instance)).toEqual([ + ['name', 'contains', 'Alex'], 'and', ['age', '=', 15], + ]); + }); + }); + + describe('when returnDataField is off', () => { + it('should return the column selector instead of the data field', async () => { + const { instance } = await createGrid({ + columns: [{ dataField: 'name', filterValue: 'Alex' }, 'age'], + }); + + const filter = instance.getCombinedFilter() as unknown as unknown[]; + + expect(typeof filter[0]).toBe('function'); + expect(filter[0]).toBe(getVisibleColumns(instance)[0].selector); + }); + }); + + describe('when the search text fits no column', () => { + it('should produce a match-nothing filter on its own', async () => { + const { instance } = await createGrid({ + searchPanel: { text: 'Al' }, + columns: [{ dataField: 'age', dataType: 'number' }], + }); + + expect(getDataFieldFilter(instance)).toEqual(['!']); + }); + + it('should collapse the other sources too', async () => { + const { instance } = await createGrid({ + searchPanel: { text: 'Al' }, + columns: [{ dataField: 'age', dataType: 'number', filterValue: 15 }], + }); + + expect(getDataFieldFilter(instance)).toEqual(['!']); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.data_source.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.data_source.test.ts deleted file mode 100644 index 0d64fec2e747..000000000000 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.data_source.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - afterEach, - beforeEach, - describe, - expect, - it, -} from '@jest/globals'; -import DataSource from '@js/data/data_source'; -import type { DataGridInstance } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; -import { - afterTest, - beforeTest, - createDataGrid, - flushAsync, -} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; - -import { DataController } from '../data_controller'; - -declare class ExposedDataController extends DataController { - public isSharedDataSource?: boolean; -} - -const DATA = [ - { id: 1, value: 'a' }, - { id: 2, value: 'b' }, -]; - -const getIsSharedDataSource = (instance: DataGridInstance): boolean | undefined => { - const dataController = instance.getController('data') as unknown as ExposedDataController; - - return dataController.isSharedDataSource; -}; - -describe('DataController data source', () => { - beforeEach(beforeTest); - afterEach(afterTest); - - describe('isSharedDataSource', () => { - it('should be true when a DataSource instance is passed', async () => { - const sharedDataSource = new DataSource({ store: DATA, key: 'id' }); - - const { instance } = await createDataGrid({ dataSource: sharedDataSource }); - - expect(getIsSharedDataSource(instance)).toBe(true); - }); - - it('should be false when a plain array is passed', async () => { - const { instance } = await createDataGrid({ dataSource: DATA }); - - expect(getIsSharedDataSource(instance)).toBe(false); - }); - - it('should reset to false after switching from a shared DataSource to a plain array', async () => { - const sharedDataSource = new DataSource({ store: DATA, key: 'id' }); - - const { instance } = await createDataGrid({ dataSource: sharedDataSource }); - expect(getIsSharedDataSource(instance)).toBe(true); - - instance.option('dataSource', DATA); - await flushAsync(); - - expect(getIsSharedDataSource(instance)).toBe(false); - }); - }); -}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.paging.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.paging.test.ts index 1294df3428d5..d2550e54c791 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.paging.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/__tests__/data_controller.paging.test.ts @@ -38,13 +38,9 @@ const getIsPaging = (instance: DataGridInstance): unknown => { return dataController._isPaging as unknown; }; -// The controller's `dataSource()` is the DataSourceAdapter, not the raw DataSource. -const getAdapter = (instance: DataGridInstance): PagingAdapter => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const dataController = instance.getController('data') as any; - - return dataController.dataSource() as PagingAdapter; -}; +const getAdapter = (instance: DataGridInstance): PagingAdapter => instance + .getController('dataSource') + .getAdapter() as unknown as PagingAdapter; describe('DataController paging', () => { beforeEach(beforeTest); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts index 5b58e6902297..5057cd96c2b0 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/data_controller.ts @@ -1,23 +1,19 @@ -import { DataSource as DataSourceClass } from '@js/common/data/data_source/data_source'; -import { normalizeDataSourceOptions } from '@js/common/data/data_source/utils'; import type { Callback } from '@js/core/utils/callbacks'; import { deferRender } from '@js/core/utils/common'; import { logger } from '@js/core/utils/console'; import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred, when } from '@js/core/utils/deferred'; -import { extend } from '@js/core/utils/extend'; import { isDefined } from '@js/core/utils/type'; -import type { StoreChange } from '@js/data/store'; import errors from '@js/ui/widget/ui.errors'; import { findChanges } from '@ts/core/utils/m_array_compare'; import { fromPromise } from '@ts/core/utils/m_deferred'; -import type Store from '@ts/data/abstract_store'; import type { DataSource } from '@ts/data/data_source/data_source'; import type { ChangingEvent } from '@ts/data/data_source/types'; import type { Column, ColumnsChanges } from '@ts/grids/grid_core/columns_controller/types'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import type { - ChangedEvent, DataSourceAdapterProvider, LoadOperation, OperationTypes, RawItemData, + ChangedEvent, LoadOperation, OperationTypes, RawItemData, } from '@ts/grids/grid_core/data_source_adapter/types'; import { isLocalStore } from '@ts/grids/grid_core/data_source_adapter/utils/store'; import modules from '@ts/grids/grid_core/m_modules'; @@ -38,6 +34,7 @@ import type { ItemChangeOptions, ItemOperationOptions, ItemProcessingOptions, + LoadAllItemsDeferred, PagingChanges, PagingDataSource, PagingOptionName, @@ -50,15 +47,13 @@ import type { UpdateItemChange, UserState, } from './types'; -import { resolvePaginate, syncPaging } from './utils/paging'; +import { syncPaging } from './utils/paging'; import { getRefreshOptions } from './utils/refresh'; import { attachChangedItems, - canDiffColumns, convertToUpdateChange, + countRowsBefore, getChangedRowIndices, - getDataRowIndex, - getGroupColumnIndices, getItemChange, getRowKey, indexRowsByKey, @@ -70,9 +65,7 @@ import { import { generateRowValues } from './utils/row_values'; export class DataController extends modules.Controller { - public _dataSource?: DataSourceAdapter | null; - - protected isSharedDataSource?: boolean; + protected _dataSource?: DataSourceAdapter | null; protected _items!: ProcessedItem[]; @@ -116,8 +109,6 @@ export class DataController extends modules.Controller { public pageChanged!: Callback<[number?]>; - public pushed!: Callback<[StoreChange[]]>; - public changed!: Callback<[DataChange]>; public loadingChanged!: Callback<[boolean, string?]>; @@ -126,27 +117,28 @@ export class DataController extends modules.Controller { public rowIndicesChanged!: Callback<[RowIndexCorrection]>; + protected dataSourceController!: DataSourceController; + // TODO public controller public _columnsController!: Controllers['columns']; - private _filterExcludedColumn: Column | null = null; + protected filterController!: Controllers['filter']; private loadErrorHandlerProxy!: (e: Error | string) => void; - private dataPushedHandlerProxy!: (changes: StoreChange[]) => void; - private dataChangedHandlerProxy!: (e?: ChangedEvent) => void; public init(): void { this._items = []; this._cachedProcessedItems = null; + this.dataSourceController = this.getController('dataSource'); this._columnsController = this.getController('columns'); + this.filterController = this.getController('filter'); this._isPaging = false; this._currentOperationTypes = null; this.dataChangedHandlerProxy = this.dataChangedHandler.bind(this); this.loadErrorHandlerProxy = this.loadErrorHandler.bind(this); - this.dataPushedHandlerProxy = this.dataPushedHandler.bind(this); this._columnsController.columnsChanged.add(this.columnsChangedHandler.bind(this)); @@ -162,18 +154,6 @@ export class DataController extends modules.Controller { this.resetDataSource(); } - /** - * TODO: Define this method only in masterDetail. - * Remove the override from adaptive behavior - * and move the implementation to masterDetail. - * - * @extended: adaptivity, master_detail - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected getRowIndicesForExpand(key: RowKey): number[] { - return []; - } - /** * @extended: virtual_scrolling */ @@ -183,7 +163,7 @@ export class DataController extends modules.Controller { } protected callbackNames(): string[] { - return ['changed', 'loadingChanged', 'dataErrorOccurred', 'pageChanged', 'dataSourceChanged', 'pushed', 'rowIndicesChanged']; + return ['changed', 'loadingChanged', 'dataErrorOccurred', 'pageChanged', 'dataSourceChanged', 'rowIndicesChanged']; } protected callbackFlags(name?: string): CallbackFlags | undefined { @@ -203,17 +183,13 @@ export class DataController extends modules.Controller { 'endCustomLoading', 'filter', 'getCombinedFilter', - 'getDataSource', 'getKeyByRowIndex', 'getRowIndexByKey', 'getVisibleRows', - 'keyOf', - 'pageCount', 'pageIndex', 'pageSize', 'refresh', 'repaintRows', - 'totalCount', ]; } @@ -237,7 +213,7 @@ export class DataController extends modules.Controller { )) { const isValueChanged = args.value !== args.previousValue; if (isValueChanged) { - const store = this.store(); + const store = this.dataSourceController.store(); if (isLocalStore(store)) { store._array = args.value; } @@ -279,21 +255,20 @@ export class DataController extends modules.Controller { case 'remoteOperations': case 'keyExpr': case 'dataSource': - case 'scrolling': args.handled = true; this.reset(); break; case 'paging': { - const dataSource = this.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); - if (dataSource) { - const changedPagingOptions = this.applyPagingOptions(dataSource); + if (dataSourceAdapter) { + const changedPagingOptions = this.applyPagingOptions(dataSourceAdapter); if (changedPagingOptions.hasChanges) { - const pageIndex = dataSource.pageIndex(); + const pageIndex = dataSourceAdapter.pageIndex(); this._isPaging = changedPagingOptions.isPageIndexChanged; - dataSource.load().done(() => { + dataSourceAdapter.load().done(() => { this._isPaging = false; this.pageChanged.fire(pageIndex); }); @@ -306,11 +281,11 @@ export class DataController extends modules.Controller { this.reset(); break; case 'columns': { - const dataSource = this.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); - if (dataSource?.isLoading() && args.name === args.fullName) { + if (dataSourceAdapter?.isLoading() && args.name === args.fullName) { this._useSortingGroupingFromColumns = true; - dataSource.load(); + dataSourceAdapter.load(); } break; } @@ -323,31 +298,22 @@ export class DataController extends modules.Controller { return !this._isLoading; } - public getDataSource(): DataSource | null { - return this._dataSource?._dataSource ?? null; - } - public getCombinedFilter(returnDataField?: boolean): DataFilter { return this.combinedFilter(undefined, returnDataField); } - public getFilterExcludedColumn(): Column | null { - return this._filterExcludedColumn; - } - public getCombinedFilterWithExcludedColumn( excludedColumn: Column | null, returnDataField?: boolean, ): DataFilter { - this._filterExcludedColumn = excludedColumn; - try { - return this.getCombinedFilter(returnDataField); - } finally { - this._filterExcludedColumn = null; - } + return this.combinedFilter(undefined, returnDataField, excludedColumn); } - private combinedFilter(filter: DataFilter, returnDataField?: boolean): DataFilter { + private combinedFilter( + filter: DataFilter, + returnDataField?: boolean, + excludedColumn: Column | null = null, + ): DataFilter { if (!this._dataSource) { return filter; } @@ -358,7 +324,7 @@ export class DataController extends modules.Controller { || this._columnsController.isAllDataTypesDefined(); if (isColumnsTypesDefined) { - const additionalFilter = this.calculateAdditionalFilter(); + const additionalFilter = this.filterController.getAdditionalFilter(excludedColumn); combined = additionalFilter ? gridCoreUtils.combineFilters([additionalFilter, combined]) @@ -572,7 +538,7 @@ export class DataController extends modules.Controller { this._isDataSourceApplying = false; const hasAdditionalFilter = (): boolean => { - const additionalFilter = this.calculateAdditionalFilter(); + const additionalFilter = this.filterController.getAdditionalFilter(); return Boolean(additionalFilter?.length); }; @@ -621,42 +587,34 @@ export class DataController extends modules.Controller { this.dataErrorOccurred.fire(e); } - protected dataPushedHandler(changes: StoreChange[]): void { - this.pushed.fire(changes); - } - public fireError(...args: unknown[]): void { this.dataErrorOccurred.fire(errors.Error(...args)); } private applyPagingOptions(dataSource: PagingDataSource): PagingChanges { - const { scrolling, paging } = this.option(); + const { paging } = this.option(); - // Not paging state to reconcile, but a per-load request flag: infinite - // scrolling detects the last page locally and needs no grand total. - dataSource.requireTotalCount(scrolling?.mode !== 'infinite'); + dataSource.requireTotalCount(this.requiresTotalCount()); return syncPaging(dataSource, { - paginate: resolvePaginate(paging?.enabled, scrolling?.mode), + paginate: this.resolvePaginate(paging?.enabled), pageSize: paging?.pageSize, pageIndex: paging?.pageIndex, }); } - protected _getSpecificDataSourceOption(): unknown { - const dataSource = this.option('dataSource'); - - if (Array.isArray(dataSource)) { - return { - store: { - type: 'array', - data: dataSource, - key: this.option('keyExpr'), - }, - }; - } + /** + * @extended: virtual_scrolling + */ + protected resolvePaginate(enabled: boolean | undefined): boolean | undefined { + return enabled; + } - return dataSource; + /** + * @extended: virtual_scrolling + */ + protected requiresTotalCount(): boolean { + return true; } /** @@ -672,7 +630,9 @@ export class DataController extends modules.Controller { protected _initDataSource(): void { const hadDataSource = !!this._dataSource; - const dataSource = this.recreateDataSource(); + this._disposeDataSource(); + + const dataSource = this.dataSourceController.createDataSource(); this._useSortingGroupingFromColumns = true; this._cachedProcessedItems = null; @@ -686,27 +646,6 @@ export class DataController extends modules.Controller { } } - private recreateDataSource(): DataSource | undefined { - const dataSourceOptions = this._getSpecificDataSourceOption(); - - this._disposeDataSource(); - - if (!dataSourceOptions) { - this.isSharedDataSource = false; - return undefined; - } - - if (dataSourceOptions instanceof DataSourceClass) { - this.isSharedDataSource = true; - return dataSourceOptions as unknown as DataSource; - } - - this.isSharedDataSource = false; - return new DataSourceClass( - extend(true, {}, normalizeDataSourceOptions(dataSourceOptions, {})), - ) as unknown as DataSource; - } - /** * @extended: selection, virtual_scrolling */ @@ -749,6 +688,16 @@ export class DataController extends modules.Controller { // eslint-disable-next-line @typescript-eslint/no-unused-vars protected getDataIndex(change: DataChange): number { return 0; } + /** + * A store change is indexed by data rows, while an insert index coming from the grid counts + * every visible row. Each module that puts its own countable rows into the stream adds their + * count on top of this one. + * @extended: grouping (DataGrid) + */ + protected adjustInsertRowIndex(visibleRowIndex: number): number { + return countRowsBefore(this.getVisibleRows(), visibleRowIndex, 'data'); + } + /** * @extended: adaptivity, editing, master_detail, virtual_scrolling */ @@ -797,7 +746,7 @@ export class DataController extends modules.Controller { return { rowType: 'data', data, - key: this.keyOf(data), + key: this.dataSourceController.keyOf(data), }; } @@ -956,19 +905,12 @@ export class DataController extends modules.Controller { const oldValue = oldRow.values[columnIndex]; const newValue = newRow.values[columnIndex]; - if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) { - return true; - } - - const isCellModified = ( - row: ProcessedItem, - ): boolean => row.modifiedValues?.[columnIndex] !== undefined; - - return isCellModified(oldRow) !== isCellModified(newRow); + return JSON.stringify(oldValue) !== JSON.stringify(newValue); } /** - * @extended: editing_row_based, editing, editing_form_based + * @extended: editing_row_based, editing, editing_form_based, grouping (DataGrid), + * summary (DataGrid) */ protected getChangedColumnIndices( oldItem: ProcessedItem, @@ -976,18 +918,17 @@ export class DataController extends modules.Controller { visibleRowIndex: number, isLiveUpdate?: boolean, ): number[] | undefined { - if (!canDiffColumns(oldItem, newItem)) { + if (oldItem.rowType !== newItem.rowType) { return undefined; } - - switch (newItem.rowType) { - case 'group': - return getGroupColumnIndices(oldItem, newItem); - case 'detail': - return []; - default: - return this.getChangedColumnIndicesCore(oldItem, newItem, visibleRowIndex, isLiveUpdate); + // The detail type is not owned by a single module: Master-Detail creates these rows, + // while form-based editing reuses the same type for its edit row. Therefore, the check + // remains in the base module for now. + if (newItem.rowType === 'detail') { + return []; } + + return this.getChangedColumnIndicesCore(oldItem, newItem, visibleRowIndex, isLiveUpdate); } private getChangedColumnIndicesCore( @@ -1150,16 +1091,13 @@ export class DataController extends modules.Controller { } private readonly changingHandler = (e: ChangingEvent): void => { - const rows = this.getVisibleRows(); - const dataSource = this.dataSource(); - - if (!dataSource) { + if (!this.dataSourceController.hasAdapter()) { return; } e.changes.forEach((change) => { if (change.type === 'insert' && change.index !== undefined && change.index >= 0) { - change.index = getDataRowIndex(rows, change.index); + change.index = this.adjustInsertRowIndex(change.index); } }); }; @@ -1202,7 +1140,7 @@ export class DataController extends modules.Controller { return; } - const operationTypes = this.dataSource()?.operationTypes() ?? undefined; + const operationTypes = this.dataSourceController.operationTypes() ?? undefined; change.isDataChanged = true; change.repaintChangesOnly = resolveRepaintChangesOnly( @@ -1224,13 +1162,6 @@ export class DataController extends modules.Controller { ); } - public loadingOperationTypes(): OperationTypes { - const dataSource = this.dataSource(); - const operationTypes: OperationTypes | undefined = dataSource?.loadingOperationTypes(); - - return operationTypes ?? {}; - } - /** * @extended: virtual_scrolling, focus */ @@ -1252,13 +1183,6 @@ export class DataController extends modules.Controller { this.loadingChanged.fire(this.isLoading(), this._loadingText); } - /** - * @extended: filter_row, filter_sync, header_filter, search - */ - protected calculateAdditionalFilter(): DataFilter { - return null; - } - /** * @extended: filter_sync, virtual_scrolling */ @@ -1359,25 +1283,12 @@ export class DataController extends modules.Controller { this.dataSourceChanged.fire(); }; - protected _getDataSourceAdapterProvider(): DataSourceAdapterProvider { - throw new Error('Method not implemented.'); - } - - protected _createDataSourceAdapter(dataSource: DataSource): DataSourceAdapter { - const dataSourceAdapterProvider = this._getDataSourceAdapterProvider(); - const dataSourceAdapter = dataSourceAdapterProvider.create(this.component); - - dataSourceAdapter.init(dataSource); - return dataSourceAdapter; - } - private subscribeToDataSource(dataSourceAdapter: DataSourceAdapter): void { dataSourceAdapter.changed.add(this.dataChangedHandlerProxy); dataSourceAdapter.loadingChanged.add(this.loadingChangedHandler); dataSourceAdapter.loadError.add(this.loadErrorHandlerProxy); dataSourceAdapter.customizeStoreLoadOptions.add(this.customizeStoreLoadOptionsHandler); dataSourceAdapter.changing.add(this.changingHandler); - dataSourceAdapter.pushed.add(this.dataPushedHandlerProxy); } private unsubscribeFromDataSource(dataSourceAdapter: DataSourceAdapter): void { @@ -1386,32 +1297,19 @@ export class DataController extends modules.Controller { dataSourceAdapter.loadError.remove(this.loadErrorHandlerProxy); dataSourceAdapter.customizeStoreLoadOptions.remove(this.customizeStoreLoadOptionsHandler); dataSourceAdapter.changing.remove(this.changingHandler); - dataSourceAdapter.pushed.remove(this.dataPushedHandlerProxy); } - private setDataSource(dataSource: DataSource | null): void { - const oldDataSource = this._dataSource; - - if (!dataSource && oldDataSource) { - oldDataSource.cancelAll(); - this.unsubscribeFromDataSource(oldDataSource); - oldDataSource.dispose(this.isSharedDataSource); - } - - const dataSourceAdapter = dataSource - ? this._createDataSourceAdapter(dataSource) - : null; + private setDataSource(dataSource: DataSource): void { + const dataSourceAdapter = this.dataSourceController.createAdapter(dataSource); this._dataSource = dataSourceAdapter; - if (dataSourceAdapter) { - this._isLoading = !dataSourceAdapter.isLoaded(); - this._needApplyFilter = true; - this._isAllDataTypesDefined = this._columnsController.isAllDataTypesDefined(); + this._isLoading = !dataSourceAdapter.isLoaded(); + this._needApplyFilter = true; + this._isAllDataTypesDefined = this._columnsController.isAllDataTypesDefined(); - this.changed.add(this.fireDataSourceChanged); - this.subscribeToDataSource(dataSourceAdapter); - } + this.changed.add(this.fireDataSourceChanged); + this.subscribeToDataSource(dataSourceAdapter); } /** @@ -1429,23 +1327,11 @@ export class DataController extends modules.Controller { return !this.items().length; } - public pageCount(): number { - return this._dataSource ? this._dataSource.pageCount() : 1; - } - - public dataSource(): DataSourceAdapter | undefined { - return this._dataSource ?? undefined; - } - - public store(): Store | undefined { - return this._dataSource?.store(); - } - public loadAllItems( data?: RawItemData[], skipFilter = false, - ): DeferredObj<ProcessedItem[]> { - const d = Deferred<ProcessedItem[]>(); + ): LoadAllItemsDeferred { + const d = Deferred<ProcessedItem[]>() as LoadAllItemsDeferred; const dataSource = this._dataSource; if (!dataSource) { @@ -1453,14 +1339,8 @@ export class DataController extends modules.Controller { return d; } - const resolveWithProcessedItems = (loadResult: CustomLoadResult): void => { - const items = this._processItems( - this._beforeProcessItems(loadResult.data), - { changeType: 'loadingAll' }, - ); - - // @ts-expect-error DataGrid-only summary leaks into grid_core - d.resolve(items, loadResult.extra?.summary); + const resolveLoaded = (loadResult: CustomLoadResult): void => { + this.resolveLoadAllItems(d, loadResult); }; if (data) { @@ -1469,11 +1349,11 @@ export class DataController extends modules.Controller { group: dataSource.group(), sort: dataSource.sort(), }) - .done(resolveWithProcessedItems) + .done(resolveLoaded) .fail(d.reject as (...args: unknown[]) => void); } else if (!dataSource.isLoading()) { dataSource.customLoader.loadAll() - .done(resolveWithProcessedItems) + .done(resolveLoaded) .fail(d.reject as (...args: unknown[]) => void); } else { d.reject(); @@ -1482,6 +1362,23 @@ export class DataController extends modules.Controller { return d; } + protected processLoadAllItems(loadResult: CustomLoadResult): ProcessedItem[] { + return this._processItems( + this._beforeProcessItems(loadResult.data), + { changeType: 'loadingAll' }, + ); + } + + /** + * @extended: summary (DataGrid) + */ + protected resolveLoadAllItems( + d: LoadAllItemsDeferred, + loadResult: CustomLoadResult, + ): void { + d.resolve(this.processLoadAllItems(loadResult)); + } + public async getAllDataRowKeys(): Promise<RowKey[]> { const items = await Promise.resolve(this.loadAllItems()); @@ -1504,12 +1401,8 @@ export class DataController extends modules.Controller { return this.items()?.[this.getRowIndexByKey(key)]; } - public keyOf(data: RawItemData): RowKey | undefined { - return this.store()?.keyOf(data); - } - private byKey(key: RowKey): DeferredObj<RawItemData> { - const store = this.store(); + const store = this.dataSourceController.store(); if (!store) { return Deferred<RawItemData>().reject(); @@ -1524,10 +1417,6 @@ export class DataController extends modules.Controller { return fromPromise(store.byKey(key)) as DeferredObj<RawItemData>; } - public key(): string | string[] | undefined { - return this.store()?.key(); - } - /** * @extended: virtual_scrolling */ @@ -1614,7 +1503,7 @@ export class DataController extends modules.Controller { public refresh(options?: boolean | RefreshOptions): DeferredObj<unknown> { const refreshOptions = getRefreshOptions(options); - const dataSource = this.getDataSource(); + const dataSource = this.dataSourceController.getDataSource(); const { changesOnly } = refreshOptions; const d = Deferred(); @@ -1650,7 +1539,16 @@ export class DataController extends modules.Controller { } protected _disposeDataSource(): void { - this.setDataSource(null); + const oldDataSource = this._dataSource; + + if (oldDataSource) { + // Before unsubscribing: cancelling in-flight loads still notifies this controller. + oldDataSource.cancelAll(); + this.unsubscribeFromDataSource(oldDataSource); + } + + this._dataSource = null; + this.dataSourceController.disposeAdapter(); } public dispose(): void { @@ -1681,26 +1579,21 @@ export class DataController extends modules.Controller { } /** - * @extended: TreeList's state_storing + * @extended: search, TreeList's state_storing */ public getUserState(): UserState { return { - searchText: this.option('searchPanel.text'), pageIndex: this.pageIndex(), pageSize: this.pageSize(), }; } - public getCachedStoreData(): RawItemData[] | undefined { - return this._dataSource?.getCachedStoreData(); - } - /** * @extended: virtual_scrolling */ public isLastPageLoaded(): boolean { const pageIndex = this.pageIndex(); - const pageCount = this.pageCount(); + const pageCount = this.dataSourceController.pageCount(); return pageIndex === (pageCount - 1); } @@ -1716,22 +1609,6 @@ export class DataController extends modules.Controller { return this._dataSource?.reload(reload, changesOnly) as DeferredObj<unknown>; } - public push(changes: StoreChange[], fromStore = false): void { - this._dataSource?.push(changes, fromStore); - } - - private itemsCount(): number { - return (this._dataSource ? this._dataSource.itemsCount() : 0); - } - - public totalItemsCount(): number { - return (this._dataSource ? this._dataSource.totalItemsCount() : 0); - } - - public hasKnownLastPage(): boolean { - return (this._dataSource ? this._dataSource.hasKnownLastPage() : true); - } - /** * @extended: state_storing */ @@ -1739,10 +1616,6 @@ export class DataController extends modules.Controller { return (this._dataSource ? this._dataSource.isLoaded() : true); } - public totalCount(): number { - return (this._dataSource ? this._dataSource.totalCount() : 0); - } - public hasLoadOperation(): boolean { const operationTypes = this._dataSource?.operationTypes() ?? {}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts index 4e2aeb8eba80..6f0aba6a1d85 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/types.ts @@ -18,9 +18,9 @@ export interface RefreshOptions { } export interface UserState { - searchText: string | undefined; pageIndex: number; pageSize: number; + searchText?: string; expandedRowKeys?: unknown[]; } @@ -73,6 +73,13 @@ export interface ProcessedItem extends GeneratedItem { watch?: RowWatch; } +type LoadAllItemsCallback = (items: ProcessedItem[], totalAggregates?: unknown[]) => void; + +export type LoadAllItemsDeferred = Omit<DeferredObj<ProcessedItem[]>, 'done' | 'resolve'> & { + done: (callback: LoadAllItemsCallback) => LoadAllItemsDeferred; + resolve: LoadAllItemsCallback; +}; + /** changes */ export type RowChangeType = 'update' | 'insert' | 'remove'; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/paging.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/paging.test.ts index 185dc4ae86a5..0a58ae260560 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/paging.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/paging.test.ts @@ -1,32 +1,7 @@ import { describe, expect, it } from '@jest/globals'; import type { PagingDataSource } from '../../types'; -import { resolvePaginate, syncPaging } from '../paging'; - -describe('resolvePaginate', () => { - it.each([ - { enabled: true, scrollingMode: 'standard', expected: true }, - { enabled: false, scrollingMode: 'standard', expected: false }, - // Virtual and infinite scrolling paginate even with paging disabled. - { enabled: false, scrollingMode: 'virtual', expected: true }, - { enabled: false, scrollingMode: 'infinite', expected: true }, - { enabled: true, scrollingMode: 'virtual', expected: true }, - { enabled: true, scrollingMode: 'infinite', expected: true }, - ])('should be $expected for enabled=$enabled, scrolling.mode=$scrollingMode', ({ - enabled, scrollingMode, expected, - }) => { - expect(resolvePaginate(enabled, scrollingMode)).toBe(expected); - }); - - // An undefined `paging.enabled` leaves the data source's paginate alone, - // even in a mode that would otherwise force it on. - it.each(['standard', 'virtual', 'infinite'])( - 'should be undefined when enabled is undefined in %s mode', - (scrollingMode) => { - expect(resolvePaginate(undefined, scrollingMode)).toBeUndefined(); - }, - ); -}); +import { syncPaging } from '../paging'; const createDataSourceMock = ( state: { paginate: boolean; pageSize: number; pageIndex: number }, diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts index 6a214c39d3a1..3f75575829b7 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/__tests__/row_changes.test.ts @@ -8,16 +8,13 @@ import type { } from '../../types'; import { attachChangedItems, - canDiffColumns, convertToUpdateChange, + countRowsBefore, getChangedRowIndices, - getDataRowIndex, - getGroupColumnIndices, getItemChange, getRowKey, getRowOperation, indexRowsByKey, - isSameGroupRowState, isSameItem, partialUpdateItem, resolveRepaintChangesOnly, @@ -96,84 +93,6 @@ describe('isSameItem', () => { }); }); -describe('isSameGroupRowState', () => { - const groupRow = (partial: Partial<ProcessedItem>): ProcessedItem => row({ - rowType: 'group', - isExpanded: true, - data: { isContinuation: false, isContinuationOnNextPage: false }, - ...partial, - }); - - it('should return true for the same state', () => { - expect(isSameGroupRowState(groupRow({}), groupRow({}))).toBe(true); - }); - - it('should compare the expanded state', () => { - expect(isSameGroupRowState(groupRow({}), groupRow({ isExpanded: false }))).toBe(false); - }); - - it('should compare the continuation flags', () => { - expect(isSameGroupRowState( - groupRow({}), - groupRow({ data: { isContinuation: true, isContinuationOnNextPage: false } }), - )).toBe(false); - - expect(isSameGroupRowState( - groupRow({}), - groupRow({ data: { isContinuation: false, isContinuationOnNextPage: true } }), - )).toBe(false); - }); - - it('should not compare the data beyond the continuation flags', () => { - expect(isSameGroupRowState( - groupRow({ data: { key: 1, isContinuation: false, isContinuationOnNextPage: false } }), - groupRow({ data: { key: 2, isContinuation: false, isContinuationOnNextPage: false } }), - )).toBe(true); - }); -}); - -describe('canDiffColumns', () => { - it('should allow the diff for the rows of the same type', () => { - expect(canDiffColumns(row({ rowType: 'data' }), row({ rowType: 'data' }))).toBe(true); - }); - - it('should forbid the diff for the rows of different types', () => { - expect(canDiffColumns(row({ rowType: 'data' }), row({ rowType: 'detail' }))).toBe(false); - }); - - it('should forbid the diff for group footers', () => { - expect(canDiffColumns(row({ rowType: 'groupFooter' }), row({ rowType: 'groupFooter' }))) - .toBe(false); - }); -}); - -describe('getGroupColumnIndices', () => { - const groupRow = (partial: Partial<ProcessedItem>): ProcessedItem => row({ - rowType: 'group', - isExpanded: true, - data: { isContinuation: false, isContinuationOnNextPage: false }, - ...partial, - }); - - it('should skip the group expand cell', () => { - const oldItem = groupRow({ - cells: [{ column: { type: 'groupExpand' } }, {}, { column: { dataField: 'name' } }], - }); - - expect(getGroupColumnIndices(oldItem, groupRow({}))).toEqual([1, 2]); - }); - - it('should return undefined when the old row has no cells', () => { - expect(getGroupColumnIndices(groupRow({}), groupRow({}))).toBeUndefined(); - }); - - it('should return undefined when the group state has changed', () => { - const oldItem = groupRow({ cells: [{}] }); - - expect(getGroupColumnIndices(oldItem, groupRow({ isExpanded: false }))).toBeUndefined(); - }); -}); - describe('getRowKey', () => { it('should tell apart the rows of different types with the same key', () => { expect(getRowKey(row({ key: 1, rowType: 'data' }))) @@ -307,7 +226,7 @@ describe('updateKeptRows', () => { }); }); -describe('getDataRowIndex', () => { +describe('countRowsBefore', () => { const rows = [ row({ rowType: 'data' }), row({ rowType: 'group' }), @@ -315,14 +234,24 @@ describe('getDataRowIndex', () => { row({ rowType: 'data' }), ]; - it('should count the data and group rows before the visible index', () => { - expect(getDataRowIndex(rows, 0)).toBe(0); - expect(getDataRowIndex(rows, 3)).toBe(2); - expect(getDataRowIndex(rows, rows.length)).toBe(3); + it('should count the rows of the requested type before the visible index', () => { + expect(countRowsBefore(rows, 0, 'data')).toBe(0); + expect(countRowsBefore(rows, 3, 'data')).toBe(1); + expect(countRowsBefore(rows, rows.length, 'data')).toBe(2); + }); + + it('should not count the rows of any other type', () => { + expect(countRowsBefore(rows, rows.length, 'group')).toBe(1); + expect(countRowsBefore(rows, rows.length, 'detail')).toBe(1); + expect(countRowsBefore(rows, rows.length, 'groupFooter')).toBe(0); }); it('should count the rows that are there when the index is out of range', () => { - expect(getDataRowIndex(rows, 10)).toBe(3); + expect(countRowsBefore(rows, 10, 'data')).toBe(2); + }); + + it('should return zero for an empty row set', () => { + expect(countRowsBefore([], 3, 'data')).toBe(0); }); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/paging.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/paging.ts index 0d4ba2ec690a..dc8f50219e70 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/paging.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/paging.ts @@ -1,16 +1,5 @@ import type { PagingChanges, PagingDataSource, SyncPagingOptions } from '../types'; -export function resolvePaginate( - enabled: boolean | undefined, - scrollingMode: string | undefined, -): boolean | undefined { - if (enabled === undefined) { - return undefined; - } - - return enabled || scrollingMode === 'virtual' || scrollingMode === 'infinite'; -} - export function syncPaging( dataSource: PagingDataSource, options: SyncPagingOptions, diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts index c4ae25369d6e..b713669479e5 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_changes.ts @@ -26,30 +26,6 @@ export function isSameItem( return isSameRowType && (!isDetailRow || isSameEditingState); } -// TODO remove after related checks moved to extenders (duplicated in grouping) -export function isSameGroupRowState(item1: ProcessedItem, item2: ProcessedItem): boolean { - return item1.isExpanded === item2.isExpanded - && item1.data?.isContinuation === item2.data?.isContinuation - && item1.data?.isContinuationOnNextPage === item2.data?.isContinuationOnNextPage; -} - -export function canDiffColumns(oldItem: ProcessedItem, newItem: ProcessedItem): boolean { - return oldItem.rowType === newItem.rowType && newItem.rowType !== 'groupFooter'; -} - -export function getGroupColumnIndices( - oldItem: ProcessedItem, - newItem: ProcessedItem, -): number[] | undefined { - if (!oldItem.cells || !isSameGroupRowState(oldItem, newItem)) { - return undefined; - } - - return oldItem.cells - .map((cell, index) => (cell.column?.type !== 'groupExpand' ? index : -1)) - .filter((index) => index >= 0); -} - /** * Rows of different types may share a key, so the row type is a part of the key * the diff is built on. @@ -122,14 +98,14 @@ export function updateKeptRows( }); } -/** - * A store change is indexed by data rows, while an insert index coming from the - * grid counts every visible row — group rows included. - */ -export function getDataRowIndex(rows: ProcessedItem[], visibleRowIndex: number): number { +export function countRowsBefore( + rows: ProcessedItem[], + visibleRowIndex: number, + rowType: ProcessedItem['rowType'], +): number { const previousRows = rows.slice(0, visibleRowIndex); - return previousRows.filter((row) => row?.rowType === 'data' || row?.rowType === 'group').length; + return previousRows.filter((row) => row?.rowType === rowType).length; } export function getChangedRowIndices( diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_values.ts b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_values.ts index 36ddc1488dea..a13f3eade9d7 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_values.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_controller/utils/row_values.ts @@ -1,4 +1,4 @@ -import { AI_COLUMN_NAME } from '@ts/grids/grid_core/ai_column/const'; +import { columnHasValue } from '@ts/grids/grid_core/columns_controller/m_columns_controller_utils'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { RawItemData } from '../../data_source_adapter/types'; @@ -13,7 +13,7 @@ export function generateRowValues( const emptyValue = isModified ? undefined : null; return columns.map((column) => { - if (column.command && column.type !== AI_COLUMN_NAME) { + if (!columnHasValue(column)) { return emptyValue; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts new file mode 100644 index 000000000000..a90d6dafe66c --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.integration.test.ts @@ -0,0 +1,319 @@ +import { + afterEach, + beforeEach, + describe, + expect, + it, + jest, +} from '@jest/globals'; +import DataSourceClass from '@js/data/data_source'; +import errors from '@js/ui/widget/ui.errors'; +import { + afterTest, + beforeTest, + createDataGrid, + flushAsync, + getMirroredAdapter, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import type { InternalGrid } from '@ts/grids/grid_core/m_types'; + +import { DataSourceController } from '../data_source_controller'; + +const DATA = [ + { id: 1, parentId: 0, value: 'a' }, + { id: 2, parentId: 1, value: 'b' }, +]; + +const OTHER_DATA = [ + { id: 3, parentId: 0, value: 'c' }, +]; + +const getControllerNames = (instance: unknown): string[] => Object + .keys((instance as InternalGrid)._controllers); + +describe('dataSource module registration', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('is reachable from DataGrid', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(instance.getController('dataSource')).toBeInstanceOf(DataSourceController); + }); + + it('owns the getDataSource widget method', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(instance.getDataSource()).toBeInstanceOf(DataSourceClass); + expect(instance.getDataSource()) + .toBe(instance.getController('dataSource').getDataSource()); + }); + + it('owns the keyOf widget method', async () => { + const { instance } = await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + + expect(instance.keyOf(DATA[1])).toBe(2); + expect(instance.keyOf(DATA[1])) + .toBe(instance.getController('dataSource').keyOf(DATA[1])); + }); + + it('owns the totalCount widget method', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(instance.totalCount()).toBe(DATA.length); + expect(instance.totalCount()) + .toBe(instance.getController('dataSource').totalCount()); + }); + + it('owns the pageCount widget method', async () => { + const { instance } = await createDataGrid({ dataSource: DATA, paging: { pageSize: 1 } }); + + expect(instance.pageCount()).toBe(DATA.length); + expect(instance.pageCount()) + .toBe(instance.getController('dataSource').pageCount()); + }); + + it('sits at the bottom of the controller order', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(getControllerNames(instance)[0]).toBe('dataSource'); + }); +}); + +describe('dataSource controller holds the adapter', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('holds the same adapter object as DataController', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + const adapter = getMirroredAdapter(instance); + + expect(adapter).toBeTruthy(); + expect(dataSourceController.hasAdapter()).toBe(true); + expect(dataSourceController.getAdapter()).toBe(adapter); + }); + + it('follows the rebuilt adapter when the dataSource option changes', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + const firstAdapter = dataSourceController.getAdapter(); + + instance.option('dataSource', OTHER_DATA); + await flushAsync(); + + expect(dataSourceController.getAdapter()).not.toBe(firstAdapter); + expect(dataSourceController.getAdapter()).toBe(getMirroredAdapter(instance)); + }); + + it('releases the adapter when the dataSource option is cleared', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + instance.option('dataSource', undefined); + await flushAsync(); + + expect(dataSourceController.hasAdapter()).toBe(false); + expect(dataSourceController.getAdapter()).toBeNull(); + expect(getMirroredAdapter(instance)).toBeNull(); + expect(dataSourceController.getDataSource()).toBeNull(); + expect(dataSourceController.store()).toBeUndefined(); + }); + + it('recovers after the dataSource option is set again', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + instance.option('dataSource', undefined); + await flushAsync(); + instance.option('dataSource', OTHER_DATA); + await flushAsync(); + + expect(dataSourceController.hasAdapter()).toBe(true); + expect(dataSourceController.getAdapter()).toBe(getMirroredAdapter(instance)); + }); + + it('still holds the same adapter after a refresh', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + const refreshed = instance.refresh(); + await flushAsync(); + await refreshed; + + expect(dataSourceController.hasAdapter()).toBe(true); + expect(dataSourceController.getAdapter()).toBe(getMirroredAdapter(instance)); + }); + + it('releases the adapter on dispose', async () => { + const { $container, instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + instance.dispose(); + $container.remove(); + + expect(getMirroredAdapter(instance)).toBeNull(); + expect(dataSourceController.hasAdapter()).toBe(false); + }); +}); + +describe('dataSource controller reads delegate to the adapter', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('delegates key', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(instance.getController('dataSource').key()).toBe('id'); + }); + + it('unwraps one hop for getDataSource', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + expect(dataSourceController.getDataSource()).toBeInstanceOf(DataSourceClass); + expect(dataSourceController.getDataSource()) + .not.toBe(dataSourceController.getAdapter()); + }); + + it('delegates remoteOperations instead of falling back to an empty object', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + const adapter = dataSourceController.getAdapter(); + + expect(dataSourceController.remoteOperations()).toBe(adapter?.remoteOperations()); + }); + + it('delegates getDataIndexGetter', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + + expect(typeof instance.getController('dataSource').getDataIndexGetter()).toBe('function'); + }); + + it('answers key and keyOf from the store when the store is keyed', async () => { + const { instance } = await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + const dataSourceController = instance.getController('dataSource'); + + expect(dataSourceController.key()).toBe('id'); + expect(dataSourceController.keyOf(DATA[1])).toBe(2); + }); + + it('answers key and keyOf from the store when the store has no key', async () => { + const { instance } = await createDataGrid({ dataSource: DATA, keyExpr: undefined }); + const dataSourceController = instance.getController('dataSource'); + + expect(dataSourceController.key()).toBeUndefined(); + // A keyless ArrayStore identifies a row by the object itself. + expect(dataSourceController.keyOf(DATA[1])).toBe(DATA[1]); + }); +}); + +describe('dataSource controller resolves its own component adapter provider', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('builds a DataGrid adapter in DataGrid', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + const adapter = instance.getController('dataSource').getAdapter(); + + expect(adapter).toBeTruthy(); + expect('forEachNode' in (adapter as object)).toBe(false); + }); +}); + +describe('dataSource controller owns the dataSource option reading', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('warns W1011 in DataGrid when keyExpr is combined with a non-array dataSource', async () => { + const log = jest.spyOn(errors, 'log').mockImplementation(() => {}); + + try { + await createDataGrid({ dataSource: { store: { type: 'array', data: DATA } }, keyExpr: 'id' }); + + expect(log).toHaveBeenCalledWith('W1011'); + } finally { + log.mockRestore(); + } + }); + + it('does not warn W1011 in DataGrid for an array dataSource', async () => { + const log = jest.spyOn(errors, 'log').mockImplementation(() => {}); + + try { + await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + + expect(log).not.toHaveBeenCalledWith('W1011'); + } finally { + log.mockRestore(); + } + }); + + it('builds a DataSource from the array option and keys it by keyExpr', async () => { + const { instance } = await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + + expect(instance.getController('dataSource').key()).toBe('id'); + }); +}); + +describe('dataSource controller owns adapter disposal', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('spares a DataSource the caller still owns when the grid is disposed', async () => { + const shared = new DataSourceClass({ store: DATA, key: 'id' }); + const dispose = jest.spyOn(shared, 'dispose'); + const { $container, instance } = await createDataGrid({ dataSource: shared }); + + instance.dispose(); + // afterTest reads the component off #gridContainer, so a disposed one must not linger. + $container.remove(); + + expect(dispose).not.toHaveBeenCalled(); + + dispose.mockRestore(); + shared.dispose(); + }); + + it('destroys a DataSource it built itself when the grid is disposed', async () => { + const { $container, instance } = await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + const built = instance.getController('dataSource').getDataSource(); + + if (!built) { + throw new Error('expected the controller to have built a DataSource'); + } + + const dispose = jest.spyOn(built, 'dispose'); + + instance.dispose(); + $container.remove(); + + expect(dispose).toHaveBeenCalledTimes(1); + + dispose.mockRestore(); + }); + + it('spares a shared DataSource when the dataSource option is replaced', async () => { + const shared = new DataSourceClass({ store: DATA, key: 'id' }); + const dispose = jest.spyOn(shared, 'dispose'); + const { instance } = await createDataGrid({ dataSource: shared }); + + instance.option('dataSource', OTHER_DATA); + await flushAsync(); + + expect(dispose).not.toHaveBeenCalled(); + + dispose.mockRestore(); + shared.dispose(); + }); + + it('leaves DataController and the controller agreeing that the adapter is gone', async () => { + const { instance } = await createDataGrid({ dataSource: DATA, keyExpr: 'id' }); + + instance.option('dataSource', undefined); + await flushAsync(); + + expect(instance.getController('dataSource').hasAdapter()).toBe(false); + expect(instance.getController('dataSource').getAdapter()).toBeNull(); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.test.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.test.ts new file mode 100644 index 000000000000..16683072e4f3 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/__tests__/data_source_controller.test.ts @@ -0,0 +1,611 @@ +import { + describe, + expect, + it, + jest, +} from '@jest/globals'; +import { DataSource as DataSourceClass } from '@js/common/data/data_source/data_source'; +import type { Callback } from '@js/core/utils/callbacks'; +import Callbacks from '@js/core/utils/callbacks'; +import type { StoreChange } from '@js/data/store'; +import type Store from '@ts/data/abstract_store'; +import type { StoreKey } from '@ts/data/abstract_store'; +import type { DataSource } from '@ts/data/data_source/data_source'; +import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; +import type { + DataSourceAdapterProvider, RawItemData, RemoteOperationsOptions, +} from '@ts/grids/grid_core/data_source_adapter/types'; +import type { InternalGrid } from '@ts/grids/grid_core/m_types'; + +import { DataSourceController } from '../data_source_controller'; + +interface AdapterStub { + _dataSource: DataSource; + store: jest.Mock<() => Store | undefined>; + key: jest.Mock<() => StoreKey | undefined>; + remoteOperations: jest.Mock<() => RemoteOperationsOptions>; + getDataIndexGetter: jest.Mock<() => (data: RawItemData) => number>; + hasKnownLastPage: jest.Mock<() => boolean>; + totalItemsCount: jest.Mock<() => number>; + totalCount: jest.Mock<() => number>; + pageCount: jest.Mock<() => number>; + dispose: jest.Mock<(isShared?: boolean) => void>; + init: jest.Mock<(dataSource: DataSource) => void>; + push: jest.Mock<(changes: StoreChange[], fromStore: boolean) => void>; + pushed: Callback<[StoreChange[]]>; +} + +interface ProviderStub { + nextAdapter: AdapterStub; + create: jest.Mock<(component: InternalGrid) => DataSourceAdapter>; + extend: jest.Mock<() => void>; +} + +const createAdapterStub = (marker: string): AdapterStub => ({ + _dataSource: { marker } as unknown as DataSource, + store: jest.fn(() => ({ marker } as unknown as Store)), + key: jest.fn(() => marker as StoreKey), + remoteOperations: jest.fn(() => ({ filtering: true } as RemoteOperationsOptions)), + getDataIndexGetter: jest.fn(() => (): number => 0), + hasKnownLastPage: jest.fn(() => false), + totalItemsCount: jest.fn(() => 42), + totalCount: jest.fn(() => 99), + pageCount: jest.fn(() => 7), + dispose: jest.fn(), + init: jest.fn(), + push: jest.fn(), + pushed: Callbacks(), +}); + +const asAdapter = (stub: AdapterStub): DataSourceAdapter => stub as unknown as DataSourceAdapter; + +class TestDataSourceController extends DataSourceController { + public providerStub!: DataSourceAdapterProvider; + + protected getAdapterProvider(): DataSourceAdapterProvider { + return this.providerStub; + } + + public readSpecificDataSourceOption(): unknown { + return this.getSpecificDataSourceOption(); + } +} + +const createControllerWith = (): { + controller: DataSourceController; + component: InternalGrid; +} => { + const component = { + _optionCache: {}, + _controllers: {}, + option: jest.fn(), + } as unknown as InternalGrid; + + return { controller: new DataSourceController(component), component }; +}; + +const createController = (): DataSourceController => createControllerWith().controller; + +const createProviderStub = (adapter: AdapterStub): ProviderStub => { + const stub: ProviderStub = { + nextAdapter: adapter, + create: jest.fn(() => asAdapter(stub.nextAdapter)), + extend: jest.fn(), + }; + + return stub; +}; + +const asProvider = ( + stub: ProviderStub, +): DataSourceAdapterProvider => stub as unknown as DataSourceAdapterProvider; + +const SOURCE = { marker: 'source' } as unknown as DataSource; + +const CHANGES: StoreChange[] = [{ type: 'remove', key: 1 }]; + +const withProvider = (adapter: AdapterStub): { + controller: TestDataSourceController; + component: InternalGrid; + provider: ProviderStub; +} => { + const component = { + _optionCache: {}, + _controllers: {}, + option: jest.fn(), + } as unknown as InternalGrid; + const controller = new TestDataSourceController(component); + const provider = createProviderStub(adapter); + + controller.providerStub = asProvider(provider); + + return { controller, component, provider }; +}; + +const withOptions = (options: Record<string, unknown>): TestDataSourceController => { + const component = { + _controllers: {}, + option: jest.fn((name?: string) => (name === undefined ? options : options[name])), + } as unknown as InternalGrid; + + return new TestDataSourceController(component); +}; + +// isShared is private and read only by disposal, so that is where it becomes observable. +const flagHandedToAdapter = ( + controller: TestDataSourceController, +): boolean | undefined => { + const probe = createAdapterStub('probe'); + + controller.providerStub = asProvider(createProviderStub(probe)); + controller.createAdapter(SOURCE); + controller.disposeAdapter(); + + return probe.dispose.mock.calls[0]?.[0]; +}; + +const withAdapter = (marker = 'first'): { + controller: TestDataSourceController; + adapter: AdapterStub; + provider: ProviderStub; +} => { + const adapter = createAdapterStub(marker); + const { controller, provider } = withProvider(adapter); + + controller.createAdapter(SOURCE); + + return { controller, adapter, provider }; +}; + +describe('DataSourceController', () => { + describe('with no adapter', () => { + it('has no adapter right after construction, without init()', () => { + const controller = createController(); + + expect(controller.hasAdapter()).toBe(false); + expect(controller.getAdapter()).toBeNull(); + }); + + it('returns null from getDataSource', () => { + expect(createController().getDataSource()).toBeNull(); + }); + + it('returns undefined from store and key', () => { + const controller = createController(); + + expect(controller.store()).toBeUndefined(); + expect(controller.key()).toBeUndefined(); + }); + + it('returns undefined from getDataIndexGetter', () => { + expect(createController().getDataIndexGetter()).toBeUndefined(); + }); + + it('reports the last page as known', () => { + expect(createController().hasKnownLastPage()).toBe(true); + }); + + it('counts no items', () => { + expect(createController().totalItemsCount()).toBe(0); + expect(createController().totalCount()).toBe(0); + }); + + it('reports a single page', () => { + expect(createController().pageCount()).toBe(1); + }); + + it('returns an empty object from remoteOperations, so callers can enumerate it', () => { + const controller = createController(); + + expect(controller.remoteOperations()).toEqual({}); + expect(Object.keys(controller.remoteOperations())).toEqual([]); + }); + }); + + describe('with an adapter', () => { + it('reports the adapter as present and hands back the same object', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.hasAdapter()).toBe(true); + expect(controller.getAdapter()).toBe(asAdapter(adapter)); + }); + + it('delegates store to the adapter once per call', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.store()).toBe(adapter.store.mock.results[0]?.value); + expect(adapter.store).toHaveBeenCalledTimes(1); + }); + + it('delegates key to the adapter', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.key()).toBe('first'); + expect(adapter.key).toHaveBeenCalledTimes(1); + }); + + it('returns the adapter remoteOperations object as-is', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.remoteOperations()).toEqual({ filtering: true }); + expect(adapter.remoteOperations).toHaveBeenCalledTimes(1); + }); + + it('delegates getDataIndexGetter to the adapter', () => { + const { controller, adapter } = withAdapter(); + const getter = controller.getDataIndexGetter(); + + expect(getter).toBe(adapter.getDataIndexGetter.mock.results[0]?.value); + expect(adapter.getDataIndexGetter).toHaveBeenCalledTimes(1); + }); + + it('delegates hasKnownLastPage to the adapter', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.hasKnownLastPage()).toBe(false); + expect(adapter.hasKnownLastPage).toHaveBeenCalledTimes(1); + }); + + it('delegates totalItemsCount to the adapter', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.totalItemsCount()).toBe(42); + expect(adapter.totalItemsCount).toHaveBeenCalledTimes(1); + }); + + it('delegates totalCount to the adapter', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.totalCount()).toBe(99); + expect(adapter.totalCount).toHaveBeenCalledTimes(1); + }); + + it('delegates pageCount to the adapter', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.pageCount()).toBe(7); + expect(adapter.pageCount).toHaveBeenCalledTimes(1); + }); + + it('returns the inner DataSource from getDataSource, not the adapter', () => { + const { controller, adapter } = withAdapter(); + + expect(controller.getDataSource()).toBe(adapter._dataSource); + expect(controller.getDataSource()).not.toBe(asAdapter(adapter)); + }); + }); + + describe('replacing the adapter', () => { + it('follows the new adapter after a replacement', () => { + const { controller, adapter: first, provider } = withAdapter(); + const second = createAdapterStub('second'); + + provider.nextAdapter = second; + controller.createAdapter(SOURCE); + + expect(controller.getAdapter()).toBe(asAdapter(second)); + expect(controller.key()).toBe('second'); + expect(controller.getDataSource()).toBe(second._dataSource); + expect(first.key).not.toHaveBeenCalled(); + }); + + it('returns to the absent state after disposeAdapter', () => { + const { controller } = withAdapter(); + + controller.disposeAdapter(); + + expect(controller.hasAdapter()).toBe(false); + expect(controller.getAdapter()).toBeNull(); + expect(controller.getDataSource()).toBeNull(); + expect(controller.store()).toBeUndefined(); + expect(controller.key()).toBeUndefined(); + expect(controller.getDataIndexGetter()).toBeUndefined(); + expect(controller.remoteOperations()).toEqual({}); + }); + }); + + describe('layering', () => { + it('reads no other controller', () => { + const { controller } = withAdapter(); + const getController = jest.spyOn(controller, 'getController'); + + controller.hasAdapter(); + controller.getAdapter(); + controller.getDataSource(); + controller.store(); + controller.key(); + controller.remoteOperations(); + controller.getDataIndexGetter(); + controller.push(CHANGES); + controller.disposeAdapter(); + + expect(getController).not.toHaveBeenCalled(); + }); + }); + + describe('createAdapter', () => { + it('builds the adapter through the provider, passing the component', () => { + const { controller, component, provider } = withProvider(createAdapterStub('built')); + + controller.createAdapter(SOURCE); + + expect(provider.create).toHaveBeenCalledTimes(1); + expect(provider.create).toHaveBeenCalledWith(component); + }); + + it('initialises the adapter with the given data source', () => { + const adapter = createAdapterStub('built'); + + withProvider(adapter).controller.createAdapter(SOURCE); + + expect(adapter.init).toHaveBeenCalledTimes(1); + expect(adapter.init).toHaveBeenCalledWith(SOURCE); + }); + + it('returns the adapter the provider produced', () => { + const adapter = createAdapterStub('built'); + + const result = withProvider(adapter).controller.createAdapter(SOURCE); + + expect(result).toBe(asAdapter(adapter)); + }); + + it('stores the adapter it built', () => { + const adapter = createAdapterStub('built'); + const { controller } = withProvider(adapter); + + controller.createAdapter(SOURCE); + + expect(controller.hasAdapter()).toBe(true); + expect(controller.getAdapter()).toBe(asAdapter(adapter)); + }); + + it('replaces a previously held adapter without disposing it', () => { + const first = createAdapterStub('first'); + const { controller, provider } = withProvider(first); + const second = createAdapterStub('second'); + + controller.createAdapter(SOURCE); + provider.nextAdapter = second; + controller.createAdapter(SOURCE); + + expect(controller.getAdapter()).toBe(asAdapter(second)); + expect(first.dispose).not.toHaveBeenCalled(); + }); + + it('throws on the base class, where no component has supplied a provider', () => { + expect(() => createController().createAdapter(SOURCE)).toThrow('Method not implemented.'); + }); + }); + + describe('getSpecificDataSourceOption', () => { + it('wraps an array option into an array store, keyed by keyExpr', () => { + const data = [{ id: 1 }]; + + const result = withOptions({ dataSource: data, keyExpr: 'id' }) + .readSpecificDataSourceOption(); + + expect(result).toEqual({ store: { type: 'array', data, key: 'id' } }); + }); + + it('keeps the caller array by reference, leaving the copy to createDataSource', () => { + const data = [{ id: 1 }]; + + const result = withOptions({ dataSource: data, keyExpr: 'id' }) + .readSpecificDataSourceOption() as { store: { data: unknown } }; + + expect(result.store.data).toBe(data); + }); + + it('passes a non-array option straight through', () => { + const config = { store: { type: 'odata', url: 'x' } }; + + const result = withOptions({ dataSource: config }).readSpecificDataSourceOption(); + + expect(result).toBe(config); + }); + + it('returns the unset option as-is, so createDataSource can see it is absent', () => { + expect(withOptions({}).readSpecificDataSourceOption()).toBeUndefined(); + }); + + it('does not throw on the base class, unlike getAdapterProvider', () => { + expect(() => withOptions({}).readSpecificDataSourceOption()).not.toThrow(); + }); + }); + + describe('createDataSource', () => { + it('returns undefined when the dataSource option is absent', () => { + const controller = withOptions({}); + + expect(controller.createDataSource()).toBeUndefined(); + }); + + it('leaves the source not-shared when the dataSource option is absent', () => { + const controller = withOptions({}); + + controller.createDataSource(); + + expect(flagHandedToAdapter(controller)).toBe(false); + }); + + it('builds a DataSource from a plain array', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + expect(controller.createDataSource()).toBeInstanceOf(DataSourceClass); + }); + + it('keys the built DataSource by keyExpr', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + expect(controller.createDataSource()?.key()).toBe('id'); + }); + + it('marks what it built not-shared, so disposal may destroy it', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + controller.createDataSource(); + + expect(flagHandedToAdapter(controller)).toBe(false); + }); + + it('builds a DataSource from a store config', () => { + const controller = withOptions({ dataSource: { store: { type: 'array', data: [] } } }); + + expect(controller.createDataSource()).toBeInstanceOf(DataSourceClass); + }); + + it('builds a fresh DataSource on every call', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + expect(controller.createDataSource()).not.toBe(controller.createDataSource()); + }); + + it('hands back the very DataSource the caller passed, without rebuilding it', () => { + const shared = new DataSourceClass({ store: [{ id: 1 }], key: 'id' }); + const controller = withOptions({ dataSource: shared }); + + expect(controller.createDataSource()).toBe(shared); + + shared.dispose(); + }); + + it('marks a caller-owned DataSource shared, so disposal spares it', () => { + const shared = new DataSourceClass({ store: [{ id: 1 }], key: 'id' }); + const controller = withOptions({ dataSource: shared }); + + controller.createDataSource(); + + expect(flagHandedToAdapter(controller)).toBe(true); + + shared.dispose(); + }); + + it('clears the shared flag when the option moves from a DataSource to an array', () => { + const shared = new DataSourceClass({ store: [{ id: 1 }], key: 'id' }); + const options: Record<string, unknown> = { dataSource: shared, keyExpr: 'id' }; + const controller = withOptions(options); + + controller.createDataSource(); + options.dataSource = [{ id: 2 }]; + controller.createDataSource(); + + expect(flagHandedToAdapter(controller)).toBe(false); + + shared.dispose(); + }); + + it('starts out not-shared, before anything has been created', () => { + expect(flagHandedToAdapter(withOptions({}))).toBe(false); + }); + + it('leaves the held adapter alone — creating a source is not creating an adapter', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + + controller.createDataSource(); + + expect(controller.hasAdapter()).toBe(false); + }); + + it('reads no other controller', () => { + const controller = withOptions({ dataSource: [{ id: 1 }], keyExpr: 'id' }); + const getController = jest.spyOn(controller, 'getController'); + + controller.createDataSource(); + controller.readSpecificDataSourceOption(); + + expect(getController).not.toHaveBeenCalled(); + }); + }); + + describe('disposeAdapter', () => { + it('disposes the adapter it holds', () => { + const { controller, adapter } = withAdapter(); + + controller.disposeAdapter(); + + expect(adapter.dispose).toHaveBeenCalledTimes(1); + }); + + it('does nothing when there is no adapter to dispose', () => { + expect(() => createController().disposeAdapter()).not.toThrow(); + }); + + it('disposes only once across repeated calls', () => { + const { controller, adapter } = withAdapter(); + + controller.disposeAdapter(); + controller.disposeAdapter(); + + expect(adapter.dispose).toHaveBeenCalledTimes(1); + }); + + it('reads no other controller', () => { + const { controller } = withAdapter(); + const getController = jest.spyOn(controller, 'getController'); + + controller.disposeAdapter(); + + expect(getController).not.toHaveBeenCalled(); + }); + }); + + describe('the pushed callback', () => { + it('re-fires what the adapter pushed', () => { + const { controller, adapter } = withAdapter(); + const handler = jest.fn<(changes: StoreChange[]) => void>(); + + controller.pushed.add(handler); + adapter.pushed.fire(CHANGES); + + expect(handler).toHaveBeenCalledTimes(1); + expect(handler).toHaveBeenCalledWith(CHANGES); + }); + + it('goes quiet once the adapter is disposed', () => { + const { controller, adapter } = withAdapter(); + const handler = jest.fn<(changes: StoreChange[]) => void>(); + + controller.pushed.add(handler); + controller.disposeAdapter(); + adapter.pushed.fire(CHANGES); + + expect(handler).not.toHaveBeenCalled(); + }); + + it('follows the adapter that replaced the previous one', () => { + const { controller, provider } = withAdapter(); + const second = createAdapterStub('second'); + const handler = jest.fn<(changes: StoreChange[]) => void>(); + + provider.nextAdapter = second; + controller.createAdapter(SOURCE); + controller.pushed.add(handler); + second.pushed.fire(CHANGES); + + expect(handler).toHaveBeenCalledTimes(1); + }); + }); + + describe('push', () => { + it('delegates to the adapter, defaulting fromStore to false', () => { + const { controller, adapter } = withAdapter(); + + controller.push(CHANGES); + + expect(adapter.push).toHaveBeenCalledWith(CHANGES, false); + }); + + it('passes fromStore through', () => { + const { controller, adapter } = withAdapter(); + + controller.push(CHANGES, true); + + expect(adapter.push).toHaveBeenCalledWith(CHANGES, true); + }); + + it('does nothing when there is no adapter', () => { + expect(() => createController().push(CHANGES)).not.toThrow(); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts new file mode 100644 index 000000000000..91cbb86c07b9 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts @@ -0,0 +1,191 @@ +import { DataSource as DataSourceClass } from '@js/common/data/data_source/data_source'; +import { normalizeDataSourceOptions } from '@js/common/data/data_source/utils'; +import type { Callback } from '@js/core/utils/callbacks'; +import { extend } from '@js/core/utils/extend'; +import type { StoreChange } from '@js/data/store'; +import type Store from '@ts/data/abstract_store'; +import type { StoreKey } from '@ts/data/abstract_store'; +import type { DataSource } from '@ts/data/data_source/data_source'; +import type { StoreLoadOptions } from '@ts/data/data_source/types'; +import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; +import type { + DataSourceAdapterProvider, LoadOperation, OperationTypes, RawItemData, RemoteOperationsOptions, +} from '@ts/grids/grid_core/data_source_adapter/types'; +import modules from '@ts/grids/grid_core/m_modules'; +import type { RowKey } from '@ts/grids/grid_core/m_types'; + +export class DataSourceController< + TAdapter extends DataSourceAdapter = DataSourceAdapter, +> extends modules.Controller { + // Absent before the first dataSource assignment and again after a reset. + protected adapter: TAdapter | null = null; + + private isShared = false; + + public pushed!: Callback<[StoreChange[]]>; + + private readonly dataPushedHandlerProxy = this.dataPushedHandler.bind(this); + + /** + * @extended: DataGrid's and TreeList's data_source_controller + */ + protected getAdapterProvider(): DataSourceAdapterProvider<TAdapter> { + throw new Error('Method not implemented.'); + } + + protected callbackNames(): string[] { + return ['pushed']; + } + + public publicMethods(): string[] { + return ['getDataSource', 'keyOf', 'pageCount', 'totalCount']; + } + + /** + * @extended: DataGrid's data_source_controller + */ + protected getSpecificDataSourceOption(): unknown { + const dataSource = this.option('dataSource'); + + if (Array.isArray(dataSource)) { + return { + store: { + type: 'array', + data: dataSource, + key: this.option('keyExpr'), + }, + }; + } + + return dataSource; + } + + public createDataSource(): DataSource | undefined { + const dataSourceOptions = this.getSpecificDataSourceOption(); + + if (!dataSourceOptions) { + this.isShared = false; + return undefined; + } + + if (dataSourceOptions instanceof DataSourceClass) { + this.isShared = true; + return dataSourceOptions as unknown as DataSource; + } + + this.isShared = false; + return new DataSourceClass( + extend(true, {}, normalizeDataSourceOptions(dataSourceOptions, {})), + ) as unknown as DataSource; + } + + public getDataSource(): DataSource | null { + return this.adapter?._dataSource ?? null; + } + + public createAdapter(dataSource: DataSource): TAdapter { + const adapter = this.getAdapterProvider().create(this.component); + + adapter.init(dataSource); + + this.adapter = adapter; + + adapter.pushed.add(this.dataPushedHandlerProxy); + + return adapter; + } + + public hasAdapter(): boolean { + return this.adapter !== null; + } + + public getAdapter(): TAdapter | null { + return this.adapter; + } + + public disposeAdapter(): void { + this.adapter?.pushed.remove(this.dataPushedHandlerProxy); + this.adapter?.dispose(this.isShared); + this.adapter = null; + } + + /** + * @extended: focus + */ + protected dataPushedHandler(changes: StoreChange[]): void { + this.pushed.fire(changes); + } + + public store(): Store | undefined { + return this.adapter?.store(); + } + + public push(changes: StoreChange[], fromStore = false): void { + this.adapter?.push(changes, fromStore); + } + + /** + * The key the component identifies rows by. Not interchangeable with `store()?.key()` (TreeList) + * Callers that need what the store itself can identify a row by have to ask the store. + * + * @extended: TreeList's data_source_controller + */ + public key(): StoreKey | undefined { + return this.adapter?.key(); + } + + /** + * @extended: TreeList's data_source_controller + */ + public keyOf(data: RawItemData): RowKey | undefined { + return this.adapter?.store()?.keyOf(data); + } + + public remoteOperations(): RemoteOperationsOptions { + return this.adapter?.remoteOperations() ?? {}; + } + + public getDataIndexGetter(): ((data: RawItemData) => number) | undefined { + return this.adapter?.getDataIndexGetter(); + } + + public operationTypes(): OperationTypes | null | undefined { + return this.adapter?.operationTypes(); + } + + public loadingOperationTypes(): OperationTypes { + return this.adapter?.loadingOperationTypes() ?? {}; + } + + public isLoading(): boolean { + return this.adapter?.isLoading() ?? false; + } + + public select(): StoreLoadOptions['select'] { + return this.adapter?.select(); + } + + public lastLoadOptions(): Partial<NonNullable<LoadOperation['lastLoadOptions']>> { + return this.adapter?.lastLoadOptions() ?? {}; + } + + public getCachedStoreData(): RawItemData[] | undefined { + return this.adapter?.getCachedStoreData(); + } + + public hasKnownLastPage(): boolean { + return this.adapter ? this.adapter.hasKnownLastPage() : true; + } + + public totalItemsCount(): number { + return this.adapter ? this.adapter.totalItemsCount() : 0; + } + + public totalCount(): number { + return this.adapter ? this.adapter.totalCount() : 0; + } + + public pageCount(): number { + return this.adapter ? this.adapter.pageCount() : 1; + } +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts index 4b135fbd8680..10c8b496f377 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts @@ -297,7 +297,6 @@ export default class DataSourceAdapter extends modules.Controller { this.resetPagesCache(true); if (this._cachedStoreData) { - // @ts-expect-error applyBatch({ keyInfo: store, data: this._cachedStoreData, @@ -365,7 +364,6 @@ export default class DataSourceAdapter extends modules.Controller { const getItemCount = () => (groupCount ? this.itemsCount() : this.items().length); const oldItemCount = getItemCount(); - // @ts-expect-error applyBatch({ keyInfo, data: this._items, @@ -374,7 +372,6 @@ export default class DataSourceAdapter extends modules.Controller { useInsertIndex: true, skipCopying: !this._needToCopyDataObject(), }); - // @ts-expect-error applyBatch({ keyInfo, data: dataSource.items(), @@ -673,7 +670,7 @@ export default class DataSourceAdapter extends modules.Controller { /** * @extended: TreeLists's data_source_adapter */ - protected customizeLoadResultHandlerCore(options) { + protected customizeLoadResultHandlerCore(options: LoadOperation): void { if (options.remoteOperations && !options.remoteOperations.paging && Array.isArray(options.data)) { if (options.skip !== undefined) { options.data = options.data.slice(options.skip); diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/provider.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/provider.ts index cbd0748990ce..1268e5291419 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/provider.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/provider.ts @@ -2,16 +2,16 @@ import type { ModuleType } from '../m_types'; import type DataSourceAdapter from './m_data_source_adapter'; import type { DataSourceAdapterProvider } from './types'; -export function createDataSourceAdapterProvider( - BaseType: ModuleType<DataSourceAdapter>, -): DataSourceAdapterProvider { +export function createDataSourceAdapterProvider<TAdapter extends DataSourceAdapter>( + BaseType: ModuleType<TAdapter>, +): DataSourceAdapterProvider<TAdapter> { let AdapterType = BaseType; return { extend(extender): void { - AdapterType = extender(AdapterType); + AdapterType = extender(AdapterType) as ModuleType<TAdapter>; }, - create(component): DataSourceAdapter { + create(component): TAdapter { return new AdapterType(component); }, }; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts index 9b0b9d52dce1..64e3088c344b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts @@ -81,7 +81,9 @@ export type DataSourceAdapterExtender = ( Base: ModuleType<DataSourceAdapter>, ) => ModuleType<DataSourceAdapter>; -export interface DataSourceAdapterProvider { +export interface DataSourceAdapterProvider< + TAdapter extends DataSourceAdapter = DataSourceAdapter, +> { extend: (extender: DataSourceAdapterExtender) => void; - create: (component: InternalGrid) => DataSourceAdapter; + create: (component: InternalGrid) => TAdapter; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts index 77a44b18000c..1184e8adf5fa 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/__tests__/editing_data_controller.row_changes.test.ts @@ -16,6 +16,11 @@ const dataRow = (partial: Partial<ProcessedItem> = {}): ProcessedItem => ({ ...partial, }); +const editFormRow = (partial: Partial<ProcessedItem> = {}): ProcessedItem => dataRow({ + rowType: 'detail', + ...partial, +}); + describe('Editing data controller row changes', () => { beforeEach(beforeTest); afterEach(afterTest); @@ -38,4 +43,25 @@ describe('Editing data controller row changes', () => { expect(change.changeTypes).toEqual(['update']); }, ); + + it('should repaint the whole edit form row instead of diffing its columns', async () => { + const change = await refreshRow( + editFormRow({ isEditing: true, values: ['Alex', 15] }), + editFormRow({ isEditing: true, values: ['Bob', 15] }), + { gridOptions: { editing: { mode: 'form' } }, isLiveUpdate: false }, + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([undefined]); + }); + + it('should report a cell whose modified mark appeared without a new value', async () => { + const change = await refreshRow( + dataRow(), + dataRow({ modified: true, modifiedValues: [undefined, 15] }), + ); + + expect(change.rowIndices).toEqual([0]); + expect(change.columnIndices).toEqual([[1]]); + }); }); diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts index 999590624de4..6e401fa19485 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/extenders/editing_data_controller.ts @@ -15,6 +15,7 @@ import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import { EDITING_EDITROWKEY_OPTION_NAME } from '../const'; import type { EditingController } from '../m_editing'; +import { isCellModified } from '../m_editing_utils'; export interface EditingDataControllerExtension { _editingController: EditingController; @@ -147,6 +148,10 @@ export const editingDataControllerExtender = ( return true; } + if (isCellModified(oldRow, columnIndex) !== isCellModified(newRow, columnIndex)) { + return true; + } + return super._isCellChanged(oldRow, newRow, visibleRowIndex, columnIndex, isLiveUpdate); } @@ -173,7 +178,7 @@ export const editingDataControllerExtender = ( return; } - const dataSourceKeys = dataSource.map((item) => this.keyOf(item)); + const dataSourceKeys = dataSource.map((item) => this.dataSourceController.keyOf(item)); const survivingChanges = changes.filter( (change) => change.type === 'insert' || dataSourceKeys.some((key) => equalByValue(change.key, key)), ); diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts index 92dcc54ab619..1f6ddaaa168c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing.ts @@ -117,6 +117,8 @@ class EditingControllerImpl extends modules.ViewController { protected _dataController!: Controllers['data']; + protected dataSourceController!: Controllers['dataSource']; + protected adaptiveColumnsController!: Controllers['adaptiveColumns']; protected _validatingController!: Controllers['validating']; @@ -170,6 +172,7 @@ class EditingControllerImpl extends modules.ViewController { public init() { this._columnsController = this.getController('columns'); this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this.adaptiveColumnsController = this.getController('adaptiveColumns'); this._validatingController = this.getController('validating'); this._editorFactoryController = this.getController('editorFactory'); @@ -309,7 +312,7 @@ class EditingControllerImpl extends modules.ViewController { } public getUpdatedData(data) { - const key = this._dataController.keyOf(data); + const key = this.dataSourceController.keyOf(data); const changes = this.getChanges(); const editIndex = gridCoreUtils.getIndexByKey(key, changes); @@ -614,8 +617,9 @@ class EditingControllerImpl extends modules.ViewController { if (change.type === 'insert') { this._addInsertInfo(change); } else { - const items = dataController.getCachedStoreData() || dataController.items()?.map((item) => item.data); - const rowIndex = gridCoreUtils.getIndexByKey(change.key, items, dataController.key()); + const items = this.dataSourceController.getCachedStoreData() + || dataController.items()?.map((item) => item.data); + const rowIndex = gridCoreUtils.getIndexByKey(change.key, items, this.dataSourceController.key()); this._addInternalData({ key: change.key, oldData: items[rowIndex] }); } }); @@ -717,7 +721,7 @@ class EditingControllerImpl extends modules.ViewController { if (equalByValue(item.key, key)) { result = index; } - } else if (equalByValue(dataController.keyOf(item as RawItemData), key)) { + } else if (equalByValue(this.dataSourceController.keyOf(item as RawItemData), key)) { result = index; } } @@ -910,7 +914,7 @@ class EditingControllerImpl extends modules.ViewController { return change.key; } - const keyExpr = this._dataController.key(); + const keyExpr = this.dataSourceController.key(); let keyValue; if (change.data && keyExpr && !Array.isArray(keyExpr)) { keyValue = change.data[keyExpr]; @@ -987,7 +991,7 @@ class EditingControllerImpl extends modules.ViewController { const newRowPosition: any = this._getNewRowPosition(); const dataController = this._dataController; const pageIndex = dataController.pageIndex(); - const lastPageIndex = dataController.pageCount() - 1; + const lastPageIndex = this.dataSourceController.pageCount() - 1; if (newRowPosition === FIRST_NEW_ROW_POSITION && pageIndex !== 0) { return 0; @@ -1003,7 +1007,7 @@ class EditingControllerImpl extends modules.ViewController { */ protected addRow(parentKey) { const dataController = this._dataController; - const store = dataController.store(); + const store = this.dataSourceController.store(); if (!store) { dataController.fireError('E1052', this.component.NAME); @@ -1015,9 +1019,7 @@ class EditingControllerImpl extends modules.ViewController { } protected _addRow(parentKey) { - const dataController = this._dataController; - const store = dataController.store(); - const key = store && store.key(); + const key = this.dataSourceController.store()?.key(); const param: any = { data: {} }; const oldEditRowIndex = this._getVisibleEditRowIndex(); // @ts-expect-error @@ -1612,7 +1614,7 @@ class EditingControllerImpl extends modules.ViewController { } private _processChanges(deferreds, results, dataChanges, changes) { - const store = this._dataController.store() as Store; + const store = this.dataSourceController.store() as Store; each(changes, (index, change) => { const oldData = this._getOldData(change.key); @@ -1812,7 +1814,7 @@ class EditingControllerImpl extends modules.ViewController { const results = []; const deferreds = []; const dataChanges = []; - const dataSource = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); when(this._fireOnSaving()) .done(({ cancel, changes }) => { @@ -1824,17 +1826,17 @@ class EditingControllerImpl extends modules.ViewController { if (deferreds.length) { this._refocusEditCell = true; - dataSource?.beginLoading(); + dataSourceAdapter?.beginLoading(); when(...deferreds).done(() => { if (this._processSaveEditDataResult(results)) { this._endSaving(dataChanges, changes, result); } else { - dataSource?.endLoading(); + dataSourceAdapter?.endLoading(); result.resolve(); } }).fail((error) => { - dataSource?.endLoading(); + dataSourceAdapter?.endLoading(); result.resolve(error); }); @@ -1855,11 +1857,11 @@ class EditingControllerImpl extends modules.ViewController { } private _endSaving(dataChanges, changes, deferred) { - const dataSource = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); this._beforeEndSaving(changes); - dataSource?.endLoading(); + dataSourceAdapter?.endLoading(); this._refreshDataAfterSave(dataChanges, changes, deferred); } @@ -1875,7 +1877,7 @@ class EditingControllerImpl extends modules.ViewController { const isFullRefresh = refreshMode !== 'reshape' && refreshMode !== 'repaint'; if (!isFullRefresh) { - dataController.push(dataChanges); + this.dataSourceController.push(dataChanges); } when(dataController.refresh({ diff --git a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_utils.ts index ddffd2ed0859..67632b7d5599 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/editing/m_editing_utils.ts @@ -1,6 +1,8 @@ import Guid from '@js/core/guid'; import { isObject } from '@js/core/utils/type'; +import type { ProcessedItem } from '../data_controller/types'; + const NEW_ROW_TEMP_KEY_PREFIX = '_DX_KEY_'; const GUID_LENGTH = 36; @@ -80,3 +82,8 @@ export const forEachFormItems = (items, callBack) => { } }); }; + +export const isCellModified = ( + row: ProcessedItem, + columnIndex: number, +): boolean => row.modifiedValues?.[columnIndex] !== undefined; diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts new file mode 100644 index 000000000000..dd6e280b862f --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_controller.ts @@ -0,0 +1,34 @@ +import type { LangParams } from '@js/common/data'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; +import modules from '@ts/grids/grid_core/m_modules'; +import type { Controllers } from '@ts/grids/grid_core/m_types'; + +export class FilterController extends modules.Controller { + protected columnsController!: Controllers['columns']; + + protected dataSourceController!: Controllers['dataSource']; + + public init(): void { + this.columnsController = this.getController('columns'); + this.dataSourceController = this.getController('dataSource'); + } + + public isFilterSyncActive(): boolean | undefined { + const filterSyncEnabled = this.option('filterSyncEnabled'); + + return filterSyncEnabled === 'auto' ? this.option('filterPanel.visible') : filterSyncEnabled; + } + + protected getLangParams(): LangParams | undefined { + return this.dataSourceController.getDataSource()?.loadOptions?.()?.langParams; + } + + /** + * @extended: filter_row, filter_sync, header_filter, search + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { + return null; + } +} diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter/filter_module.ts b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_module.ts new file mode 100644 index 000000000000..942a85e95802 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/filter/filter_module.ts @@ -0,0 +1,9 @@ +import type { Module } from '@ts/grids/grid_core/m_types'; + +import { FilterController } from './filter_controller'; + +export const filterModule: Module = { + controllers: { + filter: FilterController, + }, +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_panel/m_filter_panel.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_panel/m_filter_panel.ts index c25f156bd2df..7b472e65d6c6 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_panel/m_filter_panel.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_panel/m_filter_panel.ts @@ -14,6 +14,7 @@ import { } from '@ts/filter_builder/m_utils'; import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { FilterSyncController } from '@ts/grids/grid_core/filter_sync/m_filter_sync'; import { registerKeyboardAction } from '@ts/grids/grid_core/m_accessibility'; import modules from '@ts/grids/grid_core/m_modules'; @@ -33,12 +34,15 @@ export class FilterPanelView extends modules.View { private _dataController!: DataController; + private dataSourceController!: DataSourceController; + private _filterSyncController!: FilterSyncController; private readonly _filterValueBuffer: any; public init() { this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._columnsController = this.getController('columns'); this._filterSyncController = this.getController('filterSync'); @@ -46,7 +50,7 @@ export class FilterPanelView extends modules.View { } public isVisible() { - return !!(this.option('filterPanel.visible') && this._dataController.dataSource()); + return !!this.option('filterPanel.visible') && this.dataSourceController.hasAdapter(); } protected _renderCore() { diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts index b4a100906344..318f6a928d94 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_row/m_filter_row.ts @@ -16,8 +16,10 @@ import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_col import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ColumnsResizerViewController } from '@ts/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering'; -import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; +import type { FilterController } from '@ts/grids/grid_core/filter/filter_controller'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/m_header_panel'; import modules from '@ts/grids/grid_core/m_modules'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; @@ -204,7 +206,11 @@ const columnHeadersView = (Base: ModuleType<ColumnHeadersView>) => class ColumnH private _applyFilterViewController!: ApplyFilterViewController; + private dataSourceController!: DataSourceController; + public init() { + this.dataSourceController = this.getController('dataSource'); + super.init(); this._applyFilterViewController = this.getController('applyFilter'); } @@ -545,12 +551,12 @@ const columnHeadersView = (Base: ModuleType<ColumnHeadersView>) => class ColumnH private _renderEditor($editorContainer, options) { $editorContainer.empty(); const $element = $('<div>').appendTo($editorContainer); - const dataSource = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); if (options.lookup && this.option('syncLookupFilterValues')) { const filter = this._dataController.getCombinedFilterWithExcludedColumn(options); - const lookupDataSource = gridCoreUtils.getWrappedLookupDataSource(options, dataSource, filter); + const lookupDataSource = gridCoreUtils.getWrappedLookupDataSource(options, dataSourceAdapter, filter); const lookupOptions = { ...options, lookup: { @@ -739,8 +745,7 @@ const columnHeadersView = (Base: ModuleType<ColumnHeadersView>) => class ColumnH } protected _handleDataChanged(e) { - const dataSource = this._dataController?.dataSource?.(); - const lastLoadOptions = dataSource?.lastLoadOptions?.(); + const lastLoadOptions = this.dataSourceController.lastLoadOptions(); // @ts-expect-error super._handleDataChanged.apply(this, arguments); @@ -760,7 +765,7 @@ const columnHeadersView = (Base: ModuleType<ColumnHeadersView>) => class ColumnH } const columns = this._columnsController.getVisibleColumns(); - const dataSource = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const rowIndex = this.element().find(`.${this.addWidgetPrefix(FILTER_ROW_CLASS)}`).index(); if (rowIndex === -1) { @@ -789,7 +794,7 @@ const columnHeadersView = (Base: ModuleType<ColumnHeadersView>) => class ColumnH editor.option('items', [...items, selectedItem]); } - const lookupDataSource = gridCoreUtils.getWrappedLookupDataSource(column, dataSource, filter); + const lookupDataSource = gridCoreUtils.getWrappedLookupDataSource(column, dataSourceAdapter, filter); editor.option('dataSource', lookupDataSource); } } @@ -811,20 +816,20 @@ const columnHeadersView = (Base: ModuleType<ColumnHeadersView>) => class ColumnH } }; -const data = (Base: ModuleType<DataController>) => class DataControllerFilterRowExtender extends Base { +const filterController = ( + Base: ModuleType<FilterController>, +) => class FilterControllerFilterRowExtender extends Base { private skipCalculateColumnFilters() { return false; } - protected calculateAdditionalFilter() { + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { if (this.skipCalculateColumnFilters()) { - return super.calculateAdditionalFilter(); + return super.getAdditionalFilter(excludedColumn); } - const filters = [super.calculateAdditionalFilter()]; - const columns = this._columnsController.getVisibleColumns(null, true); - - const excludedColumn = this.getFilterExcludedColumn(); + const filters = [super.getAdditionalFilter(excludedColumn)]; + const columns = this.columnsController.getVisibleColumns(null, true); each(columns, function () { const shouldSkip = excludedColumn?.index === this.index; @@ -1046,7 +1051,7 @@ export const filterRowModule = { }, extenders: { controllers: { - data, + filter: filterController, columnsResizer, editing, }, diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/__tests__/filter_sync.filter_value_edge_cases.test.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/__tests__/filter_sync.filter_value_edge_cases.test.ts new file mode 100644 index 000000000000..a1a293d60d4c --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/__tests__/filter_sync.filter_value_edge_cases.test.ts @@ -0,0 +1,93 @@ +import { + afterEach, + beforeEach, + describe, + expect, + it, +} from '@jest/globals'; +import type { dxElementWrapper } from '@js/core/renderer'; +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; +import type { DataGridModel } from '@ts/grids/data_grid/__tests__/__mock__/model/data_grid'; +import type { DataGridInstance } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import { + afterTest, + beforeTest, + createDataGrid, + toPlainFilter, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; + +const DATA = [ + { id: 1, name: 'Alex', age: 15 }, + { id: 2, name: 'Dan', age: 20 }, +]; + +const SAVED_STATE_WITHOUT_IDENTIFIER = { + columns: [ + { visibleIndex: 0, dataField: 'name', filterValue: 'Alex' }, + { visibleIndex: 1, filterValue: 42, allowFiltering: false }, + ], +}; + +const createGrid = (options: DataGridProperties): Promise<{ + $container: dxElementWrapper; + component: DataGridModel; + instance: DataGridInstance; +}> => createDataGrid({ dataSource: DATA, ...options }); + +describe('Filter sync filterValue', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('when a condition has only two elements', () => { + it('should filter the data as if the operation were =', async () => { + const { instance } = await createGrid({ + filterPanel: { visible: true }, + filterSyncEnabled: true, + filterValue: ['name', 'Alex'], + columns: ['name', 'age'], + }); + + const names = instance.getVisibleRows().map((row) => (row.data as { name: string }).name); + + expect(toPlainFilter(instance.getCombinedFilter(true))).toEqual(['name', '=', 'Alex']); + expect(names).toEqual(['Alex']); + }); + + it('should fill both the filter row and the header filter of the column', async () => { + const { instance } = await createGrid({ + filterPanel: { visible: true }, + filterSyncEnabled: true, + filterValue: ['name', 'Alex'], + columns: ['name', 'age'], + }); + + expect(instance.columnOption('name', 'filterValue')).toBe('Alex'); + expect(instance.columnOption('name', 'filterValues')).toEqual(['Alex']); + }); + }); + + describe('when a saved column has no identifier', () => { + it('should throw while restoring the state', async () => { + const { instance } = await createGrid({ + filterPanel: { visible: true }, + columns: ['name', 'age'], + stateStoring: { enabled: true, type: 'custom', customLoad: () => Promise.resolve({}) }, + }); + + expect(() => instance.state(SAVED_STATE_WITHOUT_IDENTIFIER)).toThrow(TypeError); + }); + + it('should leave behind a filterValue whose field is undefined', async () => { + const { instance } = await createGrid({ + filterPanel: { visible: true }, + columns: ['name', 'age'], + stateStoring: { enabled: true, type: 'custom', customLoad: () => Promise.resolve({}) }, + }); + + expect(() => instance.state(SAVED_STATE_WITHOUT_IDENTIFIER)).toThrow(TypeError); + expect(instance.option('filterValue')).toEqual([ + ['name', 'contains', 'Alex'], 'and', [undefined, 'contains', 42], + ]); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_column_headers_view.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_column_headers_view.ts index c55044878687..ba42c4a876ce 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_column_headers_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_column_headers_view.ts @@ -1,15 +1,20 @@ import { filterHasField } from '@ts/filter_builder/m_utils'; import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_column_headers'; -import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { FilterController } from '@ts/grids/grid_core/filter/filter_controller'; import type { ModuleType, OptionChanged } from '@ts/grids/grid_core/m_types'; import { getColumnIdentifier } from '../utils'; -import type { FilterSyncDataControllerExtension } from './filter_sync_data_controller'; export const filterSyncColumnHeadersViewExtender = ( Base: ModuleType<ColumnHeadersView>, ): ModuleType<ColumnHeadersView> => class ColumnHeadersViewFilterSyncExtender extends Base { - declare protected _dataController: DataController & FilterSyncDataControllerExtension; + private filterController!: FilterController; + + public init(): void { + this.filterController = this.getController('filter'); + + super.init(); + } public optionChanged(args: OptionChanged): void { if (args.name === 'filterValue') { @@ -21,7 +26,7 @@ export const filterSyncColumnHeadersViewExtender = ( } private _isHeaderFilterEmpty(column): boolean { - if (this._dataController.isFilterSyncActive()) { + if (this.filterController.isFilterSyncActive()) { return !filterHasField(this.option('filterValue'), getColumnIdentifier(column)); } @@ -30,6 +35,6 @@ export const filterSyncColumnHeadersViewExtender = ( } private _needUpdateFilterIndicators(): boolean { - return !this._dataController.isFilterSyncActive(); + return !this.filterController.isFilterSyncActive(); } }; diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_data_controller.ts index f55b1c37f880..053fee3c9810 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_data_controller.ts @@ -1,28 +1,14 @@ import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred } from '@js/core/utils/deferred'; -import { isDefined } from '@js/core/utils/type'; -import { - getFilterExpression, - removeFieldConditionsFromFilter, -} from '@ts/filter_builder/m_utils'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; -import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; import { FILTER_TYPES_EXCLUDE } from '@ts/grids/grid_core/filter_sync/const'; import type { FilterSyncController } from '@ts/grids/grid_core/filter_sync/m_filter_sync'; -import { getColumnIdentifier } from '@ts/grids/grid_core/filter_sync/utils'; import type { ModuleType, OptionChanged } from '@ts/grids/grid_core/m_types'; -import gridCoreUtils from '@ts/grids/grid_core/m_utils'; - -export interface FilterSyncDataControllerExtension { - isFilterSyncActive: () => boolean | undefined; -} export const filterSyncDataControllerExtender = ( Base: ModuleType<DataController>, -): ModuleType< - DataController & FilterSyncDataControllerExtension -> => class FilterSyncDataControllerExtender extends Base { +): ModuleType<DataController> => class FilterSyncDataControllerExtender extends Base { private filterSyncController!: FilterSyncController; public init(): void { @@ -36,7 +22,7 @@ export const filterSyncDataControllerExtender = ( case 'filterValue': this.applyFilter(); - if (this.isFilterSyncActive()) { + if (this.filterController.isFilterSyncActive()) { this.filterSyncController.syncFilterValue(); } args.handled = true; @@ -45,7 +31,7 @@ export const filterSyncDataControllerExtender = ( args.handled = true; break; case 'columns': - if (this.isFilterSyncActive()) { + if (this.filterController.isFilterSyncActive()) { const column: Column = this._columnsController.getColumnByPath(args.fullName); if (column && !this.filterSyncController.isSyncingColumnOptions()) { @@ -66,50 +52,6 @@ export const filterSyncDataControllerExtender = ( } } - public isFilterSyncActive(): boolean | undefined { - const filterSyncEnabledValue = this.option('filterSyncEnabled'); - return filterSyncEnabledValue === 'auto' ? this.option('filterPanel.visible') : filterSyncEnabledValue; - } - - private skipCalculateColumnFilters(): boolean | undefined { - const hasFilterValueOrSyncing = isDefined(this.option('filterValue')) - || this.filterSyncController.isSyncingColumnOptions(); - - return hasFilterValueOrSyncing && this.isFilterSyncActive(); - } - - protected calculateAdditionalFilter(): DataFilter { - const columns = this._columnsController.getFilteringColumns(); - const isFilterValueDisabled = this.option('filterPanel.filterEnabled') === false; - - if (!columns?.length || isFilterValueDisabled) { - return super.calculateAdditionalFilter(); - } - - const filters = [super.calculateAdditionalFilter()]; - let filterValue = this.option('filterValue'); - - if (this.isFilterSyncActive()) { - const excludedColumn = this.getFilterExcludedColumn(); - - if (isDefined(excludedColumn) && filterValue) { - filterValue = removeFieldConditionsFromFilter( - filterValue, - getColumnIdentifier(excludedColumn), - ); - } - } - - const customOperations = this.filterSyncController.getCustomFilterOperations(); - const calculatedFilterValue: DataFilter = getFilterExpression(filterValue, columns, customOperations, 'filterBuilder'); - - if (calculatedFilterValue) { - filters.push(calculatedFilterValue); - } - - return gridCoreUtils.combineFilters(filters); - } - private parseColumnPropertyName(fullName: string): string | null { const matched = /.*\.(.*)/.exec(fullName); diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_filter_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_filter_controller.ts new file mode 100644 index 000000000000..2f7eaacf733a --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/extenders/filter_sync_filter_controller.ts @@ -0,0 +1,59 @@ +import { isDefined } from '@js/core/utils/type'; +import { + getFilterExpression, + removeFieldConditionsFromFilter, +} from '@ts/filter_builder/m_utils'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; +import type { FilterController } from '@ts/grids/grid_core/filter/filter_controller'; +import type { FilterSyncController } from '@ts/grids/grid_core/filter_sync/m_filter_sync'; +import { getColumnIdentifier } from '@ts/grids/grid_core/filter_sync/utils'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; +import gridCoreUtils from '@ts/grids/grid_core/m_utils'; + +export const filterSyncFilterControllerExtender = ( + Base: ModuleType<FilterController>, +): ModuleType<FilterController> => class FilterControllerFilterSyncExtender extends Base { + private filterSyncController!: FilterSyncController; + + public init(): void { + this.filterSyncController = this.getController('filterSync'); + + super.init(); + } + + private skipCalculateColumnFilters(): boolean | undefined { + const hasFilterValueOrSyncing = isDefined(this.option('filterValue')) + || this.filterSyncController.isSyncingColumnOptions(); + + return hasFilterValueOrSyncing && this.isFilterSyncActive(); + } + + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { + const columns = this.columnsController.getFilteringColumns(); + const isFilterValueDisabled = this.option('filterPanel.filterEnabled') === false; + + if (!columns?.length || isFilterValueDisabled) { + return super.getAdditionalFilter(excludedColumn); + } + + const filters = [super.getAdditionalFilter(excludedColumn)]; + let filterValue = this.option('filterValue'); + + if (this.isFilterSyncActive() && isDefined(excludedColumn) && filterValue) { + filterValue = removeFieldConditionsFromFilter( + filterValue, + getColumnIdentifier(excludedColumn), + ); + } + + const customOperations = this.filterSyncController.getCustomFilterOperations(); + const calculatedFilterValue: DataFilter = getFilterExpression(filterValue, columns, customOperations, 'filterBuilder'); + + if (calculatedFilterValue) { + filters.push(calculatedFilterValue); + } + + return gridCoreUtils.combineFilters(filters); + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/filter_sync_module.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/filter_sync_module.ts index dc3ee7dd096f..64b53f5f3a5d 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/filter_sync_module.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/filter_sync_module.ts @@ -2,6 +2,7 @@ import type { InternalGridOptions } from '@ts/grids/grid_core/m_types'; import { filterSyncColumnHeadersViewExtender } from './extenders/filter_sync_column_headers_view'; import { filterSyncDataControllerExtender } from './extenders/filter_sync_data_controller'; +import { filterSyncFilterControllerExtender } from './extenders/filter_sync_filter_controller'; import { FilterSyncController } from './m_filter_sync'; export const filterSyncModule = { @@ -17,6 +18,7 @@ export const filterSyncModule = { extenders: { controllers: { data: filterSyncDataControllerExtender, + filter: filterSyncFilterControllerExtender, }, views: { columnHeadersView: filterSyncColumnHeadersViewExtender, diff --git a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/m_filter_sync.ts b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/m_filter_sync.ts index 426b8c33f8ff..c93a7ff34294 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/filter_sync/m_filter_sync.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/filter_sync/m_filter_sync.ts @@ -11,11 +11,9 @@ import type { } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { FilterValue, FilterValueCondition } from '@ts/grids/grid_core/data_controller/types'; +import type { FilterController } from '@ts/grids/grid_core/filter/filter_controller'; import modules from '@ts/grids/grid_core/m_modules'; -import type { - FilterSyncDataControllerExtension, -} from './extenders/filter_sync_data_controller'; import { anyOf, noneOf } from './m_filter_custom_operations'; import { checkForErrors, @@ -31,15 +29,18 @@ import { export class FilterSyncController extends modules.Controller { private skipSyncColumnOptions = false; - private dataController!: DataController & FilterSyncDataControllerExtension; + private dataController!: DataController; private columnsController!: ColumnsController; + private filterController!: FilterController; + public init(): void { - this.dataController = this.getController('data') as DataController & FilterSyncDataControllerExtension; + this.dataController = this.getController('data'); this.columnsController = this.getController('columns'); + this.filterController = this.getController('filter'); - if (this.dataController.isFilterSyncActive()) { + if (this.filterController.isFilterSyncActive()) { if (this.columnsController.isAllDataTypesDefined()) { this.initSync(); } else { @@ -132,7 +133,7 @@ export class FilterSyncController extends modules.Controller { public getFilterValueFromColumns( columns: ColumnUserState[] | undefined, ): FilterValue { - if (!this.dataController.isFilterSyncActive()) { + if (!this.filterController.isFilterSyncActive()) { return null; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/__tests__/focus.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/__tests__/focus.integration.test.ts new file mode 100644 index 000000000000..0fa8c9bfa5d8 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/__tests__/focus.integration.test.ts @@ -0,0 +1,70 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import type { Properties as DataGridProperties } from '@js/ui/data_grid'; + +import { + afterTest, + beforeTest, + createDataGrid, + flushAsync, +} from '../../__tests__/__mock__/helpers/utils'; + +const DATA = [ + { id: 1, name: 'Alex' }, + { id: 2, name: 'Dan' }, +]; + +const setup = async (options: DataGridProperties) => { + const { instance } = await createDataGrid({ + keyExpr: 'id', + focusedRowEnabled: true, + ...options, + }); + + await flushAsync(); + + return { + columnsController: instance.getController('columns'), + dataSourceController: instance.getController('dataSource'), + }; +}; + +describe('Focus columns extender', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('getSortDataSourceParameters', () => { + it('sorts by the data index when every operation is local', async () => { + const { columnsController, dataSourceController } = await setup({ dataSource: DATA }); + + const result = columnsController.getSortDataSourceParameters(); + + expect(result).toHaveLength(1); + expect(result[0].selector).toBe(dataSourceController.getDataIndexGetter()); + expect(result[0].desc).toBe(false); + }); + + it('sorts by the key columns when operations are remote', async () => { + const { columnsController } = await setup({ + dataSource: DATA, + remoteOperations: true, + }); + + const result = columnsController.getSortDataSourceParameters(); + + expect(result).toEqual([{ selector: 'id', desc: false }]); + }); + + it('leaves the base sort parameters alone when there is no data source', async () => { + const { columnsController, dataSourceController } = await setup({ + columns: [{ dataField: 'name', sortOrder: 'asc' }], + }); + + expect(dataSourceController.getAdapter()).toBeNull(); + expect(columnsController.getSortDataSourceParameters()).toEqual([ + { selector: 'name', desc: false }, + ]); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/extenders/__tests__/focus_data_source_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/extenders/__tests__/focus_data_source_controller.integration.test.ts new file mode 100644 index 000000000000..8c3510eebd06 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/extenders/__tests__/focus_data_source_controller.integration.test.ts @@ -0,0 +1,80 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import { + afterTest, beforeTest, createDataGrid, flushAsync, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; + +describe('Focus data source controller', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe("when a push empties the grid and a pushed subscriber's own change cycle runs first", () => { + it('still resets the focused row', async () => { + const { instance } = await createDataGrid({ + dataSource: { + store: { + type: 'array', + key: 'id', + data: [ + { id: 1, name: 'Item 1' }, + { id: 2, name: 'Item 2' }, + ], + }, + reshapeOnPush: true, + pushAggregationTimeout: 0, + }, + focusedRowEnabled: true, + focusedRowKey: 1, + selection: { mode: 'multiple' }, + selectedRowKeys: [1], + columns: ['id', 'name'], + }); + + instance.getDataSource().store().push([ + { type: 'remove', key: 1 }, + { type: 'remove', key: 2 }, + ]); + await flushAsync(); + + expect(instance.getVisibleRows()).toHaveLength(0); + expect(instance.getSelectedRowKeys()).toEqual([]); + expect(instance.option('focusedRowKey')).toBeNull(); + expect(instance.option('focusedRowIndex')).toBe(-1); + }); + }); + + describe('when an unrelated change empties the grid after an earlier push was handled', () => { + it('keeps the focused row, because one push forces exactly one update', async () => { + const { instance } = await createDataGrid({ + dataSource: { + store: { + type: 'array', + key: 'id', + data: [ + { id: 1, name: 'Item 1' }, + { id: 2, name: 'Item 2' }, + { id: 3, name: 'Item 3' }, + ], + }, + reshapeOnPush: true, + pushAggregationTimeout: 0, + }, + focusedRowEnabled: true, + focusedRowKey: 2, + columns: ['id', 'name'], + }); + + instance.getDataSource().store().push([ + { type: 'update', key: 3, data: { id: 3, name: 'Item 3 renamed' } }, + ]); + await flushAsync(); + + instance.filter(['name', '=', 'nothing matches']); + await flushAsync(); + + expect(instance.getVisibleRows()).toHaveLength(0); + expect(instance.option('focusedRowKey')).toBe(2); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/extenders/focus_data_source_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/extenders/focus_data_source_controller.ts new file mode 100644 index 000000000000..587e4f1a3f95 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/extenders/focus_data_source_controller.ts @@ -0,0 +1,32 @@ +import { isDefined } from '@js/core/utils/type'; +import type { StoreChange } from '@js/data/store'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; +import type { ModuleType } from '@ts/grids/grid_core/m_types'; + +export interface FocusDataSourceControllerExtension { + consumeDataPushed: () => boolean; +} + +export const focusDataSourceControllerExtender = ( + Base: ModuleType<DataSourceController>, +): ModuleType< + DataSourceController & FocusDataSourceControllerExtension +> => class FocusDataSourceControllerExtender extends Base { + private isDataPushed = false; + + public consumeDataPushed(): boolean { + const wasDataPushed = this.isDataPushed; + + this.isDataPushed = false; + + return wasDataPushed; + } + + protected dataPushedHandler(changes: StoreChange[]): void { + super.dataPushedHandler(changes); + + const focusedRowKey = this.option('focusedRowKey'); + + this.isDataPushed = isDefined(focusedRowKey) && !!changes.length; + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/focus_module.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/focus_module.ts new file mode 100644 index 000000000000..b247b7eef03d --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/focus_module.ts @@ -0,0 +1,44 @@ +import type { InternalGridOptions } from '@ts/grids/grid_core/m_types'; + +import { + focusDataSourceControllerExtender, +} from './extenders/focus_data_source_controller'; +import { + columns, + editing, + FocusController, + focusDataControllerExtender, + focusEditorFactoryViewControllerExtender, + keyboardNavigation, + rowsView, +} from './m_focus'; + +type FocusDefaultProperties = 'focusedRowEnabled' | 'autoNavigateToFocusedRow' | 'focusedRowKey' | 'focusedRowIndex' | 'focusedColumnIndex'; + +export const focusModule = { + defaultOptions(): Pick<InternalGridOptions, FocusDefaultProperties> { + return { + focusedRowEnabled: false, + autoNavigateToFocusedRow: true, + focusedRowKey: null, + focusedRowIndex: -1, + focusedColumnIndex: -1, + }; + }, + controllers: { + focus: FocusController, + }, + extenders: { + controllers: { + keyboardNavigation, + editorFactory: focusEditorFactoryViewControllerExtender, + columns, + data: focusDataControllerExtender, + dataSource: focusDataSourceControllerExtender, + editing, + }, + views: { + rowsView, + }, + }, +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts index fb91bbd9d68d..8d8cfca5fda1 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/focus/m_focus.ts @@ -4,7 +4,7 @@ import { equalByValue } from '@js/core/utils/common'; import { Deferred, type DeferredObj, when } from '@js/core/utils/deferred'; import { each } from '@js/core/utils/iterator'; import { isBoolean, isDefined } from '@js/core/utils/type'; -import type { StoreChange } from '@js/data/store'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { Key } from '@ts/grids/new/grid_core/data_controller/types'; import type { ColumnsController } from '../columns_controller/m_columns_controller'; @@ -18,6 +18,7 @@ import type { ModuleType } from '../m_types'; import gridCoreUtils from '../m_utils'; import type { RowsView } from '../views/m_rows_view'; import type { VirtualScrollingDataControllerExtension } from '../virtual_scrolling/index'; +import type { FocusDataSourceControllerExtension } from './extenders/focus_data_source_controller'; import { UiGridCoreFocusUtils } from './m_focus_utils'; const ROW_FOCUSED_CLASS = 'dx-row-focused'; @@ -39,6 +40,10 @@ export class FocusController extends core.ViewController { return this.getController('data'); } + private getDataSourceController(): DataSourceController { + return this.getController('dataSource'); + } + public init() { this.component._optionsByReference.focusedRowKey = true; } @@ -213,7 +218,7 @@ export class FocusController extends core.ViewController { const d = new Deferred(); const rowsView = this.getView('rowsView'); - if (key === undefined || !this.getDataController().dataSource()) { + if (key === undefined || !this.getDataSourceController().hasAdapter()) { return d.reject().promise(); } @@ -283,7 +288,7 @@ export class FocusController extends core.ViewController { const offset = rowsScrollController.getItemOffset(focusedRowIndex); const triggerUpdateFocusedRow = () => { - if (this.getDataController().totalCount() && !this.getDataController().items().length) { + if (this.getDataSourceController().totalCount() && !this.getDataController().items().length) { return; } this.component.off('contentReady', triggerUpdateFocusedRow); @@ -441,7 +446,7 @@ export class FocusController extends core.ViewController { } } -const keyboardNavigation = (Base: ModuleType<KeyboardNavigationController>) => class FocusKeyboardNavigationExtender extends Base { +export const keyboardNavigation = (Base: ModuleType<KeyboardNavigationController>) => class FocusKeyboardNavigationExtender extends Base { public init() { const rowIndex = this.option('focusedRowIndex'); const columnIndex = this.option('focusedColumnIndex'); @@ -500,7 +505,7 @@ const keyboardNavigation = (Base: ModuleType<KeyboardNavigationController>) => c } }; -const focusEditorFactoryViewControllerExtender = ( +export const focusEditorFactoryViewControllerExtender = ( Base: ModuleType<EditorFactory>, ) => class FocusEditorFactoryExtender extends Base { protected keyboardNavigationController!: KeyboardNavigationController; @@ -531,11 +536,14 @@ const focusEditorFactoryViewControllerExtender = ( } }; -const columns = (Base: ModuleType<ColumnsController>) => class FocusColumnsExtender extends Base { +export const columns = (Base: ModuleType<ColumnsController>) => class FocusColumnsExtender extends Base { protected focusController!: FocusController; + protected dataSourceController!: DataSourceController; + public init(isApplyingUserState?: boolean): void { this.focusController = this.getController('focus'); + this.dataSourceController = this.getController('dataSource'); super.init(isApplyingUserState); } @@ -543,9 +551,8 @@ const columns = (Base: ModuleType<ColumnsController>) => class FocusColumnsExten public getSortDataSourceParameters(_, sortByKey?) { // @ts-expect-error let result = super.getSortDataSourceParameters.apply(this, arguments); - const dataSource = this._dataController._dataSource; - let key = dataSource?.store()?.key(); - const remoteOperations = dataSource && dataSource.remoteOperations() || {}; + let key = this.dataSourceController.store()?.key(); + const remoteOperations = this.dataSourceController.remoteOperations(); const isLocalOperations = Object.keys(remoteOperations).every((operationName) => !remoteOperations[operationName]); if (key && (this.option('focusedRowEnabled') && this.focusController.isAutoNavigateToFocusedRow() !== false || sortByKey)) { @@ -555,7 +562,7 @@ const columns = (Base: ModuleType<ColumnsController>) => class FocusColumnsExten if (notSortedKeys.length) { result = result || []; if (isLocalOperations) { - result.push({ selector: dataSource?.getDataIndexGetter(), desc: false }); + result.push({ selector: this.dataSourceController.getDataIndexGetter(), desc: false }); } else { notSortedKeys.forEach((notSortedKey) => result.push({ selector: notSortedKey, desc: false })); } @@ -566,10 +573,11 @@ const columns = (Base: ModuleType<ColumnsController>) => class FocusColumnsExten } }; -const focusDataControllerExtender = ( +export const focusDataControllerExtender = ( Base: ModuleType<DataController & Partial<VirtualScrollingDataControllerExtension>>, ) => class FocusDataControllerExtender extends Base { - private _isDataPushed = false; + protected declare dataSourceController: DataSourceController + & FocusDataSourceControllerExtension; private _lastRenderingPageIndex?: number; @@ -595,9 +603,7 @@ const focusDataControllerExtender = ( protected _fireChanged(e) { super._fireChanged(e); - const forceUpdateFocusedRow = this._isDataPushed; - - this._isDataPushed = false; + const forceUpdateFocusedRow = this.dataSourceController.consumeDataPushed(); if (this.option('focusedRowEnabled') && this._dataSource) { const isPartialUpdate = e.changeType === 'update' && e.repaintChangesOnly; @@ -637,14 +643,6 @@ const focusDataControllerExtender = ( } } - protected dataPushedHandler(changes: StoreChange[]): void { - super.dataPushedHandler(changes); - - const focusedRowKey = this.option('focusedRowKey'); - - this._isDataPushed = isDefined(focusedRowKey) && !!changes.length; - } - private _updatePageIndexes() { const prevRenderingPageIndex = this._lastRenderingPageIndex || 0; const renderingPageIndex = this._rowsScrollController ? this._rowsScrollController.pageIndex() : 0; @@ -892,7 +890,7 @@ const focusDataControllerExtender = ( } }; -const editing = (Base: ModuleType<EditingController>) => class FocusEditingControllerExtender extends Base { +export const editing = (Base: ModuleType<EditingController>) => class FocusEditingControllerExtender extends Base { protected _deleteRowCore(rowIndex) { // @ts-expect-error const deferred = super._deleteRowCore.apply(this, arguments); @@ -909,7 +907,7 @@ const editing = (Base: ModuleType<EditingController>) => class FocusEditingContr } }; -const rowsView = (Base: ModuleType<RowsView>) => class RowsViewFocusController extends Base { +export const rowsView = (Base: ModuleType<RowsView>) => class RowsViewFocusController extends Base { private _scrollToFocusOnResize: any; protected _createRow(row) { @@ -931,7 +929,7 @@ const rowsView = (Base: ModuleType<RowsView>) => class RowsViewFocusController e super._checkRowKeys.apply(this, arguments); if (this.option('focusedRowEnabled') && this.option('dataSource')) { - const store = this._dataController.store(); + const store = this.dataSourceController.store(); if (store && !store.key()) { this._dataController.fireError('E1042', 'Row focusing'); } @@ -965,8 +963,7 @@ const rowsView = (Base: ModuleType<RowsView>) => class RowsViewFocusController e let columnIndex = this.option('focusedColumnIndex')!; const $row = this._findRowElementForTabIndex(); - const dataSource = this._dataController.dataSource(); - const operationTypes = dataSource?.operationTypes(); + const operationTypes = this.dataSourceController.operationTypes(); const isPaging = !operationTypes || operationTypes.paging; if (!isDefined(this._scrollToFocusOnResize)) { @@ -1055,41 +1052,3 @@ const rowsView = (Base: ModuleType<RowsView>) => class RowsViewFocusController e return d.resolve(); } }; - -export const focusModule = { - defaultOptions() { - return { - focusedRowEnabled: false, - - autoNavigateToFocusedRow: true, - - focusedRowKey: null, - - focusedRowIndex: -1, - - focusedColumnIndex: -1, - }; - }, - - controllers: { - focus: FocusController, - }, - - extenders: { - controllers: { - keyboardNavigation, - - editorFactory: focusEditorFactoryViewControllerExtender, - - columns, - - data: focusDataControllerExtender, - - editing, - }, - - views: { - rowsView, - }, - }, -}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts index 10d151fa7380..b0d69199395c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/header_filter/m_header_filter.ts @@ -15,7 +15,11 @@ import { isDefined, isFunction, isObject } from '@js/core/utils/type'; import { restoreFocus, saveFocusedElementInfo } from '@js/ui/shared/accessibility'; import filterUtils from '@js/ui/shared/filtering'; import type { ColumnHeadersView } from '@ts/grids/grid_core/column_headers/m_column_headers'; +import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataFilter } from '@ts/grids/grid_core/data_controller/types'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; +import type { FilterController } from '@ts/grids/grid_core/filter/filter_controller'; import type { HeaderPanel } from '@ts/grids/grid_core/header_panel/m_header_panel'; import Modules from '@ts/grids/grid_core/m_modules'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; @@ -79,13 +83,11 @@ function ungroupUTCDates(items, dateParts?, dates?) { export function convertDataFromUTCToLocal(data, column) { const dates = ungroupUTCDates(data); - // @ts-expect-error const query = dataQuery(dates); const group = gridCoreUtils.getHeaderFilterGroupParameters({ ...column, calculateCellValue: (date) => date, }); - // @ts-expect-error return storeHelper.queryByOptions(query, { group }).toArray(); } @@ -122,11 +124,14 @@ export class HeaderFilterController extends Modules.ViewController { private _dataController!: DataController; + private dataSourceController!: DataSourceController; + private _headerFilterView!: HeaderFilterView; public init() { this._columnsController = this.getController('columns'); this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._headerFilterView = this.getView('headerFilterView'); } @@ -232,8 +237,8 @@ export class HeaderFilterController extends Modules.ViewController { } private getDataSource(column) { - const dataSource = this._dataController.dataSource(); - const remoteGrouping = dataSource?.remoteOperations().grouping; + const dataSourceAdapter = this.dataSourceController.getAdapter(); + const remoteGrouping = this.dataSourceController.remoteOperations().grouping; const group = gridCoreUtils.getHeaderFilterGroupParameters(column, remoteGrouping); const headerFilterDataSource = column.headerFilter?.dataSource; const headerFilterOptions = this.option('headerFilter'); @@ -242,10 +247,9 @@ export class HeaderFilterController extends Modules.ViewController { component: this.component, }; - if (!dataSource) return; + if (!dataSourceAdapter) return; if (isDefined(headerFilterDataSource) && !isFunction(headerFilterDataSource)) { - // @ts-expect-error options.dataSource = normalizeDataSourceOptions(headerFilterDataSource); } else if (column.lookup) { isLookup = true; @@ -253,7 +257,7 @@ export class HeaderFilterController extends Modules.ViewController { if (this.option('syncLookupFilterValues')) { const filter = this._dataController.getCombinedFilterWithExcludedColumn(column); - options.dataSource = gridCoreUtils.getWrappedLookupDataSource(column, dataSource, filter); + options.dataSource = gridCoreUtils.getWrappedLookupDataSource(column, dataSourceAdapter, filter); } else { options.dataSource = gridCoreUtils.normalizeLookupDataSource(column.lookup); } @@ -272,9 +276,10 @@ export class HeaderFilterController extends Modules.ViewController { // TODO remove in 16.1 options.dataField = column.dataField || column.name; - dataSource.customLoader.load(options).done(({ data }) => { + dataSourceAdapter.customLoader.load(options).done(({ data }) => { const convertUTCDates = remoteGrouping && isUTCFormat(column.serializationFormat) && cutoffLevel > 3; if (convertUTCDates) { + // @ts-expect-error queryByOptions().toArray() is typed as unknown[] data = convertDataFromUTCToLocal(data, column); } that._processGroupItems(data, null, null, { @@ -351,8 +356,7 @@ export class HeaderFilterController extends Modules.ViewController { if (column) { const groupInterval = filterUtils.getGroupInterval(column); - const dataSource = that._dataController.dataSource(); - const remoteFiltering = dataSource && dataSource.remoteOperations().filtering; + const remoteFiltering = that.dataSourceController.remoteOperations().filtering; const previousOnHidden = options.onHidden; extend(options, column, { @@ -493,20 +497,20 @@ export function invertFilterExpression(filter) { return ['!', filter]; } -const data = (Base: ModuleType<DataController>) => class DataControllerFilterRowExtender extends Base { +const filterController = ( + Base: ModuleType<FilterController>, +) => class FilterControllerHeaderFilterExtender extends Base { private skipCalculateColumnFilters() { return false; } - protected calculateAdditionalFilter() { + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { if (this.skipCalculateColumnFilters()) { - return super.calculateAdditionalFilter(); + return super.getAdditionalFilter(excludedColumn); } - const that = this; - const filters = [super.calculateAdditionalFilter()]; - const columns = that._columnsController.getVisibleColumns(null, true); - const excludedColumn = this.getFilterExcludedColumn(); + const filters = [super.getAdditionalFilter(excludedColumn)]; + const columns = this.columnsController.getVisibleColumns(null, true); each(columns, (_, column) => { let filter; @@ -576,7 +580,7 @@ export const headerFilterModule = { }, extenders: { controllers: { - data, + filter: filterController, }, views: { columnHeadersView, diff --git a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts index cb1e1d542bdb..c148ee88bd9b 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/keyboard_navigation/m_keyboard_navigation.ts @@ -28,6 +28,7 @@ import type { AdaptiveColumnsController } from '@ts/grids/grid_core/adaptivity/m import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; import type { RowIndexCorrection } from '@ts/grids/grid_core/data_controller/types'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { EditingController } from '@ts/grids/grid_core/editing/m_editing'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; import type { RowsViewScrollEvent } from '@ts/grids/grid_core/views/types'; @@ -131,6 +132,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo protected _dataController!: DataController & Partial<VirtualScrollingDataControllerExtension>; + protected dataSourceController!: DataSourceController; + private _selectionController!: Controllers['selection']; protected _editingController!: Controllers['editing']; @@ -152,6 +155,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo // #region Initialization public init() { this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._selectionController = this.getController('selection'); this._editingController = this.getController('editing'); this._editorFactory = this.getController('editorFactory'); @@ -704,7 +708,6 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo const $row = this._focusedView && this._focusedView.getRow(visibleRowIndex); const $event = eventArgs.originalEvent; const isUpArrow = eventArgs.keyName === 'upArrow'; - const dataSource = this._dataController.dataSource(); const isRowEditingInCurrentRow = this._editingController?.isEditRowByIndex?.(visibleRowIndex); const isEditingNavigationMode = this._isFastEditingStarted(); const isInsideMasterDetail = this.isInsideMasterDetail($($event?.target)); @@ -719,8 +722,8 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo if ( this._isVirtualRowRender() && isUpArrow - && dataSource - && !dataSource.isLoading() + && this.dataSourceController.hasAdapter() + && !this.dataSourceController.isLoading() ) { const rowHeight = getOuterHeight($row); const rowIndex = this._focusedCellPosition.rowIndex - 1; @@ -734,7 +737,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo private _pageUpDownKeyHandler(eventArgs) { const pageIndex = this._dataController.pageIndex(); - const pageCount = this._dataController.pageCount(); + const pageCount = this.dataSourceController.pageCount(); const pagingEnabled = this.option('paging.enabled'); const isPageUp = eventArgs.keyName === 'pageUp'; const pageStep = isPageUp ? -1 : 1; @@ -1460,7 +1463,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo private getFirstOrLastRowIndex(needFirstRow: boolean): number { const rowCount = this._isVirtualScrolling() - ? this._dataController.totalItemsCount() + ? this.dataSourceController.totalItemsCount() : this._dataController.items(true)?.length; return needFirstRow ? 0 : rowCount - 1; @@ -2588,7 +2591,7 @@ export class KeyboardNavigationController extends KeyboardNavigationControllerCo private _fireFocusedRowChanging(eventArgs: any, $newFocusedRow: dxElementWrapper) { const newRowIndex = this._getRowIndex($newFocusedRow); const prevFocusedRowIndex = this.option('focusedRowIndex'); - const loadingOperationTypes = this._dataController.loadingOperationTypes(); + const loadingOperationTypes = this.dataSourceController.loadingOperationTypes(); const args: any = { rowElement: $newFocusedRow, diff --git a/packages/devextreme/js/__internal/grids/grid_core/m_types.ts b/packages/devextreme/js/__internal/grids/grid_core/m_types.ts index 662de74a6193..9204705e55f0 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/m_types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/m_types.ts @@ -203,12 +203,14 @@ export interface Controllers { columnsResizer: import('./columns_resizing_reordering/m_columns_resizing_reordering').ColumnsResizerViewController; contextMenu: import('./context_menu/m_context_menu').ContextMenuController; data: import('./data_controller/data_controller').DataController; + dataSource: import('./data_source/data_source_controller').DataSourceController; draggingHeader: import('./columns_resizing_reordering/m_columns_resizing_reordering').DraggingHeaderViewController; // todo: export is dataGrid-only controller editing: import('./editing/m_editing').EditingController; editorFactory: import('./editor_factory/m_editor_factory').EditorFactory; errorHandling: import('./error_handling/error_handling_view_controller').ErrorHandlingViewController; export: import('../data_grid/export/m_export').ExportController; + filter: import('./filter/filter_controller').FilterController; filterSync: import('./filter_sync/m_filter_sync').FilterSyncController; focus: import('./focus/m_focus').FocusController; headerFilter: import('./header_filter/m_header_filter').HeaderFilterController; diff --git a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts index ae057d8d7009..971de5eadb7f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/m_utils.ts @@ -674,11 +674,10 @@ export default { lookupDataSourceOptions = lookupDataSourceOptions({}); } } - // @ts-expect-error return normalizeDataSourceOptions(lookupDataSourceOptions); }, - getWrappedLookupDataSource(column, dataSource: DataSourceAdapter | undefined, filter) { + getWrappedLookupDataSource(column, dataSource: DataSourceAdapter | null | undefined, filter) { if (!dataSource) { return []; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/master_detail/__tests__/get_row_indices_for_expand.test.ts b/packages/devextreme/js/__internal/grids/grid_core/master_detail/__tests__/get_row_indices_for_expand.test.ts new file mode 100644 index 000000000000..d54dad4f1b78 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/master_detail/__tests__/get_row_indices_for_expand.test.ts @@ -0,0 +1,47 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; +import { + afterTest, beforeTest, createDataGrid, flushAsync, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { MasterDetailDataControllerExtension } from '@ts/grids/grid_core/master_detail/m_master_detail'; +import { + afterTest as afterTreeListTest, + beforeTest as beforeTreeListTest, + createTreeList, +} from '@ts/grids/tree_list/__tests__/__mock__/helpers/utils'; + +type MasterDetailDataController = DataController & MasterDetailDataControllerExtension; + +const DATA = [{ id: 1 }, { id: 2 }, { id: 3 }]; + +describe('DataController getRowIndicesForExpand', () => { + describe('DataGrid', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should reach master_detail through the adaptivity chain', async () => { + const { instance } = await createDataGrid({ dataSource: DATA }); + await flushAsync(); + const dataController = instance.getController('data') as MasterDetailDataController; + + expect(dataController.getRowIndicesForExpand(1)).toEqual([0, 1]); + expect(dataController.getRowIndicesForExpand(3)).toEqual([2, 3]); + }); + }); + + describe('TreeList', () => { + beforeEach(beforeTreeListTest); + afterEach(afterTreeListTest); + + it('should reach master_detail through the adaptivity chain', async () => { + const { instance } = await createTreeList({ dataSource: DATA }); + await flushAsync(); + const dataController = instance.getController('data') as MasterDetailDataController; + + expect(dataController.getRowIndicesForExpand(1)).toEqual([0, 1]); + expect(dataController.getRowIndicesForExpand(3)).toEqual([2, 3]); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts b/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts index 2ab0f0fdebca..2be2bbe8aad4 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/master_detail/m_master_detail.ts @@ -44,7 +44,14 @@ const initMasterDetail = function (that) { that._isExpandAll = that.option('masterDetail.autoExpandAll'); }; -export const dataMasterDetailExtenderMixin = (Base: ModuleType<DataController>) => class DataMasterDetailExtender extends Base { +export interface MasterDetailDataControllerExtension { + // eslint-disable-next-line @typescript-eslint/method-signature-style + getRowIndicesForExpand(key: RowKey): number[]; +} + +export const dataMasterDetailExtenderMixin = ( + Base: ModuleType<DataController>, +): ModuleType<DataController & MasterDetailDataControllerExtension> => class DataMasterDetailExtender extends Base { private _isExpandAll: any; private _expandedItems: any; @@ -98,7 +105,7 @@ export const dataMasterDetailExtenderMixin = (Base: ModuleType<DataController>) return !!(that._isExpandAll ^ (expandIndex >= 0 && that._expandedItems[expandIndex].visible)); } - protected getRowIndicesForExpand(key: RowKey): number[] { + public getRowIndicesForExpand(key: RowKey): number[] { const rowIndex = this.getRowIndexByKey(key); return [rowIndex, rowIndex + 1]; diff --git a/packages/devextreme/js/__internal/grids/grid_core/pager/__tests__/pager_view.integration.test.ts b/packages/devextreme/js/__internal/grids/grid_core/pager/__tests__/pager_view.integration.test.ts new file mode 100644 index 000000000000..7f3f61f1dca0 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/pager/__tests__/pager_view.integration.test.ts @@ -0,0 +1,83 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; + +import type { DataGridInstance } from '../../__tests__/__mock__/helpers/utils'; +import { + afterTest, + beforeTest, + createDataGrid, +} from '../../__tests__/__mock__/helpers/utils'; + +const ITEMS = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]; + +interface Pagination { + option: (name: string) => unknown; +} + +const getPagination = (instance: DataGridInstance): Pagination => ( + instance.getView('pagerView') as unknown as { getPager: () => Pagination } +).getPager(); + +const isPagerVisible = (instance: DataGridInstance): boolean => instance + .getView('pagerView') + .isVisible(); + +describe('PagerView', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('without a data source', () => { + it('reports one page and no items', async () => { + const { instance } = await createDataGrid({ + pager: { visible: true }, + }); + + expect(getPagination(instance).option('pageCount')).toBe(1); + expect(getPagination(instance).option('itemCount')).toBe(0); + expect(getPagination(instance).option('hasKnownLastPage')).toBe(true); + }); + + it('is hidden in auto mode', async () => { + const { instance } = await createDataGrid({ + pager: { visible: 'auto' }, + }); + + expect(isPagerVisible(instance)).toBe(false); + }); + }); + + describe('with a data source', () => { + it('reports the page count and the item count', async () => { + const { instance } = await createDataGrid({ + dataSource: ITEMS, + paging: { pageSize: 2 }, + pager: { visible: true }, + }); + + expect(getPagination(instance).option('pageCount')).toBe(3); + expect(getPagination(instance).option('itemCount')).toBe(5); + expect(getPagination(instance).option('hasKnownLastPage')).toBe(true); + }); + + it('is visible in auto mode when there is more than one page', async () => { + const { instance } = await createDataGrid({ + dataSource: ITEMS, + paging: { pageSize: 2 }, + pager: { visible: 'auto' }, + }); + + expect(isPagerVisible(instance)).toBe(true); + }); + + it('is hidden in auto mode when a single page holds every item', async () => { + const { instance } = await createDataGrid({ + dataSource: ITEMS, + paging: { pageSize: 10 }, + pager: { visible: 'auto' }, + }); + + expect(isPagerVisible(instance)).toBe(false); + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/pager/m_pager.ts b/packages/devextreme/js/__internal/grids/grid_core/pager/m_pager.ts index e645cf17d489..729e3b8e06ba 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/pager/m_pager.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/pager/m_pager.ts @@ -1,6 +1,7 @@ import messageLocalization from '@js/common/core/localization/message'; import { isDefined } from '@js/core/utils/type'; import { hasWindow } from '@js/core/utils/window'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import Pagination from '@ts/pagination/wrappers/pagination'; import modules from '../m_modules'; @@ -19,9 +20,13 @@ export class PagerView extends modules.View { private _pageSizes: any; + private dataSourceController!: DataSourceController; + public init() { const dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); + dataController.changed.add((e) => { if (e && e.repaintChangesOnly) { const pager = this._pager; @@ -29,9 +34,9 @@ export class PagerView extends modules.View { pager.option({ pageIndex: getPageIndex(dataController), pageSize: dataController.pageSize(), - pageCount: dataController.pageCount(), - itemCount: dataController.totalCount(), - hasKnownLastPage: dataController.hasKnownLastPage(), + pageCount: this.dataSourceController.pageCount(), + itemCount: this.dataSourceController.totalCount(), + hasKnownLastPage: this.dataSourceController.hasKnownLastPage(), }); } else { this.render(); @@ -86,7 +91,7 @@ export class PagerView extends modules.View { const options: any = { maxPagesCount: MAX_PAGES_COUNT, pageIndex: getPageIndex(dataController), - pageCount: dataController.pageCount(), + pageCount: that.dataSourceController.pageCount(), pageSize: dataController.pageSize(), showPageSizeSelector: pagerOptions.showPageSizeSelector, showInfo: pagerOptions.showInfo, @@ -95,8 +100,8 @@ export class PagerView extends modules.View { showNavigationButtons: pagerOptions.showNavigationButtons, label: pagerOptions.label, allowedPageSizes: that.getPageSizes(), - itemCount: dataController.totalCount(), - hasKnownLastPage: dataController.hasKnownLastPage(), + itemCount: that.dataSourceController.totalCount(), + hasKnownLastPage: that.dataSourceController.hasKnownLastPage(), rtlEnabled: that.option('rtlEnabled'), isGridCompatibilityMode: true, _getParentComponentRootNode: () => this.component.element(), @@ -164,7 +169,8 @@ export class PagerView extends modules.View { if (scrolling && (scrolling.mode === 'virtual' || scrolling.mode === 'infinite')) { pagerVisible = false; } else { - pagerVisible = dataController.pageCount() > 1 || (dataController.isLoaded() && !dataController.hasKnownLastPage()); + pagerVisible = this.dataSourceController.pageCount() > 1 + || (dataController.isLoaded() && !this.dataSourceController.hasKnownLastPage()); } } return !!pagerVisible; diff --git a/packages/devextreme/js/__internal/grids/grid_core/search/__tests__/search_data_controller.user_state.test.ts b/packages/devextreme/js/__internal/grids/grid_core/search/__tests__/search_data_controller.user_state.test.ts new file mode 100644 index 000000000000..30c939574e2a --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/search/__tests__/search_data_controller.user_state.test.ts @@ -0,0 +1,27 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; + +import { + afterTest, + beforeTest, + createDataGrid, +} from '../../__tests__/__mock__/helpers/utils'; + +describe('Search data controller user state', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should include the search panel text', async () => { + const { instance } = await createDataGrid({ + dataSource: [], + searchPanel: { text: 'Alex' }, + }); + + expect(instance.getController('data').getUserState()).toEqual({ + searchText: 'Alex', + pageIndex: 0, + pageSize: 20, + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts index f35045af3838..796df8e7148a 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/search/m_search.ts @@ -12,7 +12,8 @@ import type { Column } from '@ts/grids/grid_core/columns_controller/types'; import type { ToolbarItem } from '@ts/grids/new/grid_core/toolbar/types'; import type { DataController } from '../data_controller/data_controller'; -import type { DataFilter } from '../data_controller/types'; +import type { DataFilter, UserState } from '../data_controller/types'; +import type { FilterController } from '../filter/filter_controller'; import type { HeaderPanel } from '../header_panel/m_header_panel'; import modules from '../m_modules'; import type { ModuleType, OptionChanged } from '../m_types'; @@ -62,23 +63,34 @@ const dataController = ( return super.publicMethods().concat(['searchByText']); } - protected calculateAdditionalFilter(): DataFilter { - const dataSource = this.getDataSource(); - const langParams = dataSource?.loadOptions?.()?.langParams; - - const filter = super.calculateAdditionalFilter(); - const searchFilter = this.calculateSearchFilter(this.option('searchPanel.text'), langParams); - - return gridCoreUtils.combineFilters([filter, searchFilter]); + public getUserState(): UserState { + return { + ...super.getUserState(), + searchText: this.option('searchPanel.text'), + }; } public searchByText(text: string | undefined): void { this.option('searchPanel.text', text); } +}; + +const filterController = ( + base: ModuleType<FilterController>, +) => class FilterControllerSearchExtender extends base { + public getAdditionalFilter(excludedColumn?: Column | null): DataFilter { + const filter = super.getAdditionalFilter(excludedColumn); + const searchFilter = this.calculateSearchFilter( + this.option('searchPanel.text'), + this.getLangParams(), + ); + + return gridCoreUtils.combineFilters([filter, searchFilter]); + } private calculateSearchFilter(text: string | undefined, langParams?: LangParams): DataFilter { let column; - const columns = this._columnsController.getColumns(); + const columns = this.columnsController.getColumns(); const searchVisibleColumnsOnly = this.option('searchPanel.searchVisibleColumnsOnly'); let lookup; const filters: any[] = []; @@ -106,9 +118,7 @@ const dataController = ( const filterValue = parseValue(column, text); if (lookup?.items) { - // @ts-expect-error dataQuery(lookup.items, { langParams }) - // @ts-expect-error .filter( column.createFilterExpression.call( { @@ -269,7 +279,7 @@ const rowsView = ( private _getStringNormalizer() { const isCaseSensitive = this.option('searchPanel.highlightCaseSensitive'); - const dataSource = this._dataController?.getDataSource?.(); + const dataSource = this.dataSourceController.getDataSource(); const langParams = dataSource?.loadOptions?.()?.langParams; return (str: string): string => toComparable(str, isCaseSensitive, langParams); @@ -421,6 +431,7 @@ export const searchModule = { extenders: { controllers: { data: dataController, + filter: filterController, }, views: { rowsView, diff --git a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts index 3af9873058e0..940b951eaa52 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/selection/m_selection.ts @@ -27,6 +27,7 @@ import type { SelectionChangeEvent, SelectionFilter, SelectionOptions } from '@t import type { DataController } from '../data_controller/data_controller'; import type { DataChange } from '../data_controller/types'; +import type { DataSourceController } from '../data_source/data_source_controller'; import { isEditRow } from '../keyboard_navigation/utils'; import modules from '../m_modules'; import gridCoreUtils from '../m_utils'; @@ -71,8 +72,7 @@ const processLongTap = function (that, dxEvent) { const isSeveralRowsSelected = function (that, selectionFilter) { let keyIndex = 0; - const store = that._dataController.store(); - const key = store?.key(); + const key = that.dataSourceController.store()?.key(); const isComplexKey = Array.isArray(key); if (!selectionFilter.length) { @@ -120,6 +120,8 @@ const selectionHeaderTemplate = (container, options) => { export class SelectionController extends modules.Controller { protected _dataController!: DataController; + protected dataSourceController!: DataSourceController; + private _columnsController!: ColumnsController; protected _stateStoringController!: StateStoringController; @@ -144,6 +146,7 @@ export class SelectionController extends modules.Controller { } this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._columnsController = this.getController('columns'); this._stateStoringController = this.getController('stateStoring'); // mode has a default value @@ -156,7 +159,7 @@ export class SelectionController extends modules.Controller { if (!this._dataPushedHandler) { this._dataPushedHandler = this._handleDataPushed.bind(this); - this._dataController.pushed.add(this._dataPushedHandler); + this.dataSourceController.pushed.add(this._dataPushedHandler); } } @@ -206,6 +209,7 @@ export class SelectionController extends modules.Controller { */ protected _getSelectionConfig(): SelectionOptions { const dataController = this._dataController; + const { dataSourceController } = this; const columnsController = this._columnsController; const selectionOptions: any = this.option('selection') ?? {}; const { deferred } = selectionOptions; @@ -230,17 +234,17 @@ export class SelectionController extends modules.Controller { return virtualPaging && !legacyScrollingMode && !hasGroupColumns && allowSelectAll && !deferred; }, key() { - return dataController?.key(); + return dataSourceController.key(); }, keyOf(item) { - return dataController?.keyOf(item); + return dataSourceController.keyOf(item); }, dataFields() { - return dataController.dataSource()?.select(); + return dataSourceController.select(); }, load(options) { // @ts-expect-error - return dataController.dataSource()?.customLoader.load(options) || new Deferred().resolve([]); + return dataSourceController.getAdapter()?.customLoader.load(options) || new Deferred().resolve([]); }, // eslint-disable-next-line plainItems(cached?) { @@ -259,9 +263,9 @@ export class SelectionController extends modules.Controller { filter() { return dataController.getCombinedFilter(deferred); }, - totalCount: () => dataController.totalCount(), + totalCount: () => dataSourceController.totalCount(), getLoadOptions(loadItemIndex, focusedItemIndex, shiftItemIndex) { - const { sort, filter } = dataController.dataSource()?.lastLoadOptions() ?? {}; + const { sort, filter } = dataSourceController.lastLoadOptions(); let minIndex = Math.min(loadItemIndex, focusedItemIndex); let maxIndex = Math.max(loadItemIndex, focusedItemIndex); diff --git a/packages/devextreme/js/__internal/grids/grid_core/state_storing/m_state_storing.ts b/packages/devextreme/js/__internal/grids/grid_core/state_storing/m_state_storing.ts index a9562c531e4a..eafb2d17ae80 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/state_storing/m_state_storing.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/state_storing/m_state_storing.ts @@ -13,7 +13,7 @@ export const rowsView = (Base: ModuleType<RowsView>) => class StateStoringRowsVi super.init(); this._dataController.stateLoaded.add(() => { - if (this._dataController.isLoaded() && !this._dataController.getDataSource()) { + if (this._dataController.isLoaded() && !this.dataSourceController.getDataSource()) { this.setLoading(false); this.renderNoDataText(); // TODO getView diff --git a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts index 1c2e43f2909b..1b2b53961b65 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/validating/m_validating.ts @@ -767,7 +767,7 @@ export const validatingEditingExtender = (Base: ModuleType<EditingController>) = const { key } = change; each(items, (i, item) => { - if (equalByValue(key, isInsert ? item.key : this._dataController.keyOf(item))) { + if (equalByValue(key, isInsert ? item.key : this.dataSourceController.keyOf(item))) { index = i; return false; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/a11y_status_container_component.ts b/packages/devextreme/js/__internal/grids/grid_core/views/a11y_status_container_component.ts index 875af6837f7d..824b1e86c46c 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/a11y_status_container_component.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/a11y_status_container_component.ts @@ -1,8 +1,9 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; const CLASSES = { - container: 'dx-gridbase-a11y-status-container', + container: SCREEN_READER_ONLY_CLASS, }; export interface A11yStatusContainerComponentProps { diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts index 2a671b7f1434..259ff155673f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_grid_view.ts @@ -11,6 +11,7 @@ import { Deferred, when } from '@js/core/utils/deferred'; import { each } from '@js/core/utils/iterator'; import { getBoundingRect } from '@js/core/utils/position'; import { getHeight, getWidth } from '@js/core/utils/size'; +import { setHeight } from '@js/core/utils/style'; import { isDefined, isNumeric, isString } from '@js/core/utils/type'; import { getWindow, hasWindow } from '@js/core/utils/window'; import * as accessibility from '@js/ui/shared/accessibility'; @@ -23,6 +24,7 @@ import type { ColumnHeadersView } from '../column_headers/m_column_headers'; import type { ColumnsController } from '../columns_controller/m_columns_controller'; import type { DataController } from '../data_controller/data_controller'; import type { DataChange } from '../data_controller/types'; +import type { DataSourceController } from '../data_source/data_source_controller'; import modules from '../m_modules'; import gridCoreUtils from '../m_utils'; import type { RowsView } from './m_rows_view'; @@ -89,6 +91,8 @@ export class ResizingController extends modules.ViewController { public _dataController!: DataController; + private dataSourceController!: DataSourceController; + protected _rowsView!: RowsView; private _columnHeadersView!: ColumnHeadersView; @@ -130,6 +134,7 @@ export class ResizingController extends modules.ViewController { public init() { this._prevContentMinHeight = null; this._dataController = this.getController('data'); + this.dataSourceController = this.getController('dataSource'); this._columnsController = this.getController('columns'); this._columnHeadersView = this.getView('columnHeadersView'); this.adaptiveColumnsController = this.getController('adaptiveColumns'); @@ -231,7 +236,7 @@ export class ResizingController extends modules.ViewController { let labelParts: string[] = []; const columnCount = this._columnsController?._columns?.filter(({ visible }) => !!visible).length ?? 0; - const totalItemsCount = Math.max(0, this._dataController.totalItemsCount()); + const totalItemsCount = Math.max(0, this.dataSourceController.totalItemsCount()); const widgetAriaLabel = this._getWidgetAriaLabel(); widgetStatusText = messageLocalization // @ts-expect-error Badly typed format method @@ -828,7 +833,7 @@ export class ResizingController extends modules.ViewController { // IE11 if (maxHeightHappened && !isMaxHeightApplied) { - $(groupElement).css('height', maxHeight); + setHeight($(groupElement), maxHeight); } if (!dataController.isLoaded()) { diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts b/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts index b5b668f7b4de..bade9810d761 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/m_rows_view.ts @@ -22,6 +22,7 @@ import type { ColumnsResizerViewController, } from '@ts/grids/grid_core/columns_resizing_reordering/m_columns_resizing_reordering'; import { generateRowValues } from '@ts/grids/grid_core/data_controller/utils/row_values'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type { FocusController } from '@ts/grids/grid_core/focus/m_focus'; import type { KeyboardNavigationController } from '@ts/grids/grid_core/keyboard_navigation/m_keyboard_navigation'; import type { ValidatingController } from '@ts/grids/grid_core/validating/m_validating'; @@ -104,6 +105,8 @@ export class RowsView extends ColumnsView { protected _validatingController!: ValidatingController; + protected dataSourceController!: DataSourceController; + public _columnHeadersView!: ColumnHeadersView; public _hasHeight: boolean | undefined; @@ -135,6 +138,7 @@ export class RowsView extends ColumnsView { this._focusController = this.getController('focus'); this._keyboardNavigationController = this.getController('keyboardNavigation'); this._validatingController = this.getController('validating'); + this.dataSourceController = this.getController('dataSource'); this._columnHeadersView = this.getView('columnHeadersView'); this._rowHeight = 0; this._scrollTop = 0; @@ -353,7 +357,7 @@ export class RowsView extends ColumnsView { $element.append('<div>'); } if (force || !that._loadPanel) { - that._renderLoadPanel($element, $element.parent(), isLocalStore(that._dataController.store())); + that._renderLoadPanel($element, $element.parent(), isLocalStore(that.dataSourceController.store())); } if ((force || !that.getScrollable()) && that._dataController.isLoaded()) { @@ -534,7 +538,7 @@ export class RowsView extends ColumnsView { $cell = that._createCell({ column: columns[i], rowType: 'freeSpace', columnIndex: i, columns, }); - isNumeric(height) && $cell.css('height', height); + isNumeric(height) && setHeight($cell, height); $row.append($cell); } @@ -578,7 +582,7 @@ export class RowsView extends ColumnsView { protected _checkRowKeys(options) { const that = this; const rows = that._getRows(options); - const keyExpr = that._dataController.store()?.key(); + const keyExpr = that.dataSourceController.store()?.key(); keyExpr && rows.some((row) => { if (row.rowType === 'data' && row.key === undefined) { @@ -1000,7 +1004,7 @@ export class RowsView extends ColumnsView { const contentElement = this._findContentElement(); const freeSpaceRowElements = this._getFreeSpaceRowElements($table); - if (freeSpaceRowElements && contentElement && dataController.totalCount() >= 0) { + if (freeSpaceRowElements && contentElement && this.dataSourceController.totalCount() >= 0) { let isFreeSpaceRowVisible = false; if (itemCount > 0) { @@ -1008,7 +1012,7 @@ export class RowsView extends ColumnsView { const freeSpaceRowCount = dataController.pageSize() - itemCount; const scrollingMode = this.option('scrolling.mode'); - if (freeSpaceRowCount > 0 && dataController.pageCount() > 1 && scrollingMode !== 'virtual' && scrollingMode !== 'infinite') { + if (freeSpaceRowCount > 0 && this.dataSourceController.pageCount() > 1 && scrollingMode !== 'virtual' && scrollingMode !== 'infinite') { setHeight(freeSpaceRowElements, freeSpaceRowCount * this._rowHeight); isFreeSpaceRowVisible = true; } @@ -1033,7 +1037,7 @@ export class RowsView extends ColumnsView { if (showFreeSpaceRow) { deferRender(() => { - freeSpaceRowElements.css('height', resultHeight); + setHeight(freeSpaceRowElements, resultHeight); isFreeSpaceRowVisible = true; freeSpaceRowElements.show(); }); @@ -1042,7 +1046,7 @@ export class RowsView extends ColumnsView { }); } } else { - freeSpaceRowElements.css('height', 0); + setHeight(freeSpaceRowElements, 0); freeSpaceRowElements.show(); this._updateLastRowBorder(true); } @@ -1236,7 +1240,7 @@ export class RowsView extends ColumnsView { return; } - if (!loadPanel && messageText !== undefined && isLocalStore(dataController.store()) && loadPanelOptions.enabled === 'auto' && $element) { + if (!loadPanel && messageText !== undefined && isLocalStore(that.dataSourceController.store()) && loadPanelOptions.enabled === 'auto' && $element) { that._renderLoadPanel($element, $element.parent()); loadPanel = that._loadPanel; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts b/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts index 9a2e10b6435a..f482ffa1e6bf 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/views/utils.ts @@ -1,13 +1,13 @@ +import { columnHasValue } from '@ts/grids/grid_core/columns_controller/m_columns_controller_utils'; import type { Column } from '@ts/grids/grid_core/columns_controller/types'; -import { AI_COLUMN_NAME } from '../ai_column/const'; import gridCoreUtils from '../m_utils'; export const getCellText = ( column: Column, displayValue: unknown, ): string => ( - !column.command || column.type === AI_COLUMN_NAME + columnHasValue(column) ? gridCoreUtils.formatValue(displayValue, column) as string : '' ); diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/__tests__/virtual_scrolling_data_controller.paging.test.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/__tests__/virtual_scrolling_data_controller.paging.test.ts new file mode 100644 index 000000000000..74c28fa4b410 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/__tests__/virtual_scrolling_data_controller.paging.test.ts @@ -0,0 +1,73 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import type { DataGridScrollMode } from '@js/ui/data_grid'; +import { + afterTest, + beforeTest, + createDataGrid, +} from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; +import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; + +declare class ExposedDataController extends DataController { + public resolvePaginate(enabled: boolean | undefined): boolean | undefined; + + public requiresTotalCount(): boolean; +} + +const withScrollingMode = async (mode: DataGridScrollMode): Promise<ExposedDataController> => { + const { instance } = await createDataGrid({ dataSource: [], scrolling: { mode } }); + + return instance.getController('data') as unknown as ExposedDataController; +}; + +describe('Virtual scrolling data controller paging', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + describe('resolvePaginate', () => { + it.each<{ mode: DataGridScrollMode; enabled: boolean; expected: boolean }>([ + { mode: 'standard', enabled: true, expected: true }, + { mode: 'standard', enabled: false, expected: false }, + { mode: 'virtual', enabled: false, expected: true }, + { mode: 'infinite', enabled: false, expected: true }, + ])('should be $expected in $mode mode with enabled=$enabled', async ({ + mode, enabled, expected, + }) => { + const dataController = await withScrollingMode(mode); + + expect(dataController.resolvePaginate(enabled)).toBe(expected); + }); + + it.each<DataGridScrollMode>(['standard', 'virtual', 'infinite'])( + 'should leave paginate untouched when enabled is undefined in %s mode', + async (mode) => { + const dataController = await withScrollingMode(mode); + + expect(dataController.resolvePaginate(undefined)).toBeUndefined(); + }, + ); + }); + + describe('requiresTotalCount', () => { + it.each<{ mode: DataGridScrollMode; expected: boolean }>([ + { mode: 'standard', expected: true }, + { mode: 'virtual', expected: true }, + { mode: 'infinite', expected: false }, + ])('should be $expected in $mode mode', async ({ mode, expected }) => { + const dataController = await withScrollingMode(mode); + + expect(dataController.requiresTotalCount()).toBe(expected); + }); + }); + + it('should reset the data controller when the scrolling option changes', async () => { + const { instance } = await createDataGrid({ dataSource: [] }); + const dataController = instance.getController('data'); + const resetSpy = jest.spyOn(dataController, 'reset'); + + instance.option('scrolling.mode', 'virtual'); + + expect(resetSpy).toHaveBeenCalled(); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts index ccef9dd99ab3..5e15798cddda 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/extenders/virtual_scrolling_data_controller.ts @@ -25,7 +25,7 @@ import type { DataChange, PagingOptionName, PagingResult, ProcessedItem, RefreshOptions, } from '@ts/grids/grid_core/data_controller/types'; import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; -import type { ModuleType } from '@ts/grids/grid_core/m_types'; +import type { ModuleType, OptionChanged } from '@ts/grids/grid_core/m_types'; import type { VirtualItemsCount } from '@ts/grids/grid_core/virtual_data_loader/types'; import gridCoreUtils from '../../m_utils'; @@ -123,7 +123,7 @@ export const virtualScrollingDataControllerExtender = ( const rowIndex = Math.floor(itemIndex) - rowIndexOffset; const { component } = this; const scrollable = component.getScrollable && component.getScrollable(); - const isSortingOperation = this.dataSource()?.operationTypes()?.sorting; + const isSortingOperation = this.dataSourceController.operationTypes()?.sorting; if (scrollable && !isSortingOperation && rowIndex >= 0) { const rowElement = component.getRowElement(rowIndex); @@ -152,7 +152,9 @@ export const virtualScrollingDataControllerExtender = ( return; } - const pageIndex = !isVirtualMode(this) && this.pageIndex() >= this.pageCount() ? this.pageCount() - 1 : this.pageIndex(); + const pageIndex = !isVirtualMode(this) && this.pageIndex() >= this.dataSourceController.pageCount() + ? this.dataSourceController.pageCount() - 1 + : this.pageIndex(); this._rowPageIndex = Math.ceil(pageIndex * this.pageSize() / this.getRowPageSize()); this._visibleItems = this.option(LEGACY_SCROLLING_MODE) === false ? null : []; this._viewportChanging = false; @@ -199,13 +201,13 @@ export const virtualScrollingDataControllerExtender = ( }, totalItemsCount() { if (isVirtualPaging(that)) { - return that.totalItemsCount(); + return that.dataSourceController.totalItemsCount(); } return that.option(LEGACY_SCROLLING_MODE) === false ? that._itemCount : that._items.filter(isItemCountable).length; }, hasKnownLastPage() { - return that.option(LEGACY_SCROLLING_MODE) === false ? that.hasKnownLastPage() : true; + return that.option(LEGACY_SCROLLING_MODE) === false ? that.dataSourceController.hasKnownLastPage() : true; }, pageIndex(index) { if (index !== undefined) { @@ -671,8 +673,8 @@ export const virtualScrollingDataControllerExtender = ( private _pageIndexIsValid(pageIndex) { let result = true; - if (isInfiniteMode(this) && this.hasKnownLastPage() || isVirtualMode(this)) { - result = pageIndex * this.pageSize() < this.totalItemsCount(); + if (isInfiniteMode(this) && this.dataSourceController.hasKnownLastPage() || isVirtualMode(this)) { + result = pageIndex * this.pageSize() < this.dataSourceController.totalItemsCount(); } return result; @@ -681,7 +683,7 @@ export const virtualScrollingDataControllerExtender = ( private isAllLoadedInAppendMode(): boolean { const loadedItemCount = this.pageSize() * (this._dataSource?.loadPageCount() ?? 0); - return isInfiniteMode(this) && this.totalItemsCount() < loadedItemCount; + return isInfiniteMode(this) && this.dataSourceController.totalItemsCount() < loadedItemCount; } // T1326786: the grid is scrolled to paging.pageIndex on the first resize only, @@ -764,7 +766,8 @@ export const virtualScrollingDataControllerExtender = ( } private handlePagesLoaded(viewportChanging: boolean): void { - const isLastPage = this.pageCount() > 0 && this.pageIndex() === this.pageCount() - 1; + const isLastPage = this.dataSourceController.pageCount() > 0 + && this.pageIndex() === this.dataSourceController.pageCount() - 1; if (viewportChanging || isLastPage) { this._updateVisiblePageIndex(); @@ -933,6 +936,28 @@ export const virtualScrollingDataControllerExtender = ( return result; } + protected resolvePaginate(enabled: boolean | undefined): boolean | undefined { + if (enabled === undefined) { + return undefined; + } + + return enabled || isVirtualPaging(this); + } + + protected requiresTotalCount(): boolean { + return !isInfiniteMode(this); + } + + public optionChanged(args: OptionChanged): void { + if (args.name === 'scrolling') { + args.handled = true; + this.reset(); + return; + } + + super.optionChanged(args); + } + public isEmpty(): boolean { return this.option(LEGACY_SCROLLING_MODE) === false ? !this.items(true).length : super.isEmpty.apply(this, arguments as any); } @@ -942,7 +967,7 @@ export const virtualScrollingDataControllerExtender = ( if (this.option(LEGACY_SCROLLING_MODE) === false && isVirtualPaging(this)) { const { pageIndex, loadPageCount } = this.getLoadPageParams(true); - const pageCount = this.pageCount(); + const pageCount = this.dataSourceController.pageCount(); result = pageIndex + loadPageCount >= pageCount; } else { diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts index 8a8071655a3b..f1aa08a5c890 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts @@ -11,6 +11,7 @@ import { getWindow } from '@js/core/utils/window'; import LoadIndicator from '@js/ui/load_indicator'; import errors from '@js/ui/widget/ui.errors'; import type { DataController } from '@ts/grids/grid_core/data_controller/data_controller'; +import type { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import type { ErrorHandlingViewController } from '@ts/grids/grid_core/error_handling/error_handling_view_controller'; import type { ModuleType } from '@ts/grids/grid_core/m_types'; @@ -39,6 +40,10 @@ import { isInfiniteMode, isVirtualMode, isVirtualPaging } from './utils/scrollin export type VirtualScrollingDataSourceAdapter = InstanceType<ReturnType<typeof dataSourceAdapterExtender>>; +interface VirtualScrollingDataSourceController extends DataSourceController { + getAdapter: () => VirtualScrollingDataSourceAdapter | null; +} + export const updateLoading = function (that) { const beginPageIndex = that._virtualScrollController.beginPageIndex(-1); @@ -488,6 +493,8 @@ export const resizing = (Base: ModuleType<ResizingController>) => class VirtualS export const rowsView = (Base: ModuleType<RowsView>) => class VirtualScrollingRowsViewExtender extends Base { protected _dataController!: DataController & Partial<StateStoringDataControllerExtension>; + protected dataSourceController!: VirtualScrollingDataSourceController; + protected _errorHandlingController!: ErrorHandlingViewController; private _isFixedTableRendering: any; @@ -502,6 +509,7 @@ export const rowsView = (Base: ModuleType<RowsView>) => class VirtualScrollingRo super.init(); this._errorHandlingController = this.getController('errorHandling'); + this.dataSourceController = this.getController('dataSource') as VirtualScrollingDataSourceController; this._dataController.pageChanged.add((pageIndex) => { const scrollTop = this._scrollTop; @@ -581,18 +589,18 @@ export const rowsView = (Base: ModuleType<RowsView>) => class VirtualScrollingRo const deferred = super._renderCore.apply(this, arguments as any); - const dataSource = this._dataController._dataSource as VirtualScrollingDataSourceAdapter | null | undefined; + const dataSourceAdapter = this.dataSourceController.getAdapter(); - if (dataSource && e) { + if (dataSourceAdapter && e) { const itemCount = e.items ? e.items.length : 20; const viewportSize = this._dataController // @ts-expect-error .viewportSize() || 20; if (gridCoreUtils.isVirtualRowRendering(this) && itemCount > 0 && this.option(LEGACY_SCROLLING_MODE) !== false) { - dataSource._renderTime = (Date.now() - startRenderTime) * viewportSize / itemCount; + dataSourceAdapter._renderTime = (Date.now() - startRenderTime) * viewportSize / itemCount; } else { - dataSource._renderTime = Date.now() - startRenderTime; + dataSourceAdapter._renderTime = Date.now() - startRenderTime; } } return deferred; @@ -707,7 +715,7 @@ export const rowsView = (Base: ModuleType<RowsView>) => class VirtualScrollingRo private _correctRowHeights(rowHeights) { const dataController = this._dataController; - const dataSource = dataController._dataSource; + const dataSourceAdapter = this.dataSourceController.getAdapter(); const correctedRowHeights: any = []; const visibleRows = dataController.getVisibleRows(); let itemSize = 0; @@ -726,7 +734,7 @@ export const rowsView = (Base: ModuleType<RowsView>) => class VirtualScrollingRo itemSize = 0; } lastLoadIndex = currentItem.loadIndex; - } else if (isItemCountableByDataSource(currentItem, dataSource as unknown as GroupCountableDataSource)) { + } else if (isItemCountableByDataSource(currentItem, dataSourceAdapter as unknown as GroupCountableDataSource)) { if (firstCountableItem) { firstCountableItem = false; } else { @@ -822,19 +830,18 @@ export const rowsView = (Base: ModuleType<RowsView>) => class VirtualScrollingRo } } - private _updateBottomLoading() { - const that = this; - const virtualMode = isVirtualMode(this); - const infiniteMode = isInfiniteMode(this); - const showBottomLoading = !that._dataController.hasKnownLastPage() && that._dataController.isLoaded() && (virtualMode || infiniteMode); - const $contentElement = that._findContentElement(); - const bottomLoadPanelElement = that._findBottomLoadPanel($contentElement); + private _updateBottomLoading(): void { + const showBottomLoading = !this.dataSourceController.hasKnownLastPage() + && this._dataController.isLoaded() + && isVirtualPaging(this); + const $contentElement = this._findContentElement(); + const bottomLoadPanelElement = this._findBottomLoadPanel($contentElement); if (showBottomLoading) { if (!bottomLoadPanelElement) { $('<div>') - .addClass(that.addWidgetPrefix(BOTTOM_LOAD_PANEL_CLASS)) - .append(that._createComponent($('<div>'), LoadIndicator, { + .addClass(this.addWidgetPrefix(BOTTOM_LOAD_PANEL_CLASS)) + .append(this._createComponent($('<div>'), LoadIndicator, { elementAttr: { role: null, 'aria-label': null, diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/__tests__/items.test.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/__tests__/items.test.ts index e1c1680aad3b..ac6f3d39f1a3 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/__tests__/items.test.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/__tests__/items.test.ts @@ -96,6 +96,14 @@ describe('isItemCountableByDataSource', () => { it('should not count other row types', () => { expect(isItemCountableByDataSource(asItem({ rowType: 'groupFooter' }), dataSource)).toBe(false); }); + + it('should not count a group row when there is no data source', () => { + expect(isItemCountableByDataSource(asItem({ rowType: 'group', data: 'countable' }), null)).toBe(false); + }); + + it('should still count a data row when there is no data source', () => { + expect(isItemCountableByDataSource(asItem({ rowType: 'data', isNewRow: false }), null)).toBe(true); + }); }); describe('updateItemIndices', () => { diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/items.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/items.ts index 28412664c581..17612088f962 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/items.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/utils/items.ts @@ -30,9 +30,9 @@ export const correctCount = ( export const isItemCountableByDataSource = ( item: ProcessedItem, - dataSource: GroupCountableDataSource, + dataSourceAdapter: GroupCountableDataSource | null | undefined, ): boolean => (item.rowType === 'data' && !item.isNewRow) - || (item.rowType === 'group' && dataSource.isGroupItemCountable(item.data)); + || (item.rowType === 'group' && (dataSourceAdapter?.isGroupItemCountable(item.data) ?? false)); export const updateItemIndices = (items: ProcessedItem[]): ProcessedItem[] => { items.forEach((item, index) => { diff --git a/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap b/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap index d3a512588033..b10cd6f13ea4 100644 --- a/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap +++ b/packages/devextreme/js/__internal/grids/new/card_view/__snapshots__/widget.test.ts.snap @@ -10,7 +10,7 @@ exports[`common initial render should be successfull 1`] = ` role="group" > <div - class="dx-gridbase-a11y-status-container dx-cardview-exclude-flexbox" + class="dx-screen-reader-only dx-cardview-exclude-flexbox" role="status" /> <div diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/accessibility/status.tsx b/packages/devextreme/js/__internal/grids/new/grid_core/accessibility/status.tsx index 819e89cdf83a..cb40c03dc84d 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/accessibility/status.tsx +++ b/packages/devextreme/js/__internal/grids/new/grid_core/accessibility/status.tsx @@ -1,10 +1,11 @@ +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; import { Component } from 'inferno'; import { CLASSES as BASE_CLASSES } from '../const'; const CLASSES = { ...BASE_CLASSES, - container: 'dx-gridbase-a11y-status-container', + container: SCREEN_READER_ONLY_CLASS, }; export interface A11yStatusContainerComponentProps { diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/public_methods.ts b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/public_methods.ts index 4d43b738af41..b1e2ccb921c4 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/public_methods.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/public_methods.ts @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import type { FilterDescriptor } from '@js/data'; import type DataSource from '@js/data/data_source'; -import { keysEqual } from '@ts/data/m_utils'; +import { keysEqual } from '@ts/data/utils'; import type { Constructor } from '../types'; import type { GridCoreNewBase } from '../widget'; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts index ac96d84c87ac..24c6f5c90bb8 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/data_controller/utils.ts @@ -6,7 +6,7 @@ import type { Store } from '@js/data'; import type { DataSourceLike } from '@js/data/data_source'; import DataSource from '@js/data/data_source'; import { normalizeDataSourceOptions } from '@js/data/data_source/utils'; -import { applyBatch } from '@ts/data/m_array_utils'; +import { applyBatch } from '@ts/data/array_utils'; import type { DataObject, @@ -35,7 +35,7 @@ export function normalizeDataSource( }; } - // TODO: research making second param not required + // @ts-expect-error the public DataSource constructor takes the public option types return new DataSource(normalizeDataSourceOptions(dataSourceLike, undefined)); } diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/data_source/compatibility.ts b/packages/devextreme/js/__internal/grids/new/grid_core/data_source/compatibility.ts new file mode 100644 index 000000000000..f618d194e289 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/new/grid_core/data_source/compatibility.ts @@ -0,0 +1,15 @@ +import { DataController } from '../data_controller/data_controller'; + +// Reused grid_core views reach the adapter through the dataSource controller. The new +// architecture has no adapter, so the mock answers from the DataSource itself. +export class CompatibilityDataSourceController { + public static dependencies = [DataController] as const; + + constructor( + private readonly realDataController: DataController, + ) {} + + public hasAdapter(): boolean { + return !!this.realDataController.dataSource.peek(); + } +} diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/di.ts b/packages/devextreme/js/__internal/grids/new/grid_core/di.ts index 56871436920a..4755ccb7a770 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/di.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/di.ts @@ -6,6 +6,7 @@ import { AccessibilityController } from './accessibility/controller'; import * as ColumnChooserModule from './column_chooser/index'; import * as ColumnsControllerModule from './columns_controller/index'; import * as DataControllerModule from './data_controller/index'; +import { CompatibilityDataSourceController } from './data_source/compatibility'; import { ConfirmController } from './editing/confirm_controller'; import { EditingController } from './editing/controller'; import { EditPopupView } from './editing/popup/view'; @@ -33,6 +34,7 @@ import { ToolbarView } from './toolbar/view'; export function register(diContext: DIContext): void { diContext.register(DataControllerModule.DataController); diContext.register(DataControllerModule.CompatibilityDataController); + diContext.register(CompatibilityDataSourceController); diContext.register(ItemsController); diContext.register(ColumnsControllerModule.ColumnsController); diContext.register(SelectionControllerModule.Controller); diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap index 6b48d20c0743..4ed2f2e07526 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap +++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap @@ -267,6 +267,12 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource 1`] = ` style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 2 + </div> </div> </div> <div @@ -635,6 +641,12 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with exclude style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 2 + </div> </div> </div> <div @@ -1003,6 +1015,12 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with exclude style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 2 + </div> </div> </div> <div @@ -1371,6 +1389,12 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with filter v style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 2 + </div> </div> </div> <div @@ -1850,6 +1874,12 @@ exports[`Options Column.HeaderFilter filterType + values: exclude filter 1`] = ` style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 5 + </div> </div> </div> <div @@ -2329,6 +2359,12 @@ exports[`Options Column.HeaderFilter filterType + values: exclude filter with va style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 5 + </div> </div> </div> <div @@ -2808,6 +2844,12 @@ exports[`Options Column.HeaderFilter filterType + values: filter values 1`] = ` style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 5 + </div> </div> </div> <div @@ -3139,6 +3181,12 @@ exports[`Options HeaderFilter texts: custom translations 1`] = ` style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 1 + </div> </div> </div> <div @@ -3470,6 +3518,12 @@ exports[`Options HeaderFilter texts: default translation 1`] = ` style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 1 + </div> </div> </div> <div diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap index 4d9df25d8c25..08a9545773b2 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap +++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap @@ -378,6 +378,12 @@ exports[`HeaderFilter View integration should render popup with list by default style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 5 + </div> </div> </div> <div @@ -857,6 +863,12 @@ exports[`HeaderFilter View integration should render popup with tree list if dat style="width: 222px; height: 90px;" /> </div> + <div + class="dx-screen-reader-only" + role="status" + > + Items: 5 + </div> </div> </div> <div diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts index 76ee06aa32f8..1072316e25ba 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/legacy_header_filter.ts @@ -16,7 +16,7 @@ import { isDefined, isFunction, isObject } from '@js/core/utils/type'; import messageLocalization from '@js/localization/message'; import filteringUtils from '@js/ui/shared/filtering'; import { extend } from '@ts/core/utils/m_extend'; -import { normalizeDataSourceOptions as oldNormalizeDataSourceOptions } from '@ts/data/data_source/m_utils'; +import { normalizeDataSourceOptions as oldNormalizeDataSourceOptions } from '@ts/data/data_source/utils'; import { convertDataFromUTCToLocal, getFormatOptions, @@ -158,7 +158,6 @@ export const getDataSourceOptions = ( const options: any = {}; if (isDefined(headerFilterDataSource) && !isFunction(headerFilterDataSource)) { - // @ts-expect-error options.dataSource = oldNormalizeDataSourceOptions(headerFilterDataSource); } else { const cutoffLevel = Array.isArray(group) ? group.length - 1 : 0; diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts index 76725b7d6086..cb045d2e63b8 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget.ts @@ -16,6 +16,7 @@ import * as ColumnChooserModule from './column_chooser/index'; import { CompatibilityColumnsController } from './columns_controller/compatibility'; import * as ColumnsControllerModule from './columns_controller/index'; import * as DataControllerModule from './data_controller/index'; +import { CompatibilityDataSourceController } from './data_source/compatibility'; import * as di from './di'; import * as EditingModule from './editing/index'; import { EditPopupView } from './editing/popup/view'; @@ -100,6 +101,7 @@ export class GridCoreNewBase< this.diContext.registerInstance(WidgetMock, new WidgetMock( this, this.diContext.get(DataControllerModule.CompatibilityDataController), + this.diContext.get(CompatibilityDataSourceController), this.diContext.get(CompatibilityColumnsController), this.diContext.get(CompatibilityHeaderFilterController), this.diContext.get(CompatibilityFilterSyncController), diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/widget_mock.ts b/packages/devextreme/js/__internal/grids/new/grid_core/widget_mock.ts index 5c99d89670cd..6eeca8104fd3 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/widget_mock.ts +++ b/packages/devextreme/js/__internal/grids/new/grid_core/widget_mock.ts @@ -1,5 +1,6 @@ import type { CompatibilityColumnsController } from './columns_controller/compatibility'; import type { CompatibilityDataController } from './data_controller'; +import type { CompatibilityDataSourceController } from './data_source/compatibility'; import type { CompatibilityFilterSyncController } from './filtering/filter_sync/compatibility'; import type { CompatibilityHeaderFilterController } from './filtering/header_filter/compatibility'; import type { GridCoreNewBase } from './widget'; @@ -9,6 +10,7 @@ export class WidgetMock { private readonly _controllers = { data: this.data, + dataSource: this.dataSource, columns: this.columns, headerFilter: this.headerFilter, filterSync: this.filterSync, @@ -18,6 +20,7 @@ export class WidgetMock { // eslint-disable-next-line @typescript-eslint/no-explicit-any private readonly widget: GridCoreNewBase<any>, private readonly data: CompatibilityDataController, + private readonly dataSource: CompatibilityDataSourceController, private readonly columns: CompatibilityColumnsController, private readonly headerFilter: CompatibilityHeaderFilterController, private readonly filterSync: CompatibilityFilterSyncController, diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/data_source/m_data_source.ts b/packages/devextreme/js/__internal/grids/pivot_grid/data_source/m_data_source.ts index b5b1683bc03f..ef1a61eaa54e 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/data_source/m_data_source.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/data_source/m_data_source.ts @@ -341,7 +341,6 @@ class PivotGridDataSource { createLocalOrRemoteStore(dataSourceOptions, notifyProgress) { const StoreConstructor = dataSourceOptions.remoteOperations || dataSourceOptions.paginate ? RemoteStore : LocalStore; - // @ts-expect-error return new StoreConstructor(extend(normalizeDataSourceOptions(dataSourceOptions), { onChanged: null, onLoadingChanged: null, diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/local_store/m_local_store.ts b/packages/devextreme/js/__internal/grids/pivot_grid/local_store/m_local_store.ts index 174166bf6091..4ee6f3179b67 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/local_store/m_local_store.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/local_store/m_local_store.ts @@ -424,7 +424,6 @@ const LocalStore = Class.inherit((function () { if (dataSource.store() instanceof CustomStore && filter) { filter = processFilter(filter, fieldSelectors); - // @ts-expect-error return dataQuery(dataSource.items()).filter(filter).toArray(); } diff --git a/packages/devextreme/js/__internal/grids/pivot_grid/remote_store/m_remote_store.ts b/packages/devextreme/js/__internal/grids/pivot_grid/remote_store/m_remote_store.ts index 3412120c14f2..b514df7df779 100644 --- a/packages/devextreme/js/__internal/grids/pivot_grid/remote_store/m_remote_store.ts +++ b/packages/devextreme/js/__internal/grids/pivot_grid/remote_store/m_remote_store.ts @@ -561,7 +561,6 @@ class RemoteStore { skip: 0, take: 20, }).done((data) => { - // @ts-expect-error const normalizedArguments = normalizeLoadResult(data); d.resolve(pivotGridUtils.discoverObjectFields(normalizedArguments.data, fields)); }).fail(d.reject); diff --git a/packages/devextreme/js/__internal/grids/tree_list/__tests__/__mock__/helpers/utils.ts b/packages/devextreme/js/__internal/grids/tree_list/__tests__/__mock__/helpers/utils.ts new file mode 100644 index 000000000000..ffd6ce2fb2dd --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/__tests__/__mock__/helpers/utils.ts @@ -0,0 +1,66 @@ +import { jest } from '@jest/globals'; +import fx from '@js/common/core/animation/fx'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import type { Properties as TreeListProperties } from '@js/ui/tree_list'; +import TreeList from '@js/ui/tree_list'; +import type { Controllers, Views } from '@ts/grids/grid_core/m_types'; +import { TreeListModel } from '@ts/grids/tree_list/__tests__/__mock__/model/tree_list'; + +export interface TreeListInstance extends TreeList { + getController: <T extends keyof Controllers>(name: T) => Controllers[T]; + getView: <T extends keyof Views>(name: T) => Views[T]; +} + +export const SELECTORS = { + treeListContainer: '#treeListContainer', +}; + +export const TREELIST_CONTAINER_ID = 'treeListContainer'; + +export const createTreeList = ( + options: TreeListProperties = {}, +): Promise<{ + $container: dxElementWrapper; + component: TreeListModel; + instance: TreeListInstance; +}> => new Promise((resolve) => { + const $container = $('<div>') + .attr('id', TREELIST_CONTAINER_ID) + .appendTo(document.body); + + const treeListOptions: TreeListProperties = { + keyExpr: 'id', + ...options, + }; + + const instance = new TreeList( + $container.get(0) as HTMLDivElement, + treeListOptions, + ) as TreeListInstance; + const component = new TreeListModel($container.get(0) as HTMLElement); + + jest.runAllTimers(); + + resolve({ + $container, + component, + instance, + }); +}); + +export const beforeTest = (): void => { + fx.off = true; + jest.useFakeTimers(); +}; + +export const afterTest = (): void => { + const $container = $(SELECTORS.treeListContainer); + const treeList = TreeList.getInstance($container.get(0)); + + treeList?.dispose(); + $container.remove(); + jest.clearAllMocks(); + jest.useRealTimers(); + fx.off = false; +}; diff --git a/packages/devextreme/js/__internal/grids/tree_list/__tests__/state_storing.user_state.test.ts b/packages/devextreme/js/__internal/grids/tree_list/__tests__/state_storing.user_state.test.ts new file mode 100644 index 000000000000..5158ac72be87 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/__tests__/state_storing.user_state.test.ts @@ -0,0 +1,28 @@ +import { + afterEach, beforeEach, describe, expect, it, +} from '@jest/globals'; + +import { + afterTest, + beforeTest, + createTreeList, +} from './__mock__/helpers/utils'; + +describe('TreeList user state', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('should include the expandedRowKeys', async () => { + const { instance } = await createTreeList({ + dataSource: [{ id: 1, parentId: 0, name: 'Alex' }], + expandedRowKeys: [1], + }); + + expect(instance.getController('data').getUserState()).toEqual({ + searchText: '', + expandedRowKeys: [1], + pageIndex: 0, + pageSize: 20, + }); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts index e87ee434b1e8..6120980c9b71 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_controller/m_data_controller.ts @@ -2,24 +2,14 @@ import { equalByValue } from '@js/core/utils/common'; import { Deferred } from '@js/core/utils/deferred'; import { extend } from '@js/core/utils/extend'; import { DataController, dataControllerModule } from '@ts/grids/grid_core/data_controller/data_controller'; -import type { DataSourceAdapterProvider } from '@ts/grids/grid_core/data_source_adapter/types'; import type { RowKey } from '@ts/grids/grid_core/m_types'; import type { DataSourceAdapterTreeList } from '../data_source_adapter/m_data_source_adapter'; -import dataSourceAdapterProvider from '../data_source_adapter/m_data_source_adapter'; import treeListCore from '../m_core'; export class TreeListDataController extends DataController { public declare _dataSource?: DataSourceAdapterTreeList | null; - public dataSource(): DataSourceAdapterTreeList | undefined { - return this._dataSource ?? undefined; - } - - protected _getDataSourceAdapterProvider(): DataSourceAdapterProvider { - return dataSourceAdapterProvider; - } - private _getNodeLevel(node) { let level = -1; while (node.parent) { @@ -83,18 +73,6 @@ export class TreeListDataController extends DataController { super.init.apply(this, arguments as any); } - public keyOf(data) { - const dataSource = this._dataSource; - - if (dataSource) { - return dataSource.keyOf(data); - } - } - - public key() { - return this._dataSource?.getKeyExpr(); - } - public publicMethods() { return super.publicMethods().concat(['expandRow', 'collapseRow', 'isRowExpanded', 'getRootNode', 'getNodeByKey', 'loadDescendants', 'forEachNode']); } diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source/__tests__/data_source_controller.integration.test.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source/__tests__/data_source_controller.integration.test.ts new file mode 100644 index 000000000000..329df8827c2e --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source/__tests__/data_source_controller.integration.test.ts @@ -0,0 +1,93 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import errors from '@js/ui/widget/ui.errors'; +import { getMirroredAdapter } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; + +import { + afterTest, + beforeTest, + createTreeList, +} from '../../__tests__/__mock__/helpers/utils'; +import { TreeListDataSourceController } from '../data_source_controller'; + +const DATA = [ + { id: 1, parentId: 0, value: 'a' }, + { id: 2, parentId: 1, value: 'b' }, +]; + +describe('TreeList dataSource controller', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('is the TreeList subclass', async () => { + const { instance } = await createTreeList({ dataSource: DATA }); + + expect(instance.getController('dataSource')) + .toBeInstanceOf(TreeListDataSourceController); + }); + + it('builds a TreeList adapter', async () => { + const { instance } = await createTreeList({ dataSource: DATA }); + const adapter = instance.getController('dataSource').getAdapter(); + + expect(adapter).toBeTruthy(); + expect('forEachNode' in (adapter as object)).toBe(true); + }); + + it('holds the same adapter object as DataController', async () => { + const { instance } = await createTreeList({ dataSource: DATA }); + const dataSourceController = instance.getController('dataSource'); + + expect(dataSourceController.hasAdapter()).toBe(true); + expect(dataSourceController.getAdapter()).toBe(getMirroredAdapter(instance)); + }); + + it('does not warn W1011, because the override does not apply to TreeList', async () => { + const log = jest.spyOn(errors, 'log').mockImplementation(() => {}); + + try { + await createTreeList({ + dataSource: { store: { type: 'array', data: DATA } }, + keyExpr: 'id', + }); + + expect(log).not.toHaveBeenCalledWith('W1011'); + } finally { + log.mockRestore(); + } + }); +}); + +describe('TreeList answers key and keyOf from the key expression', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + // The base controller answers both from the store, which has no key in either case here. + it('falls back to the default key expression when nothing declares a key', async () => { + const { instance } = await createTreeList({ dataSource: DATA, keyExpr: undefined }); + const dataSourceController = instance.getController('dataSource'); + + expect(dataSourceController.key()).toBe('id'); + expect(dataSourceController.keyOf(DATA[1])).toBe(2); + }); + + it('parts company with the store key, which is what store-based callers still read', async () => { + const { instance } = await createTreeList({ dataSource: DATA, keyExpr: undefined }); + const dataSourceController = instance.getController('dataSource'); + + expect(dataSourceController.store()?.key()).toBeUndefined(); + expect(dataSourceController.key()).toBe('id'); + }); + + it('uses the declared key expression when the store has no key', async () => { + const { instance } = await createTreeList({ + dataSource: { store: { type: 'array', data: DATA } }, + keyExpr: 'value', + }); + const dataSourceController = instance.getController('dataSource'); + + expect(dataSourceController.key()).toBe('value'); + expect(dataSourceController.keyOf(DATA[1])).toBe('b'); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts new file mode 100644 index 000000000000..87467224ba13 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_controller.ts @@ -0,0 +1,21 @@ +import type { StoreKey } from '@ts/data/abstract_store'; +import { DataSourceController } from '@ts/grids/grid_core/data_source/data_source_controller'; +import type { DataSourceAdapterProvider, RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; +import type { RowKey } from '@ts/grids/grid_core/m_types'; +import type { DataSourceAdapterTreeList } from '@ts/grids/tree_list/data_source_adapter/m_data_source_adapter'; +import dataSourceAdapterProvider from '@ts/grids/tree_list/data_source_adapter/m_data_source_adapter'; + +export class TreeListDataSourceController + extends DataSourceController<DataSourceAdapterTreeList> { + protected getAdapterProvider(): DataSourceAdapterProvider<DataSourceAdapterTreeList> { + return dataSourceAdapterProvider; + } + + public key(): StoreKey | undefined { + return this.adapter?.getKeyExpr(); + } + + public keyOf(data: RawItemData): RowKey | undefined { + return this.adapter?.keyOf(data); + } +} diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_module.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_module.ts new file mode 100644 index 000000000000..d3b5072248af --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source/data_source_module.ts @@ -0,0 +1,9 @@ +import treeListCore from '@ts/grids/tree_list/m_core'; + +import { TreeListDataSourceController } from './data_source_controller'; + +treeListCore.registerModule('dataSource', { + controllers: { + dataSource: TreeListDataSourceController, + }, +}); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts index b76ec5607f45..35ad2a94f1b2 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/m_data_source_adapter.ts @@ -15,9 +15,15 @@ import type { BeforePushEvent } from '@ts/data/types'; import type { CustomLoadResult } from '@ts/grids/grid_core/data_source_adapter/custom_loader'; import DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import { createDataSourceAdapterProvider } from '@ts/grids/grid_core/data_source_adapter/provider'; +import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import treeListCore from '../m_core'; +import type { LoadOperation, NodeByKey, TreeNode } from './types'; +import type { NodesContext } from './utils/nodes'; +import { + convertItemToNode, createNodesByItems, fillNodes, getVisibleNodes, +} from './utils/nodes'; const { queryByOptions } = storeHelper; @@ -39,9 +45,7 @@ const getChildKeys = function (that, keys) { return childKeys; }; -// @ts-expect-error const applySorting = (data: any[], sort: any): any => queryByOptions( - // @ts-expect-error query(data), { sort, @@ -67,11 +71,11 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { private _isChildrenLoaded: any; - private _nodeByKey: any; + private _nodeByKey!: NodeByKey; private _isReload: any; - private _rootNode: any; + private _rootNode?: TreeNode; public _isNodesInitializing = false; @@ -139,85 +143,15 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { }); } - private _calculateHasItems(node, options) { - const that = this; - const { parentIds } = options.storeLoadOptions; - let hasItems; - const isFullBranch = isFullBranchFilterMode(that); - - if (that._hasItemsGetter && (parentIds || !options.storeLoadOptions.filter || isFullBranch)) { - hasItems = that._hasItemsGetter(node.data); - } - - if (hasItems === undefined) { - if (!that._isChildrenLoaded[node.key] && options.remoteOperations.filtering && (parentIds || isFullBranch)) { - hasItems = true; - } else if (options.loadOptions.filter && !options.remoteOperations.filtering && isFullBranch) { - hasItems = node.children.length; - } else { - hasItems = node.hasChildren; - } - } - return !!hasItems; - } - - private _fillVisibleItemsByNodes(nodes, options, result) { - for (let i = 0; i < nodes.length; i++) { - if (nodes[i].visible) { - result.push(nodes[i]); - } - - if ((this.isRowExpanded(nodes[i].key, options) || !nodes[i].visible) && nodes[i].hasChildren && nodes[i].children.length) { - this._fillVisibleItemsByNodes(nodes[i].children, options, result); - } - } - } - - private _convertItemToNode(item, rootValue, nodeByKey) { - const key = this._keyGetter(item); - let parentId = this._parentIdGetter(item); - - parentId = isDefined(parentId) ? parentId : rootValue; - const parentNode = nodeByKey[parentId] = nodeByKey[parentId] || { key: parentId, children: [] }; - - const node = nodeByKey[key] = nodeByKey[key] || { key, children: [] }; - node.data = item; - node.parent = parentNode; - - return node; - } - - private _createNodesByItems(items, visibleItems) { - const that = this; - const rootValue: any = that.option('rootValue'); - const visibleByKey = {}; - const nodeByKey = that._nodeByKey = {}; - let i; - - if (visibleItems) { - for (i = 0; i < visibleItems.length; i++) { - visibleByKey[this._keyGetter(visibleItems[i])] = true; - } - } - - for (i = 0; i < items.length; i++) { - const node = that._convertItemToNode(items[i], rootValue, nodeByKey); - - if (node.key === undefined) { - return; - } - - node.visible = !visibleItems || !!visibleByKey[node.key]; - if (node.parent) { - node.parent.children.push(node); - } - } - - const rootNode = nodeByKey[rootValue] || { key: rootValue, children: [] }; - - rootNode.level = -1; - - return rootNode; + private _getNodesContext(): NodesContext { + return { + rootValue: this.option('rootValue'), + isFullBranchFilterMode: isFullBranchFilterMode(this), + keyGetter: this._keyGetter, + parentIdGetter: this._parentIdGetter, + hasItemsGetter: this._hasItemsGetter, + isChildrenLoaded: this._isChildrenLoaded, + }; } private _convertDataToPlainStructure(data, parentId?, result?) { @@ -227,7 +161,6 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { result = result || []; for (let i = 0; i < data.length; i++) { - // @ts-expect-error const item = createObjectWithChanges(data[i]); key = this._keyGetter(item); @@ -438,11 +371,13 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { return d.resolve(data); } - let cachedNodes = keys.map((id) => this.getNodeByKey(id)).filter((node) => node && node.data); + let cachedNodes = keys + .map((id) => this.getNodeByKey(id)) + .filter((node) => node && node.data) as TreeNode[]; if (cachedNodes.length === keys.length) { if (needChildren) { - cachedNodes = cachedNodes.reduce((result, node) => result.concat(node.children), []); + cachedNodes = cachedNodes.reduce((result: TreeNode[], node) => result.concat(node.children), []); } if (cachedNodes.length) { @@ -479,7 +414,6 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { if (loadedData.length) { if (needLocalFiltering) { - // @ts-expect-error loadedData = query(loadedData).filter(filter).toArray(); } @@ -571,20 +505,20 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { const parentNode = that.getNodeByKey(parentId); if (parentNode) { - const rootValue = that.option('rootValue'); - const node = that._convertItemToNode(change.data, rootValue, that._nodeByKey); + const node = convertItemToNode(change.data, that._nodeByKey, that._getNodesContext()); node.hasChildren = false; - node.level = parentNode.level + 1; + node.level = parentNode.level! + 1; node.visible = true; parentNode.children.push(node); - that._isChildrenLoaded[node.key] = true; + that._isChildrenLoaded[node.key as string] = true; that._setHasItems(parentNode, true); if ((!parentNode.parent || that.isRowExpanded(parentNode.key)) && change.index !== undefined) { + // @ts-expect-error Badly typed items() let index = that.items().indexOf(parentNode) + 1; index += change.index >= 0 ? Math.min(change.index, parentNode.children.length) : parentNode.children.length; @@ -625,7 +559,6 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { public customizeLoadResultHandler(options) { const data = options.data = this._convertDataToPlainStructure(options.data); if (!options.remoteOperations.filtering && options.loadOptions.filter) { - // @ts-expect-error options.fullData = queryByOptions(query(options.data), { sort: options.loadOptions && options.loadOptions.sort }).toArray(); } this._updateHasItemsMap(options); @@ -640,61 +573,29 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { } } - private _fillNodes(nodes, options, expandedRowKeys, level?) { - const isFullBranch = isFullBranchFilterMode(this); - - level = level || 0; - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - let needToExpand = false; - - // node.hasChildren = false; - this._fillNodes(nodes[i].children, options, expandedRowKeys, level + 1); - - node.level = level; - node.hasChildren = this._calculateHasItems(node, options); - - if (node.visible && node.hasChildren) { - if (isFullBranch) { - if (node.children.filter((node) => node.visible).length) { - needToExpand = true; - } else if (node.children.length) { - treeListCore.foreachNodes(node.children, (node) => { - node.visible = true; - }); - } - } else { - needToExpand = true; - } - if (options.expandVisibleNodes && needToExpand) { - expandedRowKeys.push(node.key); - } - } - - if (node.visible || node.hasChildren) { - node.parent.hasChildren = true; - } - } - } - - private _processTreeStructure(options, visibleItems?) { - let { data } = options; + private _processTreeStructure(options: LoadOperation, visibleItems?: RawItemData[]): void { + let data = options.data as RawItemData[]; const { parentIds } = options.storeLoadOptions; - const expandedRowKeys = []; if (parentIds && parentIds.length || this._isReload) { if (options.fullData) { data = options.fullData; - visibleItems = visibleItems || options.data; + visibleItems ??= options.data as RawItemData[]; } - this._rootNode = this._createNodesByItems(data, visibleItems); + const nodesContext = this._getNodesContext(); + const { rootNode, nodeByKey } = createNodesByItems(data, visibleItems, nodesContext); + + this._nodeByKey = nodeByKey; + this._rootNode = rootNode; + if (!this._rootNode) { - // @ts-expect-error - options.data = new Deferred().reject(errors.Error('E1046', this.getKeyExpr())); + // @ts-expect-error badly typed Deferred + options.data = Deferred().reject(errors.Error('E1046', this.getKeyExpr())); return; } - this._fillNodes(this._rootNode.children, options, expandedRowKeys); + + const expandedRowKeys = fillNodes(this._rootNode.children, options, nodesContext); this._isNodesInitializing = true; if (options.collapseVisibleNodes || expandedRowKeys.length) { @@ -705,18 +606,20 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { this._isNodesInitializing = false; } - const resultData = []; - - this._fillVisibleItemsByNodes(this._rootNode.children, options, resultData); + const resultData = getVisibleNodes( + this._rootNode!.children, + (key) => this.isRowExpanded(key, options), + ); + // @ts-expect-error From here on the rows are nodes, not the loaded items the base type describes. options.data = resultData; this._totalItemsCount = resultData.length; } - protected customizeLoadResultHandlerCore(options) { + protected customizeLoadResultHandlerCore(options: LoadOperation): void { const that = this; const { data } = options; - const filter = options.storeLoadOptions.filter || options.loadOptions.filter; + const filter = options.storeLoadOptions.filter || options.loadOptions?.filter; const filterMode = that.option('filterMode'); let visibleItems; const { parentIds } = options.storeLoadOptions; @@ -730,6 +633,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { if (filterMode === 'matchOnly') { visibleItems = data; } + return that._loadParents(data, options).done((data) => { that._loadChildrenIfNeed(data, options).done((data) => { options.data = data; @@ -847,10 +751,12 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { return this._isNodesInitializing ? new Deferred().resolve() : this.load(); } - public getNodeByKey(key) { + public getNodeByKey(key): TreeNode | undefined { if (this._nodeByKey) { return this._nodeByKey[key]; } + + return undefined; } private getNodeLeafKeys() { @@ -918,7 +824,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any public forEachNode(nodeCallback?: (node: any) => void) { - let nodes = []; + let nodes: TreeNode[] = []; let callback; if (arguments.length === 1) { @@ -926,7 +832,7 @@ export class DataSourceAdapterTreeList extends DataSourceAdapter { callback = arguments[0]; const rootNode = this.getRootNode(); - nodes = rootNode && rootNode.children || []; + nodes = rootNode?.children ?? []; } else if (arguments.length === 2) { // eslint-disable-next-line prefer-destructuring callback = arguments[1]; diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/types.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/types.ts new file mode 100644 index 000000000000..443ab021fa11 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/types.ts @@ -0,0 +1,22 @@ +import type { LoadOperation as BaseLoadOperation, RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; + +export interface LoadOperation extends BaseLoadOperation { + storeLoadOptions: BaseLoadOperation['storeLoadOptions'] & { + parentIds?: unknown[]; + }; + fullData?: RawItemData[]; + collapseVisibleNodes?: boolean; + expandVisibleNodes?: boolean; +} + +export interface TreeNode { + key: unknown; + children: TreeNode[]; + data?: unknown; + parent?: TreeNode; + level?: number; + visible?: boolean; + hasChildren?: boolean; +} + +export type NodeByKey = Record<string, TreeNode>; diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/__tests__/nodes.test.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/__tests__/nodes.test.ts new file mode 100644 index 000000000000..ea1141373c60 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/__tests__/nodes.test.ts @@ -0,0 +1,637 @@ +import { describe, expect, it } from '@jest/globals'; + +import type { LoadOperation, NodeByKey, TreeNode } from '../../types'; +import type { NodesContext } from '../nodes'; +import { + convertItemToNode, createNodesByItems, fillNodes, getVisibleNodes, +} from '../nodes'; + +interface Item { + [field: string]: unknown; + id?: unknown; + parentId?: unknown; + hasItems?: unknown; +} + +const ROOT = 0; + +function createContext(overrides?: Partial<NodesContext>): NodesContext { + return { + rootValue: ROOT, + isFullBranchFilterMode: false, + keyGetter: (data): unknown => (data as Item).id, + parentIdGetter: (data): unknown => (data as Item).parentId, + hasItemsGetter: undefined, + isChildrenLoaded: {}, + ...overrides, + }; +} + +function createLoadOptions(options: { + parentIds?: unknown[]; + storeFilter?: unknown; + loadFilter?: unknown; + remoteFiltering?: boolean; + expandVisibleNodes?: boolean; +} = {}): LoadOperation { + return { + storeLoadOptions: { + parentIds: options.parentIds, + filter: options.storeFilter, + }, + loadOptions: { + filter: options.loadFilter, + }, + remoteOperations: { + filtering: options.remoteFiltering, + }, + expandVisibleNodes: options.expandVisibleNodes, + }; +} + +function buildTree(items: Item[], visibleKeys?: unknown[]): { root: TreeNode; nodes: NodeByKey } { + const visibleItems = visibleKeys && items.filter((item) => visibleKeys.includes(item.id)); + const { rootNode, nodeByKey } = createNodesByItems(items, visibleItems, createContext()); + + return { root: rootNode as TreeNode, nodes: nodeByKey }; +} + +describe('convertItemToNode', () => { + it('wires the node to a placeholder parent and registers both by key', () => { + const nodeByKey = {}; + const item = { id: 2, parentId: 1 }; + + const node = convertItemToNode(item, nodeByKey, createContext()); + + expect(node).toMatchObject({ key: 2, data: item, children: [] }); + expect(node.parent).toEqual({ key: 1, children: [] }); + expect(nodeByKey).toEqual({ 1: node.parent, 2: node }); + }); + + const rootFallbackCases: [string, unknown][] = [ + ['undefined', undefined], + ['null', null], + ]; + + it.each(rootFallbackCases)('attaches the item to rootValue when its parent id is %s', (_, parentId) => { + const node = convertItemToNode({ id: 1, parentId }, {}, createContext()); + + expect(node.parent?.key).toBe(ROOT); + }); + + it('reuses an already registered node, keeping the children it accumulated', () => { + const nodeByKey = {}; + const context = createContext(); + + // The child is converted first, so node `1` only exists as a placeholder parent. + const child = convertItemToNode({ id: 2, parentId: 1 }, nodeByKey, context); + child.parent?.children.push(child); + + const parentItem = { id: 1, parentId: ROOT }; + const parent = convertItemToNode(parentItem, nodeByKey, context); + + expect(parent).toBe(child.parent); + expect(parent.data).toBe(parentItem); + expect(parent.children).toEqual([child]); + }); + + it('reuses an already registered parent', () => { + const nodeByKey = {}; + const context = createContext(); + + const first = convertItemToNode({ id: 2, parentId: 1 }, nodeByKey, context); + const second = convertItemToNode({ id: 3, parentId: 1 }, nodeByKey, context); + + expect(second.parent).toBe(first.parent); + }); +}); + +describe('createNodesByItems', () => { + it('builds a tree and indexes every node by key', () => { + const items = [ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 1 }, + ]; + + const { rootNode, nodeByKey } = createNodesByItems(items, undefined, createContext()); + + expect(rootNode?.key).toBe(ROOT); + expect(rootNode?.level).toBe(-1); + expect(rootNode?.children.map((node) => node.key)).toEqual([1]); + expect(rootNode?.children[0].children.map((node) => node.key)).toEqual([2, 3]); + expect(Object.keys(nodeByKey)).toEqual(['0', '1', '2', '3']); + }); + + it('does not depend on item order — a child may precede its parent', () => { + const items = [ + { id: 2, parentId: 1 }, + { id: 1, parentId: ROOT }, + ]; + + const { rootNode } = createNodesByItems(items, undefined, createContext()); + + expect(rootNode?.children.map((node) => node.key)).toEqual([1]); + expect(rootNode?.children[0].children.map((node) => node.key)).toEqual([2]); + expect(rootNode?.children[0].data).toBe(items[1]); + }); + + it('leaves an orphaned branch out of the root, but keeps it indexed', () => { + const items = [{ id: 2, parentId: 'missing' }]; + + const { rootNode, nodeByKey } = createNodesByItems(items, undefined, createContext()); + + expect(rootNode?.children).toEqual([]); + expect(nodeByKey.missing.children.map((node) => node.key)).toEqual([2]); + }); + + const rootValueCases: [string, unknown][] = [ + ['null', null], + ['a string', 'root'], + ]; + + it.each(rootValueCases)('supports %s as rootValue', (_, rootValue) => { + const items = [{ id: 1, parentId: rootValue }]; + + const { rootNode } = createNodesByItems(items, undefined, createContext({ rootValue })); + + expect(rootNode?.key).toBe(rootValue); + expect(rootNode?.children.map((node) => node.key)).toEqual([1]); + }); + + it('returns a synthetic root when there are no items', () => { + const { rootNode, nodeByKey } = createNodesByItems([], undefined, createContext()); + + expect(rootNode).toEqual({ key: ROOT, children: [], level: -1 }); + expect(nodeByKey).toEqual({}); + }); + + it('marks every node visible when visibleItems is not passed', () => { + const items = [{ id: 1, parentId: ROOT }, { id: 2, parentId: 1 }]; + + const { nodeByKey } = createNodesByItems(items, undefined, createContext()); + + expect(nodeByKey[1].visible).toBe(true); + expect(nodeByKey[2].visible).toBe(true); + }); + + it('marks only the nodes listed in visibleItems visible', () => { + const items = [{ id: 1, parentId: ROOT }, { id: 2, parentId: 1 }]; + + const { nodeByKey } = createNodesByItems(items, [items[1]], createContext()); + + expect(nodeByKey[1].visible).toBe(false); + expect(nodeByKey[2].visible).toBe(true); + }); + + it('hides every node when visibleItems is an empty array', () => { + const items = [{ id: 1, parentId: ROOT }]; + + const { nodeByKey } = createNodesByItems(items, [], createContext()); + + expect(nodeByKey[1].visible).toBe(false); + }); + + it('bails out without a root node when an item has no key (E1046)', () => { + const items = [ + { id: 1, parentId: ROOT }, + { id: undefined, parentId: 1 }, + { id: 3, parentId: 1 }, + ]; + + const { rootNode, nodeByKey } = createNodesByItems(items, undefined, createContext()); + + expect(rootNode).toBeUndefined(); + // The adapter keeps this partially filled map, so the nodes seen so far stay indexed. + expect(nodeByKey[1]).toMatchObject({ key: 1 }); + expect(nodeByKey[3]).toBeUndefined(); + }); +}); + +describe('fillNodes', () => { + it('assigns the level of every node depth first', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + { id: 4, parentId: ROOT }, + ]); + + fillNodes(root.children, createLoadOptions(), createContext()); + + expect([1, 2, 3, 4].map((key) => nodes[key].level)).toEqual([0, 1, 2, 0]); + }); + + it('returns no keys for an empty node list', () => { + expect(fillNodes([], createLoadOptions(), createContext())).toEqual([]); + }); + + describe('hasChildren propagation', () => { + it('propagates up from the visible leaves', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + ], [3]); + + fillNodes(root.children, createLoadOptions(), createContext()); + + expect([1, 2, 3].map((key) => nodes[key].hasChildren)).toEqual([true, true, false]); + expect(root.hasChildren).toBe(true); + }); + + it('stays false on a branch of hidden leaves', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + ], []); + + fillNodes(root.children, createLoadOptions(), createContext()); + + expect(nodes[1].hasChildren).toBe(false); + expect(root.hasChildren).toBeUndefined(); + }); + + it('is overridden by hasItemsExpr on the node itself', () => { + const context = createContext({ + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: false }, + { id: 2, parentId: 1 }, + ], [2]); + + fillNodes(root.children, createLoadOptions(), context); + + expect(nodes[1].hasChildren).toBe(false); + }); + }); + + describe('hasChildren of one node', () => { + interface Fixture { + items: Item[]; + visibleKeys: unknown[]; + } + + const leaf: Fixture = { + items: [{ id: 1, parentId: ROOT }], + visibleKeys: [], + }; + + const withVisibleChild: Fixture = { + items: [{ id: 1, parentId: ROOT }, { id: 2, parentId: 1 }], + visibleKeys: [2], + }; + + const withHiddenChild: Fixture = { + items: [{ id: 1, parentId: ROOT }, { id: 2, parentId: 1 }], + visibleKeys: [], + }; + + function calculateHasChildren( + fixture: Fixture, + options: LoadOperation, + context: NodesContext, + ): boolean | undefined { + const { root, nodes } = buildTree(fixture.items, fixture.visibleKeys); + + fillNodes(root.children, options, context); + + return nodes[1].hasChildren; + } + + describe('hasItemsExpr', () => { + const getterCases: [string, unknown, boolean][] = [ + ['true', true, true], + ['false', false, false], + ]; + + it.each(getterCases)('follows the getter when it returns %s', (_, hasItems, expected) => { + const context = createContext({ hasItemsGetter: () => hasItems }); + + // Shaped so the fallback would answer the opposite: the getter has to win. + const fixture = expected ? leaf : withVisibleChild; + + expect(calculateHasChildren(fixture, createLoadOptions(), context)).toBe(expected); + }); + + it('falls back to the children when the getter returns undefined', () => { + const context = createContext({ hasItemsGetter: () => undefined }); + + expect(calculateHasChildren(withVisibleChild, createLoadOptions(), context)).toBe(true); + }); + + it('is ignored while a store filter is applied', () => { + const context = createContext({ hasItemsGetter: () => true }); + const options = createLoadOptions({ storeFilter: ['id', '=', 1] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(false); + }); + + it('is used under a store filter when parentIds are requested', () => { + const context = createContext({ hasItemsGetter: () => true }); + const options = createLoadOptions({ storeFilter: ['id', '=', 1], parentIds: [ROOT] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(true); + }); + + // Pins current behaviour: an empty parentIds array is truthy here, while + // _processTreeStructure treats it as "no parent ids requested". + it('is used under a store filter when parentIds is an empty array', () => { + const context = createContext({ hasItemsGetter: () => true }); + const options = createLoadOptions({ storeFilter: ['id', '=', 1], parentIds: [] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(true); + }); + + it('is used under a store filter in fullBranch mode', () => { + const context = createContext({ + hasItemsGetter: () => true, + isFullBranchFilterMode: true, + }); + const options = createLoadOptions({ storeFilter: ['id', '=', 1] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(true); + }); + }); + + describe('children not loaded yet', () => { + it('assumes items exist under remote filtering when parentIds are requested', () => { + const options = createLoadOptions({ remoteFiltering: true, parentIds: [ROOT] }); + + expect(calculateHasChildren(leaf, options, createContext())).toBe(true); + }); + + it('assumes items exist under remote filtering in fullBranch mode', () => { + const context = createContext({ isFullBranchFilterMode: true }); + const options = createLoadOptions({ remoteFiltering: true }); + + expect(calculateHasChildren(leaf, options, context)).toBe(true); + }); + + it('assumes nothing once the children are loaded', () => { + const context = createContext({ isChildrenLoaded: { 1: true } }); + const options = createLoadOptions({ remoteFiltering: true, parentIds: [ROOT] }); + + expect(calculateHasChildren(leaf, options, context)).toBe(false); + }); + + it('assumes nothing without remote filtering', () => { + const options = createLoadOptions({ parentIds: [ROOT] }); + + expect(calculateHasChildren(leaf, options, createContext())).toBe(false); + }); + }); + + describe('local filtering in fullBranch mode', () => { + const options = createLoadOptions({ loadFilter: ['id', '=', 1] }); + const context = createContext({ isFullBranchFilterMode: true }); + + it('reports items for any child that survived the filter, visible or not', () => { + expect(calculateHasChildren(withHiddenChild, options, context)).toBe(true); + }); + + it('reports no items when every child was filtered out', () => { + expect(calculateHasChildren(leaf, options, context)).toBe(false); + }); + }); + + describe('fallback', () => { + it('reports items once a descendant propagated them', () => { + expect(calculateHasChildren(withVisibleChild, createLoadOptions(), createContext())) + .toBe(true); + }); + + it('reports none for a node nothing propagated into', () => { + expect(calculateHasChildren(leaf, createLoadOptions(), createContext())).toBe(false); + }); + }); + }); + + describe('expandedRowKeys', () => { + it('collects the keys to expand deepest first', () => { + const { root } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + ]); + const expandedRowKeys = fillNodes( + root.children, + createLoadOptions({ expandVisibleNodes: true }), + createContext(), + ); + + expect(expandedRowKeys).toEqual([2, 1]); + }); + + it('collects nothing when expandVisibleNodes is not set', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + ]); + const expandedRowKeys = fillNodes(root.children, createLoadOptions(), createContext()); + + expect(expandedRowKeys).toEqual([]); + expect(nodes[1].hasChildren).toBe(true); + }); + + it('skips a node that is hidden itself', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + ], [2]); + const expandedRowKeys = fillNodes( + root.children, + createLoadOptions({ expandVisibleNodes: true }), + createContext(), + ); + + expect(nodes[1].hasChildren).toBe(true); + expect(expandedRowKeys).toEqual([]); + }); + + it('skips a node without children', () => { + const { root } = buildTree([{ id: 1, parentId: ROOT }]); + const expandedRowKeys = fillNodes( + root.children, + createLoadOptions({ expandVisibleNodes: true }), + createContext(), + ); + + expect(expandedRowKeys).toEqual([]); + }); + }); + + describe('fullBranch mode', () => { + const context = createContext({ + isFullBranchFilterMode: true, + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const options = createLoadOptions({ expandVisibleNodes: true }); + + it('expands a node that kept at least one visible child', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 1 }, + ], [1, 2]); + const expandedRowKeys = fillNodes(root.children, options, context); + + expect(expandedRowKeys).toEqual([1]); + expect(nodes[3].visible).toBe(false); + }); + + it('reveals the branch instead of expanding it when every child was filtered out', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: true }, + { id: 2, parentId: 1 }, + ], [1]); + const expandedRowKeys = fillNodes(root.children, options, context); + + expect(expandedRowKeys).toEqual([]); + expect(nodes[2].visible).toBe(true); + }); + + it('stops revealing a branch at a node that reports no items of its own', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: true }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + ], [1]); + + fillNodes(root.children, options, context); + + // foreachNodes only descends into nodes whose hasChildren is already truthy, + // and node 2 reports none once its own children were filtered out. + expect(nodes[2].visible).toBe(true); + expect(nodes[3].visible).toBe(false); + }); + + it('keeps revealing a branch through a node that reports items', () => { + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: true }, + { id: 2, parentId: 1, hasItems: true }, + { id: 3, parentId: 2 }, + ], [1]); + + fillNodes(root.children, options, context); + + expect(nodes[3].visible).toBe(true); + }); + + it('handles a node that reports items but has no loaded children', () => { + const { root, nodes } = buildTree([{ id: 1, parentId: ROOT, hasItems: true }], [1]); + const expandedRowKeys = fillNodes(root.children, options, context); + + expect(expandedRowKeys).toEqual([]); + expect(nodes[1].hasChildren).toBe(true); + }); + + it('expands a visible node with children when the mode is off', () => { + const plainContext = createContext({ + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const { root, nodes } = buildTree([ + { id: 1, parentId: ROOT, hasItems: true }, + { id: 2, parentId: 1 }, + ], [1]); + const expandedRowKeys = fillNodes(root.children, options, plainContext); + + expect(expandedRowKeys).toEqual([1]); + expect(nodes[2].visible).toBe(false); + }); + }); +}); + +describe('getVisibleNodes', () => { + const noneExpanded = (): boolean => false; + const allExpanded = (): boolean => true; + + // Mirrors the adapter: the tree is filled first, then flattened into rows. + function getVisibleKeys( + items: Item[], + visibleKeys: unknown[] | undefined, + isRowExpanded: (key: unknown) => boolean, + context = createContext(), + ): unknown[] { + const { root } = buildTree(items, visibleKeys); + + fillNodes(root.children, createLoadOptions(), context); + + return getVisibleNodes(root.children, isRowExpanded).map((node) => node.key); + } + + const parentAndChild: Item[] = [ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + ]; + + it('returns the node objects themselves', () => { + const { root, nodes } = buildTree([{ id: 1, parentId: ROOT }]); + + fillNodes(root.children, createLoadOptions(), createContext()); + + expect(getVisibleNodes(root.children, noneExpanded)[0]).toBe(nodes[1]); + }); + + it('returns nothing for an empty node list', () => { + expect(getVisibleNodes([], noneExpanded)).toEqual([]); + }); + + it('stops at a collapsed node', () => { + expect(getVisibleKeys(parentAndChild, undefined, noneExpanded)).toEqual([1]); + }); + + it('includes the children of an expanded node', () => { + expect(getVisibleKeys(parentAndChild, undefined, allExpanded)).toEqual([1, 2]); + }); + + it('returns the nodes in tree order', () => { + const items = [ + { id: 1, parentId: ROOT }, + { id: 2, parentId: 1 }, + { id: 3, parentId: 2 }, + { id: 4, parentId: ROOT }, + ]; + + expect(getVisibleKeys(items, undefined, allExpanded)).toEqual([1, 2, 3, 4]); + }); + + // How matchOnly/fullBranch surface a match whose ancestors were filtered out. + it('descends through a hidden node without listing it', () => { + expect(getVisibleKeys(parentAndChild, [2], noneExpanded)).toEqual([2]); + }); + + it('does not descend into a node that reports no children', () => { + const context = createContext({ + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const items = [ + { id: 1, parentId: ROOT, hasItems: false }, + { id: 2, parentId: 1 }, + ]; + + expect(getVisibleKeys(items, undefined, allExpanded, context)).toEqual([1]); + }); + + it('handles a node that reports children it has not loaded', () => { + const context = createContext({ + hasItemsGetter: (data): unknown => (data as Item).hasItems, + }); + const items = [{ id: 1, parentId: ROOT, hasItems: true }]; + + expect(getVisibleKeys(items, undefined, allExpanded, context)).toEqual([1]); + }); + + it('asks about expansion using each node key', () => { + const asked: unknown[] = []; + + getVisibleKeys(parentAndChild, undefined, (key) => { + asked.push(key); + + return true; + }); + + expect(asked).toEqual([1, 2]); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/nodes.ts b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/nodes.ts new file mode 100644 index 000000000000..ffbb7bd84f30 --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/data_source_adapter/utils/nodes.ts @@ -0,0 +1,194 @@ +import { isDefined } from '@js/core/utils/type'; +import type { RawItemData } from '@ts/grids/grid_core/data_source_adapter/types'; + +import treeListCore from '../../m_core'; +import type { LoadOperation, NodeByKey, TreeNode } from '../types'; + +export interface NodesContext { + rootValue: unknown; + isFullBranchFilterMode: boolean; + keyGetter: (data: unknown) => unknown; + parentIdGetter: (data: unknown) => unknown; + hasItemsGetter?: (data: unknown) => unknown; + isChildrenLoaded: Record<string, boolean>; +} + +export type ConvertContext = Pick<NodesContext, 'keyGetter' | 'parentIdGetter' | 'rootValue'>; + +export type FillNodesContext = Pick<NodesContext, 'hasItemsGetter' | 'isChildrenLoaded' | 'isFullBranchFilterMode'>; + +export function convertItemToNode( + item: unknown, + nodeByKey: NodeByKey, + context: ConvertContext, +): TreeNode { + const key = context.keyGetter(item) as string; + const itemParentId = context.parentIdGetter(item); + const parentId = (isDefined(itemParentId) ? itemParentId : context.rootValue) as string; + + const parentNode = nodeByKey[parentId] ?? { key: parentId, children: [] }; + nodeByKey[parentId] = parentNode; + + const node = nodeByKey[key] ?? { key, children: [] }; + nodeByKey[key] = node; + + node.data = item; + node.parent = parentNode; + + return node; +} + +export function createNodesByItems( + items: RawItemData[], + visibleItems: RawItemData[] | undefined, + context: ConvertContext, +): { rootNode?: TreeNode; nodeByKey: NodeByKey } { + const nodeByKey: NodeByKey = {}; + const visibleByKey: Record<string, boolean> = {}; + + visibleItems?.forEach((item) => { + visibleByKey[context.keyGetter(item) as string] = true; + }); + + for (const item of items) { + const node = convertItemToNode(item, nodeByKey, context); + + if (node.key === undefined) { + return { nodeByKey }; + } + + node.visible = !visibleItems || !!visibleByKey[node.key as string]; + if (node.parent) { + node.parent.children.push(node); + } + } + + const rootNode = nodeByKey[context.rootValue as string] ?? { + key: context.rootValue, + children: [], + }; + + rootNode.level = -1; + + return { rootNode, nodeByKey }; +} + +function calculateHasChildren( + node: TreeNode, + options: LoadOperation, + context: FillNodesContext, +): boolean { + const { parentIds } = options.storeLoadOptions; + const isFullBranch = context.isFullBranchFilterMode; + const canUseHasItemsGetter = !!parentIds || !options.storeLoadOptions.filter || isFullBranch; + + const hasItemsFromData = context.hasItemsGetter && canUseHasItemsGetter + ? context.hasItemsGetter(node.data) + : undefined; + + if (hasItemsFromData !== undefined) { + return !!hasItemsFromData; + } + + const isChildrenLoaded = context.isChildrenLoaded[node.key as string]; + + if (!isChildrenLoaded && options.remoteOperations?.filtering && (parentIds || isFullBranch)) { + return true; + } + + if (options.loadOptions?.filter && !options.remoteOperations?.filtering && isFullBranch) { + return !!node.children.length; + } + + return !!node.hasChildren; +} + +// In `fullBranch` mode a node whose children are all filtered out stays collapsed +// and its whole branch becomes visible instead. +function resolveNeedToExpand(node: TreeNode, isFullBranch: boolean): boolean { + if (!isFullBranch) { + return true; + } + + if (node.children.some((child) => child.visible)) { + return true; + } + + if (node.children.length) { + treeListCore.foreachNodes(node.children, (child: TreeNode) => { + child.visible = true; + }); + } + + return false; +} + +function fillNodesCore( + nodes: TreeNode[], + options: LoadOperation, + context: FillNodesContext, + expandedRowKeys: unknown[], + level: number, +): void { + nodes.forEach((node) => { + fillNodesCore(node.children, options, context, expandedRowKeys, level + 1); + + node.level = level; + node.hasChildren = calculateHasChildren(node, options, context); + + if (node.visible && node.hasChildren) { + const needToExpand = resolveNeedToExpand(node, context.isFullBranchFilterMode); + + if (options.expandVisibleNodes && needToExpand) { + expandedRowKeys.push(node.key); + } + } + + if (node.parent && (node.visible || node.hasChildren)) { + node.parent.hasChildren = true; + } + }); +} + +export function fillNodes( + nodes: TreeNode[], + options: LoadOperation, + context: FillNodesContext, +): unknown[] { + const expandedRowKeys: unknown[] = []; + + fillNodesCore(nodes, options, context, expandedRowKeys, 0); + + return expandedRowKeys; +} + +function collectVisibleNodes( + nodes: TreeNode[], + isRowExpanded: (key: unknown) => boolean, + result: TreeNode[], +): void { + nodes.forEach((node) => { + if (node.visible) { + result.push(node); + } + + const shouldDescend = (isRowExpanded(node.key) || !node.visible) + && node.hasChildren + && node.children.length > 0; + + if (shouldDescend) { + collectVisibleNodes(node.children, isRowExpanded, result); + } + }); +} + +export function getVisibleNodes( + nodes: TreeNode[], + isRowExpanded: (key: unknown) => boolean, +): TreeNode[] { + const result: TreeNode[] = []; + + collectVisibleNodes(nodes, isRowExpanded, result); + + return result; +} diff --git a/packages/devextreme/js/__internal/grids/tree_list/editing/__tests__/add_row_generated_key.integration.test.ts b/packages/devextreme/js/__internal/grids/tree_list/editing/__tests__/add_row_generated_key.integration.test.ts new file mode 100644 index 000000000000..6a9448f832ec --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/editing/__tests__/add_row_generated_key.integration.test.ts @@ -0,0 +1,82 @@ +import { + afterEach, beforeEach, describe, expect, it, jest, +} from '@jest/globals'; +import { flushAsync } from '@ts/grids/grid_core/__tests__/__mock__/helpers/utils'; + +import { + afterTest, + beforeTest, + createTreeList, +} from '../../__tests__/__mock__/helpers/utils'; + +const DATA = [ + { id: 1, parentId: 0, value: 'a' }, + { id: 2, parentId: 1, value: 'b' }, +]; + +interface InitNewRowEvent { + data: Record<string, unknown>; +} + +const addRowAndReadNewData = async ( + keyExpr: string | undefined, +): Promise<Record<string, unknown>> => { + const onInitNewRow = jest.fn<(e: InitNewRowEvent) => void>(); + + // A store built from a plain object option carries no key, unlike the array shorthand, which + // copies keyExpr onto the store it generates. + const { instance } = await createTreeList({ + dataSource: { store: { type: 'array', data: DATA } }, + keyExpr, + editing: { mode: 'row', allowAdding: true }, + onInitNewRow, + }); + + const added = instance.addRow(); + await flushAsync(); + await added; + + expect(onInitNewRow).toHaveBeenCalledTimes(1); + + return onInitNewRow.mock.calls[0][0].data; +}; + +describe('TreeList adds a generated key to a new row the store cannot identify', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + // TreeList answers key() from its key expression, which always resolves — to 'id' by default. + // Only the store knows whether it can identify a row, so _addRow has to ask the store. + it('generates __KEY__ when the store declares no key', async () => { + const data = await addRowAndReadNewData(undefined); + + expect(data.__KEY__).toEqual(expect.any(String)); + }); + + it('generates __KEY__ even when a key expression is declared, because the store still has none', async () => { + const data = await addRowAndReadNewData('id'); + + expect(data.__KEY__).toEqual(expect.any(String)); + }); +}); + +describe('TreeList leaves a new row alone when the store can identify it', () => { + beforeEach(beforeTest); + afterEach(afterTest); + + it('does not generate __KEY__ for a keyed store', async () => { + const onInitNewRow = jest.fn<(e: InitNewRowEvent) => void>(); + const { instance } = await createTreeList({ + dataSource: { store: { type: 'array', data: DATA, key: 'id' } }, + editing: { mode: 'row', allowAdding: true }, + onInitNewRow, + }); + + const added = instance.addRow(); + await flushAsync(); + await added; + + expect(onInitNewRow).toHaveBeenCalledTimes(1); + expect(onInitNewRow.mock.calls[0][0].data).not.toHaveProperty('__KEY__'); + }); +}); diff --git a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts index 7c23b3a932b7..ca79247b904a 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/editing/m_editing.ts @@ -15,6 +15,7 @@ import gridCoreUtils from '@ts/grids/grid_core/m_utils'; import type { RowsView } from '../../grid_core/views/m_rows_view'; import type { TreeListDataController } from '../data_controller/m_data_controller'; +import type { TreeListDataSourceController } from '../data_source/data_source_controller'; import treeListCore from '../m_core'; const TREELIST_EXPAND_ICON_CONTAINER_CLASS = 'dx-treelist-icon-container'; @@ -25,6 +26,8 @@ const DATA_EDIT_DATA_INSERT_TYPE = 'insert'; class EditingController extends editingModule.controllers.editing { protected declare _dataController: TreeListDataController; + protected declare dataSourceController: TreeListDataSourceController; + protected _generateNewItem(key) { const item: any = super._generateNewItem(key); @@ -43,7 +46,7 @@ class EditingController extends editingModule.controllers.editing { } protected _setInsertAfterOrBeforeKey(change, parentKey) { - const dataSourceAdapter = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const key = parentKey || dataSourceAdapter?.parentKeyOf(change.data); if (key !== undefined && key !== this.option('rootValue')) { @@ -55,7 +58,7 @@ class EditingController extends editingModule.controllers.editing { } protected _getLoadedRowIndex(items, change, isProcessedItems?) { - const dataSourceAdapter = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const insertParentKey = this._getInternalData(change.key)?.insertInfo?.parentKey; const parentKey = insertParentKey !== undefined ? insertParentKey @@ -108,8 +111,7 @@ class EditingController extends editingModule.controllers.editing { const result = super._beforeSaveEditData.apply(this, arguments); if (change && change.type !== DATA_EDIT_DATA_INSERT_TYPE) { - const store = this._dataController?.store(); - const key = store?.key(); + const key = this.dataSourceController.store()?.key(); if (!isDefined(key)) { throw errors.Error('E1045'); @@ -135,7 +137,7 @@ class EditingController extends editingModule.controllers.editing { protected _addRowCore(data, parentKey, oldEditRowIndex) { const rootValue = this.option('rootValue'); - const dataSourceAdapter = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const parentKeyGetter = dataSourceAdapter?.createParentIdGetter(); parentKey = parentKeyGetter ? parentKeyGetter(data) : parentKey; @@ -159,7 +161,7 @@ class EditingController extends editingModule.controllers.editing { } protected _initNewRow(options, parentKey?) { - const dataSourceAdapter = this._dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const parentIdSetter = dataSourceAdapter?.createParentIdSetter(); parentIdSetter?.(options.data, parentKey); diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts b/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts index cf1ae9b9f206..d8163f931d51 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_focus.ts @@ -1,5 +1,5 @@ import { Deferred } from '@js/core/utils/deferred'; -import { focusModule } from '@ts/grids/grid_core/focus/m_focus'; +import { focusModule } from '@ts/grids/grid_core/focus/focus_module'; import type { DataController } from '../grid_core/data_controller/data_controller'; import type { ModuleType } from '../grid_core/m_types'; @@ -114,7 +114,7 @@ const data = ( return; } - const offset = findIndex(nodes, (node) => this.keyOf(node.data) === key); + const offset = findIndex(nodes, (node) => this.dataSourceController.keyOf(node.data) === key); let pageIndex = -1; diff --git a/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts b/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts index c933bd272e7e..e226743ad372 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/m_widget_base.ts @@ -1,5 +1,7 @@ import './module_not_extended/column_headers'; import './m_columns_controller'; +import './data_source/data_source_module'; +import './module_not_extended/filter'; import './data_controller/m_data_controller'; import './module_not_extended/sorting'; import './rows/m_rows'; @@ -19,6 +21,7 @@ import treeListCore from './m_core'; const TREELIST_CLASS = 'dx-treelist'; treeListCore.registerModulesOrder([ + 'dataSource', 'stateStoring', 'columns', 'selection', @@ -32,6 +35,7 @@ treeListCore.registerModulesOrder([ 'masterDetail', 'validating', 'adaptivity', + 'filter', 'data', 'virtualScrolling', 'aiColumn', diff --git a/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/filter.ts b/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/filter.ts new file mode 100644 index 000000000000..dc4b83b8545c --- /dev/null +++ b/packages/devextreme/js/__internal/grids/tree_list/module_not_extended/filter.ts @@ -0,0 +1,5 @@ +import { filterModule } from '@ts/grids/grid_core/filter/filter_module'; + +import treeListCore from '../m_core'; + +treeListCore.registerModule('filter', filterModule); diff --git a/packages/devextreme/js/__internal/grids/tree_list/selection/m_selection.ts b/packages/devextreme/js/__internal/grids/tree_list/selection/m_selection.ts index 985a78826091..7e6ba1084bcf 100644 --- a/packages/devextreme/js/__internal/grids/tree_list/selection/m_selection.ts +++ b/packages/devextreme/js/__internal/grids/tree_list/selection/m_selection.ts @@ -95,14 +95,14 @@ SelectionController & TreeListSelectionControllerExtension config.plainItems = (cached) => { let result; if (cached) { - result = this._dataController.getCachedStoreData(); + result = this.dataSourceController.getCachedStoreData(); } result ||= plainItems.apply(this, arguments as any).map((item) => item.data); return result || []; }; config.isItemSelected = (item) => { - const key = this._dataController.keyOf(item); + const key = this.dataSourceController.keyOf(item); return this.isRowSelected(key); }; diff --git a/packages/devextreme/js/__internal/integration/knockout/validation.ts b/packages/devextreme/js/__internal/integration/knockout/validation.ts index 945c78a188ed..ccdd10307bd4 100644 --- a/packages/devextreme/js/__internal/integration/knockout/validation.ts +++ b/packages/devextreme/js/__internal/integration/knockout/validation.ts @@ -1,6 +1,6 @@ +import { EventsStrategy } from '@ts/core/events_strategy'; import { Guid } from '@ts/core/guid'; import Class from '@ts/core/m_class'; -import { EventsStrategy } from '@ts/core/m_events_strategy'; import { Deferred } from '@ts/core/utils/m_deferred'; import { extend } from '@ts/core/utils/m_extend'; import { each, map } from '@ts/core/utils/m_iterator'; diff --git a/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/model/scheduler.ts b/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/model/scheduler.ts index ebc5ff07fa50..80418b75a673 100644 --- a/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/model/scheduler.ts +++ b/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/model/scheduler.ts @@ -1,4 +1,5 @@ import { within } from '@testing-library/dom'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; import { ToolbarModel } from '@ts/scheduler/__tests__/__mock__/model/toolbar'; import { APPOINTMENT_POPUP_CLASS } from '../../../appointment_popup/popup'; @@ -55,7 +56,7 @@ export class SchedulerModel { } getStatusContent(): string { - const statusElement = this.container.querySelector('.dx-screen-reader-only'); + const statusElement = this.container.querySelector(`.${SCREEN_READER_ONLY_CLASS}`); return statusElement?.textContent ?? ''; } diff --git a/packages/devextreme/js/__internal/scheduler/a11y_status/a11y_status_render.ts b/packages/devextreme/js/__internal/scheduler/a11y_status/a11y_status_render.ts index 558f93c69c36..788d4d742fd2 100644 --- a/packages/devextreme/js/__internal/scheduler/a11y_status/a11y_status_render.ts +++ b/packages/devextreme/js/__internal/scheduler/a11y_status/a11y_status_render.ts @@ -1,8 +1,9 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; const CLASSES = { - container: 'dx-screen-reader-only', + container: SCREEN_READER_ONLY_CLASS, }; export const createA11yStatusContainer = (statusText = ''): dxElementWrapper => $('<div>') diff --git a/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts b/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts index ccf48a23701c..982fd38239af 100644 --- a/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts +++ b/packages/devextreme/js/__internal/scheduler/utils/loader/utils.ts @@ -1,6 +1,6 @@ import type { DataSourceLike } from '@js/data/data_source'; import DataSource from '@js/data/data_source'; -import { normalizeDataSourceOptions } from '@ts/data/data_source/m_utils'; +import { normalizeDataSourceOptions } from '@ts/data/data_source/utils'; export const normalizeDataSource = <T>( dataSourceOptions: DataSourceLike<T> | null | undefined, @@ -19,6 +19,7 @@ export const normalizeDataSource = <T>( ...options, }; + // @ts-expect-error the public DataSource constructor takes the public option types return new DataSource(result); }; diff --git a/packages/devextreme/js/__internal/ui/button/button.tsx b/packages/devextreme/js/__internal/ui/button/button.tsx index e9b8ef9ce442..fdc740883193 100644 --- a/packages/devextreme/js/__internal/ui/button/button.tsx +++ b/packages/devextreme/js/__internal/ui/button/button.tsx @@ -1,10 +1,7 @@ import { click } from '@js/common/core/events/short'; -import messageLocalization from '@js/common/core/localization/message'; import devices from '@js/core/devices'; import type { DefaultOptionsRule } from '@js/core/options/utils'; import { convertRulesToOptions, createDefaultOptionRules } from '@js/core/options/utils'; -import { getImageSourceType } from '@js/core/utils/icon'; -import { camelize } from '@js/core/utils/inflector'; import type { DxEvent } from '@js/events'; import type { Properties as ButtonProperties, TemplateData } from '@js/ui/button.d'; import { current, isMaterial } from '@js/ui/themes'; @@ -16,6 +13,7 @@ import type { EffectReturn } from '@ts/core/r1/utils/effect_return'; import { getTemplate } from '@ts/core/r1/utils/index'; import { Widget } from '@ts/core/r1/widget'; import { combineClasses } from '@ts/core/utils/combine_classes'; +import { getImageAriaLabel } from '@ts/core/utils/m_icon'; import { createRef as infernoCreateRef } from 'inferno'; import { Icon } from './icon'; @@ -229,34 +227,7 @@ export class Button extends InfernoWrapperComponent<ButtonProps> { get aria(): Record<string, string> { const { icon, text } = this.props; - let label = text ?? ''; - - if (!text && icon) { - const iconSource = getImageSourceType(icon); - - switch (iconSource) { - case 'image': - { - const notURLRegexp = /^(?!(?:https?:\/\/)|(?:ftp:\/\/)|(?:www\.))[^\s]+$/; - const isPathToImage = !icon.includes('base64') && notURLRegexp.test(icon); - label = isPathToImage ? icon.replace(/.+\/([^.]+)\..+$/, '$1') : ''; - break; - } - case 'dxIcon': - label = messageLocalization.format(camelize(icon, true)) || icon; - break; - case 'fontIcon': - label = icon; - break; - case 'svg': { - const titleRegexp = /<title>(.*?)<\/title>/; - label = titleRegexp.exec(icon)?.[1] ?? ''; - break; - } - default: - break; - } - } + const label = !text && icon ? getImageAriaLabel(icon) : text; return { role: 'button', diff --git a/packages/devextreme/js/__internal/ui/calendar/calendar.views.ts b/packages/devextreme/js/__internal/ui/calendar/calendar.views.ts index ee55cec31e05..6383dbca9d1c 100644 --- a/packages/devextreme/js/__internal/ui/calendar/calendar.views.ts +++ b/packages/devextreme/js/__internal/ui/calendar/calendar.views.ts @@ -8,6 +8,7 @@ import dateSerialization from '@js/core/utils/date_serialization'; import type { CalendarSelectionMode, DayOfWeek, WeekNumberRule, } from '@js/ui/calendar'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; import type { BaseViewProperties } from './calendar.base_view'; import BaseView from './calendar.base_view'; @@ -18,8 +19,6 @@ const CALENDAR_WEEK_NUMBER_CELL_CLASS = 'dx-calendar-week-number-cell'; const CALENDAR_WEEK_NUMBER_HEADER_CELL_CLASS = 'dx-week-number-header'; const CALENDAR_WEEK_SELECTION_CLASS = 'dx-calendar-week-selection'; -const SCREEN_READER_ONLY_CLASS = 'dx-screen-reader-only'; - const WEEK_NUMBER_TEXT = 'WeekNumber'; export interface MonthViewProperties extends BaseViewProperties { diff --git a/packages/devextreme/js/__internal/ui/chat/message_box/chat_text_area.ts b/packages/devextreme/js/__internal/ui/chat/message_box/chat_text_area.ts index b06013080e0d..6a809d60bb82 100644 --- a/packages/devextreme/js/__internal/ui/chat/message_box/chat_text_area.ts +++ b/packages/devextreme/js/__internal/ui/chat/message_box/chat_text_area.ts @@ -619,7 +619,7 @@ class ChatTextArea extends TextArea<Properties> { return maxHeight; } - _keyPressHandler(e: { originalEvent: InputEvent & KeyboardEvent }): void { + _keyPressHandler(e: DxEvent): void { super._keyPressHandler(e); this._updateButtonsState(); diff --git a/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts b/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts index 06d89a472973..85bcffe6447f 100644 --- a/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts +++ b/packages/devextreme/js/__internal/ui/collection/collection_widget.edit.ts @@ -277,7 +277,6 @@ class CollectionWidget< if (that._disposed) { return; } - // @ts-expect-error arguments const items = normalizeLoadResult(loadResult).data; dataController.applyMapFunction(items); diff --git a/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts b/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts index d1d0c0fdfd0b..a73e374f5136 100644 --- a/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts +++ b/packages/devextreme/js/__internal/ui/collection/collection_widget.live_update.ts @@ -204,7 +204,6 @@ class CollectionWidgetLiveUpdate< } else { const changedItem = items[indexByKey(keyInfo, items, change.key)]; if (changedItem) { - // @ts-expect-error ts-error update(keyInfo, items, change.key, change.data).done(() => { this._renderItem( items.indexOf(changedItem), @@ -224,7 +223,6 @@ class CollectionWidgetLiveUpdate< isPartialRefresh?: boolean, ): void { when( - // @ts-expect-error ts-error isPartialRefresh ?? insert(keyInfo, items, change.data, change.index), ).done(() => { this._beforeItemElementInserted(change); diff --git a/packages/devextreme/js/__internal/ui/color_box/color_view.ts b/packages/devextreme/js/__internal/ui/color_box/color_view.ts index 349261fdff67..55072f72e081 100644 --- a/packages/devextreme/js/__internal/ui/color_box/color_view.ts +++ b/packages/devextreme/js/__internal/ui/color_box/color_view.ts @@ -18,8 +18,8 @@ import { isCommandKeyPressed } from '@ts/events/utils/index'; import Draggable from '@ts/m_draggable'; import type { EditorProperties, ValueChangedEvent } from '@ts/ui/editor/editor'; import Editor from '@ts/ui/editor/editor'; -import NumberBox from '@ts/ui/number_box/m_number_box'; -import { WIDGET_CLASS as NUMBERBOX_CLASS } from '@ts/ui/number_box/m_number_box.base'; +import NumberBox from '@ts/ui/number_box/number_box'; +import { WIDGET_CLASS as NUMBERBOX_CLASS } from '@ts/ui/number_box/number_box.base'; import TextBox from '@ts/ui/text_box/text_box'; const COLOR_VIEW_CLASS = 'dx-colorview'; diff --git a/packages/devextreme/js/__internal/ui/date_box/date_box.mask.ts b/packages/devextreme/js/__internal/ui/date_box/date_box.mask.ts index d62dd2b23c62..378a4486fb42 100644 --- a/packages/devextreme/js/__internal/ui/date_box/date_box.mask.ts +++ b/packages/devextreme/js/__internal/ui/date_box/date_box.mask.ts @@ -273,7 +273,7 @@ class DateBoxMask< this._caret(this._getActivePartProp('caret')); } - _keyPressHandler(e: { originalEvent: InputEvent & KeyboardEvent }): void { + _keyPressHandler(e: DxEvent<InputEvent>): void { const { originalEvent: event } = e; const isBackwardDeletion = event?.inputType === 'deleteContentBackward'; diff --git a/packages/devextreme/js/__internal/ui/date_box/time_view.ts b/packages/devextreme/js/__internal/ui/date_box/time_view.ts index a8ba0626d342..314967f395a4 100644 --- a/packages/devextreme/js/__internal/ui/date_box/time_view.ts +++ b/packages/devextreme/js/__internal/ui/date_box/time_view.ts @@ -9,8 +9,8 @@ import Box from '@ts/ui/box'; import dateUtils from '@ts/ui/date_box/date_utils'; import type { EditorProperties } from '@ts/ui/editor/editor'; import Editor from '@ts/ui/editor/editor'; -import NumberBox from '@ts/ui/number_box/m_number_box'; -import type { NumberBoxMaskProperties } from '@ts/ui/number_box/m_number_box.mask'; +import NumberBox from '@ts/ui/number_box/number_box'; +import type { NumberBoxMaskProperties } from '@ts/ui/number_box/number_box.mask'; import SelectBox from '@ts/ui/select_box'; const TIMEVIEW_CLASS = 'dx-timeview'; diff --git a/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_button.ts b/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_button.ts index f528e4b59f2e..4038648987ff 100644 --- a/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_button.ts +++ b/packages/devextreme/js/__internal/ui/drop_down_editor/drop_down_button.ts @@ -35,9 +35,7 @@ interface EditorWithDropDown { export default class DropDownButton< // eslint-disable-next-line @typescript-eslint/no-explicit-any TComponent extends Editor<any> & EditorWithDropDown = DropDownEditor, -> extends TextEditorButton<TComponent> { - declare instance: Button | null; - +> extends TextEditorButton<TComponent, Button> { currentTemplate: DropDownEditorProperties['dropDownButtonTemplate'] | null; constructor(name: string, editor: TComponent, options: ButtonProperties) { diff --git a/packages/devextreme/js/__internal/ui/hierarchical_collection/data_adapter.ts b/packages/devextreme/js/__internal/ui/hierarchical_collection/data_adapter.ts index 4e5370ec834f..c5048c5be6d0 100644 --- a/packages/devextreme/js/__internal/ui/hierarchical_collection/data_adapter.ts +++ b/packages/devextreme/js/__internal/ui/hierarchical_collection/data_adapter.ts @@ -691,8 +691,8 @@ class DataAdapter { lookForParents(matches, 0); if (this.options.sort) { + // @ts-expect-error queryByOptions().toArray() is typed as unknown[] matches = storeHelper - // @ts-expect-error ts-error .queryByOptions(query(matches), { sort: this.options.sort, langParams: this.options.langParams, diff --git a/packages/devextreme/js/__internal/ui/list/list.base.ts b/packages/devextreme/js/__internal/ui/list/list.base.ts index d3411be672fd..3cb11fe713f5 100644 --- a/packages/devextreme/js/__internal/ui/list/list.base.ts +++ b/packages/devextreme/js/__internal/ui/list/list.base.ts @@ -45,6 +45,7 @@ import { render } from '@ts/core/utils/ink_ripple'; import supportUtils from '@ts/core/utils/m_support'; import type { OptionChanged } from '@ts/core/widget/types'; import type { SupportedKeys } from '@ts/core/widget/widget'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; import { getDataSourceOptions } from '@ts/data/data_converter/grouped'; import type { CollectionItemInfo, @@ -132,6 +133,8 @@ export class ListBase extends CollectionWidget<ListBaseProperties, Item> { _$nextButton!: dxElementWrapper | null; + _$a11yStatusContainer?: dxElementWrapper; + _holdTimer?: ReturnType<typeof setTimeout>; _loadNextPageTimer?: ReturnType<typeof setTimeout>; @@ -975,6 +978,7 @@ export class ListBase extends CollectionWidget<ListBaseProperties, Item> { this._itemElementsCache = $(); this.$element().addClass(LIST_CLASS); + this._renderA11yStatusContainer(); super._initMarkup(); const { useInkRipple } = this.option(); @@ -997,10 +1001,37 @@ export class ListBase extends CollectionWidget<ListBaseProperties, Item> { const isEmpty = super._renderEmptyMessage(rootNodes); this.setAria({ role: isEmpty ? undefined : 'application' }, this._focusTarget()); + this._updateA11yStatusText(); return isEmpty; } + _renderA11yStatusContainer(): void { + const isContainerRendered = this._$a11yStatusContainer?.parent().is(this.$element()); + + if (isContainerRendered) { + return; + } + + this._$a11yStatusContainer = $('<div>') + .addClass(SCREEN_READER_ONLY_CLASS) + .attr('role', 'status') + .appendTo(this.$element()); + } + + _updateA11yStatusText(): void { + if (this._dataController.isLoading()) { + this._$a11yStatusContainer?.text(''); + return; + } + + const { noDataText } = this.option(); + const itemsCount = this._editStrategy.itemsGetter().length; + const itemsLabel = messageLocalization.format('dxList-listAriaLabel'); + + this._$a11yStatusContainer?.text(itemsCount ? `${itemsLabel}: ${itemsCount}` : noDataText ?? ''); + } + _isMultiSelectMode(): boolean { const { selectionMode } = this.option(); return selectionMode === 'multiple' || selectionMode === 'all'; diff --git a/packages/devextreme/js/__internal/ui/number_box/__tests__/utils.test.ts b/packages/devextreme/js/__internal/ui/number_box/__tests__/utils.test.ts new file mode 100644 index 000000000000..a767c38ea29e --- /dev/null +++ b/packages/devextreme/js/__internal/ui/number_box/__tests__/utils.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from '@jest/globals'; + +import { + asPattern, getNthOccurrence, getRealSeparatorIndex, splitByIndex, +} from '../utils'; + +describe('getNthOccurrence', () => { + it('returns the position of the requested occurrence', () => { + expect(getNthOccurrence('1.234.567', '.', 1)).toBe(1); + expect(getNthOccurrence('1.234.567', '.', 2)).toBe(5); + }); + + it('returns -1 when the character is absent', () => { + expect(getNthOccurrence('1234', '.', 1)).toBe(-1); + expect(getNthOccurrence('', '.', 1)).toBe(-1); + }); + + it('returns -1 when there are fewer occurrences than requested', () => { + expect(getNthOccurrence('abc.d', '.', 2)).toBe(-1); + expect(getNthOccurrence('abc.d', '.', 3)).toBe(-1); + expect(getNthOccurrence('a.b.c', '.', 4)).toBe(-1); + expect(getNthOccurrence('a.b.c', '.', 5)).toBe(-1); + }); + + it('returns -1 when no occurrence is requested', () => { + expect(getNthOccurrence('1.2', '.', 0)).toBe(-1); + }); + + it('counts adjacent occurrences separately', () => { + expect(getNthOccurrence('...', '.', 1)).toBe(0); + expect(getNthOccurrence('...', '.', 3)).toBe(2); + expect(getNthOccurrence('...', '.', 4)).toBe(-1); + }); +}); + +describe('getRealSeparatorIndex', () => { + it('reports the first separator of a plain pattern', () => { + expect(getRealSeparatorIndex('#0.00')).toEqual({ occurrence: 1, index: 2 }); + expect(getRealSeparatorIndex('#,##0.##')).toEqual({ occurrence: 1, index: 5 }); + }); + + it('skips separators inside escaped stubs', () => { + expect(getRealSeparatorIndex("'.'#0.00")).toEqual({ occurrence: 2, index: 5 }); + expect(getRealSeparatorIndex("'..' #0.00")).toEqual({ occurrence: 3, index: 7 }); + }); + + it('reports a missing separator for patterns without a float part', () => { + expect(getRealSeparatorIndex('#,##0')).toEqual({ occurrence: 1, index: -1 }); + }); + + it('treats a format that is not a pattern as having no separator', () => { + expect(getRealSeparatorIndex({ type: 'fixedPoint', precision: 2 })) + .toEqual({ occurrence: 1, index: -1 }); + expect(getRealSeparatorIndex(undefined)).toEqual({ occurrence: 1, index: -1 }); + }); + + it('locates the separator of a stubbed pattern in the formatted text', () => { + const { occurrence } = getRealSeparatorIndex("'...' #0.0"); + + expect(getNthOccurrence('... 12.5', '.', occurrence)).toBe(6); + expect(getNthOccurrence('.. 12', '.', occurrence)).toBe(-1); + }); +}); + +describe('splitByIndex', () => { + it('splits the text around the given index', () => { + expect(splitByIndex('12.34', 2)).toEqual(['12', '34']); + }); + + it('keeps the text whole when there is no split point', () => { + expect(splitByIndex('1234', -1)).toEqual(['1234']); + }); +}); + +describe('asPattern', () => { + it('passes a pattern string through', () => { + expect(asPattern('#0.00')).toBe('#0.00'); + }); + + it('reports an empty pattern for formats that are not strings', () => { + expect(asPattern(undefined)).toBe(''); + expect(asPattern({ type: 'currency' })).toBe(''); + expect(asPattern((value: number): string => String(value))).toBe(''); + }); +}); diff --git a/packages/devextreme/js/__internal/ui/number_box/m_number_box.caret.ts b/packages/devextreme/js/__internal/ui/number_box/m_number_box.caret.ts deleted file mode 100644 index 0ba137034deb..000000000000 --- a/packages/devextreme/js/__internal/ui/number_box/m_number_box.caret.ts +++ /dev/null @@ -1,142 +0,0 @@ -import number from '@js/common/core/localization/number'; -import { escapeRegExp } from '@js/core/utils/common'; -import { fitIntoRange } from '@js/core/utils/math'; - -import { getNthOccurrence, getRealSeparatorIndex, splitByIndex } from './m_utils'; - -export const getCaretBoundaries = function (text, format) { - if (typeof format === 'string') { - const signParts = format.split(';'); - const sign = number.getSign(text, format); - - signParts[1] = signParts[1] || `-${signParts[0]}`; - format = signParts[sign < 0 ? 1 : 0]; - - const mockEscapedStubs = (str) => str.replace(/'([^']*)'/g, (str) => str.split('').map(() => ' ').join('').substr(2)); - - format = mockEscapedStubs(format); - - // @ts-expect-error - const prefixStubLength = /^[^#0.,]*/.exec(format)[0].length; - // @ts-expect-error - const postfixStubLength = /[^#0.,]*$/.exec(format)[0].length; - - return { - start: prefixStubLength, - end: text.length - postfixStubLength, - }; - } - return { start: 0, end: text.length }; -}; - -// eslint-disable-next-line @typescript-eslint/naming-convention -const _getDigitCountBeforeIndex = function (index, text) { - const decimalSeparator = number.getDecimalSeparator(); - const regExp = new RegExp(`[^0-9${escapeRegExp(decimalSeparator)}]`, 'g'); - const textBeforePosition = text.slice(0, index); - - return textBeforePosition.replace(regExp, '').length; -}; - -// eslint-disable-next-line @typescript-eslint/naming-convention -const _reverseText = function (text) { - return text.split('').reverse().join(''); -}; - -// eslint-disable-next-line @typescript-eslint/naming-convention -const _getDigitPositionByIndex = function (digitIndex, text) { - if (!digitIndex) { - return -1; - } - - const regExp = /[0-9]/g; - let counter = 1; - let index: number | null = null; - let result = regExp.exec(text); - - while (result) { - index = result.index; - if (counter >= digitIndex) { - return index; - } - counter++; - result = regExp.exec(text); - } - - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - return index === null ? text.length : index; -}; - -// eslint-disable-next-line @typescript-eslint/naming-convention -const _trimNonNumericCharsFromEnd = function (text) { - return text.replace(/[^0-9e]+$/, ''); -}; - -export const getCaretWithOffset = function (caret, offset) { - if (caret.start === undefined) { - caret = { start: caret, end: caret }; - } - - return { - start: caret.start + offset, - end: caret.end + offset, - }; -}; - -export const getCaretAfterFormat = function (text, formatted, caret, format) { - caret = getCaretWithOffset(caret, 0); - - const point = number.getDecimalSeparator(); - const isSeparatorBasedText = isSeparatorBasedString(text); - const realSeparatorOccurrenceIndex = getRealSeparatorIndex(format).occurrence; - const pointPosition = isSeparatorBasedText ? 0 : getNthOccurrence(text, point, realSeparatorOccurrenceIndex); - const newPointPosition = getNthOccurrence(formatted, point, realSeparatorOccurrenceIndex); - const textParts = splitByIndex(text, pointPosition); - const formattedParts = splitByIndex(formatted, newPointPosition); - const isCaretOnFloat = pointPosition !== -1 && caret.start > pointPosition; - - if (isCaretOnFloat) { - const relativeIndex = caret.start - pointPosition - 1; - const digitsBefore = _getDigitCountBeforeIndex(relativeIndex, textParts[1]); - const newPosition = formattedParts[1] ? newPointPosition + 1 + _getDigitPositionByIndex(digitsBefore, formattedParts[1]) + 1 : formatted.length; - - return getCaretInBoundaries(newPosition, formatted, format); - } - const formattedIntPart = _trimNonNumericCharsFromEnd(formattedParts[0]); - const positionFromEnd = textParts[0].length - caret.start; - const digitsFromEnd = _getDigitCountBeforeIndex(positionFromEnd, _reverseText(textParts[0])); - const newPositionFromEnd = _getDigitPositionByIndex(digitsFromEnd, _reverseText(formattedIntPart)); - const newPositionFromBegin = formattedIntPart.length - (newPositionFromEnd + 1); - - return getCaretInBoundaries(newPositionFromBegin, formatted, format); -}; - -function isSeparatorBasedString(text) { - return text.length === 1 && !!text.match(/^[,.][0-9]*$/g); -} - -export const isCaretInBoundaries = function (caret, text, format) { - caret = getCaretWithOffset(caret, 0); - - const boundaries = getCaretInBoundaries(caret, text, format); - return caret.start >= boundaries.start && caret.end <= boundaries.end; -}; - -export function getCaretInBoundaries(caret, text, format) { - caret = getCaretWithOffset(caret, 0); - - const boundaries = getCaretBoundaries(text, format); - const adjustedCaret = { - start: fitIntoRange(caret.start, boundaries.start, boundaries.end), - end: fitIntoRange(caret.end, boundaries.start, boundaries.end), - }; - - return adjustedCaret; -} - -export const getCaretOffset = function (previousText, newText, format) { - const previousBoundaries = getCaretBoundaries(previousText, format); - const newBoundaries = getCaretBoundaries(newText, format); - - return newBoundaries.start - previousBoundaries.start; -}; diff --git a/packages/devextreme/js/__internal/ui/number_box/m_number_box.spins.ts b/packages/devextreme/js/__internal/ui/number_box/m_number_box.spins.ts deleted file mode 100644 index 75560e162804..000000000000 --- a/packages/devextreme/js/__internal/ui/number_box/m_number_box.spins.ts +++ /dev/null @@ -1,132 +0,0 @@ -import eventsEngine from '@js/common/core/events/core/events_engine'; -import pointer from '@js/common/core/events/pointer'; -import { addNamespace } from '@js/common/core/events/utils/index'; -import type { dxElementWrapper } from '@js/core/renderer'; -import $ from '@js/core/renderer'; -import { extend } from '@js/core/utils/extend'; -import TextEditorButton from '@ts/ui/text_box/texteditor_button_collection/button'; - -import SpinButton from './m_number_box.spin'; - -const SPIN_CLASS = 'dx-numberbox-spin'; -const SPIN_CONTAINER_CLASS = 'dx-numberbox-spin-container'; -const SPIN_TOUCH_FRIENDLY_CLASS = 'dx-numberbox-spin-touch-friendly'; - -export default class SpinButtons extends TextEditorButton { - _attachEvents( - instance: dxElementWrapper, - $spinContainer: dxElementWrapper, - ): void { - const { editor } = this; - - if (!editor) { - return; - } - - const eventName = addNamespace(pointer.down, editor.NAME ?? ''); - const $spinContainerChildren = $spinContainer.children(); - - const pointerDownAction = editor._createAction( - // @ts-expect-error Private API - (e) => { this.editor?._spinButtonsPointerDownHandler(e); }, - ); - - eventsEngine.off($spinContainer, eventName); - - eventsEngine.on( - $spinContainer, - eventName, - (e) => pointerDownAction({ event: e }), - ); - - SpinButton.getInstance($spinContainerChildren.eq(0)).option( - 'onChange', - // @ts-expect-error Private API - (e) => { this.editor?._spinUpChangeHandler(e); }, - ); - - SpinButton.getInstance($spinContainerChildren.eq(1)).option( - 'onChange', - // @ts-expect-error Private API - (e) => { this.editor?._spinDownChangeHandler(e); }, - ); - } - - _create(): { - $element: dxElementWrapper; - instance: dxElementWrapper; - } { - const { editor } = this; - - const $spinContainer = $('<div>').addClass(SPIN_CONTAINER_CLASS); - const $spinUp = $('<div>').appendTo($spinContainer); - const $spinDown = $('<div>').appendTo($spinContainer); - const options = this._getOptions(); - - this._addToContainer($spinContainer); - - editor?._createComponent($spinUp, SpinButton, extend({ direction: 'up' }, options)); - editor?._createComponent($spinDown, SpinButton, extend({ direction: 'down' }, options)); - - this._legacyRender(editor?.$element(), this._isTouchFriendly(), options.visible); - - return { - instance: $spinContainer, - $element: $spinContainer, - }; - } - - _getOptions() { - const { editor } = this; - - const visible = this._isVisible(); - const disabled = editor?.option('disabled'); - - return { - visible, - disabled, - }; - } - - // @ts-expect-error - _isVisible() { - const { editor } = this; - - return super._isVisible() && editor?.option('showSpinButtons'); - } - - _isTouchFriendly() { - const { editor } = this; - - return editor?.option('showSpinButtons') && editor?.option('useLargeSpinButtons'); - } - - // TODO: get rid of it - _legacyRender($editor, isTouchFriendly, isVisible) { - $editor.toggleClass(SPIN_TOUCH_FRIENDLY_CLASS, isTouchFriendly); - $editor.toggleClass(SPIN_CLASS, isVisible); - } - - // @ts-expect-error - update(): void { - const shouldUpdate = super.update(); - - if (shouldUpdate) { - const { editor, instance } = this; - - const $editor = editor?.$element(); - const isVisible = this._isVisible(); - const isTouchFriendly = this._isTouchFriendly(); - // @ts-expect-error - const $spinButtons = instance.children(); - const spinUp = SpinButton.getInstance($spinButtons.eq(0)); - const spinDown = SpinButton.getInstance($spinButtons.eq(1)); - const options = this._getOptions(); - - spinUp.option(options); - spinDown.option(options); - - this._legacyRender($editor, isTouchFriendly, isVisible); - } - } -} diff --git a/packages/devextreme/js/__internal/ui/number_box/m_utils.ts b/packages/devextreme/js/__internal/ui/number_box/m_utils.ts deleted file mode 100644 index 388587c56f7f..000000000000 --- a/packages/devextreme/js/__internal/ui/number_box/m_utils.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { adjust, roundFloatPart } from '@js/core/utils/math'; - -const getRealSeparatorIndex = function (str) { - let quoteBalance = 0; - let separatorCount = 0; - - for (let i = 0; i < str.length; ++i) { - if (str[i] === '\'') { - quoteBalance++; - } - if (str[i] === '.') { - ++separatorCount; - if (quoteBalance % 2 === 0) { - return { - occurrence: separatorCount, - index: i, - }; - } - } - } - - return { occurrence: 1, index: -1 }; -}; - -const getNthOccurrence = function (str, c, n) { - let i = -1; - - while (n-- && i++ < str.length) { - i = str.indexOf(c, i); - } - - return i; -}; - -const splitByIndex = function (str, index) { - if (index === -1) { - return [str]; - } - - return [str.slice(0, index), str.slice(index + 1)]; -}; - -const adjustPercentValue = function (rawValue, interval) { - if (!rawValue) { - return rawValue; - } - - return adjust(rawValue / 100, interval / 100); -}; - -const roundFloatPartPercentValue = function (rawValue: number, precision: number) { - if (!rawValue) { - return rawValue; - } - return roundFloatPart(rawValue / 100, precision); -}; - -export { - adjustPercentValue, - getNthOccurrence, - getRealSeparatorIndex, - roundFloatPartPercentValue, - splitByIndex, -}; diff --git a/packages/devextreme/js/__internal/ui/number_box/m_number_box.base.ts b/packages/devextreme/js/__internal/ui/number_box/number_box.base.ts similarity index 61% rename from packages/devextreme/js/__internal/ui/number_box/m_number_box.base.ts rename to packages/devextreme/js/__internal/ui/number_box/number_box.base.ts index 76577f3c5d31..c87bcd6f5ad1 100644 --- a/packages/devextreme/js/__internal/ui/number_box/m_number_box.base.ts +++ b/packages/devextreme/js/__internal/ui/number_box/number_box.base.ts @@ -13,25 +13,37 @@ import browser from '@js/core/utils/browser'; import { // @ts-expect-error ts-error applyServerDecimalSeparator, - ensureDefined, } from '@js/core/utils/common'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred } from '@js/core/utils/deferred'; import { fitIntoRange, inRange } from '@js/core/utils/math'; import { isDefined } from '@js/core/utils/type'; +import type { DxEvent, InteractionEvent } from '@js/events'; +import type { Format } from '@js/localization'; import { getGlobalFormatByDataType } from '@ts/core/global_format_config'; +import type { OptionChanged } from '@ts/core/widget/types'; +import type { SupportedKeys } from '@ts/core/widget/widget'; +import type { DxMouseWheelEvent } from '@ts/ui/scroll_view/types'; import TextEditor from '@ts/ui/text_box/text_editor'; -import type { TextEditorBaseProperties } from '../text_box/text_editor.base'; +import type { TextEditorBaseProperties, TextEditorInputAttributes } from '../text_box/text_editor.base'; import type { TextEditorButtonInfo } from '../text_box/texteditor_button_collection/index'; -import SpinButtons from './m_number_box.spins'; - -const math = Math; +import type { SpinChangeEvent } from './number_box.spin'; +import SpinButtons from './number_box.spins'; export const WIDGET_CLASS = 'dx-numberbox'; const FIREFOX_CONTROL_KEYS = ['tab', 'del', 'backspace', 'leftArrow', 'rightArrow', 'home', 'end', 'enter']; const FORCE_VALUECHANGE_EVENT_NAMESPACE = 'NumberBoxForceValueChange'; +export type NumberBoxValue = number | null; + +export type SpinValueChangeEvent = DxEvent<InteractionEvent> | DxMouseWheelEvent; + +const getSpinEvent = ( + e: SpinChangeEvent | DxEvent<InteractionEvent>, +): SpinValueChangeEvent => ('event' in e ? e.event : e); + export interface NumberBoxBaseProperties extends TextEditorBaseProperties { min?: number; max?: number; @@ -39,6 +51,9 @@ export interface NumberBoxBaseProperties extends TextEditorBaseProperties { showSpinButtons?: boolean; useLargeSpinButtons?: boolean; invalidValueMessage?: string; + format?: Format | null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + displayValueFormatter?: ((value: any) => string); } class NumberBoxBase< @@ -48,24 +63,24 @@ class NumberBoxBase< _$submitElement!: dxElementWrapper; - _supportedKeys(): Record<string, (e: KeyboardEvent) => void> { + _supportedKeys(): SupportedKeys { return { ...super._supportedKeys(), - upArrow(e): void { + upArrow: (e): void => { if (!isCommandKeyPressed(e)) { e.preventDefault(); e.stopPropagation(); this._spinUpChangeHandler(e); } }, - downArrow(e): void { + downArrow: (e): void => { if (!isCommandKeyPressed(e)) { e.preventDefault(); e.stopPropagation(); this._spinDownChangeHandler(e); } }, - enter(): void {}, + enter: (): void => {}, }; } @@ -80,12 +95,10 @@ class NumberBoxBase< useLargeSpinButtons: true, mode: 'text', invalidValueMessage: messageLocalization.format('dxNumberBox-invalidValueMessage'), - // eslint-disable-next-line no-void - buttons: void 0, + buttons: undefined, }; } - // eslint-disable-next-line class-methods-use-this _useTemplates(): boolean { return false; } @@ -95,32 +108,27 @@ class NumberBoxBase< return super._getDefaultButtons().concat([{ name: 'spins', Ctor: SpinButtons }]); } - _isSupportInputMode() { - // @ts-expect-error ts-error - const version = parseFloat(browser.version); + _isSupportInputMode(): boolean { + const version = parseFloat(browser.version ?? ''); + + const isSupportedChrome = !!browser.chrome && version >= 66; + const isSupportedSafari = !!browser.safari && version >= 12; - return ( - browser.chrome && version >= 66 - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - || browser.safari && version >= 12 - ); + return isSupportedChrome || isSupportedSafari; } _defaultOptionsRules(): DefaultOptionsRule<TProperties>[] { // @ts-expect-error ts-error return super._defaultOptionsRules().concat([ { - device() { - return devices.real().generic && !devices.isSimulator(); - }, + device: (): boolean => !!devices.real().generic && !devices.isSimulator(), options: { useLargeSpinButtons: false, }, }, { - device: function () { - return devices.real().deviceType !== 'desktop' && !this._isSupportInputMode(); - }.bind(this), + device: (): boolean => devices.real().deviceType !== 'desktop' + && !this._isSupportInputMode(), options: { mode: 'number', }, @@ -136,7 +144,7 @@ class NumberBoxBase< this._toggleTabIndex(); } - _getDefaultAttributes() { + _getDefaultAttributes(): TextEditorInputAttributes { const attributes = super._getDefaultAttributes(); // eslint-disable-next-line spellcheck/spell-checker attributes.inputmode = 'decimal'; @@ -144,18 +152,25 @@ class NumberBoxBase< } _renderContentImpl(): void { - this.option('isValid') && this._validateValue(this.option('value')); + const { isValid, value } = this.option(); + + if (isValid) { + this._validateValue(value); + } + this.setAria('role', 'spinbutton'); } _renderSubmitElement(): void { + const { value } = this.option(); + this._$submitElement = $('<input>') .attr('type', 'hidden') .appendTo(this.$element()); - this._setSubmitValue(this.option('value')); + this._setSubmitValue(value); } - _setSubmitValue(value) { + _setSubmitValue(value: NumberBoxValue | undefined): void { this._getSubmitElement().val(applyServerDecimalSeparator(value)); } @@ -163,7 +178,7 @@ class NumberBoxBase< return this._$submitElement; } - _keyPressHandler(e) { + _keyPressHandler(e: DxEvent<KeyboardEvent>): void { super._keyPressHandler(); const char = getChar(e); @@ -173,7 +188,7 @@ class NumberBoxBase< if (!isInputCharValid) { const keyName = normalizeKeyName(e); // NOTE: Additional check for Firefox control keys - if (isCommandKeyPressed(e) || keyName && FIREFOX_CONTROL_KEYS.includes(keyName)) { + if (isCommandKeyPressed(e) || (keyName && FIREFOX_CONTROL_KEYS.includes(keyName))) { return; } @@ -188,13 +203,14 @@ class NumberBoxBase< return true; } - _onMouseWheel(dxEvent): void { - dxEvent.delta > 0 ? this._spinValueChange(1, dxEvent) : this._spinValueChange(-1, dxEvent); + _onMouseWheel(e: DxMouseWheelEvent): void { + this._spinValueChange(e.delta > 0 ? 1 : -1, e); } - _renderValue() { + _renderValue(): DeferredObj<unknown> { const inputValue = this._input().val(); - const value = this.option('value'); + const { value } = this.option(); + if (!inputValue.length || Number(inputValue) !== value) { this._forceValueRender(); this._toggleEmptinessEventHandler(); @@ -203,9 +219,8 @@ class NumberBoxBase< const valueText = isDefined(value) ? null : messageLocalization.format('dxNumberBox-noDataText'); this.setAria({ - // @ts-expect-error ts-error // eslint-disable-next-line spellcheck/spell-checker - valuenow: ensureDefined(value, ''), + valuenow: value ?? '', // eslint-disable-next-line spellcheck/spell-checker valuetext: valueText, }); @@ -217,17 +232,18 @@ class NumberBoxBase< } _forceValueRender(): void { - const value = this.option('value'); - const number = Number(value); - const formattedValue = isNaN(number) + const { value } = this.option(); + const formattedValue = isNaN(Number(value)) ? '' : this._applyDisplayValueFormatter(value); this._renderDisplayText(formattedValue); } - _applyDisplayValueFormatter(value): string | undefined { - if (!this.option('format')) { + _applyDisplayValueFormatter(value: NumberBoxValue | undefined): string | undefined { + const { format, displayValueFormatter } = this.option(); + + if (!format) { const globalNumberFormat = getGlobalFormatByDataType('number'); if (globalNumberFormat) { @@ -238,70 +254,66 @@ class NumberBoxBase< } } - const { displayValueFormatter } = this.option(); - return displayValueFormatter?.(value); } _renderProps(): void { + const { min, max, step } = this.option(); + // @ts-expect-error ts-error - this._input().prop({ - min: this.option('min'), - max: this.option('max'), - step: this.option('step'), - }); + this._input().prop({ min, max, step }); this.setAria({ - // @ts-expect-error ts-error // eslint-disable-next-line spellcheck/spell-checker - valuemin: ensureDefined(this.option('min'), ''), - // @ts-expect-error ts-error + valuemin: min ?? '', // eslint-disable-next-line spellcheck/spell-checker - valuemax: ensureDefined(this.option('max'), ''), + valuemax: max ?? '', }); } _spinButtonsPointerDownHandler(): void { + const { useLargeSpinButtons } = this.option(); const $input = this._input(); - if (!this.option('useLargeSpinButtons') && domAdapter.getActiveElement() !== $input[0]) { + + if (!useLargeSpinButtons && domAdapter.getActiveElement() !== $input[0]) { // @ts-expect-error ts-error eventsEngine.trigger($input, 'focus'); } } - _spinUpChangeHandler(e): void { - if (!this.option('readOnly')) { - this._spinValueChange(1, e.event || e); + _spinUpChangeHandler(e: SpinChangeEvent | DxEvent<InteractionEvent>): void { + const { readOnly } = this.option(); + + if (!readOnly) { + this._spinValueChange(1, getSpinEvent(e)); } } - _spinDownChangeHandler(e): void { - if (!this.option('readOnly')) { - this._spinValueChange(-1, e.event || e); + _spinDownChangeHandler(e: SpinChangeEvent | DxEvent<InteractionEvent>): void { + const { readOnly } = this.option(); + + if (!readOnly) { + this._spinValueChange(-1, getSpinEvent(e)); } } - _spinValueChange(sign, dxEvent): void { - // @ts-expect-error ts-error - const step = parseFloat(this.option('step')); + _spinValueChange(sign: number, dxEvent?: SpinValueChangeEvent): void { + const { step: stepOption, min, max } = this.option(); + const step = parseFloat(String(stepOption)); + if (step === 0) { return; } - // @ts-expect-error ts-error - let value = parseFloat(this._normalizeInputValue()) || 0; - value = this._correctRounding(value, step * sign); + let value = parseFloat(String(this._normalizeInputValue())) || 0; - const min = this.option('min'); - const max = this.option('max'); + value = this._correctRounding(value, step * sign); if (isDefined(min)) { - // @ts-expect-error ts-error value = Math.max(min, value); } if (isDefined(max)) { - // @ts-expect-error ts-error value = Math.min(max, value); } @@ -309,35 +321,28 @@ class NumberBoxBase< this.option('value', value); } - _correctRounding(value, step) { + _correctRounding(value: number, step: number): number { const regex = /[,.](.*)/; - const isFloatValue = regex.test(value); - const isFloatStep = regex.test(step); + const valueText = String(value); + const stepText = String(step); + const isFloatValue = regex.test(valueText); + const isFloatStep = regex.test(stepText); if (isFloatValue || isFloatStep) { - // @ts-expect-error - const valueAccuracy = isFloatValue ? regex.exec(value)[0].length : 0; - // @ts-expect-error - const stepAccuracy = isFloatStep ? regex.exec(step)[0].length : 0; - const accuracy = math.max(valueAccuracy, stepAccuracy); - - value = this._round(value + step, accuracy); + const valueAccuracy = isFloatValue ? regex.exec(valueText)?.[0].length ?? 0 : 0; + const stepAccuracy = isFloatStep ? regex.exec(stepText)?.[0].length ?? 0 : 0; + const accuracy = Math.max(valueAccuracy, stepAccuracy); - return value; + return this._round(value + step, accuracy); } return value + step; } - _round(value, precision) { - precision = precision || 0; - + _round(value: number, precision = 0): number { const multiplier = 10 ** precision; - value *= multiplier; - value = Math.round(value) / multiplier; - - return value; + return Math.round(value * multiplier) / multiplier; } _renderValueChangeEvent(): void { @@ -348,20 +353,21 @@ class NumberBoxBase< eventsEngine.on(this.element(), forceValueChangeEvent, this._forceRefreshInputValue.bind(this)); } - _forceRefreshInputValue() { - const { mode } = this.option(); + _forceRefreshInputValue(): void { + const { mode, value } = this.option(); + if (mode === 'number') { return; } const $input = this._input(); - const formattedValue = this._applyDisplayValueFormatter(this.option('value')); + const formattedValue = this._applyDisplayValueFormatter(value); // @ts-expect-error ts-error $input.val(null); $input.val(formattedValue); } - _valueChangeEventHandler(e) { + _valueChangeEventHandler(e: DxEvent): void { const $input = this._input(); const inputValue = this._normalizeText(); const value = this._parseValue(inputValue); @@ -373,8 +379,7 @@ class NumberBoxBase< } if (valueHasDigits) { - // @ts-expect-error ts-error - super._valueChangeEventHandler(e, isNaN(value) ? null : value); + super._valueChangeEventHandler(e, isNaN(Number(value)) ? null : value); } this._applyValueBoundaries(inputValue, value); @@ -385,9 +390,9 @@ class NumberBoxBase< }); } - _applyValueBoundaries(inputValue, parsedValue) { + _applyValueBoundaries(inputValue: string, parsedValue: NumberBoxValue | undefined): void { const isValueIncomplete = this._isValueIncomplete(inputValue); - const isValueCorrect = this._isValueInRange(inputValue); + const isValueCorrect = this._isValueInRange(Number(inputValue)); if (!isValueIncomplete && !isValueCorrect && parsedValue !== null) { if (Number(inputValue) !== parsedValue) { @@ -396,20 +401,19 @@ class NumberBoxBase< } } - _replaceCommaWithPoint(value): string { + _replaceCommaWithPoint(value: string): string { return value.replace(',', '.'); } _inputIsInvalid(): boolean { const { mode } = this.option(); const isNumberMode = mode === 'number'; - // @ts-expect-error ts-error - const validityState = this._input().get(0).validity; + const input = this._input().get(0) as HTMLInputElement | undefined; - return isNumberMode && validityState?.badInput; + return isNumberMode && !!input?.validity.badInput; } - _renderDisplayText(text) { + _renderDisplayText(text: string | undefined): void { if (this._inputIsInvalid()) { return; } @@ -417,20 +421,23 @@ class NumberBoxBase< super._renderDisplayText(text); } - _isValueIncomplete(value) { + _isValueIncomplete(value: string): boolean { const incompleteRegex = /(^-$)|(^-?\d*\.$)|(\d+e-?$)/i; return incompleteRegex.test(value); } - _isValueInRange(value) { - return inRange(value, this.option('min'), this.option('max')); + _isValueInRange(value: number): boolean { + const { min, max } = this.option(); + + return inRange(value, min, max); } - _isNumber(value) { + _isNumber(value: string): boolean { return this._parseValue(value) !== null; } - _validateValue(value?) { + _validateValue(value?: NumberBoxValue): boolean { + const { invalidValueMessage } = this.option(); const inputValue = this._normalizeText(); const isValueValid = this._isValueValid(); let isValid = true; @@ -450,31 +457,32 @@ class NumberBoxBase< isValid, validationError: isValid ? null : { editorSpecific: true, - message: this.option('invalidValueMessage'), + message: invalidValueMessage, }, }); return isValid; } - _normalizeInputValue(): number | null { + _normalizeInputValue(): NumberBoxValue | undefined { return this._parseValue(this._normalizeText()); } - _normalizeText() { + _normalizeText(): string { const value = this._input().val().trim(); return this._replaceCommaWithPoint(value); } - _parseValue(value): number | null { - const number = parseFloat(value); + _parseValue(value?: string | NumberBoxValue): NumberBoxValue | undefined { + const { min, max } = this.option(); + const parsedValue = parseFloat(String(value ?? '')); - if (isNaN(number)) { + if (isNaN(parsedValue)) { return null; } - return fitIntoRange(number, this.option('min'), this.option('max')); + return fitIntoRange(parsedValue, min, max); } _clearValue(): void { @@ -486,7 +494,9 @@ class NumberBoxBase< } clear(): void { - if (this.option('value') === null) { + const { value } = this.option(); + + if (value === null) { this.option('text', ''); if (this._input().length) { this._renderValue(); @@ -496,22 +506,28 @@ class NumberBoxBase< } } - _optionChanged(args) { + _optionChanged(args: OptionChanged<TProperties>): void { switch (args.name) { - case 'value': - this._validateValue(args.value); - this._setSubmitValue(args.value); + case 'value': { + const value = args.value as NumberBoxValue | undefined; + + this._validateValue(value); + this._setSubmitValue(value); super._optionChanged(args); this._resumeValueChangeAction(); break; + } case 'step': this._renderProps(); break; case 'min': - case 'max': + case 'max': { + const { value } = this.option(); + this._renderProps(); - this.option('value', this._parseValue(this.option('value'))); + this.option('value', this._parseValue(value)); break; + } case 'showSpinButtons': case 'useLargeSpinButtons': this._updateButtons(['spins']); diff --git a/packages/devextreme/js/__internal/ui/number_box/number_box.caret.ts b/packages/devextreme/js/__internal/ui/number_box/number_box.caret.ts new file mode 100644 index 000000000000..426ef848ce10 --- /dev/null +++ b/packages/devextreme/js/__internal/ui/number_box/number_box.caret.ts @@ -0,0 +1,161 @@ +import number from '@js/common/core/localization/number'; +import { escapeRegExp } from '@js/core/utils/common'; +import { fitIntoRange } from '@js/core/utils/math'; +import type { Format } from '@js/localization'; +import type { CaretRange } from '@ts/ui/text_box/utils.caret'; + +import { getNthOccurrence, getRealSeparatorIndex, splitByIndex } from './utils'; + +export interface CaretBoundaries { + start: number; + end: number; +} + +export type CaretPosition = number | CaretRange; + +const mockEscapedStubs = (format: string): string => format.replace( + /'([^']*)'/g, + (stub) => stub.split('').map(() => ' ').join('').substr(2), +); + +const getDigitCountBeforeIndex = (index: number, text: string): number => { + const decimalSeparator: string = number.getDecimalSeparator(); + const regExp = new RegExp(`[^0-9${escapeRegExp(decimalSeparator)}]`, 'g'); + const textBeforePosition = text.slice(0, index); + + return textBeforePosition.replace(regExp, '').length; +}; + +const reverseText = (text: string): string => text.split('').reverse().join(''); + +const getDigitPositionByIndex = (digitIndex: number, text: string): number => { + if (!digitIndex) { + return -1; + } + + const regExp = /[0-9]/g; + let counter = 1; + let index: number | null = null; + let result = regExp.exec(text); + + while (result) { + index = result.index; + if (counter >= digitIndex) { + return index; + } + counter += 1; + result = regExp.exec(text); + } + + return index ?? text.length; +}; + +const trimNonNumericCharsFromEnd = (text: string): string => text.replace(/[^0-9e]+$/, ''); + +const isSeparatorBasedString = (text: string): boolean => text.length === 1 + && !!text.match(/^[,.][0-9]*$/g); + +export const getCaretBoundaries = (text: string, format: Format): CaretBoundaries => { + if (typeof format === 'string') { + const signParts = format.split(';'); + const sign = number.getSign(text, format); + + signParts[1] = signParts[1] || `-${signParts[0]}`; + + const signFormat = mockEscapedStubs(signParts[sign < 0 ? 1 : 0]); + + const prefixStubLength = /^[^#0.,]*/.exec(signFormat)?.[0].length ?? 0; + const postfixStubLength = /[^#0.,]*$/.exec(signFormat)?.[0].length ?? 0; + + return { + start: prefixStubLength, + end: text.length - postfixStubLength, + }; + } + return { start: 0, end: text.length }; +}; + +export const getCaretWithOffset = ( + caret: CaretPosition, + offset: number, +): CaretBoundaries => { + const range = typeof caret === 'number' ? { start: caret, end: caret } : caret; + + return { + start: (range.start ?? 0) + offset, + end: (range.end ?? 0) + offset, + }; +}; + +export const getCaretInBoundaries = ( + caret: CaretPosition, + text: string, + format: Format, +): CaretBoundaries => { + const normalizedCaret = getCaretWithOffset(caret, 0); + const boundaries = getCaretBoundaries(text, format); + + return { + start: fitIntoRange(normalizedCaret.start, boundaries.start, boundaries.end), + end: fitIntoRange(normalizedCaret.end, boundaries.start, boundaries.end), + }; +}; + +export const getCaretAfterFormat = ( + text: string, + formatted: string, + caret: CaretPosition, + format: Format, +): CaretBoundaries => { + const normalizedCaret = getCaretWithOffset(caret, 0); + + const point: string = number.getDecimalSeparator(); + const realSeparatorOccurrenceIndex = getRealSeparatorIndex(format).occurrence; + const pointPosition = isSeparatorBasedString(text) + ? 0 + : getNthOccurrence(text, point, realSeparatorOccurrenceIndex); + const newPointPosition = getNthOccurrence(formatted, point, realSeparatorOccurrenceIndex); + const textParts = splitByIndex(text, pointPosition); + const formattedParts = splitByIndex(formatted, newPointPosition); + const isCaretOnFloat = pointPosition !== -1 && normalizedCaret.start > pointPosition; + + if (isCaretOnFloat) { + const relativeIndex = normalizedCaret.start - pointPosition - 1; + const digitsBefore = getDigitCountBeforeIndex(relativeIndex, textParts[1]); + const newPosition = formattedParts[1] + ? newPointPosition + 1 + getDigitPositionByIndex(digitsBefore, formattedParts[1]) + 1 + : formatted.length; + + return getCaretInBoundaries(newPosition, formatted, format); + } + + const formattedIntPart = trimNonNumericCharsFromEnd(formattedParts[0]); + const positionFromEnd = textParts[0].length - normalizedCaret.start; + const digitsFromEnd = getDigitCountBeforeIndex(positionFromEnd, reverseText(textParts[0])); + const newPositionFromEnd = getDigitPositionByIndex(digitsFromEnd, reverseText(formattedIntPart)); + const newPositionFromBegin = formattedIntPart.length - (newPositionFromEnd + 1); + + return getCaretInBoundaries(newPositionFromBegin, formatted, format); +}; + +export const isCaretInBoundaries = ( + caret: CaretPosition, + text: string, + format: Format, +): boolean => { + const normalizedCaret = getCaretWithOffset(caret, 0); + const boundaries = getCaretInBoundaries(normalizedCaret, text, format); + + return normalizedCaret.start >= boundaries.start && normalizedCaret.end <= boundaries.end; +}; + +export const getCaretOffset = ( + previousText: string, + newText: string, + format: Format, +): number => { + const previousBoundaries = getCaretBoundaries(previousText, format); + const newBoundaries = getCaretBoundaries(newText, format); + + return newBoundaries.start - previousBoundaries.start; +}; diff --git a/packages/devextreme/js/__internal/ui/number_box/m_number_box.mask.ts b/packages/devextreme/js/__internal/ui/number_box/number_box.mask.ts similarity index 61% rename from packages/devextreme/js/__internal/ui/number_box/m_number_box.mask.ts rename to packages/devextreme/js/__internal/ui/number_box/number_box.mask.ts index 11a9995839ad..72f9dabe79e3 100644 --- a/packages/devextreme/js/__internal/ui/number_box/m_number_box.mask.ts +++ b/packages/devextreme/js/__internal/ui/number_box/number_box.mask.ts @@ -3,30 +3,36 @@ import { name as dxDblClickEvent } from '@js/common/core/events/double_click'; import { addNamespace, getChar, isCommandKeyPressed, normalizeKeyName, } from '@js/common/core/events/utils/index'; -import { getFormat as getLDMLFormat } from '@js/common/core/localization/ldml/number'; import number from '@js/common/core/localization/number'; import devices from '@js/core/devices'; -import { ensureDefined, escapeRegExp } from '@js/core/utils/common'; +import { escapeRegExp } from '@js/core/utils/common'; +import type { DeferredObj } from '@js/core/utils/deferred'; import { fitIntoRange, inRange } from '@js/core/utils/math'; import { isDefined, isFunction, isNumeric, isPlainObject, isString, } from '@js/core/utils/type'; -import type { NativeEventInfo } from '@js/events'; +import type { DxEvent, NativeEventInfo } from '@js/events'; import type { Format, FormatObject } from '@js/localization'; import type { Properties } from '@js/ui/number_box'; import { getGlobalFormatByDataType } from '@ts/core/global_format_config'; +import { getFormat as getLDMLFormat } from '@ts/core/localization/ldml/number'; +import type { OptionChanged } from '@ts/core/widget/types'; +import type { SupportedKeys } from '@ts/core/widget/widget'; +import type { KeyboardKeyDownEvent } from '@ts/events/core/keyboard_processor'; import type { TextEditorInternalProperties } from '@ts/ui/text_box/text_editor.base'; +import type { CaretRange } from '@ts/ui/text_box/utils.caret'; -import NumberBoxBase from './m_number_box.base'; +import type { NumberBoxBaseProperties, NumberBoxValue } from './number_box.base'; +import NumberBoxBase from './number_box.base'; import { getCaretAfterFormat, getCaretBoundaries, getCaretInBoundaries, getCaretOffset, getCaretWithOffset, isCaretInBoundaries, -} from './m_number_box.caret'; +} from './number_box.caret'; import { - adjustPercentValue, getNthOccurrence, getRealSeparatorIndex, + adjustPercentValue, asPattern, getNthOccurrence, getRealSeparatorIndex, splitByIndex, -} from './m_utils'; +} from './utils'; const NUMBER_FORMATTER_NAMESPACE = 'dxNumberFormatter'; const MOVE_FORWARD = 1 as const; @@ -46,17 +52,24 @@ const asFormatObject = (format: Format | undefined): FormatObject | undefined => isPlainObject(format) ? format as FormatObject : undefined ); +const isNegativeValue = (value: NumberBoxValue | undefined): boolean => { + const parsedValue = Number(value); + + return parsedValue < 0 || 1 / parsedValue === -Infinity; +}; + export interface NumberBoxMaskProperties extends Omit<Properties, 'onChange' | 'onCopy' | 'onCut' | 'onEnterKey' | 'onFocusIn' | 'onFocusOut' | 'onInput' | 'onKeyDown' | 'onKeyUp' | 'onPaste' | 'onValueChanged' | 'onContentReady' | 'onDisposing' -| 'onOptionChanged' | 'onInitialized' >, Omit<TextEditorInternalProperties, 'displayValueFormatter'> { +| 'onOptionChanged' | 'onInitialized' | 'format' >, Omit<TextEditorInternalProperties, 'displayValueFormatter'> { + format?: Format | null; + useMaskBehavior?: boolean; - displayValueFormatter?: ((value: any) => string); + displayValueFormatter?: NumberBoxBaseProperties['displayValueFormatter']; onValueChanged?: ( - e: NativeEventInfo<unknown> & { value?: number | null; previousValue?: number | null }, + e: NativeEventInfo<unknown> & { value?: NumberBoxValue; previousValue?: NumberBoxValue }, ) => void; - } class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { @@ -64,7 +77,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { _focusOutOccurs?: boolean; - _parsedValue?: number | null; + _parsedValue?: NumberBoxValue; _lastKeyName?: string | null; @@ -72,40 +85,35 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { _formattedValue?: string; - _isValuePasted?: boolean; - - _currentFormat?: any; + _currentFormat?: Format; _getDefaultOptions(): NumberBoxMaskProperties { return { ...super._getDefaultOptions(), useMaskBehavior: true, - // @ts-expect-error ts-error format: null, }; } - _isDeleteKey(key): boolean { + _isDeleteKey(key: string | null | undefined): boolean { return key === 'del'; } - _supportedKeys() { + _supportedKeys(): SupportedKeys { if (!this._useMaskBehavior()) { return super._supportedKeys(); } - const that = this; - return { ...super._supportedKeys(), - minus: that._revertSign.bind(that), - del: that._removeHandler.bind(that), - backspace: that._removeHandler.bind(that), - leftArrow: that._arrowHandler.bind(that, MOVE_BACKWARD), - rightArrow: that._arrowHandler.bind(that, MOVE_FORWARD), - home: that._boundaryKeyHandler.bind(that, MOVE_FORWARD), - enter: that._updateFormattedValue.bind(that), - end: that._boundaryKeyHandler.bind(that, MOVE_BACKWARD), + minus: (e): void => this._revertSign(e), + del: (e): void => this._removeHandler(e), + backspace: (e): void => this._removeHandler(e), + leftArrow: (e): void => this._arrowHandler(MOVE_BACKWARD, e), + rightArrow: (e): void => this._arrowHandler(MOVE_FORWARD, e), + home: (e): void => this._boundaryKeyHandler(MOVE_FORWARD, e), + enter: (): void => this._updateFormattedValue(), + end: (e): void => this._boundaryKeyHandler(MOVE_BACKWARD, e), }; } @@ -117,15 +125,13 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { : getGlobalFormatByDataType('number'); } - _getTextSeparatorIndex(text) { - const decimalSeparator = number.getDecimalSeparator(); - const formatPattern = this._getFormatPattern(); - const patternString = isString(formatPattern) ? formatPattern : ''; - const realSeparatorOccurrenceIndex = getRealSeparatorIndex(patternString).occurrence; + _getTextSeparatorIndex(text: string): number { + const decimalSeparator: string = number.getDecimalSeparator(); + const realSeparatorOccurrenceIndex = getRealSeparatorIndex(this._getFormatPattern()).occurrence; return getNthOccurrence(text, decimalSeparator, realSeparatorOccurrenceIndex); } - _focusInHandler(e): void { + _focusInHandler(e: DxEvent<FocusEvent>): void { if (!this._preventNestedFocusEvent(e)) { this.clearCaretTimeout(); this._caretTimeout = setTimeout(() => { @@ -148,7 +154,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { super._focusInHandler(e); } - _focusOutHandler(e): void { + _focusOutHandler(e: DxEvent): void { const shouldHandleEvent = !this._preventNestedFocusEvent(e); if (shouldHandleEvent) { @@ -165,15 +171,15 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { } } - _hasValueBeenChanged(inputValue) { + _hasValueBeenChanged(inputValue: string): boolean { const format = this._getFormatPattern(); - const value = this.option('value'); + const { value } = this.option(); const formatted = this._format(value, format) || ''; return formatted !== inputValue; } - _updateFormattedValue() { + _updateFormattedValue(): void { const inputValue = this._getInputVal(); if (this._hasValueBeenChanged(inputValue)) { @@ -191,19 +197,26 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { } } - _arrowHandler(step, e) { + _arrowHandler(step: CaretMoveDirection, e: DxEvent<KeyboardEvent>): void { if (!this._useMaskBehavior()) { return; } + const caret = this._caret(); + + if (!caret) { + return; + } + const text = this._getInputVal(); const format = this._getFormatPattern(); - let nextCaret = getCaretWithOffset(this._caret(), step); + const nextCaret = getCaretWithOffset(caret, step); if (!isCaretInBoundaries(nextCaret, text, format)) { - nextCaret = step === MOVE_FORWARD ? nextCaret.end : nextCaret.start; + const nextCaretPosition = step === MOVE_FORWARD ? nextCaret.end : nextCaret.start; + e.preventDefault(); - this._caret(getCaretInBoundaries(nextCaret, text, format)); + this._caret(getCaretInBoundaries(nextCaretPosition, text, format)); this._scrollInputTo(step === MOVE_FORWARD ? 'end' : 'start'); } } @@ -232,7 +245,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { this._caret(newCaret); } - _boundaryKeyHandler(direction: CaretMoveDirection, e: KeyboardEvent): void { + _boundaryKeyHandler(direction: CaretMoveDirection, e: DxEvent<KeyboardEvent>): void { if (!this._useMaskBehavior() || e.shiftKey) { return; } @@ -242,19 +255,25 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { this._scrollInputTo(direction === MOVE_FORWARD ? 'start' : 'end'); } - _shouldMoveCaret(text, caret) { + _shouldMoveCaret(text: string, caret: CaretRange | undefined): boolean { + if (!caret) { + return false; + } + const decimalSeparator = number.getDecimalSeparator(); - const isDecimalSeparatorNext = text.charAt(caret.end) === decimalSeparator; - const moveToFloat = (this._lastKey === decimalSeparator || this._lastKey === '.' || this._lastKey === ',') && isDecimalSeparatorNext; + const isDecimalSeparatorNext = text.charAt(caret.end ?? 0) === decimalSeparator; + const isSeparatorKey = this._lastKey === decimalSeparator || this._lastKey === '.' || this._lastKey === ','; - return moveToFloat; + return isSeparatorKey && isDecimalSeparatorNext; } - _getInputVal() { - return number.convertDigits(this._input().val(), true); + _getInputVal(): string { + const inputValue: string = number.convertDigits(this._input().val(), true); + + return inputValue; } - _keyboardHandler(e): boolean { + _keyboardHandler(e: KeyboardKeyDownEvent): boolean { this.clearCaretTimeout(); this._lastKey = number.convertDigits(getChar(e), true); @@ -267,11 +286,11 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { const normalizedText = this._getInputVal(); const caret = this._caret(); - let enteredChar; + let enteredChar = this._lastKey; if (this._lastKeyName === MINUS_KEY) { enteredChar = ''; - } else { - enteredChar = e.which === NUMPAD_DOT_KEY_CODE ? number.getDecimalSeparator() : this._lastKey; + } else if (e.which === NUMPAD_DOT_KEY_CODE) { + enteredChar = number.getDecimalSeparator(); } const newValue = this._tryParse(normalizedText, caret, enteredChar); @@ -291,13 +310,13 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { return super._keyboardHandler(e); } - _keyPressHandler(e): void { + _keyPressHandler(e: DxEvent<KeyboardEvent>): void { if (!this._useMaskBehavior()) { super._keyPressHandler(e); } } - _removeHandler(e) { + _removeHandler(e: DxEvent<KeyboardEvent>): void { const caret = this._caret(); const text = this._getInputVal(); @@ -312,14 +331,15 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { if (start === end) { const caretPosition = start; - const canDelete = isBackspaceKey - && caretPosition > 0 - || isDeleteKey - && caretPosition < text.length; + const canDelete = (isBackspaceKey && caretPosition > 0) + || (isDeleteKey && caretPosition < text.length); if (canDelete) { - isDeleteKey && end++; - isBackspaceKey && start--; + if (isDeleteKey) { + end += 1; + } else { + start -= 1; + } } else { e.preventDefault(); return; @@ -330,12 +350,11 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { if (this._isStub(char)) { this._moveCaret(isDeleteKey ? 1 : -1); - // @ts-expect-error ts-error - if (this._parsedValue < 0 || 1 / this._parsedValue === -Infinity) { + if (isNegativeValue(this._parsedValue)) { this._revertSign(e); this._setTextByParsedValue(); - // @ts-expect-error ts-error - const shouldTriggerInputEvent = this.option('valueChangeEvent').split(' ').includes('input'); + const { valueChangeEvent } = this.option(); + const shouldTriggerInputEvent = valueChangeEvent?.split(' ').includes('input'); if (shouldTriggerInputEvent) { // @ts-expect-error ts-error eventsEngine.trigger(this._input(), 'input'); @@ -345,7 +364,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { return; } - const decimalSeparator = number.getDecimalSeparator(); + const decimalSeparator: string = number.getDecimalSeparator(); if (char === decimalSeparator) { const decimalSeparatorIndex = text.indexOf(decimalSeparator); if (this._isNonStubAfter(decimalSeparatorIndex + 1)) { @@ -360,8 +379,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { const noDigits = editedText.search(/[0-9]/) < 0; if (noDigits && this._isValueInRange(0)) { - // @ts-expect-error ts-error - this._parsedValue = this._parsedValue < 0 || 1 / this._parsedValue === -Infinity ? -0 : 0; + this._parsedValue = isNegativeValue(this._parsedValue) ? -0 : 0; return; } } @@ -374,48 +392,52 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { } } - _isPercentFormat() { - const format = this._getFormatPattern(); - const noEscapedFormat = format.replace(/'[^']+'/g, ''); + _isPercentFormat(): boolean { + const noEscapedFormat = asPattern(this._getFormatPattern()).replace(/'[^']+'/g, ''); - return noEscapedFormat.indexOf('%') !== -1; + return noEscapedFormat.includes('%'); } - _parse(text, format) { - const formatOption = this._getEffectiveFormatOption(); - const customParser = asFormatObject(formatOption)?.parser; - const isCustomParser = isFunction(customParser); - const parser = isCustomParser ? customParser : number.parse; + _parse(text: string, format: Format): NumberBoxValue | undefined { + const customParser = asFormatObject(this._getEffectiveFormatOption())?.parser; + + if (isFunction(customParser)) { + const customParsedValue = customParser(text); + + return customParsedValue instanceof Date ? undefined : customParsedValue; + } + let integerPartStartIndex = 0; - if (!isCustomParser) { + if (isString(format)) { const formatPointIndex = getRealSeparatorIndex(format).index; const textPointIndex = this._getTextSeparatorIndex(text); const formatIntegerPartLength = formatPointIndex !== -1 ? formatPointIndex : format.length; const textIntegerPartLength = textPointIndex !== -1 ? textPointIndex : text.length; - if (textIntegerPartLength > formatIntegerPartLength && format.indexOf('#') === -1) { + if (textIntegerPartLength > formatIntegerPartLength && !format.includes('#')) { integerPartStartIndex = textIntegerPartLength - formatIntegerPartLength; } } - text = text.substr(integerPartStartIndex); - - return parser(text, format); + return number.parse( + text.substr(integerPartStartIndex), + isString(format) ? format : asFormatObject(format), + ); } - _format(value, format) { + _format(value: NumberBoxValue | undefined, format: Format): string | undefined { const formatOption = this._getEffectiveFormatOption(); const customFormatter = asFormatObject(formatOption)?.formatter ?? formatOption; - const formatter = isFunction(customFormatter) ? customFormatter : number.format; - - const formattedValue = value === null ? '' : formatter(value, format); + const formatter = (isFunction(customFormatter) ? customFormatter : number.format) as ( + value: number, valueFormat: Format, + ) => string | undefined; - return formattedValue; + return isDefined(value) ? formatter(value, format) : ''; } - _getFormatPattern() { + _getFormatPattern(): Format { if (!this._currentFormat) { this._updateFormat(); } @@ -433,28 +455,30 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { this._currentFormat = shouldUseFormatAsIs ? format - : getLDMLFormat((value) => { - const text = this._format(value, format); - return number.convertDigits(text, true); + : getLDMLFormat((value: number): string => { + const text = this._format(value, format) ?? ''; + const convertedText: string = number.convertDigits(text, true); + + return convertedText; }); } - _getFormatForSign(text) { + _getFormatForSign(text: string): string { const format = this._getFormatPattern(); if (isString(format)) { const signParts = format.split(';'); - const sign = number.getSign(text, format); + const sign: number = number.getSign(text, format); signParts[1] = signParts[1] || `-${signParts[0]}`; return sign < 0 ? signParts[1] : signParts[0]; } - const sign = number.getSign(text); + const sign: number = number.getSign(text); return sign < 0 ? '-' : ''; } - _removeStubs(text, excludeComma?): string { + _removeStubs(text: string, excludeComma?: boolean): string { const format = this._getFormatForSign(text); - const thousandsSeparator = number.getThousandsSeparator(); + const thousandsSeparator: string = number.getThousandsSeparator(); const stubs = this._getStubs(format); let result = text; @@ -472,42 +496,54 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { return result; } - _getStubs(format) { - const regExpResult = /[^']([#0.,]+)/g.exec(format); - const pattern = regExpResult && regExpResult[0].trim(); + _getStubs(format: string): string[] { + const patternMatch = /[^']([#0.,]+)/g.exec(format); + const pattern = patternMatch?.[0].trim(); + const stubs = isDefined(pattern) ? format.split(pattern) : [format]; - return format - .split(pattern) - .map((stub) => stub.replace(/'/g, '')); + return stubs.map((stub) => stub.replace(/'/g, '')); } - _truncateToPrecision(value, maxPrecision) { + _truncateToPrecision( + value: NumberBoxValue | undefined, + maxPrecision: number, + ): NumberBoxValue | undefined { if (isDefined(value)) { const strValue = value.toString(); const decimalSeparatorIndex = strValue.indexOf('.'); if (strValue && decimalSeparatorIndex > -1) { - const parsedValue = parseFloat(strValue.substr(0, decimalSeparatorIndex + maxPrecision + 1)); + const truncatedValue = strValue.substr(0, decimalSeparatorIndex + maxPrecision + 1); + const parsedValue = parseFloat(truncatedValue); + return isNaN(parsedValue) ? value : parsedValue; } } return value; } - _tryParse(text, selection, char?) { - const isTextSelected = selection.start !== selection.end; - const isWholeTextSelected = isTextSelected && selection.start === 0 && selection.end === text.length; - const decimalSeparator = number.getDecimalSeparator(); + _tryParse( + text: string, + selection: CaretRange | undefined, + char?: string | null, + ): NumberBoxValue | undefined { + const { start = 0, end = 0 } = selection ?? {}; + const isTextSelected = start !== end; + const isWholeTextSelected = isTextSelected && start === 0 && end === text.length; + const decimalSeparator: string = number.getDecimalSeparator(); if (isWholeTextSelected && char === decimalSeparator) { return 0; } - const editedText = this._replaceSelectedText(text, selection, char); + const editedText = this._replaceSelectedText(text, { start, end }, char ?? undefined); const format = this._getFormatPattern(); + const hasCustomParser = isFunction(asFormatObject(format)?.parser); let parsedValue = this._getParsedValue(editedText, format); - const maxPrecision = !format.parser && this._getPrecisionLimits(editedText).max; + const maxPrecision = hasCustomParser + ? undefined + : this._getPrecisionLimits(this._getFormatForSign(editedText)).max; const isValueChanged = parsedValue !== this._parsedValue; const isDecimalPointRestricted = char === decimalSeparator && maxPrecision === 0; @@ -521,8 +557,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { } if (this._removeStubs(editedText) === '') { - // @ts-expect-error ts-error - parsedValue = Math.abs(this._parsedValue * 0); + parsedValue = Math.abs(Number(this._parsedValue) * 0); } if (isNaN(Number(parsedValue))) { @@ -532,8 +567,8 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { const value = parsedValue === null ? this._parsedValue : parsedValue; parsedValue = maxPrecision ? this._truncateToPrecision(value, maxPrecision) : parsedValue; - if (!format.parser && this._isPercentFormat()) { - const interval = this._getIntervalFromPrecision(maxPrecision); + if (!hasCustomParser && this._isPercentFormat()) { + const interval = this._getIntervalFromPrecision(maxPrecision ?? 0); return adjustPercentValue(parsedValue, interval); } @@ -541,7 +576,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { return parsedValue; } - _getIntervalFromPrecision(precision) { + _getIntervalFromPrecision(precision: number): number { if (precision < 1) { return 1; } @@ -549,23 +584,24 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { return 10 ** -precision; } - _getParsedValue(text, format) { - const sign = number.getSign(text, format?.formatter || format); + _getParsedValue(text: string, format: Format): NumberBoxValue | undefined { + const signFormat = asFormatObject(format)?.formatter ?? format; + const sign: number = number.getSign(text, signFormat); const textWithoutStubs = this._removeStubs(text, true); const parsedValue = this._parse(textWithoutStubs, format); - const parsedValueSign = parsedValue != null && parsedValue < 0 ? -1 : 1; - const parsedValueWithSign = isNumeric(parsedValue) && sign !== parsedValueSign ? sign * parsedValue : parsedValue; + const parsedValueSign = Number(parsedValue) < 0 ? -1 : 1; + const shouldRevertSign = isNumeric(parsedValue) && sign !== parsedValueSign; - return parsedValueWithSign; + return shouldRevertSign ? sign * parsedValue : parsedValue; } - _isValueIncomplete(text) { + _isValueIncomplete(text: string): boolean { if (!this._useMaskBehavior()) { return super._isValueIncomplete(text); } const caret = this._caret(); - const point = number.getDecimalSeparator(); + const point: string = number.getDecimalSeparator(); const pointIndex = this._getTextSeparatorIndex(text); const isCaretOnFloat = pointIndex >= 0 && pointIndex < (caret?.start ?? 0); const textParts = this._removeStubs(text, true).split(point); @@ -575,34 +611,41 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { } const floatLength = textParts[1].length; - const format = this._getFormatPattern(); - const isCustomParser = !!format.parser; - const precision = !isCustomParser && this._getPrecisionLimits(this._getFormatPattern()); - const isPrecisionInRange = isCustomParser - ? true - // @ts-expect-error ts-error - : inRange(floatLength, precision.min, precision.max); const endsWithZero = textParts[1].charAt(floatLength - 1) === '0'; + const isFloatPartComplete = endsWithZero || !floatLength; + + const format = this._getFormatPattern(); - return isPrecisionInRange && (endsWithZero || !floatLength); + if (isFunction(asFormatObject(format)?.parser)) { + return isFloatPartComplete; + } + + const precision = this._getPrecisionLimits(this._getFormatForSign(asPattern(format))); + + return inRange(floatLength, precision.min, precision.max) && isFloatPartComplete; } - _isValueInRange(value) { - const min = ensureDefined(this.option('min'), -Infinity); - const max = ensureDefined(this.option('max'), Infinity); + _isValueInRange(value: number): boolean { + const { min, max } = this.option(); - return inRange(value, min, max); + return inRange(value, min ?? -Infinity, max ?? Infinity); } - _setInputText(text): void { - const normalizedText = number.convertDigits(text, true); - const newCaret = getCaretAfterFormat(this._getInputVal(), normalizedText, this._caret(), this._getFormatPattern()); + _setInputText(text: string): void { + const normalizedText: string = number.convertDigits(text, true); + const caret = this._caret(); + const newCaret = caret && getCaretAfterFormat( + this._getInputVal(), + normalizedText, + caret, + this._getFormatPattern(), + ); this._input().val(text); this._toggleEmptinessEventHandler(); this._formattedValue = text; - if (!this._focusOutOccurs) { + if (newCaret && !this._focusOutOccurs) { this._caret(newCaret); } } @@ -625,24 +668,31 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { } } - _isChar(str) { + _isChar(str: string | null | undefined): boolean { return isString(str) && str.length === 1; } - _moveCaret(offset?): void { - if (!offset) { + _moveCaret(offset?: number): void { + const caret = this._caret(); + + if (!offset || !caret) { return; } - const newCaret = getCaretWithOffset(this._caret(), offset); - const adjustedCaret = getCaretInBoundaries(newCaret, this._getInputVal(), this._getFormatPattern()); + const newCaret = getCaretWithOffset(caret, offset); + const adjustedCaret = getCaretInBoundaries( + newCaret, + this._getInputVal(), + this._getFormatPattern(), + ); this._caret(adjustedCaret); } - _shouldHandleKey(e): boolean { + _shouldHandleKey(e: KeyboardEvent): boolean { const keyName = normalizeKeyName(e); - const isSpecialChar = isCommandKeyPressed(e) || e.altKey || e.shiftKey || !this._isChar(keyName); + const isSpecialChar = isCommandKeyPressed(e) || e.altKey || e.shiftKey + || !this._isChar(keyName); const isMinusKey = keyName === MINUS_KEY; const useMaskBehavior = this._useMaskBehavior(); @@ -654,7 +704,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { this._renderFormatter(); } - _renderFormatter() { + _renderFormatter(): void { this._clearCache(); this._detachFormatterEvents(); @@ -663,32 +713,37 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { } } - _detachFormatterEvents() { + _detachFormatterEvents(): void { eventsEngine.off(this._input(), `.${NUMBER_FORMATTER_NAMESPACE}`); } - _isInputFromPaste(e) { - const inputType = e.originalEvent?.inputType; - - if (isDefined(inputType)) { - return inputType === 'insertFromPaste'; - } - return this._isValuePasted; + _isInputFromPaste(e: DxEvent<InputEvent>): boolean { + return e.originalEvent?.inputType === 'insertFromPaste'; } _attachFormatterEvents(): void { const $input = this._input(); - eventsEngine.on($input, addNamespace(INPUT_EVENT, NUMBER_FORMATTER_NAMESPACE), (e) => { - this._formatValue(e); - this._isValuePasted = false; - }); + eventsEngine.on( + $input, + addNamespace(INPUT_EVENT, NUMBER_FORMATTER_NAMESPACE), + (e: DxEvent<InputEvent>) => { this._formatValue(e); }, + ); eventsEngine.on($input, addNamespace('dxclick', NUMBER_FORMATTER_NAMESPACE), () => { if (!this._caretTimeout) { this._caretTimeout = setTimeout(() => { this._caretTimeout = undefined; - this._caret(getCaretInBoundaries(this._caret(), this._getInputVal(), this._getFormatPattern())); + + const caret = this._caret(); + + if (caret) { + this._caret(getCaretInBoundaries( + caret, + this._getInputVal(), + this._getFormatPattern(), + )); + } }, CARET_TIMEOUT_DURATION); } }); @@ -703,44 +758,44 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { this._caretTimeout = undefined; } - _forceRefreshInputValue() { + _forceRefreshInputValue(): void { if (!this._useMaskBehavior()) { - return super._forceRefreshInputValue(); + super._forceRefreshInputValue(); } } - _isNonStubAfter(index) { + _isNonStubAfter(index: number): boolean { const text = this._getInputVal().slice(index); - return text && !this._isStub(text, true); + + return !!text && !this._isStub(text, true); } - _isStub(str, isString?) { + _isStub(str: string | null | undefined, allowMultipleChars?: boolean): boolean { const escapedDecimalSeparator = escapeRegExp(number.getDecimalSeparator()); const regExpString = `^[^0-9${escapedDecimalSeparator}]+$`; const stubRegExp = new RegExp(regExpString, 'g'); - return stubRegExp.test(str) && (isString || this._isChar(str)); + return stubRegExp.test(str ?? '') && (!!allowMultipleChars || this._isChar(str)); } - _parseValue(text?): number | null { + _parseValue(text?: string | NumberBoxValue): NumberBoxValue | undefined { if (!this._useMaskBehavior()) { return super._parseValue(text); } - // @ts-expect-error ts-error + return this._parsedValue; } - _getPrecisionLimits(text): { min: number; max: number } { - const currentFormat = this._getFormatForSign(text); - const realSeparatorIndex = getRealSeparatorIndex(currentFormat).index; - const floatPart = (splitByIndex(currentFormat, realSeparatorIndex)[1] || '').replace(/[^#0]/g, ''); + _getPrecisionLimits(pattern: string): { min: number; max: number } { + const realSeparatorIndex = getRealSeparatorIndex(pattern).index; + const floatPart = (splitByIndex(pattern, realSeparatorIndex)[1] || '').replace(/[^#0]/g, ''); const minPrecision = floatPart.replace(/^(0*)#*/, '$1').length; const maxPrecision = floatPart.length; return { min: minPrecision, max: maxPrecision }; } - _revertSign(e) { + _revertSign(e: DxEvent<KeyboardEvent>): void { if (!this._useMaskBehavior()) { return; } @@ -758,8 +813,12 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { this._applyRevertedSign(e, caret); } - _applyRevertedSign(e, caret, preserveSelectedText?): void { - const newValue = -1 * ensureDefined(this._parsedValue, null); + _applyRevertedSign( + e: DxEvent<KeyboardEvent>, + caret: CaretRange | undefined, + preserveSelectedText?: boolean, + ): void { + const newValue = -1 * (this._parsedValue ?? 0); if (this._isValueInRange(newValue) || newValue === 0) { this._parsedValue = newValue; @@ -771,25 +830,26 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { this._setTextByParsedValue(); e.preventDefault(); - const currentText = this._getInputVal(); - const offset = getCaretOffset(previousText, currentText, format); - - caret = getCaretWithOffset(caret, offset); + if (caret) { + const currentText = this._getInputVal(); + const offset = getCaretOffset(previousText, currentText, format); + const caretWithOffset = getCaretWithOffset(caret, offset); + const caretInBoundaries = getCaretInBoundaries(caretWithOffset, currentText, format); - const caretInBoundaries = getCaretInBoundaries(caret, currentText, format); - - this._caret(caretInBoundaries); + this._caret(caretInBoundaries); + } } } } - _removeMinusFromText(text, caret) { - const isMinusPressed = this._lastKeyName === MINUS_KEY && text.charAt(caret.start - 1) === MINUS; + _removeMinusFromText(text: string, caret: CaretRange | undefined): string { + const caretStart = caret?.start ?? 0; + const isMinusPressed = this._lastKeyName === MINUS_KEY + && text.charAt(caretStart - 1) === MINUS; - return isMinusPressed ? this._replaceSelectedText(text, { - start: caret.start - 1, - end: caret.start, - }, '') : text; + return isMinusPressed + ? this._replaceSelectedText(text, { start: caretStart - 1, end: caretStart }, '') + : text; } _setTextByParsedValue(): void { @@ -800,7 +860,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { this._setInputText(formatted); } - _formatValue(e): void { + _formatValue(e: DxEvent<InputEvent>): void { let normalizedText = this._getInputVal(); const caret = this._caret(); const textWithoutMinus = this._removeMinusFromText(normalizedText, caret); @@ -817,7 +877,8 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { return; } - const textWasChanged = number.convertDigits(this._formattedValue, true) !== normalizedText; + const formattedValue: string | undefined = number.convertDigits(this._formattedValue, true); + const textWasChanged = formattedValue !== normalizedText; if (textWasChanged) { const value = this._tryParse(normalizedText, caret, ''); @@ -830,18 +891,18 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { this._setTextByParsedValue(); } - _renderDisplayText(): void { + _renderDisplayText(text?: string): void { if (this._useMaskBehavior()) { this._toggleEmptinessEventHandler(); } else { - // @ts-expect-error ts-error - super._renderDisplayText.apply(this, arguments); + super._renderDisplayText(text); } } - _renderValue() { + _renderValue(): DeferredObj<unknown> { if (this._useMaskBehavior()) { const { value } = this.option(); + this._parsedValue = value; this._setTextByParsedValue(); } @@ -867,12 +928,15 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { return; } - this._parsedValue = fitIntoRange(parsedValue, this.option('min'), this.option('max')); + const { min, max } = this.option(); + + this._parsedValue = fitIntoRange(parsedValue, min, max); } - _valueChangeEventHandler(e): void { + _valueChangeEventHandler(e: DxEvent): void { if (!this._useMaskBehavior()) { - return super._valueChangeEventHandler(e); + super._valueChangeEventHandler(e); + return; } const caret = this._caret(); @@ -890,7 +954,7 @@ class NumberBoxMask extends NumberBoxBase<NumberBoxMaskProperties> { } } - _optionChanged(args): void { + _optionChanged(args: OptionChanged<NumberBoxMaskProperties>): void { switch (args.name) { case 'format': case 'useMaskBehavior': diff --git a/packages/devextreme/js/__internal/ui/number_box/m_number_box.spin.ts b/packages/devextreme/js/__internal/ui/number_box/number_box.spin.ts similarity index 92% rename from packages/devextreme/js/__internal/ui/number_box/m_number_box.spin.ts rename to packages/devextreme/js/__internal/ui/number_box/number_box.spin.ts index d739bae81ba5..2ec292543b26 100644 --- a/packages/devextreme/js/__internal/ui/number_box/m_number_box.spin.ts +++ b/packages/devextreme/js/__internal/ui/number_box/number_box.spin.ts @@ -8,6 +8,7 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import type { DeferredObj } from '@js/core/utils/deferred'; import { Deferred } from '@js/core/utils/deferred'; +import type { DxEvent, PointerInteractionEvent } from '@js/events/events.types'; import type { OptionChanged } from '@ts/core/widget/types'; import type { WidgetProperties } from '@ts/core/widget/widget'; import Widget from '@ts/core/widget/widget'; @@ -21,10 +22,14 @@ const NUMBER_BOX = 'dxNumberBox'; const POINTERUP_EVENT_NAME = addNamespace(pointerEvents.up, NUMBER_BOX); const POINTERCANCEL_EVENT_NAME = addNamespace(pointerEvents.cancel, NUMBER_BOX); +export interface SpinChangeEvent { + event: DxEvent<PointerInteractionEvent>; +} + export interface SpinButtonProperties extends WidgetProperties { direction?: string; - onChange?: () => void; + onChange?: ((e: SpinChangeEvent) => void) | null; } class SpinButton extends Widget<SpinButtonProperties> { @@ -40,7 +45,6 @@ class SpinButton extends Widget<SpinButtonProperties> { return { ...super._getDefaultOptions(), direction: 'up', - // @ts-expect-error ts-error onChange: null, activeStateEnabled: true, hoverStateEnabled: true, @@ -73,7 +77,7 @@ class SpinButton extends Widget<SpinButtonProperties> { this._spinChangeHandler = this._createActionByOption('onChange'); } - _spinDownHandler(e): void { + _spinDownHandler(e: DxEvent<PointerInteractionEvent>): void { e.preventDefault(); this._clearTimer(); diff --git a/packages/devextreme/js/__internal/ui/number_box/number_box.spins.ts b/packages/devextreme/js/__internal/ui/number_box/number_box.spins.ts new file mode 100644 index 000000000000..1c289dd3c24a --- /dev/null +++ b/packages/devextreme/js/__internal/ui/number_box/number_box.spins.ts @@ -0,0 +1,140 @@ +import eventsEngine from '@js/common/core/events/core/events_engine'; +import pointer from '@js/common/core/events/pointer'; +import { addNamespace } from '@js/common/core/events/utils/index'; +import type { dxElementWrapper } from '@js/core/renderer'; +import $ from '@js/core/renderer'; +import type { DxEvent, PointerInteractionEvent } from '@js/events/events.types'; +import TextEditorButton from '@ts/ui/text_box/texteditor_button_collection/button'; + +import type NumberBoxBase from './number_box.base'; +import type { SpinChangeEvent } from './number_box.spin'; +import SpinButton from './number_box.spin'; + +const SPIN_CLASS = 'dx-numberbox-spin'; +const SPIN_CONTAINER_CLASS = 'dx-numberbox-spin-container'; +const SPIN_TOUCH_FRIENDLY_CLASS = 'dx-numberbox-spin-touch-friendly'; + +interface SpinButtonsOptions { + visible: boolean; + disabled: boolean; +} + +export default class SpinButtons extends TextEditorButton<NumberBoxBase, dxElementWrapper> { + _attachEvents( + instance: dxElementWrapper, + $spinContainer: dxElementWrapper, + ): void { + const { editor } = this; + + if (!editor) { + return; + } + + const eventName = addNamespace(pointer.down, editor.NAME ?? ''); + const $spinContainerChildren = $spinContainer.children(); + + const pointerDownAction = editor._createAction( + () => { this.editor?._spinButtonsPointerDownHandler(); }, + ); + + eventsEngine.off($spinContainer, eventName); + + eventsEngine.on( + $spinContainer, + eventName, + (e: DxEvent<PointerInteractionEvent>) => pointerDownAction({ event: e }), + ); + + SpinButton.getInstance<SpinButton>($spinContainerChildren.eq(0)).option( + 'onChange', + (e: SpinChangeEvent) => { this.editor?._spinUpChangeHandler(e); }, + ); + + SpinButton.getInstance<SpinButton>($spinContainerChildren.eq(1)).option( + 'onChange', + (e: SpinChangeEvent) => { this.editor?._spinDownChangeHandler(e); }, + ); + } + + _create(): { + $element: dxElementWrapper; + instance: dxElementWrapper; + } | undefined { + const { editor } = this; + + if (!editor) { + return undefined; + } + + const $spinContainer = $('<div>').addClass(SPIN_CONTAINER_CLASS); + const $spinUp = $('<div>').appendTo($spinContainer); + const $spinDown = $('<div>').appendTo($spinContainer); + const options = this._getOptions(); + + this._addToContainer($spinContainer); + + editor._createComponent($spinUp, SpinButton, { direction: 'up', ...options }); + editor._createComponent($spinDown, SpinButton, { direction: 'down', ...options }); + + this._legacyRender(editor.$element(), this._isTouchFriendly(), options.visible); + + return { + instance: $spinContainer, + $element: $spinContainer, + }; + } + + _getOptions(): SpinButtonsOptions { + const { editor } = this; + + const { disabled } = editor?.option() ?? {}; + + return { + visible: this._isVisible(), + disabled: !!disabled, + }; + } + + _isVisible(): boolean { + const { editor } = this; + const { showSpinButtons } = editor?.option() ?? {}; + + return super._isVisible() && !!showSpinButtons; + } + + _isTouchFriendly(): boolean { + const { editor } = this; + const { showSpinButtons, useLargeSpinButtons } = editor?.option() ?? {}; + + return !!showSpinButtons && !!useLargeSpinButtons; + } + + // TODO: get rid of it + _legacyRender( + $editor: dxElementWrapper, + isTouchFriendly: boolean, + isVisible: boolean, + ): void { + $editor.toggleClass(SPIN_TOUCH_FRIENDLY_CLASS, isTouchFriendly); + $editor.toggleClass(SPIN_CLASS, isVisible); + } + + update(): boolean { + const shouldUpdate = super.update(); + const { editor, instance } = this; + + if (shouldUpdate && instance && editor) { + const $spinButtons = instance.children(); + const spinUp = SpinButton.getInstance<SpinButton>($spinButtons.eq(0)); + const spinDown = SpinButton.getInstance<SpinButton>($spinButtons.eq(1)); + const options = this._getOptions(); + + spinUp.option(options); + spinDown.option(options); + + this._legacyRender(editor.$element(), this._isTouchFriendly(), options.visible); + } + + return shouldUpdate; + } +} diff --git a/packages/devextreme/js/__internal/ui/number_box/m_number_box.ts b/packages/devextreme/js/__internal/ui/number_box/number_box.ts similarity index 77% rename from packages/devextreme/js/__internal/ui/number_box/m_number_box.ts rename to packages/devextreme/js/__internal/ui/number_box/number_box.ts index 9b862fa02055..1b484d08e202 100644 --- a/packages/devextreme/js/__internal/ui/number_box/m_number_box.ts +++ b/packages/devextreme/js/__internal/ui/number_box/number_box.ts @@ -1,6 +1,6 @@ import registerComponent from '@js/core/component_registrator'; -import NumberBoxMask from './m_number_box.mask'; +import NumberBoxMask from './number_box.mask'; // STYLE numberBox diff --git a/packages/devextreme/js/__internal/ui/number_box/utils.ts b/packages/devextreme/js/__internal/ui/number_box/utils.ts new file mode 100644 index 000000000000..ca4d4ec140f2 --- /dev/null +++ b/packages/devextreme/js/__internal/ui/number_box/utils.ts @@ -0,0 +1,74 @@ +import { adjust } from '@js/core/utils/math'; +import { isString } from '@js/core/utils/type'; +import type { Format } from '@js/localization'; + +export interface SeparatorPosition { + occurrence: number; + index: number; +} + +const asPattern = (format: Format): string => (isString(format) ? format : ''); + +const getRealSeparatorIndex = (format: Format): SeparatorPosition => { + const pattern = asPattern(format); + let quoteBalance = 0; + let separatorCount = 0; + + for (let i = 0; i < pattern.length; i += 1) { + if (pattern[i] === '\'') { + quoteBalance += 1; + } + if (pattern[i] === '.') { + separatorCount += 1; + if (quoteBalance % 2 === 0) { + return { + occurrence: separatorCount, + index: i, + }; + } + } + } + + return { occurrence: 1, index: -1 }; +}; + +const getNthOccurrence = (str: string, char: string, occurrence: number): number => { + let index = -1; + + for (let remaining = occurrence; remaining > 0; remaining -= 1) { + index = str.indexOf(char, index + 1); + + if (index === -1) { + return -1; + } + } + + return index; +}; + +const splitByIndex = (str: string, index: number): string[] => { + if (index === -1) { + return [str]; + } + + return [str.slice(0, index), str.slice(index + 1)]; +}; + +const adjustPercentValue = ( + rawValue: number | null | undefined, + interval: number, +): number | null | undefined => { + if (!rawValue) { + return rawValue; + } + + return adjust(rawValue / 100, interval / 100); +}; + +export { + adjustPercentValue, + asPattern, + getNthOccurrence, + getRealSeparatorIndex, + splitByIndex, +}; diff --git a/packages/devextreme/js/__internal/ui/overlay/overlay.ts b/packages/devextreme/js/__internal/ui/overlay/overlay.ts index d6b3068d30ee..e1a18de96da5 100644 --- a/packages/devextreme/js/__internal/ui/overlay/overlay.ts +++ b/packages/devextreme/js/__internal/ui/overlay/overlay.ts @@ -39,6 +39,7 @@ import type { } from '@js/events'; import type { dxOverlayAnimation, Properties } from '@js/ui/overlay'; import uiErrors from '@js/ui/widget/ui.errors'; +import documentSizeCallbacks from '@ts/core/utils/document_size_callbacks'; import domUtils from '@ts/core/utils/m_dom'; import selectors from '@ts/core/utils/m_selectors'; import windowUtils from '@ts/core/utils/m_window'; @@ -262,6 +263,8 @@ class Overlay< _hideTopOverlayHandler!: () => void; + _documentSizeChangedHandler?: () => void; + _hideAnimationProcessing?: boolean; _showAnimationProcessing?: boolean; @@ -565,14 +568,13 @@ class Overlay< super._initTemplates(); } - _isTopOverlay(): boolean { + _isTopFocusableOverlay(): boolean { const overlayStack = this._overlayStack(); for (let i = overlayStack.length - 1; i >= 0; i -= 1) { const tabbableElements = overlayStack[i]._findTabbableBounds(); if (tabbableElements.$first || tabbableElements.$last) { - // @ts-ignore expected: types Overlay<OverlayProperties> and this have no overlap return overlayStack[i] === this; } } @@ -580,6 +582,25 @@ class Overlay< return false; } + _isLastInOverlayStack(): boolean { + const overlayStack = this._overlayStack(); + + return overlayStack[overlayStack.length - 1] === this; + } + + _handlesDocumentEscapeKey(): boolean { + return false; + } + + _isEscapeHandledByOverlayAbove(): boolean { + const overlayStack = this._overlayStack(); + const index = overlayStack.indexOf(this); + + return overlayStack + .slice(index + 1) + .some((overlay) => overlay._handlesDocumentEscapeKey()); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any _overlayStack(): Overlay<any>[] { return OVERLAY_STACK; @@ -1052,7 +1073,7 @@ class Overlay< } _tabKeyHandler(e: KeyboardEvent): void { - if (normalizeKeyName(e) !== TAB_KEY || !this._isTopOverlay()) { + if (normalizeKeyName(e) !== TAB_KEY || !this._isTopFocusableOverlay()) { return; } @@ -1086,6 +1107,19 @@ class Overlay< if (windowUtils.hasWindow()) { this._toggleHideTopOverlayCallback(enabled); this._toggleHideOnParentsScrollSubscription(enabled); + this._toggleDocumentSizeSubscription(enabled); + } + } + + _toggleDocumentSizeSubscription(subscribe: boolean): void { + this._documentSizeChangedHandler ??= (): void => { + this._dimensionChanged(); + }; + + if (subscribe) { + documentSizeCallbacks.add(this._documentSizeChangedHandler); + } else { + documentSizeCallbacks.remove(this._documentSizeChangedHandler); } } diff --git a/packages/devextreme/js/__internal/ui/popover/popover.ts b/packages/devextreme/js/__internal/ui/popover/popover.ts index fa5d0fbf46e5..d071cd9f425e 100644 --- a/packages/devextreme/js/__internal/ui/popover/popover.ts +++ b/packages/devextreme/js/__internal/ui/popover/popover.ts @@ -225,19 +225,19 @@ class Popover< _initEscapeKeyHandler(): void { this._documentEscapeKeyHandler = (e: KeyboardEvent): void => { - const { visible } = this.option(); - - const overlayStack = this._overlayStack(); - // @ts-ignore expected: types Overlay<OverlayProperties> and this have no overlap - const isTopOverlay = overlayStack[overlayStack.length - 1] === this; - - if (normalizeKeyName(e) === ESC_KEY_NAME && visible && isTopOverlay) { + if (normalizeKeyName(e) === ESC_KEY_NAME && this._handlesDocumentEscapeKey()) { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.hide(); } }; } + _handlesDocumentEscapeKey(): boolean { + const { visible } = this.option(); + + return Boolean(visible) && this._isLastInOverlayStack(); + } + _attachEscapeKeyHandler(): void { const eventName = addNamespace('keydown', this.NAME as string); diff --git a/packages/devextreme/js/__internal/ui/popup/popup.ts b/packages/devextreme/js/__internal/ui/popup/popup.ts index efbd2cf74297..ace3c8ebbc71 100644 --- a/packages/devextreme/js/__internal/ui/popup/popup.ts +++ b/packages/devextreme/js/__internal/ui/popup/popup.ts @@ -245,10 +245,14 @@ class Popup< const e = options.originalEvent; const $target = $(e.target); - if (this._$content && !$target.is(this._$content) - && options.keyName === ESC_KEY_NAME - && !e.isDefaultPrevented() - && !_ignoreCloseOnChildEscape) { + const shouldCloseOnChildEscape = !!this._$content + && !$target.is(this._$content) + && options.keyName === ESC_KEY_NAME + && !e.isDefaultPrevented() + && !_ignoreCloseOnChildEscape + && !this._isEscapeHandledByOverlayAbove(); + + if (shouldCloseOnChildEscape) { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.hide(); } diff --git a/packages/devextreme/js/__internal/ui/selection/selection.strategy.deferred.ts b/packages/devextreme/js/__internal/ui/selection/selection.strategy.deferred.ts index d6e370a5d233..a261105c07d6 100644 --- a/packages/devextreme/js/__internal/ui/selection/selection.strategy.deferred.ts +++ b/packages/devextreme/js/__internal/ui/selection/selection.strategy.deferred.ts @@ -89,7 +89,6 @@ export default class DeferredStrategy< const queryParams = this._getQueryParams(); - // @ts-expect-error dataQuery return !!dataQuery([itemData], queryParams).filter(selectionFilter).toArray().length; } diff --git a/packages/devextreme/js/__internal/ui/selection/selection.strategy.standard.ts b/packages/devextreme/js/__internal/ui/selection/selection.strategy.standard.ts index 4e1ae4bd8b5d..8e711f66e938 100644 --- a/packages/devextreme/js/__internal/ui/selection/selection.strategy.standard.ts +++ b/packages/devextreme/js/__internal/ui/selection/selection.strategy.standard.ts @@ -139,11 +139,11 @@ export default class StandardStrategy< forceCombinedFilter, ); - let deselectedItems = []; + let deselectedItems: TItem[] = []; if (isDeselect) { const { selectedItems } = this.options; + // @ts-expect-error dataQuery().toArray() is typed as unknown[] deselectedItems = combinedFilter && keys.length !== selectedItems.length - // @ts-expect-error dataQuery ? dataQuery(selectedItems).filter(combinedFilter).toArray() : selectedItems.slice(0); } diff --git a/packages/devextreme/js/__internal/ui/selection/selection.strategy.ts b/packages/devextreme/js/__internal/ui/selection/selection.strategy.ts index 932eb2111af9..1a7b9056d022 100644 --- a/packages/devextreme/js/__internal/ui/selection/selection.strategy.ts +++ b/packages/devextreme/js/__internal/ui/selection/selection.strategy.ts @@ -199,7 +199,6 @@ export default class SelectionStrategy< if (localFilter && !isSelectAll) { filteredItems = filteredItems.filter(localFilter); } else if (needLoadAllData) { - // @ts-expect-error dataQuary filteredItems = dataQuery(filteredItems).filter(remoteFilter).toArray(); } diff --git a/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts b/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts index 0458f8f1c274..501d8e2b300b 100644 --- a/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts +++ b/packages/devextreme/js/__internal/ui/shared/ui.editor_factory_mixin.ts @@ -180,7 +180,6 @@ function prepareLookupEditor(options): void { } if (isObject(dataSource) || Array.isArray(dataSource)) { - // @ts-expect-error ts- dataSource = normalizeDataSourceOptions(dataSource); if (isFilterRow) { postProcess = dataSource.postProcess; diff --git a/packages/devextreme/js/__internal/ui/tab_panel/tab_panel.ts b/packages/devextreme/js/__internal/ui/tab_panel/tab_panel.ts index 0022f9aa56d5..827c7cdbe7ad 100644 --- a/packages/devextreme/js/__internal/ui/tab_panel/tab_panel.ts +++ b/packages/devextreme/js/__internal/ui/tab_panel/tab_panel.ts @@ -12,11 +12,11 @@ import { getPublicElement } from '@js/core/element'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import { BindableTemplate } from '@js/core/templates/bindable_template'; -import { getImageContainer } from '@js/core/utils/icon'; import { isDefined, isPlainObject } from '@js/core/utils/type'; import type { DxEvent } from '@js/events'; import type { Item, Properties } from '@js/ui/tab_panel'; import { current as currentTheme, isFluent, isMaterialBased } from '@js/ui/themes'; +import { getImageAriaLabel, getImageContainer } from '@ts/core/utils/m_icon'; import supportUtils from '@ts/core/utils/m_support'; import type { OptionChanged } from '@ts/core/widget/types'; import type { MultiViewProperties } from '@ts/ui/multi_view/multi_view'; @@ -388,6 +388,13 @@ class TabPanel extends MultiView<TabPanelProperties> { iconPosition, stylingMode, _itemAttributes: { class: TABPANEL_TABS_ITEM_CLASS }, + _itemAriaLabelExpr: (data: Item): string | undefined => { + if (data?.title || !data?.icon) { + return undefined; + } + + return getImageAriaLabel(data.icon); + }, _indicatorPosition: tabsIndicatorPosition, }; } diff --git a/packages/devextreme/js/__internal/ui/tabs/tabs.ts b/packages/devextreme/js/__internal/ui/tabs/tabs.ts index 9a8d09132c7e..8b24c7fe49dd 100644 --- a/packages/devextreme/js/__internal/ui/tabs/tabs.ts +++ b/packages/devextreme/js/__internal/ui/tabs/tabs.ts @@ -9,7 +9,6 @@ import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import resizeObserverSingleton from '@js/core/resize_observer'; import { BindableTemplate } from '@js/core/templates/bindable_template'; -import { getImageContainer } from '@js/core/utils/icon'; import { each } from '@js/core/utils/iterator'; import { getHeight, getOuterWidth, getWidth } from '@js/core/utils/size'; import { isDefined, isPlainObject } from '@js/core/utils/type'; @@ -27,8 +26,13 @@ import type { } from '@js/ui/tabs'; import { current as currentTheme, isFluent, isMaterial } from '@js/ui/themes'; import { render } from '@ts/core/utils/ink_ripple'; +import { getImageAriaLabel, getImageContainer } from '@ts/core/utils/m_icon'; import type { OptionChanged } from '@ts/core/widget/types'; -import type { CollectionItemInfo, InkRippleEvent } from '@ts/ui/collection/collection_widget.base'; +import type { + CollectionItemInfo, + InkRippleEvent, + PostprocessRenderItemInfo, +} from '@ts/ui/collection/collection_widget.base'; import type { CollectionWidgetLiveUpdateProperties } from '@ts/ui/collection/collection_widget.live_update'; import Scrollable from '@ts/ui/scroll_view/scrollable'; import { @@ -136,6 +140,8 @@ export interface TabsProperties extends Properties, Omit< badgeExpr?: (data) => string | undefined; + _itemAriaLabelExpr?: (data: Item) => string | undefined; + _indicatorPosition?: Position | null; } @@ -180,6 +186,13 @@ class Tabs extends CollectionWidgetLiveUpdate<TabsProperties> { badgeExpr(data: Item): string | undefined { return data?.badge; }, + _itemAriaLabelExpr(data: Item): string | undefined { + if (data?.text || data?.html || !data?.icon) { + return undefined; + } + + return getImageAriaLabel(data.icon); + }, _itemAttributes: { role: 'tab' }, _indicatorPosition: null, }; @@ -318,6 +331,20 @@ class Tabs extends CollectionWidgetLiveUpdate<TabsProperties> { this._renderScrolling(); } + _postprocessRenderItem(args: PostprocessRenderItemInfo<Item>): void { + super._postprocessRenderItem(args); + + this._renderItemAriaLabel(args); + } + + _renderItemAriaLabel({ itemData, itemElement }: PostprocessRenderItemInfo<Item>): void { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { _itemAriaLabelExpr } = this.option(); + const label = _itemAriaLabelExpr?.(itemData); + + this.setAria('label', label, itemElement); + } + _renderScrolling(): void { const removeClasses = [TABS_STRETCHED_CLASS, TABS_EXPANDED_CLASS, OVERFLOW_HIDDEN_CLASS]; this.$element().removeClass(removeClasses.join(' ')); @@ -836,6 +863,7 @@ class Tabs extends CollectionWidgetLiveUpdate<TabsProperties> { super._optionChanged(args); break; case 'badgeExpr': + case '_itemAriaLabelExpr': this._invalidate(); break; case 'focusedElement': { diff --git a/packages/devextreme/js/__internal/ui/text_box/text_editor.base.ts b/packages/devextreme/js/__internal/ui/text_box/text_editor.base.ts index a95ae65b7456..6fe15ab75575 100644 --- a/packages/devextreme/js/__internal/ui/text_box/text_editor.base.ts +++ b/packages/devextreme/js/__internal/ui/text_box/text_editor.base.ts @@ -34,6 +34,15 @@ import { TextEditorLabel } from './text_editor.label'; import type { TextEditorButtonInfo } from './texteditor_button_collection/index'; import TextEditorButtonCollection from './texteditor_button_collection/index'; +export interface TextEditorInputAttributes { + autocomplete: string; + + placeholder: string | null; + + // eslint-disable-next-line spellcheck/spell-checker + inputmode?: string; +} + export interface TextEditorInternalProperties extends EditorInternalProperties { // eslint-disable-next-line @typescript-eslint/no-explicit-any displayValueFormatter?: ((value: string | any[]) => string); @@ -446,12 +455,7 @@ class TextEditorBase< return value; } - _getDefaultAttributes(): { - autocomplete: string; - placeholder: string | null; - // eslint-disable-next-line spellcheck/spell-checker - inputmode?: string; - } { + _getDefaultAttributes(): TextEditorInputAttributes { const defaultAttributes = { autocomplete: 'off', placeholder: this._getPlaceholderAttr(), @@ -773,7 +777,7 @@ class TextEditorBase< } // eslint-disable-next-line @typescript-eslint/no-unused-vars - _keyPressHandler(e?: { originalEvent: InputEvent & KeyboardEvent }): void { + _keyPressHandler(e?: DxEvent): void { this.option('text', this._input().val()); } diff --git a/packages/devextreme/js/__internal/ui/text_box/text_editor.clear.ts b/packages/devextreme/js/__internal/ui/text_box/text_editor.clear.ts index f072220c37d5..4935f48cee4f 100644 --- a/packages/devextreme/js/__internal/ui/text_box/text_editor.clear.ts +++ b/packages/devextreme/js/__internal/ui/text_box/text_editor.clear.ts @@ -32,7 +32,7 @@ interface EditorWithClearButton { export default class ClearButton< // eslint-disable-next-line @typescript-eslint/no-explicit-any TComponent extends Editor<any> & EditorWithClearButton = TextEditorBase, -> extends TextEditorButton<TComponent> { +> extends TextEditorButton<TComponent, dxElementWrapper> { _create(): { instance: dxElementWrapper; $element: dxElementWrapper; @@ -101,7 +101,6 @@ export default class ClearButton< const isVisible = this._isVisible(); if (instance) { - // @ts-expect-error instance is dxElementWrapper instance.toggleClass(STATE_INVISIBLE_CLASS, !isVisible); } diff --git a/packages/devextreme/js/__internal/ui/text_box/text_editor.mask.ts b/packages/devextreme/js/__internal/ui/text_box/text_editor.mask.ts index 46d0e038133a..4a18e79436ff 100644 --- a/packages/devextreme/js/__internal/ui/text_box/text_editor.mask.ts +++ b/packages/devextreme/js/__internal/ui/text_box/text_editor.mask.ts @@ -340,7 +340,7 @@ class TextEditorMask< _replaceSelectedText( text: string, selection: CaretRange, - char: string, + char: string | undefined, ): string { if (char === undefined) { return text; diff --git a/packages/devextreme/js/__internal/ui/text_box/texteditor_button_collection/button.ts b/packages/devextreme/js/__internal/ui/text_box/texteditor_button_collection/button.ts index f519c2a6e2e2..04f31a4fb39e 100644 --- a/packages/devextreme/js/__internal/ui/text_box/texteditor_button_collection/button.ts +++ b/packages/devextreme/js/__internal/ui/text_box/texteditor_button_collection/button.ts @@ -5,7 +5,7 @@ import Button from '@js/ui/button'; import type Widget from '@ts/core/widget/widget'; import type TextEditorBase from '@ts/ui/text_box/text_editor.base'; -type TextEditorButtonInstance = dxElementWrapper | Button; +export type TextEditorButtonInstance = dxElementWrapper | Button; export const isButtonInstance = ( instance: unknown, @@ -14,12 +14,13 @@ export const isButtonInstance = ( export default class TextEditorButton< // eslint-disable-next-line @typescript-eslint/no-explicit-any TComponent extends Widget<any> = TextEditorBase, + TInstance extends TextEditorButtonInstance = TextEditorButtonInstance, > { $container!: dxElementWrapper | null; $placeMarker?: dxElementWrapper | null; - instance?: TextEditorButtonInstance | null; + instance?: TInstance | null; editor!: TComponent | null; @@ -55,12 +56,12 @@ export default class TextEditorButton< } // eslint-disable-next-line @typescript-eslint/no-unused-vars - _attachEvents(instance: unknown, $element?: dxElementWrapper): void { + _attachEvents(instance: TInstance | undefined, $element?: dxElementWrapper): void { throw new Error('Not implemented'); } _create(): { - instance: Button | dxElementWrapper; + instance: TInstance; $element: dxElementWrapper; } | undefined { throw new Error('Not implemented'); diff --git a/packages/devextreme/js/__internal/ui/text_box/texteditor_button_collection/custom.ts b/packages/devextreme/js/__internal/ui/text_box/texteditor_button_collection/custom.ts index fdb6748a60fb..225953ea1f41 100644 --- a/packages/devextreme/js/__internal/ui/text_box/texteditor_button_collection/custom.ts +++ b/packages/devextreme/js/__internal/ui/text_box/texteditor_button_collection/custom.ts @@ -6,11 +6,11 @@ import $ from '@js/core/renderer'; import type { Properties as ButtonProperties } from '@js/ui/button'; import Button from '@js/ui/button'; import type TextEditorBase from '@ts/ui/text_box/text_editor.base'; -import TextEditorButton, { isButtonInstance } from '@ts/ui/text_box/texteditor_button_collection/button'; +import TextEditorButton from '@ts/ui/text_box/texteditor_button_collection/button'; const CUSTOM_BUTTON_HOVERED_CLASS = 'dx-custom-button-hovered'; -export default class CustomButton extends TextEditorButton { +export default class CustomButton extends TextEditorButton<TextEditorBase, Button> { _attachEvents( instance: Button, $element: dxElementWrapper, @@ -70,7 +70,7 @@ export default class CustomButton extends TextEditorButton { update(): boolean { const isUpdated = super.update(); - if (isButtonInstance(this.instance)) { + if (this.instance) { this.instance.option('disabled', this._isDisabled()); } @@ -86,7 +86,7 @@ export default class CustomButton extends TextEditorButton { _isDisabled(): boolean | undefined { const isDefinedByUser = this.options.disabled !== undefined; if (isDefinedByUser) { - if (isButtonInstance(this.instance)) { + if (this.instance) { return this.instance.option('disabled'); } diff --git a/packages/devextreme/js/__internal/ui/validation_summary.ts b/packages/devextreme/js/__internal/ui/validation_summary.ts index 13538c1d59f7..8bbdf63d43b6 100644 --- a/packages/devextreme/js/__internal/ui/validation_summary.ts +++ b/packages/devextreme/js/__internal/ui/validation_summary.ts @@ -3,6 +3,7 @@ import registerComponent from '@js/core/component_registrator'; import type { dxElementWrapper } from '@js/core/renderer'; import $ from '@js/core/renderer'; import type { OptionChanged } from '@ts/core/widget/types'; +import { SCREEN_READER_ONLY_CLASS } from '@ts/core/widget/widget'; import type { PostprocessRenderItemInfo } from '@ts/ui/collection/collection_widget.base'; import type { CollectionWidgetEditProperties } from '@ts/ui/collection/collection_widget.edit'; import CollectionWidget from '@ts/ui/collection/collection_widget.edit'; @@ -17,7 +18,6 @@ import ValidationEngine from '@ts/ui/validation_engine'; import type Validator from '@ts/ui/validator'; const VALIDATION_SUMMARY_CLASS = 'dx-validationsummary'; -const SCREEN_READER_ONLY_CLASS = 'dx-screen-reader-only'; const ITEM_CLASS = `${VALIDATION_SUMMARY_CLASS}-item`; const ITEM_DATA_KEY = `${VALIDATION_SUMMARY_CLASS}-item-data`; diff --git a/packages/devextreme/js/__internal/viz/axes/base_axis.ts b/packages/devextreme/js/__internal/viz/axes/base_axis.ts index 66b8919a6f1d..8811d932f81b 100644 --- a/packages/devextreme/js/__internal/viz/axes/base_axis.ts +++ b/packages/devextreme/js/__internal/viz/axes/base_axis.ts @@ -313,7 +313,8 @@ function getConstantLineSharpDirection(coord, axisCanvas) { return Math.max(axisCanvas.start, axisCanvas.end) !== coord ? 1 : -1; } -export const Axis = function (renderSettings) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Axis = function (renderSettings) { const that = this; that._renderer = renderSettings.renderer; @@ -2839,3 +2840,9 @@ Axis.prototype = { shift: _noop, /// #ENDDEBUG }; + +/// #DEBUG +export function DEBUG_set_Axis(value: typeof Axis): void { + Axis = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/axes/tick_generator.ts b/packages/devextreme/js/__internal/viz/axes/tick_generator.ts index 09d459922448..7cba1e2fee6f 100644 --- a/packages/devextreme/js/__internal/viz/axes/tick_generator.ts +++ b/packages/devextreme/js/__internal/viz/axes/tick_generator.ts @@ -806,7 +806,8 @@ function dateGenerator(options) { ); } -export const tickGenerator = function (options) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let tickGenerator = function (options) { let result; if (options.rangeIsEmpty) { @@ -823,3 +824,11 @@ export const tickGenerator = function (options) { return result; }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_tickGenerator(value: typeof tickGenerator): void { + tickGenerator = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/chart_components/crosshair.ts b/packages/devextreme/js/__internal/viz/chart_components/crosshair.ts index 61174b64b40d..f6bac1473ba0 100644 --- a/packages/devextreme/js/__internal/viz/chart_components/crosshair.ts +++ b/packages/devextreme/js/__internal/viz/chart_components/crosshair.ts @@ -69,13 +69,14 @@ function getLabelCheckerPosition(x, y, isHorizontal, canvas) { }; } -export function Crosshair(renderer, options, params, group) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Crosshair = function (renderer, options, params, group) { const that = this; that._renderer = renderer; that._crosshairGroup = group; that._options = {}; that.update(options, params); -} +}; Crosshair.prototype = { constructor: Crosshair, @@ -323,3 +324,11 @@ Crosshair.prototype = { } }, }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_Crosshair(value: typeof Crosshair): void { + Crosshair = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/chart_components/layout_manager.ts b/packages/devextreme/js/__internal/viz/chart_components/layout_manager.ts index f74d419697b7..69500d196043 100644 --- a/packages/devextreme/js/__internal/viz/chart_components/layout_manager.ts +++ b/packages/devextreme/js/__internal/viz/chart_components/layout_manager.ts @@ -122,8 +122,9 @@ function getInnerRadius({ type, innerRadius }) { return type === 'pie' ? 0 : _isNumber(innerRadius) ? Number(innerRadius) : DEFAULT_INNER_RADIUS; } -function LayoutManager() { -} +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +let LayoutManager = function () { +}; function getAverageLabelWidth(centerX, radius, canvas, sizeLabels) { return (centerX - radius - RADIAL_LABEL_INDENT - canvas.left) / sizeLabels.outerLabelsCount; @@ -278,3 +279,9 @@ LayoutManager.prototype = { }; export { LayoutManager }; + +/// #DEBUG +export function DEBUG_set_LayoutManager(value: typeof LayoutManager): void { + LayoutManager = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/chart_components/scroll_bar.ts b/packages/devextreme/js/__internal/viz/chart_components/scroll_bar.ts index 449be66e01ea..4d744fc09aaf 100644 --- a/packages/devextreme/js/__internal/viz/chart_components/scroll_bar.ts +++ b/packages/devextreme/js/__internal/viz/chart_components/scroll_bar.ts @@ -23,7 +23,8 @@ const _min = Math.min; const _max = Math.max; const MIN_SCROLL_BAR_SIZE = 10; -export const ScrollBar = function (renderer, group) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let ScrollBar = function (renderer, group) { this._translator = new Translator2D({}, {}, {}); this._scroll = renderer.rect().append(group); this._addEvents(); @@ -283,3 +284,9 @@ ScrollBar.prototype = { }); }, }; + +/// #DEBUG +export function DEBUG_set_ScrollBar(value: typeof ScrollBar): void { + ScrollBar = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/components/chart_theme_manager.ts b/packages/devextreme/js/__internal/viz/components/chart_theme_manager.ts index 214be1c8e5fe..2c2434452d85 100644 --- a/packages/devextreme/js/__internal/viz/components/chart_theme_manager.ts +++ b/packages/devextreme/js/__internal/viz/components/chart_theme_manager.ts @@ -26,7 +26,8 @@ import { import { BaseThemeManager } from '@ts/viz/core/base_theme_manager'; import { extractColor, normalizeEnum as _normalizeEnum } from '@ts/viz/core/utils'; -export const ThemeManager = BaseThemeManager.inherit((function () { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let ThemeManager = BaseThemeManager.inherit((function () { const ctor = function (params) { const that = this; @@ -245,3 +246,11 @@ export const ThemeManager = BaseThemeManager.inherit((function () { }, }; })()); + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_ThemeManager(value: typeof ThemeManager): void { + ThemeManager = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/components/data_validator.ts b/packages/devextreme/js/__internal/viz/components/data_validator.ts index ce855b2f2e60..df90aae1b489 100644 --- a/packages/devextreme/js/__internal/viz/components/data_validator.ts +++ b/packages/devextreme/js/__internal/viz/components/data_validator.ts @@ -505,7 +505,8 @@ function verifyData(source, incidentOccurred) { return data; } -export function validateData(data, groupsData, incidentOccurred, options) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let validateData = function (data, groupsData, incidentOccurred, options) { data = verifyData(data, incidentOccurred); groupsData.argumentType = groupsData.argumentAxisType = null; @@ -524,4 +525,10 @@ export function validateData(data, groupsData, incidentOccurred, options) { const dataByArgumentFields = sortData(data, groupsData, options, getUniqueArgumentFields(groupsData)); return dataByArgumentFields; +}; + +/// #DEBUG +export function DEBUG_set_validateData(value: typeof validateData): void { + validateData = value; } +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/core/base_theme_manager.ts b/packages/devextreme/js/__internal/viz/core/base_theme_manager.ts index f6b3c66ed460..c380f0b72eef 100644 --- a/packages/devextreme/js/__internal/viz/core/base_theme_manager.ts +++ b/packages/devextreme/js/__internal/viz/core/base_theme_manager.ts @@ -32,7 +32,8 @@ function getThemePart(theme, path) { return _theme; } -export const BaseThemeManager = Class.inherit({ // TODO: test hack +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let BaseThemeManager = Class.inherit({ // TODO: test hack ctor(options) { this._themeSection = options.themeSection; this._fontFields = options.fontFields || []; @@ -115,3 +116,9 @@ export const BaseThemeManager = Class.inherit({ // TODO: test hack _extend(font, this._font, _extend({}, font)); }, }); + +/// #DEBUG +export function DEBUG_set_BaseThemeManager(value: typeof BaseThemeManager): void { + BaseThemeManager = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/core/plaque.ts b/packages/devextreme/js/__internal/viz/core/plaque.ts index 33f3c017989e..bb4c67808f04 100644 --- a/packages/devextreme/js/__internal/viz/core/plaque.ts +++ b/packages/devextreme/js/__internal/viz/core/plaque.ts @@ -223,7 +223,8 @@ function getCloudPoints({ width, height }, x, y, anchorX, anchorY, { arrowWidth, return buildPath('M', points, 'Z'); } -export class Plaque { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Plaque = class { widget; options; @@ -480,4 +481,10 @@ export class Plaque { const { width, height } = this._size || {}; return Math.abs(x - this.x) <= width / 2 && Math.abs(y - this.y) <= height / 2; } +}; + +/// #DEBUG +export function DEBUG_set_Plaque(value: typeof Plaque): void { + Plaque = value; } +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/core/renderers/animation.ts b/packages/devextreme/js/__internal/viz/core/renderers/animation.ts index eadbcee48f76..ece2b3283446 100644 --- a/packages/devextreme/js/__internal/viz/core/renderers/animation.ts +++ b/packages/devextreme/js/__internal/viz/core/renderers/animation.ts @@ -144,13 +144,14 @@ Animation.prototype = { }, }; -export function AnimationController(element) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let AnimationController = function (element) { const that = this; that._animationCount = 0; that._timerId = null; that._animations = {}; that.element = element; -} +}; AnimationController.prototype = { _loop() { @@ -239,3 +240,11 @@ AnimationController.prototype = { !hasUnstoppableInAnimations && this.stop(); }, }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_AnimationController(value: typeof AnimationController): void { + AnimationController = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/core/renderers/renderer.ts b/packages/devextreme/js/__internal/viz/core/renderers/renderer.ts index 1f911e926157..9dd0b3b6d9ed 100644 --- a/packages/devextreme/js/__internal/viz/core/renderers/renderer.ts +++ b/packages/devextreme/js/__internal/viz/core/renderers/renderer.ts @@ -1843,7 +1843,7 @@ function unlinkItem(target) { updateIndexes(items, i); } -export function Renderer(options) { +export let Renderer = function (options) { const that = this; that.root = that._createElement('svg', { xmlns: 'http://www.w3.org/2000/svg', @@ -1868,7 +1868,7 @@ export function Renderer(options) { that.root.append({ element: options.container }); that._locker = 0; that._backed = false; -} +}; Renderer.prototype = { constructor: Renderer, @@ -2362,10 +2362,15 @@ const DEBUG_set_ArcSvgElement = function (value) { const DEBUG_set_TextSvgElement = function (value) { TextSvgElement = value; }; + +const DEBUG_set_Renderer = function (value) { + Renderer = value; +}; /// #ENDDEBUG /// #DEBUG exports.DEBUG_set_ArcSvgElement = DEBUG_set_ArcSvgElement; +exports.DEBUG_set_Renderer = DEBUG_set_Renderer; exports.DEBUG_set_PathSvgElement = DEBUG_set_PathSvgElement; exports.DEBUG_set_RectSvgElement = DEBUG_set_RectSvgElement; exports.DEBUG_set_SvgElement = DEBUG_set_SvgElement; diff --git a/packages/devextreme/js/__internal/viz/core/series_family.ts b/packages/devextreme/js/__internal/viz/core/series_family.ts index 25ca9ce1c54c..727ac86c658b 100644 --- a/packages/devextreme/js/__internal/viz/core/series_family.ts +++ b/packages/devextreme/js/__internal/viz/core/series_family.ts @@ -458,7 +458,8 @@ function adjustBubbleSeriesDimensions() { }); } -export function SeriesFamily(options) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let SeriesFamily = function (options) { /// #DEBUG debug.assert(options.type, 'type was not passed or empty'); /// #ENDDEBUG @@ -519,7 +520,7 @@ export function SeriesFamily(options) { that.adjustSeriesDimensions = adjustBubbleSeriesDimensions; break; } -} +}; SeriesFamily.prototype = { constructor: SeriesFamily, @@ -543,3 +544,9 @@ SeriesFamily.prototype = { this.series = _map(series, (singleSeries) => (singleSeries.type === type ? singleSeries : null)); }, }; + +/// #DEBUG +export function DEBUG_set_SeriesFamily(value: typeof SeriesFamily): void { + SeriesFamily = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/core/utils.ts b/packages/devextreme/js/__internal/viz/core/utils.ts index fde5ddc1087e..4c65756fbeb6 100644 --- a/packages/devextreme/js/__internal/viz/core/utils.ts +++ b/packages/devextreme/js/__internal/viz/core/utils.ts @@ -91,7 +91,8 @@ export const degreesToRadians = function (value) { // Calculates sin and cos for <angle> in degrees // Expects number, no validation -export const getCosAndSin = function (angle) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let getCosAndSin = function (angle) { const angleInRadians = degreesToRadians(angle); return { cos: _cos(angleInRadians), sin: _sin(angleInRadians) }; }; @@ -276,7 +277,8 @@ export const enumParser = function (values) { }; }; -export const patchFontOptions = function (options) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let patchFontOptions = function (options) { const fontOptions = {}; each(options || {}, (key, value) => { if (/^(cursor)$/i.test(key)) { @@ -422,7 +424,8 @@ export function normalizePanesHeight(panes) { } } -export function updatePanesCanvases(panes, canvas, rotated) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let updatePanesCanvases = function (panes, canvas, rotated) { let distributedSpace = 0; const padding = PANE_PADDING; const paneSpace = rotated ? canvas.width - canvas.left - canvas.right : canvas.height - canvas.top - canvas.bottom; @@ -441,7 +444,7 @@ export function updatePanesCanvases(panes, canvas, rotated) { distributedSpace = distributedSpace + calcLength + padding; setCanvasValues(pane.canvas); }); -} +}; export const unique = function (array) { const values = {}; @@ -671,7 +674,8 @@ export function pointInCanvas(canvas, x, y) { return x >= canvas.left && x <= canvas.right && y >= canvas.top && y <= canvas.bottom; } -export const getNextDefsSvgId = () => `DevExpress_${numDefsSvgElements++}`; +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let getNextDefsSvgId = () => `DevExpress_${numDefsSvgElements++}`; export function extractColor(color, isBase?) { if (isString(color) || !color) { @@ -681,3 +685,21 @@ export function extractColor(color, isBase?) { } return color.fillId || color.base; } + +/// #DEBUG +export function DEBUG_set_getCosAndSin(value: typeof getCosAndSin): void { + getCosAndSin = value; +} + +export function DEBUG_set_patchFontOptions(value: typeof patchFontOptions): void { + patchFontOptions = value; +} + +export function DEBUG_set_updatePanesCanvases(value: typeof updatePanesCanvases): void { + updatePanesCanvases = value; +} + +export function DEBUG_set_getNextDefsSvgId(value: typeof getNextDefsSvgId): void { + getNextDefsSvgId = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/palette.ts b/packages/devextreme/js/__internal/viz/palette.ts index 6f857efb3b05..8d00c26d2486 100644 --- a/packages/devextreme/js/__internal/viz/palette.ts +++ b/packages/devextreme/js/__internal/viz/palette.ts @@ -194,10 +194,11 @@ export function registerPalette(name, palette) { } } -export function getAccentColor(palette, themeDefault) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let getAccentColor = function (palette, themeDefault) { palette = getPalette(palette, { themeDefault }); return palette.accentColor || palette[0]; -} +}; function RingBuf(buf) { let ind = 0; @@ -430,7 +431,8 @@ function getColorMixer(palette, parameters) { }; } -export function createPalette(palette, parameters, themeDefaultPalette?) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let createPalette = function (palette, parameters, themeDefaultPalette?) { const paletteObj = { dispose() { this._extensionStrategy = null; @@ -469,7 +471,7 @@ export function createPalette(palette, parameters, themeDefaultPalette?) { paletteObj.reset(); return paletteObj; -} +}; function getAlteredPalette(originalPalette, step) { const palette = []; @@ -495,7 +497,8 @@ function getLightness(color) { return color.r * 0.3 + color.g * 0.59 + color.b * 0.11; } -export function getDiscretePalette(source, size, themeDefaultPalette) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let getDiscretePalette = function (source, size, themeDefaultPalette) { const palette = size > 0 ? createDiscreteColors(getPalette(source, { type: 'gradientSet', themeDefault: themeDefaultPalette }), size) : []; return { @@ -503,7 +506,7 @@ export function getDiscretePalette(source, size, themeDefaultPalette) { return palette[index] || null; }, }; -} +}; function createDiscreteColors(source, count) { const colorCount = count - 1; @@ -550,3 +553,15 @@ export function getGradientPalette(source, themeDefaultPalette) { /// #DEBUG export const _DEBUG_palettes = palettes; /// #ENDDEBUG + +/// #DEBUG +export function DEBUG_set_getAccentColor(value: typeof getAccentColor): void { + getAccentColor = value; +} +export function DEBUG_set_createPalette(value: typeof createPalette): void { + createPalette = value; +} +export function DEBUG_set_getDiscretePalette(value: typeof getDiscretePalette): void { + getDiscretePalette = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/range_selector/range_view.ts b/packages/devextreme/js/__internal/viz/range_selector/range_view.ts index 3d5424ba3beb..ed73becd129f 100644 --- a/packages/devextreme/js/__internal/viz/range_selector/range_view.ts +++ b/packages/devextreme/js/__internal/viz/range_selector/range_view.ts @@ -36,11 +36,12 @@ function merge(a, b) { return a !== undefined ? a : b; } -export function RangeView(params) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let RangeView = function (params) { this._params = params; this._clipRect = params.renderer.clipRect(); params.root.attr({ 'clip-path': this._clipRect.id }); -} +}; RangeView.prototype = { constructor: RangeView, @@ -84,3 +85,11 @@ RangeView.prototype = { } }, }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_RangeView(value: typeof RangeView): void { + RangeView = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/range_selector/series_data_source.ts b/packages/devextreme/js/__internal/viz/range_selector/series_data_source.ts index 46a17f76d824..8eaa6ddb6d7d 100644 --- a/packages/devextreme/js/__internal/viz/range_selector/series_data_source.ts +++ b/packages/devextreme/js/__internal/viz/range_selector/series_data_source.ts @@ -57,7 +57,8 @@ const processSeriesFamilies = function (series, minBubbleSize, maxBubbleSize, ba return families; }; -export const SeriesDataSource = function (options) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let SeriesDataSource = function (options) { const that = this; const themeManager = that._themeManager = createThemeManager(options.chart); @@ -278,3 +279,11 @@ SeriesDataSource.prototype = { return this._themeManager; }, }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_SeriesDataSource(value: typeof SeriesDataSource): void { + SeriesDataSource = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/range_selector/sliders_controller.ts b/packages/devextreme/js/__internal/viz/range_selector/sliders_controller.ts index f2c26e68e6b8..ae3898626ea9 100644 --- a/packages/devextreme/js/__internal/viz/range_selector/sliders_controller.ts +++ b/packages/devextreme/js/__internal/viz/range_selector/sliders_controller.ts @@ -75,7 +75,8 @@ function restoreSetSelectedRange(controller) { delete controller.setSelectedRange; } -export function SlidersController(params) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let SlidersController = function (params) { const that = this; const sliderParams = { renderer: params.renderer, root: params.root, trackersGroup: params.trackersGroup, translator: params.translator, @@ -89,7 +90,7 @@ export function SlidersController(params) { // It seems that there is no special reasons to suppress first event - it was accidentally suppressed. // Let it stay so for now. that._processSelectionChanged = dummyProcessSelectionChanged; -} +}; SlidersController.prototype = { constructor: SlidersController, @@ -519,3 +520,11 @@ SlidersController.prototype = { this._sliders[index].toForeground(); }, }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_SlidersController(value: typeof SlidersController): void { + SlidersController = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/range_selector/tracker.ts b/packages/devextreme/js/__internal/viz/range_selector/tracker.ts index fcc50b508ad1..ee91d4d4b430 100644 --- a/packages/devextreme/js/__internal/viz/range_selector/tracker.ts +++ b/packages/devextreme/js/__internal/viz/range_selector/tracker.ts @@ -222,7 +222,8 @@ function initializeSliderEvents(controller, sliders, state, getRootOffsetLeft) { return docEvents; } -export function Tracker(params) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Tracker = function (params) { const state = this._state = {}; const targets = params.controller.getTrackerTargets(); if (msPointerEnabled) { @@ -242,7 +243,7 @@ export function Tracker(params) { function getRootOffsetLeft() { return params.renderer.getRootOffset().left; } -} +}; Tracker.prototype = { constructor: Tracker, @@ -260,3 +261,11 @@ Tracker.prototype = { state.manualRangeSelectionEnabled = behavior.manualRangeSelectionEnabled; }, }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_Tracker(value: typeof Tracker): void { + Tracker = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/series/base_series.ts b/packages/devextreme/js/__internal/viz/series/base_series.ts index 3c7a86040693..864025498e3d 100644 --- a/packages/devextreme/js/__internal/viz/series/base_series.ts +++ b/packages/devextreme/js/__internal/viz/series/base_series.ts @@ -179,7 +179,8 @@ function mergePointOptions(base, extra) { return options; } -export function Series(settings, options) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Series = function (settings, options) { const that = this; that.fullState = 0; that._extGroups = settings; @@ -191,7 +192,7 @@ export function Series(settings, options) { that._legendCallback = _noop; that.updateOptions(options, settings); -} +}; function getData(pointData) { return pointData.data; @@ -1382,3 +1383,11 @@ Series.prototype = { }; // @ts-expect-error export const mixins = seriesNS.mixins; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_Series(value: typeof Series): void { + Series = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/series/points/base_point.ts b/packages/devextreme/js/__internal/viz/series/points/base_point.ts index ccb51c7e0c34..acc91bc39396 100644 --- a/packages/devextreme/js/__internal/viz/series/points/base_point.ts +++ b/packages/devextreme/js/__internal/viz/series/points/base_point.ts @@ -89,7 +89,8 @@ function isNoneMode(mode) { return _normalizeEnum(mode) === 'none'; } -export function Point(series, dataItem, options) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Point = function (series, dataItem, options) { this.fullState = NORMAL_STATE; this.series = series; this.update(dataItem, options); @@ -104,7 +105,7 @@ export function Point(series, dataItem, options) { dashStyle: null, filter: null, }; -} +}; // @ts-expect-error mixins.symbolPoint = symbolPoint; // @ts-expect-error @@ -547,3 +548,9 @@ Point.prototype = { }; }, }; + +/// #DEBUG +export function DEBUG_set_Point(value: typeof Point): void { + Point = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/series/points/label.ts b/packages/devextreme/js/__internal/viz/series/points/label.ts index 1792537c2d71..327f2763845d 100644 --- a/packages/devextreme/js/__internal/viz/series/points/label.ts +++ b/packages/devextreme/js/__internal/viz/series/points/label.ts @@ -290,13 +290,14 @@ function formatText(data, options) { return options.customizeText ? options.customizeText.call(data, data) : options.displayFormat ? processDisplayFormat(options.displayFormat, data) : data.valueText; } -export function Label(renderSettings) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Label = function (renderSettings) { this._renderer = renderSettings.renderer; this._container = renderSettings.labelsGroup; this._point = renderSettings.point; this._strategy = renderSettings.strategy; this._rowCount = 1; -} +}; Label.prototype = { constructor: Label, @@ -551,5 +552,12 @@ Label.prototype = { }; /// #DEBUG -Label._DEBUG_formatText = formatText; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(Label as any)._DEBUG_formatText = formatText; +/// #ENDDEBUG + +/// #DEBUG +export function DEBUG_set_Label(value: typeof Label): void { + Label = value; +} /// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/translators/range.ts b/packages/devextreme/js/__internal/viz/translators/range.ts index be556c949927..d4ed7f96c59d 100644 --- a/packages/devextreme/js/__internal/viz/translators/range.ts +++ b/packages/devextreme/js/__internal/viz/translators/range.ts @@ -43,7 +43,8 @@ function compareAndReplace(thisValue, otherValue, setValue, compare) { } } -export const Range = function (range?) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Range = function (range?) { range && extend(this, range); }; @@ -156,3 +157,9 @@ _Range.prototype = { } }, }; + +/// #DEBUG +export function DEBUG_set_Range(value: typeof Range): void { + Range = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/translators/translator1d.ts b/packages/devextreme/js/__internal/viz/translators/translator1d.ts index f0caa9bd9a9e..3f9d1d7479be 100644 --- a/packages/devextreme/js/__internal/viz/translators/translator1d.ts +++ b/packages/devextreme/js/__internal/viz/translators/translator1d.ts @@ -9,9 +9,10 @@ const _Number = Number; -export function Translator1D() { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Translator1D = function () { this.setDomain(arguments[0], arguments[1]).setCodomain(arguments[2], arguments[3]).setInverted(false); -} +}; Translator1D.prototype = { constructor: Translator1D, @@ -87,3 +88,9 @@ Translator1D.prototype = { return result; }, }; + +/// #DEBUG +export function DEBUG_set_Translator1D(value: typeof Translator1D): void { + Translator1D = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/translators/translator2d.ts b/packages/devextreme/js/__internal/viz/translators/translator2d.ts index a14d04727579..c354e9a42896 100644 --- a/packages/devextreme/js/__internal/viz/translators/translator2d.ts +++ b/packages/devextreme/js/__internal/viz/translators/translator2d.ts @@ -164,7 +164,8 @@ function getCheckingMethodsAboutBreaks(inverted) { }; } -const _Translator2d = function (businessRange, canvas, options) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +let _Translator2d = function (businessRange, canvas, options) { this.update(businessRange, canvas, options); }; @@ -722,3 +723,9 @@ _Translator2d.prototype = { }; export { _Translator2d as Translator2D }; + +/// #DEBUG +export function DEBUG_set_Translator2D(value: typeof _Translator2d): void { + _Translator2d = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/vector_map/control_bar/control_bar.ts b/packages/devextreme/js/__internal/viz/vector_map/control_bar/control_bar.ts index dff2890bb1ef..5c13173ec984 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/control_bar/control_bar.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/control_bar/control_bar.ts @@ -73,7 +73,8 @@ COMMAND_TO_TYPE_MAP[COMMAND_MOVE_UP] = COMMAND_TO_TYPE_MAP[COMMAND_MOVE_RIGHT] = COMMAND_TO_TYPE_MAP[COMMAND_ZOOM_IN] = COMMAND_TO_TYPE_MAP[COMMAND_ZOOM_OUT] = ZoomCommand; COMMAND_TO_TYPE_MAP[COMMAND_ZOOM_DRAG] = ZoomDragCommand; -export function ControlBar(parameters) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let ControlBar = function (parameters) { const that = this; that._params = parameters; that._createElements(parameters.renderer, parameters.container, parameters.dataKey); @@ -81,7 +82,7 @@ export function ControlBar(parameters) { that._subscribeToProjection(parameters.projection); that._subscribeToTracker(parameters.tracker); that._createCallbacks(parameters.projection); -} +}; ControlBar.prototype = { constructor: ControlBar, @@ -472,3 +473,9 @@ exports._TESTS_restoreCommandToTypeMap = function () { COMMAND_TO_TYPE_MAP = COMMAND_TO_TYPE_MAP__ORIGINAL; }; /// #ENDDEBUG + +/// #DEBUG +export function DEBUG_set_ControlBar(value: typeof ControlBar): void { + ControlBar = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/vector_map/data_exchanger.ts b/packages/devextreme/js/__internal/viz/vector_map/data_exchanger.ts index 3f0cf7a4f8a6..7078a0d1096f 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/data_exchanger.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/data_exchanger.ts @@ -6,9 +6,10 @@ import Callbacks from '@js/core/utils/callbacks'; -export function DataExchanger() { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let DataExchanger = function () { this._store = {}; -} +}; DataExchanger.prototype = { constructor: DataExchanger, @@ -43,3 +44,11 @@ DataExchanger.prototype = { return this; }, }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_DataExchanger(value: typeof DataExchanger): void { + DataExchanger = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/vector_map/gesture_handler.ts b/packages/devextreme/js/__internal/viz/vector_map/gesture_handler.ts index 0fffd4195425..5e81a443e38a 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/gesture_handler.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/gesture_handler.ts @@ -8,13 +8,14 @@ const _ln = Math.log; const _LN2 = Math.LN2; -export function GestureHandler(params) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let GestureHandler = function (params) { const that = this; that._projection = params.projection; that._renderer = params.renderer; that._x = that._y = 0; that._subscribeToTracker(params.tracker); -} +}; GestureHandler.prototype = { constructor: GestureHandler, @@ -106,3 +107,9 @@ GestureHandler.prototype = { } }, }; + +/// #DEBUG +export function DEBUG_set_GestureHandler(value: typeof GestureHandler): void { + GestureHandler = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/vector_map/layout.ts b/packages/devextreme/js/__internal/viz/vector_map/layout.ts index 8de01f8433b4..f440424f6c94 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/layout.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/layout.ts @@ -207,7 +207,8 @@ function applyLayout(canvas, items) { } } -export function LayoutControl(widget) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let LayoutControl = function (widget) { const that = this; that._items = []; that._suspended = 0; @@ -215,7 +216,7 @@ export function LayoutControl(widget) { that._updateLayout = function () { that._update(); }; -} +}; LayoutControl.prototype = { constructor: LayoutControl, @@ -266,3 +267,9 @@ LayoutControl.prototype = { } }, }; + +/// #DEBUG +export function DEBUG_set_LayoutControl(value: typeof LayoutControl): void { + LayoutControl = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/vector_map/legend.ts b/packages/devextreme/js/__internal/viz/vector_map/legend.ts index eb5d39c0c227..a0cc3472d8da 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/legend.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/legend.ts @@ -119,11 +119,11 @@ Legend.prototype = _extend(clone(_BaseLegend.prototype), { }, }); -export function LegendsControl(parameters) { +export let LegendsControl = function (parameters) { this._params = parameters; this._items = []; parameters.container.virtualLink('legend-base'); -} +}; LegendsControl.prototype = { constructor: LegendsControl, @@ -170,3 +170,9 @@ exports._TESTS_restoreLegendType = function () { Legend = originalLegend; }; /// #ENDDEBUG + +/// #DEBUG +export function DEBUG_set_LegendsControl(value: typeof LegendsControl): void { + LegendsControl = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/vector_map/map_layer.ts b/packages/devextreme/js/__internal/viz/vector_map/map_layer.ts index fadc174a8208..cb2bbc518577 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/map_layer.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/map_layer.ts @@ -1596,7 +1596,8 @@ function projectLineLabel(coordinates) { return resultData || [[], []]; } -export function MapLayerCollection(params) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let MapLayerCollection = function (params) { const that = this; const renderer = params.renderer; that._params = params; @@ -1609,7 +1610,7 @@ export function MapLayerCollection(params) { that._container = renderer.g().attr({ class: 'dxm-layers', 'clip-path': that._clip.id }).append(renderer.root).enableLinks(); that._subscribeToTracker(params.tracker, renderer, params.eventTrigger); that._dataReady = params.dataReady; -} +}; MapLayerCollection.prototype = { constructor: MapLayerCollection, @@ -1745,3 +1746,9 @@ export const _TESTS_stub_groupBySize = function (stub) { export const _TESTS_groupBySize = groupBySize; export const _TESTS_findGroupingIndex = findGroupingIndex; /// #ENDDEBUG + +/// #DEBUG +export function DEBUG_set_MapLayerCollection(value: typeof MapLayerCollection): void { + MapLayerCollection = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/vector_map/projection.main.ts b/packages/devextreme/js/__internal/viz/vector_map/projection.main.ts index 3a960f5aaac9..953b23ccb8ee 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/projection.main.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/projection.main.ts @@ -65,7 +65,8 @@ function getEngine(engine) { return (engine instanceof Engine && engine) || projection.get(engine) || projection(engine) || projection.get(DEFAULT_ENGINE_NAME); } -export const Projection = function (parameters) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let Projection = function (parameters) { const that = this; that._initEvents(); that._params = parameters; @@ -553,3 +554,9 @@ function createProjectUnprojectMethods(project, unproject, p1, p2, delta) { /// #DEBUG export { Engine as _TESTS_Engine }; /// #ENDDEBUG + +/// #DEBUG +export function DEBUG_set_Projection(value: typeof Projection): void { + Projection = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/vector_map/tooltip_viewer.ts b/packages/devextreme/js/__internal/viz/vector_map/tooltip_viewer.ts index 5e51270096a5..74be4028610a 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/tooltip_viewer.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/tooltip_viewer.ts @@ -9,9 +9,10 @@ const TOOLTIP_OFFSET = 12; // TODO: Somehow it should be merged with the core.Tooltip -export function TooltipViewer(params) { +// eslint-disable-next-line import/no-mutable-exports -- description seam for tests +export let TooltipViewer = function (params) { this._subscribeToTracker(params.tracker, params.tooltip, params.layerCollection); -} +}; TooltipViewer.prototype = { constructor: TooltipViewer, @@ -45,3 +46,11 @@ TooltipViewer.prototype = { }); }, }; + +/// #DEBUG +/* eslint-disable-next-line @typescript-eslint/naming-convention + -- description seam setter for tests stubs */ +export function DEBUG_set_TooltipViewer(value: typeof TooltipViewer): void { + TooltipViewer = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/__internal/viz/vector_map/tracker.ts b/packages/devextreme/js/__internal/viz/vector_map/tracker.ts index 55be5e250222..afbf4e5e91ad 100644 --- a/packages/devextreme/js/__internal/viz/vector_map/tracker.ts +++ b/packages/devextreme/js/__internal/viz/vector_map/tracker.ts @@ -57,7 +57,7 @@ let Focus; setupEvents(); -export function Tracker(parameters) { +export let Tracker = function (parameters) { const that = this; that._root = parameters.root; that._createEventHandlers(parameters.dataKey); @@ -67,7 +67,7 @@ export function Tracker(parameters) { that._fire(name, arg); }); that._attachHandlers(); -} +}; Tracker.prototype = { constructor: Tracker, @@ -580,3 +580,9 @@ function adjustWheelDelta(delta, lock) { } return sign * _delta; } + +/// #DEBUG +export function DEBUG_set_Tracker(value: typeof Tracker): void { + Tracker = value; +} +/// #ENDDEBUG diff --git a/packages/devextreme/js/common/core/events/visibility_change.js b/packages/devextreme/js/common/core/events/visibility_change.js index 54357eeb9208..3fc155a6fcc6 100644 --- a/packages/devextreme/js/common/core/events/visibility_change.js +++ b/packages/devextreme/js/common/core/events/visibility_change.js @@ -1,7 +1,24 @@ import VisibilityChangeModule from '../../../__internal/events/visibility_change'; -export const triggerShownEvent = VisibilityChangeModule.triggerShownEvent; -export const triggerHidingEvent = VisibilityChangeModule.triggerHidingEvent; -export const triggerResizeEvent = VisibilityChangeModule.triggerResizeEvent; +// eslint-disable-next-line import/no-mutable-exports -- test seam for QUnit stubs +export let triggerShownEvent = VisibilityChangeModule.triggerShownEvent; +// eslint-disable-next-line import/no-mutable-exports -- test seam for QUnit stubs +export let triggerHidingEvent = VisibilityChangeModule.triggerHidingEvent; +// eslint-disable-next-line import/no-mutable-exports -- test seam for QUnit stubs +export let triggerResizeEvent = VisibilityChangeModule.triggerResizeEvent; + +/// #DEBUG +export function DEBUG_set_triggerShownEvent(value) { + triggerShownEvent = value; +} + +export function DEBUG_set_triggerHidingEvent(value) { + triggerHidingEvent = value; +} + +export function DEBUG_set_triggerResizeEvent(value) { + triggerResizeEvent = value; +} +/// #ENDDEBUG export default VisibilityChangeModule; diff --git a/packages/devextreme/js/common/data/array_utils.js b/packages/devextreme/js/common/data/array_utils.js index 74993adf03d4..d3f8e91646bc 100644 --- a/packages/devextreme/js/common/data/array_utils.js +++ b/packages/devextreme/js/common/data/array_utils.js @@ -1 +1 @@ -export * from '../../__internal/data/m_array_utils'; +export * from '../../__internal/data/array_utils'; diff --git a/packages/devextreme/js/common/data/data_source/operation_manager.js b/packages/devextreme/js/common/data/data_source/operation_manager.js index 059f55b8e65f..6de10df1d202 100644 --- a/packages/devextreme/js/common/data/data_source/operation_manager.js +++ b/packages/devextreme/js/common/data/data_source/operation_manager.js @@ -1 +1 @@ -export { default } from '../../../__internal/data/data_source/m_operation_manager'; +export { default } from '../../../__internal/data/data_source/operation_manager'; diff --git a/packages/devextreme/js/common/data/data_source/utils.js b/packages/devextreme/js/common/data/data_source/utils.js index 8b2686832ca6..52e070da838e 100644 --- a/packages/devextreme/js/common/data/data_source/utils.js +++ b/packages/devextreme/js/common/data/data_source/utils.js @@ -1 +1 @@ -export * from '../../../__internal/data/data_source/m_utils'; +export * from '../../../__internal/data/data_source/utils'; diff --git a/packages/devextreme/js/common/data/endpoint_selector.js b/packages/devextreme/js/common/data/endpoint_selector.js index e613c011c0ce..ea7b56fad2d1 100644 --- a/packages/devextreme/js/common/data/endpoint_selector.js +++ b/packages/devextreme/js/common/data/endpoint_selector.js @@ -4,4 +4,4 @@ * @param1 options:Object * @hidden */ -export { default } from '../../__internal/data/m_endpoint_selector'; +export { default } from '../../__internal/data/endpoint_selector'; diff --git a/packages/devextreme/js/common/data/errors.js b/packages/devextreme/js/common/data/errors.js index f2ad9f675151..255b551e3774 100644 --- a/packages/devextreme/js/common/data/errors.js +++ b/packages/devextreme/js/common/data/errors.js @@ -88,4 +88,4 @@ * @name ErrorsData.W4002 */ -export * from '../../__internal/data/m_errors'; +export * from '../../__internal/data/errors'; diff --git a/packages/devextreme/js/common/data/local_store.js b/packages/devextreme/js/common/data/local_store.js index 2b903d32d98b..48ec3d1bcc87 100644 --- a/packages/devextreme/js/common/data/local_store.js +++ b/packages/devextreme/js/common/data/local_store.js @@ -1 +1 @@ -export { default } from '../../__internal/data/m_local_store'; +export { default } from '../../__internal/data/local_store'; diff --git a/packages/devextreme/js/common/data/odata/query_adapter.js b/packages/devextreme/js/common/data/odata/query_adapter.js index a56104665e6c..dc61288953ee 100644 --- a/packages/devextreme/js/common/data/odata/query_adapter.js +++ b/packages/devextreme/js/common/data/odata/query_adapter.js @@ -1 +1 @@ -export * from '../../../__internal/data/odata/m_query_adapter'; +export * from '../../../__internal/data/odata/query_adapter'; diff --git a/packages/devextreme/js/common/data/odata/request_dispatcher.js b/packages/devextreme/js/common/data/odata/request_dispatcher.js index 13fc3dc1b4c6..434e13d5a8eb 100644 --- a/packages/devextreme/js/common/data/odata/request_dispatcher.js +++ b/packages/devextreme/js/common/data/odata/request_dispatcher.js @@ -1 +1 @@ -export { default } from '../../../__internal/data/odata/m_request_dispatcher'; +export { default } from '../../../__internal/data/odata/request_dispatcher'; diff --git a/packages/devextreme/js/common/data/query.js b/packages/devextreme/js/common/data/query.js index b1e2c444e038..bbb50289ac52 100644 --- a/packages/devextreme/js/common/data/query.js +++ b/packages/devextreme/js/common/data/query.js @@ -1 +1 @@ -export { default } from '../../__internal/data/m_query'; +export { default } from '../../__internal/data/query'; diff --git a/packages/devextreme/js/common/data/remote_query.js b/packages/devextreme/js/common/data/remote_query.js index 592ebfe65d2c..51f3df760e1e 100644 --- a/packages/devextreme/js/common/data/remote_query.js +++ b/packages/devextreme/js/common/data/remote_query.js @@ -1 +1 @@ -export { default } from '../../__internal/data/m_remote_query'; +export { default } from '../../__internal/data/remote_query'; diff --git a/packages/devextreme/js/common/data/store_helper.js b/packages/devextreme/js/common/data/store_helper.js index bb0fca23c7d1..5b66fa3425b1 100644 --- a/packages/devextreme/js/common/data/store_helper.js +++ b/packages/devextreme/js/common/data/store_helper.js @@ -1 +1 @@ -export { default } from '../../__internal/data/m_store_helper'; +export { default } from '../../__internal/data/store_helper'; diff --git a/packages/devextreme/js/common/data/utils.js b/packages/devextreme/js/common/data/utils.js index a59212919375..ba1bc28b4c72 100644 --- a/packages/devextreme/js/common/data/utils.js +++ b/packages/devextreme/js/common/data/utils.js @@ -3,7 +3,7 @@ import { compileGetter, compileSetter } from '../../core/utils/data'; /** * @name Utils */ -export * from '../../__internal/data/m_utils'; +export * from '../../__internal/data/utils'; export { compileGetter, diff --git a/packages/devextreme/js/core/events_strategy.js b/packages/devextreme/js/core/events_strategy.js index d28bcbfc9214..dd556120ee5f 100644 --- a/packages/devextreme/js/core/events_strategy.js +++ b/packages/devextreme/js/core/events_strategy.js @@ -1,2 +1,2 @@ // deprecated -export { EventsStrategy } from '../__internal/core/m_events_strategy'; +export { EventsStrategy } from '../__internal/core/events_strategy'; diff --git a/packages/devextreme/js/exporter.js b/packages/devextreme/js/exporter.js index cfcf606cc8ee..0cdb267d06a1 100644 --- a/packages/devextreme/js/exporter.js +++ b/packages/devextreme/js/exporter.js @@ -1,13 +1,16 @@ import * as clientExporter from './__internal/exporter/exporter'; -// Re-exported through local bindings on purpose: `export { … } from './…'` compiles to -// getter-only, non-configurable properties, while tests stub these members -// (see testing/tests/DevExpress.viz.core/export.tests.js). -const _export = clientExporter.export; +let _export = clientExporter.export; export const fileSaver = clientExporter.fileSaver; export const image = clientExporter.image; export const pdf = clientExporter.pdf; export const svg = clientExporter.svg; +/// #DEBUG +export function DEBUG_set_export(value) { + _export = value; +} +/// #ENDDEBUG + export { _export as export }; diff --git a/packages/devextreme/js/ui/number_box.js b/packages/devextreme/js/ui/number_box.js index 5ea396acda04..a682bbaa8a92 100644 --- a/packages/devextreme/js/ui/number_box.js +++ b/packages/devextreme/js/ui/number_box.js @@ -1,4 +1,4 @@ -import NumberBox from '../__internal/ui/number_box/m_number_box'; +import NumberBox from '../__internal/ui/number_box/number_box'; export default NumberBox; diff --git a/packages/devextreme/package.json b/packages/devextreme/package.json index 4fa54817a82c..8cb57946551c 100644 --- a/packages/devextreme/package.json +++ b/packages/devextreme/package.json @@ -130,11 +130,6 @@ "minimist": "1.2.8", "qunit": "2.25.0", "sinon": "18.0.1", - "systemjs": "0.19.41", - "systemjs-plugin-babel": "0.0.25", - "systemjs-plugin-css": "0.1.37", - "systemjs-plugin-json": "0.3.0", - "systemjs-plugin-text": "0.0.11", "terser-webpack-plugin": "5.3.17", "ts-jest": "29.4.12", "tsc-alias": "1.8.16", diff --git a/packages/devextreme/project.json b/packages/devextreme/project.json index 19efa18de2d0..aa39d154dbbc 100644 --- a/packages/devextreme/project.json +++ b/packages/devextreme/project.json @@ -152,6 +152,20 @@ "{projectRoot}/artifacts/npm/devextreme-internal/bundles/dx.custom.config.js" ] }, + "copy:qunit:dx-custom": { + "executor": "devextreme-nx-infra-plugin:copy-files", + "options": { + "files": [ + { + "from": "./build/bundle-templates/dx.custom.js", + "to": "./artifacts/transpiled-esm-npm/bundles/dx.custom.js" + } + ] + }, + "dependsOn": ["build:devextreme-bundler-config"], + "inputs": ["{projectRoot}/build/bundle-templates/dx.custom.js"], + "outputs": ["{projectRoot}/artifacts/transpiled-esm-npm/bundles/dx.custom.js"] + }, "build:devextreme-bundler-config:watch": { "executor": "devextreme-nx-infra-plugin:concatenate-files", "cache": false, @@ -280,31 +294,6 @@ "{projectRoot}/artifacts/transpiled-renovation-npm" ] }, - "build:cjs:watch": { - "executor": "devextreme-nx-infra-plugin:babel-transform", - "cache": false, - "options": { - "babelConfigPath": "./build/transpile-config.js", - "configKey": "cjs", - "sourcePattern": "./js/**/*.{js,jsx}", - "excludePatterns": [ - "./js/**/*.d.ts", - "./js/__internal/**/*" - ], - "outDir": "./artifacts/transpiled", - "watch": true, - "copyAssets": [ - { "from": "./js/localization/messages", "to": "./localization/messages" }, - { "from": "./js/viz/vector_map.utils/_settings.json", "to": "./viz/vector_map.utils/_settings.json" } - ] - }, - "configurations": { - "production": { - "outDir": "./artifacts/transpiled-renovation-npm", - "removeDebug": true - } - } - }, "build:npm:esm": { "executor": "devextreme-nx-infra-plugin:babel-transform", "options": { @@ -322,6 +311,11 @@ { "from": "./js/viz/vector_map.utils/_settings.json", "to": "./viz/vector_map.utils/_settings.json" } ] }, + "configurations": { + "qunit": { + "removeDebug": false + } + }, "inputs": [ "jsSourcesProduction", "jsAssetsProduction" @@ -378,44 +372,95 @@ "{projectRoot}/artifacts/transpiled-renovation-npm/__internal" ] }, - "build:cjs:internal:watch": { + "build:npm:esm:internal": { "executor": "devextreme-nx-infra-plugin:babel-transform", - "cache": false, "options": { "babelConfigPath": "./build/transpile-config.js", - "configKey": "tsCjs", + "configKey": "esm", "sourcePattern": "./artifacts/dist_ts/__internal/**/*.{js,jsx}", - "outDir": "./artifacts/transpiled/__internal", + "outDir": "./artifacts/transpiled-esm-npm/esm/__internal", + "removeDebug": true, "renameExtensions": { ".jsx": ".js" - }, - "watch": true + } }, "configurations": { - "production": { - "outDir": "./artifacts/transpiled-renovation-npm/__internal", - "removeDebug": true + "qunit": { + "removeDebug": false + } + }, + "inputs": [ + "internalTsArtifacts" + ], + "outputs": [ + "{projectRoot}/artifacts/transpiled-esm-npm/esm/__internal" + ] + }, + "build:npm:esm:watch": { + "executor": "devextreme-nx-infra-plugin:babel-transform", + "cache": false, + "options": { + "babelConfigPath": "./build/transpile-config.js", + "configKey": "esm", + "sourcePattern": "./js/**/*.{js,jsx}", + "excludePatterns": [ + "./js/**/*.d.ts", + "./js/__internal/**/*" + ], + "outDir": "./artifacts/transpiled-esm-npm/esm", + "removeDebug": true, + "watch": true, + "copyAssets": [ + { "from": "./js/localization/messages", "to": "./localization/messages" }, + { "from": "./js/viz/vector_map.utils/_settings.json", "to": "./viz/vector_map.utils/_settings.json" } + ] + }, + "configurations": { + "qunit": { + "removeDebug": false } } }, - "build:npm:esm:internal": { + "build:npm:esm:internal:watch": { "executor": "devextreme-nx-infra-plugin:babel-transform", + "cache": false, "options": { "babelConfigPath": "./build/transpile-config.js", "configKey": "esm", "sourcePattern": "./artifacts/dist_ts/__internal/**/*.{js,jsx}", "outDir": "./artifacts/transpiled-esm-npm/esm/__internal", "removeDebug": true, + "watch": true, "renameExtensions": { ".jsx": ".js" } }, - "inputs": [ - "internalTsArtifacts" + "configurations": { + "qunit": { + "removeDebug": false + } + } + }, + "build:qunit-esm": { + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "parallel": true, + "commands": [ + "pnpm nx run devextreme:build:npm:esm -c qunit", + "pnpm nx run devextreme:build:npm:esm:internal -c qunit" + ] + }, + "dependsOn": [ + "build:ts:internal" ], "outputs": [ - "{projectRoot}/artifacts/transpiled-esm-npm/esm/__internal" - ] + "{projectRoot}/artifacts/transpiled-esm-npm/esm" + ], + "cache": true, + "metadata": { + "description": "ESM artifacts for QUnit native import-map loader (?loader=esm)." + } }, "build:npm:cjs:internal": { "executor": "devextreme-nx-infra-plugin:babel-transform", @@ -530,14 +575,12 @@ "pnpm nx build:devextreme-bundler-config devextreme", "pnpm nx build:devextreme-bundler-config devextreme -c prod", "pnpm nx build:ts:internal devextreme", - "pnpm nx run-many --targets=build:cjs,build:cjs:internal,build:cjs:bundles --projects=devextreme --parallel", - "pnpm nx run-many --targets=build:cjs,build:cjs:internal,build:cjs:bundles --projects=devextreme --parallel -c production", - "pnpm nx run-many --targets=build:npm:cjs,build:npm:cjs:internal --projects=devextreme --parallel", + "pnpm nx run-many --targets=build:npm:esm,build:npm:esm:internal --projects=devextreme --parallel -c qunit", + "pnpm nx copy:qunit:dx-custom devextreme", "pnpm nx clean:dist-ts devextreme" ], "outputs": [ - "{projectRoot}/artifacts/transpiled", - "{projectRoot}/artifacts/transpiled-renovation-npm", + "{projectRoot}/artifacts/transpiled-esm-npm", "{projectRoot}/build/bundle-templates/dx.custom.js", "{projectRoot}/artifacts/npm/devextreme/bundles/dx.custom.config.js" ] @@ -570,10 +613,8 @@ "options": { "commands": [ "pnpm nx build:ts:internal:watch devextreme", - "pnpm nx build:cjs:watch devextreme", - "pnpm nx build:cjs:watch devextreme -c production", - "pnpm nx build:cjs:internal:watch devextreme", - "pnpm nx build:cjs:internal:watch devextreme -c production" + "pnpm nx build:npm:esm:watch devextreme -c qunit", + "pnpm nx build:npm:esm:internal:watch devextreme -c qunit" ], "cwd": "{projectRoot}", "parallel": true @@ -712,34 +753,6 @@ "{projectRoot}/artifacts/js/dx.{all,web,viz,ai-integration,custom}.debug.js" ] }, - "bundle:watch": { - "executor": "devextreme-nx-infra-plugin:bundle", - "options": { - "watch": true, - "entries": [ - "bundles/dx.all.js", - "bundles/dx.web.js", - "bundles/dx.viz.js", - "bundles/dx.ai-integration.js", - "bundles/dx.custom.js" - ], - "sourceDir": "./artifacts/transpiled-renovation-npm", - "outDir": "./artifacts/js", - "mode": "debug", - "webpackConfigPath": "./webpack.config.js", - "applyLicenseHeaders": { - "prependAfterLicense": "\"use strict\";\n\n", - "separator": "", - "includePatterns": ["dx.*.debug.js"] - } - }, - "configurations": { - "production": { - "sourceMap": false - } - }, - "cache": false - }, "bundle:prod": { "executor": "nx:run-commands", "options": { @@ -1729,7 +1742,7 @@ "pnpm nx clean:artifacts devextreme", "pnpm nx build:localization devextreme", "pnpm nx build:transpile devextreme -c ci", - "pnpm nx run-many --targets=bundle:debug,build:vectormap,copy:vendor --projects=devextreme --parallel" + "pnpm nx run-many --targets=build:vectormap,copy:vendor --projects=devextreme --parallel" ], "parallel": false }, @@ -1755,58 +1768,9 @@ ], "cache": true, "metadata": { - "description": "Dev/CI test build. Skips prod bundles, aspnet, declarations, npm, and license checks." + "description": "Dev/CI QUnit build (native ESM import-map). Skips all CJS transpile/bundle steps, npm CJS dual-mode, prod bundles, aspnet, declarations, npm packing, and license checks." } }, - "build:systemjs": { - "executor": "nx:run-commands", - "options": { - "cwd": "{projectRoot}", - "parallel": true, - "commands": [ - "node testing/systemjs-builder.js --transpile=modules", - "node testing/systemjs-builder.js --transpile=testing", - "node testing/systemjs-builder.js --transpile=css", - "node testing/systemjs-builder.js --transpile=js-vendors" - ] - }, - "dependsOn": [ - "build:dev" - ], - "inputs": [ - "internalPackageEnv", - { - "env": "DEVEXTREME_TEST_CI" - }, - { - "dependentTasksOutputFiles": "packages/devextreme/artifacts/transpiled/**/*" - }, - { - "dependentTasksOutputFiles": "packages/devextreme/artifacts/css/dx.light.css" - }, - { - "dependentTasksOutputFiles": "packages/devextreme/artifacts/css/dx.material.blue.light.css" - }, - { - "dependentTasksOutputFiles": "packages/devextreme/artifacts/css/dx.fluent.blue.light.css" - }, - { - "dependentTasksOutputFiles": "packages/devextreme/artifacts/css/dx-gantt.css" - }, - "{projectRoot}/testing/content/**/*", - "{projectRoot}/testing/helpers/**/*", - "{projectRoot}/testing/tests/**/*", - "{projectRoot}/testing/systemjs-builder.js", - "{workspaceRoot}/pnpm-lock.yaml" - ], - "outputs": [ - "{projectRoot}/artifacts/transpiled-systemjs", - "{projectRoot}/artifacts/transpiled-testing", - "{projectRoot}/artifacts/css-systemjs", - "{projectRoot}/artifacts/js-systemjs" - ], - "cache": true - }, "dev": { "executor": "nx:run-commands", "cache": false, @@ -1827,7 +1791,6 @@ "commands": [ "pnpm nx build:transpile:watch devextreme", "pnpm nx build:devextreme-bundler-config:watch devextreme", - "pnpm nx bundle:watch devextreme", "pnpm nx test-env devextreme" ] } @@ -1849,15 +1812,6 @@ "lint-texts" ] }, - "lint-css": { - "executor": "nx:run-script", - "options": { - "script": "lint-css" - }, - "inputs": [ - "{projectRoot}/scss/**/*" - ] - }, "lint-dts": { "executor": "nx:run-script", "options": { @@ -1925,17 +1879,6 @@ "{projectRoot}/build/**/*", "{projectRoot}/jest.*" ] - }, - "test-styles": { - "executor": "nx:run-script", - "options": { - "script": "test-styles" - }, - "inputs": [ - "default", - "!{projectRoot}/js/**/*", - "!{projectRoot}/ts/**/*" - ] } }, "namedInputs": { diff --git a/packages/devextreme/testing/helpers/ajaxMock.js b/packages/devextreme/testing/helpers/ajaxMock.js index c29c8181937e..462ef5951119 100644 --- a/packages/devextreme/testing/helpers/ajaxMock.js +++ b/packages/devextreme/testing/helpers/ajaxMock.js @@ -1,7 +1,8 @@ -const ajax = require('core/utils/ajax'); -const extend = require('core/utils/extend').extend; -const typeUtils = require('core/utils/type'); -const $ = require('jquery'); +import $ from 'jquery'; +import ajax from 'core/utils/ajax'; +import { extend } from 'core/utils/extend'; +import { isDefined } from 'core/utils/type'; + const originSendRequest = ajax.sendRequest; let urlMap = {}; const timers = []; @@ -20,7 +21,7 @@ const findUrlOptions = function(requestUrl) { } }; -exports.setup = function(options) { +export function setup(options) { urlMap[options.url] = options; ajax.sendRequest = function(request) { @@ -29,7 +30,7 @@ exports.setup = function(options) { const mockOptions = findUrlOptions(request.url); const jQueryTextStatus = mockOptions.jQueryTextStatus; - response.status = typeUtils.isDefined(mockOptions.status) ? mockOptions.status : 200; + response.status = isDefined(mockOptions.status) ? mockOptions.status : 200; response.statusText = mockOptions.statusText || '200 OK'; response.responseText = mockOptions.responseText; @@ -48,12 +49,14 @@ exports.setup = function(options) { return deferred.promise(); }; -}; +} -exports.clear = function() { +export function clear() { ajax.sendRequest = originSendRequest; urlMap = {}; timers.forEach(function(timerId) { clearTimeout(timerId); }); -}; +} + +export default { setup, clear }; diff --git a/packages/devextreme/testing/helpers/chartMocks.js b/packages/devextreme/testing/helpers/chartMocks.js index 5e7a2ba7c4d5..6d5945d1c21c 100644 --- a/packages/devextreme/testing/helpers/chartMocks.js +++ b/packages/devextreme/testing/helpers/chartMocks.js @@ -16,6 +16,7 @@ import { } from './vizMocks.js'; import { Range } from 'viz/translators/range'; + const LoadingIndicatorOrig = loadingIndicatorModule.LoadingIndicator; const firstCategory = 'First'; @@ -281,16 +282,28 @@ function createAxis(translatorData, orthogonalTranslatorData, allOptions, isHori return axis; } +// Modules that dropped their generated facade expose a DEBUG_set_* seam; the +// rest are still plain mutable objects. +function setItem(itemKey, moduleName, value) { + const setter = moduleName['DEBUG_set_' + itemKey]; + + if(typeof setter === 'function') { + setter(value); + } else { + moduleName[itemKey] = value; + } +} + function mockItem(itemKey, moduleName, mock) { if(sourceItemsToMocking[itemKey]) { throw 'Item ' + itemKey + ' already mocked'; } sourceItemsToMocking[itemKey] = moduleName[itemKey]; - moduleName[itemKey] = mock; + setItem(itemKey, moduleName, mock); } function restoreItem(itemKey, moduleName) { - moduleName[itemKey] = sourceItemsToMocking[itemKey]; + setItem(itemKey, moduleName, sourceItemsToMocking[itemKey]); sourceItemsToMocking[itemKey] = null; } @@ -394,9 +407,9 @@ export const resetMockFactory = function resetMockFactory() { }; export const setupSeriesFamily = function() { - seriesFamilyModule.SeriesFamily = function(options) { + seriesFamilyModule.DEBUG_set_SeriesFamily(function(options) { return new MockSeriesFamily(options); - }; + }); }; // Translator diff --git a/packages/devextreme/testing/helpers/data.errorHandlingHelper.js b/packages/devextreme/testing/helpers/data.errorHandlingHelper.js index 55a4040abade..9715aceb7ebc 100644 --- a/packages/devextreme/testing/helpers/data.errorHandlingHelper.js +++ b/packages/devextreme/testing/helpers/data.errorHandlingHelper.js @@ -1,16 +1,7 @@ -(function(root, factory) { - root.DevExpress = root.DevExpress || {}; - root.DevExpress.data = root.DevExpress.data || {}; - root.DevExpress.data.testing = root.DevExpress.data.testing || {}; - - if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - root.DevExpress.data.testing.ErrorHandlingHelper = module.exports = factory(require('jquery'), require('common/data/errors')); - }); - } else { - root.DevExpress.data.testing.ErrorHandlingHelper = factory(window.jQuery, DevExpress.data); - } -}(window, function($, errorsModule) { +import $ from 'jquery'; +import * as errorsModule from 'common/data/errors'; + +const __moduleExports = (function($, errorsModule) { return class ErrorHandlingHelper { constructor() { @@ -76,4 +67,11 @@ }); } }; -})); +})($, errorsModule); + +window.DevExpress = window.DevExpress || {}; +window.DevExpress.data = window.DevExpress.data || {}; +window.DevExpress.data.testing = window.DevExpress.data.testing || {}; +window.DevExpress.data.testing.ErrorHandlingHelper = __moduleExports; + +export default __moduleExports; diff --git a/packages/devextreme/testing/helpers/dataGridMocks.js b/packages/devextreme/testing/helpers/dataGridMocks.js index 966f4ccb369a..418f18963e79 100644 --- a/packages/devextreme/testing/helpers/dataGridMocks.js +++ b/packages/devextreme/testing/helpers/dataGridMocks.js @@ -1,32 +1,46 @@ -let gridBaseMock; +import $ from 'jquery'; +import gridCoreModule from '__internal/grids/data_grid/m_core'; +import columnResizingReorderingModule from '__internal/grids/data_grid/module_not_extended/columns_resizing_reordering'; +import domUtilsModule from '__internal/core/utils/m_dom'; +import commonUtilsModule from '__internal/core/utils/m_common'; +import typeUtilsModule from '__internal/core/utils/m_type'; +import ArrayStoreModule from 'common/data/array_store'; +import gridBaseMockModule from './gridBaseMocks.js'; -/* global jQuery */ -if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - gridBaseMock = require('./gridBaseMocks.js'); +const gridBaseMock = gridBaseMockModule.default ?? gridBaseMockModule; +const gridCore = gridCoreModule.default ?? gridCoreModule; +const columnResizingReordering = columnResizingReorderingModule.default ?? columnResizingReorderingModule; +const domUtils = domUtilsModule.default ?? domUtilsModule; +const commonUtils = commonUtilsModule.default ?? commonUtilsModule; +const typeUtils = typeUtilsModule.default ?? typeUtilsModule; +const ArrayStore = ArrayStoreModule.default ?? ArrayStoreModule; - window.dataGridMocks = module.exports = gridBaseMock( - require('jquery'), - require('__internal/grids/data_grid/m_core').default, - require('__internal/grids/data_grid/module_not_extended/columns_resizing_reordering').default, - require('__internal/core/utils/m_dom'), - require('__internal/core/utils/m_common'), - require('__internal/core/utils/m_type'), - require('common/data/array_store'), - 'DataGrid' - ); - }); -} else { - gridBaseMock = DevExpress.require('./gridBaseMocks.js'); +const dataGridMocks = gridBaseMock( + $, + gridCore, + columnResizingReordering, + domUtils, + commonUtils, + typeUtils, + ArrayStore, + 'DataGrid' +); - jQuery.extend(window, gridBaseMock( - jQuery, - DevExpress.require('__internal/grids/data_grid/m_core'), - DevExpress.require('__internal/grids/data_grid/module_not_extended/columns_resizing_reordering'), - DevExpress.require('__internal/core/utils/m_dom'), - DevExpress.require('__internal/core/utils/m_common'), - DevExpress.require('__internal/core/utils/m_type'), - DevExpress.require('common/data/array_store'), - 'DataGrid' - )); -} +window.dataGridMocks = dataGridMocks; + +export const setupDataGridModules = dataGridMocks.setupDataGridModules; +export const MockDataController = dataGridMocks.MockDataController; +export const MockEditingController = dataGridMocks.MockEditingController; +export const MockSelectionController = dataGridMocks.MockSelectionController; +export const MockColumnsController = dataGridMocks.MockColumnsController; +export const MockTablePositionViewController = dataGridMocks.MockTablePositionViewController; +export const MockGridDataSource = dataGridMocks.MockGridDataSource; +export const getCells = dataGridMocks.getCells; +export const MockColumnsSeparatorView = dataGridMocks.MockColumnsSeparatorView; +export const MockTrackerView = dataGridMocks.MockTrackerView; +export const MockDraggingPanel = dataGridMocks.MockDraggingPanel; +export const TestDraggingHeader = dataGridMocks.TestDraggingHeader; +export const generateItems = dataGridMocks.generateItems; +export const generateNestedData = dataGridMocks.generateNestedData; + +export default dataGridMocks; diff --git a/packages/devextreme/testing/helpers/esm-shims/fluent_blue_light.css.js b/packages/devextreme/testing/helpers/esm-shims/fluent_blue_light.css.js new file mode 100644 index 000000000000..28ba858f51a8 --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/fluent_blue_light.css.js @@ -0,0 +1,5 @@ +import { injectStylesheet } from './injectStylesheet.js'; + +await injectStylesheet('/packages/devextreme/artifacts/css/dx.fluent.blue.light.css', { + themeName: 'fluent.blue.light', +}); diff --git a/packages/devextreme/testing/helpers/esm-shims/gantt.css.js b/packages/devextreme/testing/helpers/esm-shims/gantt.css.js new file mode 100644 index 000000000000..e247d21d5527 --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/gantt.css.js @@ -0,0 +1,3 @@ +import { injectStylesheet } from './injectStylesheet.js'; + +await injectStylesheet('/packages/devextreme/artifacts/css/dx-gantt.css'); diff --git a/packages/devextreme/testing/helpers/esm-shims/injectStylesheet.js b/packages/devextreme/testing/helpers/esm-shims/injectStylesheet.js new file mode 100644 index 000000000000..55eacd3c7014 --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/injectStylesheet.js @@ -0,0 +1,57 @@ +/** + * Injects a stylesheet once for ESM import-map QUnit mode + * (`*.css!` suite imports resolve here). + * + * Only appends `<link rel=stylesheet data-theme=…>`. + * Do not add dx-theme-* classes on body — that belongs to themes.attachCssClasses + * on `.dx-viewport` and would change typography/layout. + * + * Returns a Promise so importers can `await` load — otherwise tests that + * assert computed styles race the async <link> fetch. + * + * @param {string} href + * @param {{ themeName?: string }} [options] + * themeName — optional `data-theme` (fluent.blue.light / generic.light / …) + */ +export function injectStylesheet(href, options = {}) { + const existing = document.querySelector(`link[data-dx-esm-css="${href}"]`); + if(existing) { + return waitForStylesheet(existing, href); + } + + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = href; + link.setAttribute('data-dx-esm-css', href); + if(options.themeName) { + link.setAttribute('data-theme', options.themeName); + } + document.head.appendChild(link); + return waitForStylesheet(link, href); +} + +function waitForStylesheet(link, href) { + if(link.sheet || link.dataset.dxEsmCssLoaded === '1') { + return Promise.resolve(); + } + + return new Promise((resolve, reject) => { + const onLoad = () => { + link.dataset.dxEsmCssLoaded = '1'; + resolve(); + }; + const onError = () => { + reject(new Error(`Failed to load stylesheet: ${href}`)); + }; + + link.addEventListener('load', onLoad, { once: true }); + link.addEventListener('error', onError, { once: true }); + + // Cached stylesheets may already be applied before listeners attach + if(link.sheet) { + link.removeEventListener('load', onLoad); + link.removeEventListener('error', onError); + onLoad(); + } + }); +} diff --git a/packages/devextreme/testing/helpers/esm-shims/jquery.js b/packages/devextreme/testing/helpers/esm-shims/jquery.js new file mode 100644 index 000000000000..a6a3df9f758a --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/jquery.js @@ -0,0 +1,17 @@ +/** + * ESM jquery shim for QUnit import-map loader. + * jQuery is loaded via classic script tag before modules run; + * run-suite calls `jQuery.noConflict()` which clears `window.$`. + * Re-attach `$` so suites that use the global alias (without importing + * jquery) keep working. + */ +const $ = window.jQuery; + +if(!$ || typeof $.fn === 'undefined') { + throw new Error('ESM jquery shim: window.jQuery is not available'); +} + +window.$ = $; + +export default $; +export { $ }; diff --git a/packages/devextreme/testing/helpers/esm-shims/jspdf_autotable.js b/packages/devextreme/testing/helpers/esm-shims/jspdf_autotable.js new file mode 100644 index 000000000000..cbd2e5ff587b --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/jspdf_autotable.js @@ -0,0 +1,37 @@ +/** + * jspdf-autotable side-effect import under native ESM. + * The vendor build attaches itself through a CJS require call, which is + * unavailable in the browser — call applyPlugin explicitly instead. + * + * Note: the `.mjs` build exports `autoTable` only as default + * (`export { …, autoTable as default }`), not as a named export. + */ +import { jsPDF } from 'jspdf'; +/* eslint-disable import/named -- vendor ESM re-exports include default + named applyPlugin */ +import autoTable, { + Cell, + CellHookData, + Column, + Row, + Table, + __createTable, + __drawTable, + applyPlugin, +} from '../../../node_modules/jspdf-autotable/dist/jspdf.plugin.autotable.mjs'; +/* eslint-enable import/named */ + +const JsPdfCtor = typeof jsPDF === 'function' ? jsPDF : jsPDF.jsPDF; +applyPlugin(JsPdfCtor); + +export { + Cell, + CellHookData, + Column, + Row, + Table, + __createTable, + __drawTable, + applyPlugin, + autoTable, +}; +export default autoTable; diff --git a/packages/devextreme/testing/helpers/esm-shims/knockout.js b/packages/devextreme/testing/helpers/esm-shims/knockout.js new file mode 100644 index 000000000000..b1f45f77edec --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/knockout.js @@ -0,0 +1,12 @@ +/** + * ESM knockout shim for QUnit import-map loader. + * Knockout is loaded via classic script tag before modules run. + */ +const ko = window.ko; + +if(!ko) { + throw new Error('ESM knockout shim: window.ko is not available'); +} + +export default ko; +export { ko }; diff --git a/packages/devextreme/testing/helpers/esm-shims/material_blue_light.css.js b/packages/devextreme/testing/helpers/esm-shims/material_blue_light.css.js new file mode 100644 index 000000000000..0c9ff647951b --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/material_blue_light.css.js @@ -0,0 +1,5 @@ +import { injectStylesheet } from './injectStylesheet.js'; + +await injectStylesheet('/packages/devextreme/artifacts/css/dx.material.blue.light.css', { + themeName: 'material.blue.light', +}); diff --git a/packages/devextreme/testing/helpers/esm-shims/themes.js b/packages/devextreme/testing/helpers/esm-shims/themes.js new file mode 100644 index 000000000000..3aa9ec10dfa9 --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/themes.js @@ -0,0 +1,48 @@ +/** + * Mutable facade for ui/themes — QUnit stubs replace api.isMaterial / isFluent / + * isMaterialBased / isGeneric / current on the default export object. + * + * Named exports always forward to the current api.* implementation so + * library `import { isMaterial }` keeps working after stubs. + * + * api is stored on globalThis so import-map and static-redirect URLs + * (cache-buster differences) still share one stubbable object. + */ +import * as original from '../../../artifacts/transpiled-esm-npm/esm/__internal/ui/themes.js?dx-original=1'; + +const GLOBAL_KEY = '__dxMutableUiThemes'; + +const api = globalThis[GLOBAL_KEY] ?? (globalThis[GLOBAL_KEY] = { + ...original, + // Keep composition live so stubbing isMaterial / isFluent affects isMaterialBased. + isMaterialBased(themeName) { + return api.isMaterial(themeName) || api.isFluent(themeName); + }, +}); + +function wrapExport(name) { + return function(...args) { + return api[name](...args); + }; +} + +export const setDefaultTimeout = wrapExport('setDefaultTimeout'); +export const init = wrapExport('init'); +export const initialized = wrapExport('initialized'); +export const resetTheme = wrapExport('resetTheme'); +export const ready = wrapExport('ready'); +export const waitWebFont = wrapExport('waitWebFont'); +export const isWebFontLoaded = wrapExport('isWebFontLoaded'); +export const isCompact = wrapExport('isCompact'); +export const isDark = wrapExport('isDark'); +export const isGeneric = wrapExport('isGeneric'); +export const isMaterial = wrapExport('isMaterial'); +export const isFluent = wrapExport('isFluent'); +export const isMaterialBased = wrapExport('isMaterialBased'); +export const detachCssClasses = wrapExport('detachCssClasses'); +export const attachCssClasses = wrapExport('attachCssClasses'); +export const current = wrapExport('current'); +export const waitForThemeLoad = wrapExport('waitForThemeLoad'); +export const isPendingThemeLoaded = wrapExport('isPendingThemeLoaded'); + +export default api; diff --git a/packages/devextreme/testing/helpers/esm-shims/tslib.js b/packages/devextreme/testing/helpers/esm-shims/tslib.js new file mode 100644 index 000000000000..377dace0c5bc --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/tslib.js @@ -0,0 +1,41 @@ +/** + * Minimal tslib fallback for QUnit when the package is not hoisted. + * Covers helpers used by rrule's ESM build. + */ +export function __assign(target) { + for(let i = 1; i < arguments.length; i++) { + const source = arguments[i]; + for(const key in source) { + if(Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + return target; +} + +export function __extends(derived, base) { + Object.setPrototypeOf(derived, base); + function PrototypeBridge() { + this.constructor = derived; + } + PrototypeBridge.prototype = base === null ? Object.create(base) : base.prototype; + // eslint-disable-next-line new-cap, no-new + derived.prototype = new PrototypeBridge(); +} + +export function __spreadArray(to, from, pack) { + if(pack || arguments.length === 2) { + let packed; + for(let i = 0, length = from.length; i < length; i++) { + if(packed || !(i in from)) { + if(!packed) { + packed = Array.prototype.slice.call(from, 0, i); + } + packed[i] = from[i]; + } + } + return to.concat(packed || Array.prototype.slice.call(from)); + } + return to.concat(from); +} diff --git a/packages/devextreme/testing/helpers/esm-shims/zod-to-json-schema.js b/packages/devextreme/testing/helpers/esm-shims/zod-to-json-schema.js new file mode 100644 index 000000000000..c1ce5b58b1a2 --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/zod-to-json-schema.js @@ -0,0 +1,8 @@ +/** + * Minimal zod-to-json-schema stub for QUnit ESM / import-map loader. + */ +export function zodToJsonSchema() { + return { type: 'object' }; +} + +export default zodToJsonSchema; diff --git a/packages/devextreme/testing/helpers/esm-shims/zod.js b/packages/devextreme/testing/helpers/esm-shims/zod.js new file mode 100644 index 000000000000..489d0bbcf5ba --- /dev/null +++ b/packages/devextreme/testing/helpers/esm-shims/zod.js @@ -0,0 +1,35 @@ +/** + * Minimal zod stub for QUnit ESM / import-map loader. + */ +const z = { + object() { return z; }, + string() { return z; }, + boolean() { return z; }, + number() { return z; }, + date() { return z; }, + null() { return z; }, + enum() { return z; }, + union() { return z; }, + array() { return z; }, + tuple() { return z; }, + literal() { return z; }, + record() { return z; }, + lazy() { return z; }, + optional() { return z; }, + nullable() { return z; }, + // eslint-disable-next-line spellcheck/spell-checker + nullish() { return z; }, + strict() { return z; }, + int() { return z; }, + // eslint-disable-next-line spellcheck/spell-checker + nonnegative() { return z; }, + positive() { return z; }, + min() { return z; }, + max() { return z; }, + transform() { return z; }, + describe() { return z; }, + safeParse() { return { success: true, data: {} }; }, +}; + +export { z }; +export default z; diff --git a/packages/devextreme/testing/helpers/executeAsyncMock.js b/packages/devextreme/testing/helpers/executeAsyncMock.js index cd52380fd3a7..5da48be090e1 100644 --- a/packages/devextreme/testing/helpers/executeAsyncMock.js +++ b/packages/devextreme/testing/helpers/executeAsyncMock.js @@ -1,28 +1,20 @@ -(function(root, factory) { - root.DevExpress = root.DevExpress || {}; - root.DevExpress.testing = root.DevExpress.testing || {}; +import commonUtils from '__internal/core/utils/m_common'; - if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - root.DevExpress.testing.executeAsyncMock = module.exports = factory(require('__internal/core/utils/m_common').default); - }); - } else { - root.DevExpress.testing.executeAsyncMock = factory(DevExpress.utils.common); - } -}(window, function(commonUtils) { - const originalExecuteAsync = commonUtils.executeAsync; - - return { - setup: function() { - commonUtils.executeAsync = function(action, context) { - return originalExecuteAsync.apply(this, [action, context, function(callback) { return callback.apply(this, arguments); }]); - }; - }, - teardown: function() { - commonUtils.executeAsync = originalExecuteAsync; - } - }; +const originalExecuteAsync = commonUtils.executeAsync; -})); +const executeAsyncMock = { + setup: function() { + commonUtils.executeAsync = function(action, context) { + return originalExecuteAsync.apply(this, [action, context, function(callback) { return callback.apply(this, arguments); }]); + }; + }, + teardown: function() { + commonUtils.executeAsync = originalExecuteAsync; + } +}; +window.DevExpress = window.DevExpress || {}; +window.DevExpress.testing = window.DevExpress.testing || {}; +window.DevExpress.testing.executeAsyncMock = executeAsyncMock; +export default executeAsyncMock; diff --git a/packages/devextreme/testing/helpers/exportMocks.js b/packages/devextreme/testing/helpers/exportMocks.js index 9e7f6998f16b..d6eab28c27fc 100644 --- a/packages/devextreme/testing/helpers/exportMocks.js +++ b/packages/devextreme/testing/helpers/exportMocks.js @@ -1,4 +1,4 @@ -const $ = require('jquery'); +import $ from 'jquery'; exports.MockDataProvider = function(data, columns) { data = data || [ diff --git a/packages/devextreme/testing/helpers/gridBaseMocks.js b/packages/devextreme/testing/helpers/gridBaseMocks.js index 05441eea0baa..d2134434a152 100644 --- a/packages/devextreme/testing/helpers/gridBaseMocks.js +++ b/packages/devextreme/testing/helpers/gridBaseMocks.js @@ -1,6 +1,96 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commonUtils, typeUtils, ArrayStore, nameWidget) { const exports = {}; + exports.MockDataSourceAdapter = function(options) { + // Loads read options.items: the stores tests pass in options.store are write-only spies. + const itemsStore = function() { + return new ArrayStore(options.items); + }; + + const loadCustomResult = (loadOptions) => { + const d = $.Deferred(); + + itemsStore().load(loadOptions) + .done((data, extra) => { + d.resolve({ data: data, extra: extra }); + }) + .fail(d.reject); + + return d; + }; + + return { + _dataSource: options.dataSource, + beginLoading: function() { + }, + endLoading: function() { + }, + remoteOperations: function() { + return {}; + }, + loadOptions: function() { + return {}; + }, + key: function() { + return options.key; + }, + isLoading: function() { + return false; + }, + lastLoadOptions: function() { + return {}; + }, + operationTypes: function() { + return undefined; + }, + select: function() { + return undefined; + }, + getDataIndexGetter: function() { + return undefined; + }, + getCachedStoreData: function() { + return undefined; + }, + loadingOperationTypes: function() { + return undefined; + }, + hasKnownLastPage: function() { + return typeUtils.isDefined(options.hasKnownLastPage) ? options.hasKnownLastPage : true; + }, + totalItemsCount: function() { + return options.totalItemsCount; + }, + totalCount: function() { + return options.totalCount || 0; + }, + pageCount: function() { + return options.pageCount; + }, + dispose: function() { + }, + store: function() { + return options.store; + }, + push: function() { + }, + pushed: $.Callbacks(), + load: function(loadOptions) { + return itemsStore().load(loadOptions); + }, + customLoader: { + load: loadCustomResult, + loadFromStore: loadCustomResult, + isLoading: function() { + return false; + }, + isLoadingAll: function() { + return false; + } + } + }; + }; + exports.MockDataController = function(options) { if(!typeUtils.isDefined(options.itemsCount)) { @@ -37,50 +127,7 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo changedArgs: [], - dataSource: function() { - const store = function() { - return new ArrayStore(options.items); - }; - - const loadCustomResult = (loadOptions) => { - const d = $.Deferred(); - - store().load(loadOptions) - .done((data, extra) => { - d.resolve({ data: data, extra: extra }); - }) - .fail(d.reject); - - return d; - }; - - return { - beginLoading: function() { - }, - endLoading: function() { - }, - remoteOperations: function() { - return {}; - }, - loadOptions: function() { - return {}; - }, - store: store, - load: function(loadOptions) { - return store().load(loadOptions); - }, - customLoader: { - load: loadCustomResult, - loadFromStore: loadCustomResult, - isLoading: function() { - return false; - }, - isLoadingAll: function() { - return false; - } - } - }; - }, + mockOptions: options, pageSize: function(value) { if(value === undefined) { @@ -95,23 +142,11 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo return typeUtils.isDefined(options.pageSizes) ? options.pageSizes : []; }, - hasKnownLastPage: function() { - return typeUtils.isDefined(options.hasKnownLastPage) ? options.hasKnownLastPage : true; - }, - updatePagesCount: function(count) { options.pageCount = count; this.changed.fire(); }, - pageCount: function() { - return options.pageCount; - }, - - totalCount: function() { - return options.totalCount || 0; - }, - pageIndex: function(index) { if(typeUtils.isDefined(index)) { options.pageIndex = index; @@ -149,10 +184,6 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo }); }, - store: function() { - return options.store; - }, - insertItems: function(insertingItems) { $.each(insertingItems, function() { options.items.push(this); @@ -196,10 +227,6 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo return options.itemsCount; }, - totalItemsCount: function() { - return options.totalItemsCount; - }, - isLoading: function() { return false; }, @@ -260,25 +287,18 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo getCombinedFilter: commonUtils.noop, - getFilterExcludedColumn: commonUtils.noop, - getCombinedFilterWithExcludedColumn: commonUtils.noop, getRowIndexByKey: function(key) { return gridCore.getIndexByKey(key, options.items); }, - loadingOperationTypes: function() { - return {}; - }, - skipProcessingPagingChange: commonUtils.noop, changed: $.Callbacks(), loadingChanged: $.Callbacks(), dataErrorOccurred: $.Callbacks('stopOnFalse'), pageChanged: $.Callbacks(), dataSourceChanged: $.Callbacks(), - pushed: $.Callbacks(), rowIndicesChanged: $.Callbacks(), fireError: function() { }, loadViewport: commonUtils.noop, @@ -994,11 +1014,15 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo _subscribeToEvents(rootElement) { } }; + // The dataSource and filter controllers are leaves that the data controller resolves + // in init(), so they are always included rather than listed by every caller. + const ALWAYS_INCLUDED_MODULES = ['dataSource', 'filter']; + exports['setup' + nameWidget + 'Modules'] = function(that, moduleNames, options) { const modules = []; $.each(gridCore.modules, function() { - if($.inArray(this.name, moduleNames) !== -1) { + if($.inArray(this.name, moduleNames) !== -1 || $.inArray(this.name, ALWAYS_INCLUDED_MODULES) !== -1) { modules.push(this); } }); @@ -1210,6 +1234,13 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo options && options.controllers && $.extend(that._controllers, options.controllers); options && options.views && $.extend(that._views, options.views); + const mockedDataController = options && options.controllers && options.controllers.data; + if(mockedDataController && mockedDataController.mockOptions && that._controllers.dataSource) { + that._controllers.dataSource.adapter = new exports.MockDataSourceAdapter( + mockedDataController.mockOptions + ); + } + $.each(that._controllers, function(name) { that[name + 'Controller'] = this; this.init && this.init(); diff --git a/packages/devextreme/testing/helpers/includeThemesLinks.js b/packages/devextreme/testing/helpers/includeThemesLinks.js index a6e01e6d0104..6d8b3d53ee93 100644 --- a/packages/devextreme/testing/helpers/includeThemesLinks.js +++ b/packages/devextreme/testing/helpers/includeThemesLinks.js @@ -1,9 +1,12 @@ -const themesList = ['generic.light', 'material.blue.light']; +const themesList = [ + { name: 'generic.light', href: '/packages/devextreme/artifacts/css/dx.light.css' }, + { name: 'material.blue.light', href: '/packages/devextreme/artifacts/css/dx.material.blue.light.css' }, +]; -themesList.forEach(theme => { +themesList.forEach(({ name, href }) => { const link = document.createElement('link'); link.setAttribute('rel', 'dx-theme'); - link.setAttribute('data-theme', theme); - link.setAttribute('href', SystemJS.normalizeSync(theme.replace(/\./g, '_') + '.css')); + link.setAttribute('data-theme', name); + link.setAttribute('href', href); document.head.appendChild(link); }); diff --git a/packages/devextreme/testing/helpers/keyboardMock.js b/packages/devextreme/testing/helpers/keyboardMock.js index ade1eefd1974..1107d8978d43 100644 --- a/packages/devextreme/testing/helpers/keyboardMock.js +++ b/packages/devextreme/testing/helpers/keyboardMock.js @@ -1,16 +1,8 @@ -let focused; - -(function(root, factory) { - if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - focused = require('__internal/core/utils/m_selectors').focused; - root.keyboardMock = module.exports = factory(require('jquery'), require('inferno')); - }); - } else { - focused = DevExpress.require('__internal/core/utils/m_selectors').focused; - root.keyboardMock = factory(root.jQuery); - } -}(window, function($, inferno) { +import $ from 'jquery'; +import * as inferno from 'inferno'; +import { focused } from '__internal/core/utils/m_selectors'; + +const keyboardMock = (function($, inferno) { let $element; let caret; @@ -427,4 +419,6 @@ let focused; } }; }; -})); +})($, inferno); + +export default keyboardMock; diff --git a/packages/devextreme/testing/helpers/memoryLeaksHelper.js b/packages/devextreme/testing/helpers/memoryLeaksHelper.js index 6a4efcca90a9..bab8140b8826 100644 --- a/packages/devextreme/testing/helpers/memoryLeaksHelper.js +++ b/packages/devextreme/testing/helpers/memoryLeaksHelper.js @@ -1,17 +1,6 @@ -(function(root, factory) { - /* global jQuery */ - if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - root.memoryLeaksHelper = module.exports = factory( - require('jquery') - ); - }); - } else { - jQuery.extend(window, factory( - jQuery - )); - } -}(window, function($) { +import $ from 'jquery'; + +const __moduleExports = (function($) { const exports = {}; @@ -142,4 +131,8 @@ }; return exports; -})); +})($); + +window.memoryLeaksHelper = __moduleExports; + +export default __moduleExports; diff --git a/packages/devextreme/testing/helpers/mockModule.js b/packages/devextreme/testing/helpers/mockModule.js deleted file mode 100644 index 5864029817d4..000000000000 --- a/packages/devextreme/testing/helpers/mockModule.js +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable no-undef */ - -const $ = require('jquery'); - -exports.mock = (module, value) => { - const normalizedName = System.normalizeSync(module); - System.delete(normalizedName); - value.__esModule = true; - $.extend({ default: value }); - System.set(normalizedName, System.newModule($.extend({ default: value }, value))); - return value; -}; diff --git a/packages/devextreme/testing/helpers/moduleSeam.js b/packages/devextreme/testing/helpers/moduleSeam.js new file mode 100644 index 000000000000..0f02d142dab4 --- /dev/null +++ b/packages/devextreme/testing/helpers/moduleSeam.js @@ -0,0 +1,18 @@ +/* global sinon */ + +export function installSeam(module, name, replacement, setterName = `DEBUG_set_${name}`) { + const original = module[name]; + + replacement.restore = () => module[setterName](original); + module[setterName](replacement); + + return replacement; +} + +export function spySeam(module, name, setterName) { + return installSeam(module, name, sinon.spy(module[name]), setterName); +} + +export function stubSeam(module, name, setterName) { + return installSeam(module, name, sinon.stub(), setterName); +} diff --git a/packages/devextreme/testing/helpers/nativePointerMock.js b/packages/devextreme/testing/helpers/nativePointerMock.js index fab9e3227981..c50749f0d0f5 100644 --- a/packages/devextreme/testing/helpers/nativePointerMock.js +++ b/packages/devextreme/testing/helpers/nativePointerMock.js @@ -1,13 +1,6 @@ -(function(root, factory) { - /* global jQuery */ - if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - root.nativePointerMock = module.exports = factory(require('jquery')); - }); - } else { - root.nativePointerMock = factory(jQuery); - } -}(window, function($) { +import $ from 'jquery'; + +const __moduleExports = (function($) { const UA = (function() { const ua = window.navigator.userAgent; let matches; @@ -980,4 +973,8 @@ return result; -})); +})($); + +window.nativePointerMock = __moduleExports; + +export default __moduleExports; diff --git a/packages/devextreme/testing/helpers/noDiagram.js b/packages/devextreme/testing/helpers/noDiagram.js index 1d0e288bbae3..189f31a52683 100644 --- a/packages/devextreme/testing/helpers/noDiagram.js +++ b/packages/devextreme/testing/helpers/noDiagram.js @@ -1,4 +1,4 @@ if(window.DevExpress) { window.DevExpress.diagram = undefined; } -module.exports = null; +export default null; diff --git a/packages/devextreme/testing/helpers/noGantt.js b/packages/devextreme/testing/helpers/noGantt.js index ed0a8a552761..abc0e1f6294f 100644 --- a/packages/devextreme/testing/helpers/noGantt.js +++ b/packages/devextreme/testing/helpers/noGantt.js @@ -1,4 +1,4 @@ if(window.DevExpress) { window.DevExpress.Gantt = undefined; } -module.exports = null; +export default null; diff --git a/packages/devextreme/testing/helpers/noJQuery.js b/packages/devextreme/testing/helpers/noJQuery.js index 25af527694e7..7646bbd17d04 100644 --- a/packages/devextreme/testing/helpers/noJQuery.js +++ b/packages/devextreme/testing/helpers/noJQuery.js @@ -1 +1 @@ -window.jQuery = module.exports = null; +export default null; diff --git a/packages/devextreme/testing/helpers/pointerMock.js b/packages/devextreme/testing/helpers/pointerMock.js index 439c852ea5b6..6b89ba1b2301 100644 --- a/packages/devextreme/testing/helpers/pointerMock.js +++ b/packages/devextreme/testing/helpers/pointerMock.js @@ -1,17 +1,9 @@ -(function(root, factory) { - /* global jQuery */ - if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - root.pointerMock = module.exports = factory( - require('jquery'), - require('inferno'), - require('common/core/events/gesture/emitter.gesture'), - require('common/core/events/click')); - }); - } else { - root.pointerMock = factory(jQuery, DevExpress.events.GestureEmitter, DevExpress.events.click); - } -}(window, function($, inferno, GestureEmitter, clickEvent) { +import $ from 'jquery'; +import * as inferno from 'inferno'; +import GestureEmitter from 'common/core/events/gesture/emitter.gesture'; +import * as clickEvent from 'common/core/events/click'; + +const pointerMock = (function($, inferno, GestureEmitter, clickEvent) { GestureEmitter.touchBoundary(0); @@ -236,4 +228,6 @@ } }; }; -})); +})($, inferno, GestureEmitter, clickEvent); + +export default pointerMock; diff --git a/packages/devextreme/testing/helpers/positionFixtures.js b/packages/devextreme/testing/helpers/positionFixtures.js index cada61dab25b..978a65f1fabc 100644 --- a/packages/devextreme/testing/helpers/positionFixtures.js +++ b/packages/devextreme/testing/helpers/positionFixtures.js @@ -1,12 +1,6 @@ -(function(root, factory) { - if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - root.fixtures = module.exports = factory(require('jquery')); - }); - } else { - root.fixtures = factory(root.jQuery); - } -}(window, function($) { +import $ from 'jquery'; + +const __moduleExports = (function($) { const fixtures = { simple: { @@ -258,4 +252,8 @@ }; return fixtures; -})); +})($); + +window.fixtures = __moduleExports; + +export default __moduleExports; diff --git a/packages/devextreme/testing/helpers/publicModulesHelper.js b/packages/devextreme/testing/helpers/publicModulesHelper.js index bff3366338de..3c39a4f42755 100644 --- a/packages/devextreme/testing/helpers/publicModulesHelper.js +++ b/packages/devextreme/testing/helpers/publicModulesHelper.js @@ -1,12 +1,6 @@ -(function(root, factory) { - if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - root.testGlobalExports = module.exports = factory(require('jquery')); - }); - } else { - root.testGlobalExports = factory(root.jQuery); - } -}(window, function($) { +import $ from 'jquery'; + +const __moduleExports = (function($) { return function(namespaces, fields) { $.each(namespaces, function(namespaceName, namespace) { $.each(fields, function(fieldName, fieldValue) { @@ -17,4 +11,8 @@ }); }); }; -})); +})($); + +window.testGlobalExports = __moduleExports; + +export default __moduleExports; diff --git a/packages/devextreme/testing/helpers/quillDependencies/noQuill.js b/packages/devextreme/testing/helpers/quillDependencies/noQuill.js index e45fa7973937..3b197fe204b6 100644 --- a/packages/devextreme/testing/helpers/quillDependencies/noQuill.js +++ b/packages/devextreme/testing/helpers/quillDependencies/noQuill.js @@ -1 +1,2 @@ -window.Quill = module.exports = null; +window.Quill = null; +export default null; diff --git a/packages/devextreme/testing/helpers/qunitExtensions.js b/packages/devextreme/testing/helpers/qunitExtensions.js index c7b3344d568b..75507728a1bb 100644 --- a/packages/devextreme/testing/helpers/qunitExtensions.js +++ b/packages/devextreme/testing/helpers/qunitExtensions.js @@ -514,7 +514,7 @@ if(timerType === 'timeouts') { if( callback.indexOf('.Deferred.exceptionHook') > -1 || // NOTE: jQuery.Deferred are now asynchronous - callback.indexOf('e._drain()') > -1 // NOTE: SystemJS Promise polyfill + callback.indexOf('e._drain()') > -1 // NOTE: legacy Promise polyfill ) { return true; } diff --git a/packages/devextreme/testing/helpers/renovationScrollViewHelper.js b/packages/devextreme/testing/helpers/renovationScrollViewHelper.js index c64026d17a8e..64c7d0e96760 100644 --- a/packages/devextreme/testing/helpers/renovationScrollViewHelper.js +++ b/packages/devextreme/testing/helpers/renovationScrollViewHelper.js @@ -1,9 +1,9 @@ -const RenovatedScrollView = require('renovation/ui/scroll_view/scroll_view.j.js'); +import RenovatedScrollView from 'renovation/ui/scroll_view/scroll_view.j.js'; // eslint-disable-next-line spellcheck/spell-checker -const reRender = require('inferno').rerender; -const Deferred = require('core/utils/deferred').Deferred; +import { rerender as reRender } from 'inferno'; +import { Deferred } from 'core/utils/deferred'; -exports.WrappedWidget = class WrappedWidget extends RenovatedScrollView { +export class WrappedWidget extends RenovatedScrollView { _initMarkup() { super._initMarkup.apply(this, arguments); @@ -82,4 +82,4 @@ exports.WrappedWidget = class WrappedWidget extends RenovatedScrollView { return new Deferred().resolve(); } -}; +} diff --git a/packages/devextreme/testing/helpers/renovationScrollableHelper.js b/packages/devextreme/testing/helpers/renovationScrollableHelper.js index a40b168a796a..62e25aa7b735 100644 --- a/packages/devextreme/testing/helpers/renovationScrollableHelper.js +++ b/packages/devextreme/testing/helpers/renovationScrollableHelper.js @@ -1,8 +1,8 @@ -const RenovatedScrollable = require('renovation/ui/scroll_view/scrollable.j.js'); +import RenovatedScrollable from 'renovation/ui/scroll_view/scrollable.j.js'; // eslint-disable-next-line spellcheck/spell-checker -const reRender = require('inferno').rerender; +import { rerender as reRender } from 'inferno'; -exports.WrappedWidget = class WrappedWidget extends RenovatedScrollable { +export class WrappedWidget extends RenovatedScrollable { _initMarkup() { super._initMarkup.apply(this, arguments); @@ -59,4 +59,4 @@ exports.WrappedWidget = class WrappedWidget extends RenovatedScrollable { super.scrollToElement.apply(this, arguments); reRender(); } -}; +} diff --git a/packages/devextreme/testing/helpers/serverSideDOMAdapterPatch.js b/packages/devextreme/testing/helpers/serverSideDOMAdapterPatch.js index d5cdcebf932a..17fefbb7f08a 100644 --- a/packages/devextreme/testing/helpers/serverSideDOMAdapterPatch.js +++ b/packages/devextreme/testing/helpers/serverSideDOMAdapterPatch.js @@ -1,5 +1,5 @@ -const domAdapter = require('core/dom_adapter'); -const readyCallbacks = require('core/utils/ready_callbacks'); +import domAdapter from 'core/dom_adapter'; +import readyCallbacks from 'core/utils/ready_callbacks'; const documentMock = (function() { const documentMock = { @@ -20,7 +20,7 @@ const documentMock = (function() { return documentMock; })(); -exports.set = function() { +export function set() { // Emulate Angular DOM Adapter considering it's restricitons domAdapter.inject({ // `document` should be used only as is @@ -73,4 +73,4 @@ exports.set = function() { // Ready callbacks should be fired by the integration readyCallbacks.fire(); -}; +} diff --git a/packages/devextreme/testing/helpers/ssrEmulator.js b/packages/devextreme/testing/helpers/ssrEmulator.js index 80b30943691b..59c00165506b 100644 --- a/packages/devextreme/testing/helpers/ssrEmulator.js +++ b/packages/devextreme/testing/helpers/ssrEmulator.js @@ -1,6 +1,6 @@ import domAdapter from '__internal/core/m_dom_adapter'; import windowUtils from 'core/utils/window'; -import serverSideDOMAdapter from './serverSideDOMAdapterPatch.js'; +import { set as setServerSideDOMAdapter } from './serverSideDOMAdapterPatch.js'; (function emulateNoContains() { const originalContains = Element.prototype.contains; @@ -229,5 +229,5 @@ QUnit.begin(function() { // Now domAdapter is allowed to use restoreOriginalDomAdapter(); // Emulate DOMAdapter integration - serverSideDOMAdapter.set(); + setServerSideDOMAdapter(); }); diff --git a/packages/devextreme/testing/helpers/stubs/zodStub.js b/packages/devextreme/testing/helpers/stubs/zodStub.js deleted file mode 100644 index 668e5fd8ecd2..000000000000 --- a/packages/devextreme/testing/helpers/stubs/zodStub.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Minimal zod stub for QUnit / SystemJS tests. - * - * Uses AMD define() when available (CSP mode) and falls back - * to global assignment for regular SystemJS (NoCsp mode). - */ - -(function() { - const z = { - // top-level constructors - object: function() { return z; }, - string: function() { return z; }, - boolean: function() { return z; }, - number: function() { return z; }, - date: function() { return z; }, - null: function() { return z; }, - enum: function() { return z; }, - union: function() { return z; }, - array: function() { return z; }, - tuple: function() { return z; }, - literal: function() { return z; }, - record: function() { return z; }, - lazy: function() { return z; }, - // chain modifiers - optional: function() { return z; }, - nullable: function() { return z; }, - // eslint-disable-next-line spellcheck/spell-checker - nullish: function() { return z; }, - strict: function() { return z; }, - int: function() { return z; }, - // eslint-disable-next-line spellcheck/spell-checker - nonnegative: function() { return z; }, - positive: function() { return z; }, - min: function() { return z; }, - max: function() { return z; }, - transform: function() { return z; }, - describe: function() { return z; }, - // validation - safeParse: function() { return { success: true, data: {} }; }, - }; - - if(typeof define === 'function') { - define(function(require, exports) { - Object.defineProperty(exports, '__esModule', { value: true }); - exports.z = z; - exports.default = z; - }); - } else { - const root = typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : globalThis; - root.z = z; - root.zod = z; - } -})(); diff --git a/packages/devextreme/testing/helpers/stubs/zodToJsonSchemaStub.js b/packages/devextreme/testing/helpers/stubs/zodToJsonSchemaStub.js deleted file mode 100644 index a7aa7e3e9d70..000000000000 --- a/packages/devextreme/testing/helpers/stubs/zodToJsonSchemaStub.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Minimal zod-to-json-schema stub for QUnit / SystemJS tests. - * - * Uses AMD define() when available (CSP mode) and falls back - * to global assignment for regular SystemJS (NoCsp mode). - */ - -(function() { - const zodToJsonSchema = function() { return { type: 'object' }; }; - - if(typeof define === 'function') { - define(function(require, exports) { - Object.defineProperty(exports, '__esModule', { value: true }); - exports.zodToJsonSchema = zodToJsonSchema; - exports.default = zodToJsonSchema; - }); - } else { - const root = typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : globalThis; - root.zodToJsonSchema = zodToJsonSchema; - } -})(); diff --git a/packages/devextreme/testing/helpers/trackerMock.js b/packages/devextreme/testing/helpers/trackerMock.js index 745abb5717e9..9b8a6f7e513c 100644 --- a/packages/devextreme/testing/helpers/trackerMock.js +++ b/packages/devextreme/testing/helpers/trackerMock.js @@ -1,16 +1,13 @@ -const mock = require('./mockModule.js').mock; -const vizMocks = require('./vizMocks.js'); -const { ChartTracker, PieTracker } = require('viz/chart_components/tracker'); -const ChartTrackerStub = vizMocks.stubClass(ChartTracker); -const PieTrackerStub = vizMocks.stubClass(PieTracker); +import { stubClass } from './vizMocks.js'; +import trackerModule from 'viz/chart_components/tracker'; -const trackerModule = mock('viz/chart_components/tracker', { - ChartTracker: sinon.spy((parameters) => new ChartTrackerStub(parameters)), - PieTracker: sinon.spy((parameters) => new PieTrackerStub(parameters)) -}); +const ChartTrackerStub = stubClass(trackerModule.ChartTracker); +const PieTrackerStub = stubClass(trackerModule.PieTracker); -exports.default = trackerModule; -exports.__esModule = true; +trackerModule.ChartTracker = sinon.spy((parameters) => new ChartTrackerStub(parameters)); +trackerModule.PieTracker = sinon.spy((parameters) => new PieTrackerStub(parameters)); -exports.ChartTracker = trackerModule.ChartTracker; -exports.PieTracker = trackerModule.PieTracker; +export default trackerModule; + +export const ChartTracker = trackerModule.ChartTracker; +export const PieTracker = trackerModule.PieTracker; diff --git a/packages/devextreme/testing/helpers/treeListMocks.js b/packages/devextreme/testing/helpers/treeListMocks.js index 45f1d23b83e0..fcb1a8d606a1 100644 --- a/packages/devextreme/testing/helpers/treeListMocks.js +++ b/packages/devextreme/testing/helpers/treeListMocks.js @@ -1,32 +1,44 @@ -let gridBaseMock; +import $ from 'jquery'; +import treeListCoreModule from '__internal/grids/tree_list/m_core'; +import domUtilsModule from '__internal/core/utils/m_dom'; +import commonUtilsModule from '__internal/core/utils/m_common'; +import typeUtilsModule from '__internal/core/utils/m_type'; +import ArrayStoreModule from 'common/data/array_store'; +import gridBaseMockModule from './gridBaseMocks.js'; -/* global jQuery */ -if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - gridBaseMock = require('./gridBaseMocks.js'); +const gridBaseMock = gridBaseMockModule.default ?? gridBaseMockModule; +const treeListCore = treeListCoreModule.default ?? treeListCoreModule; +const domUtils = domUtilsModule.default ?? domUtilsModule; +const commonUtils = commonUtilsModule.default ?? commonUtilsModule; +const typeUtils = typeUtilsModule.default ?? typeUtilsModule; +const ArrayStore = ArrayStoreModule.default ?? ArrayStoreModule; - window.treeListMocks = module.exports = gridBaseMock( - require('jquery'), - require('__internal/grids/tree_list/m_core').default, - null, - require('__internal/core/utils/m_dom'), - require('__internal/core/utils/m_common'), - require('__internal/core/utils/m_type'), - require('common/data/array_store'), - 'TreeList' - ); - }); -} else { - gridBaseMock = require('./gridBaseMocks.js'); +const treeListMocks = gridBaseMock( + $, + treeListCore, + null, + domUtils, + commonUtils, + typeUtils, + ArrayStore, + 'TreeList' +); - jQuery.extend(window, gridBaseMock( - jQuery, - DevExpress.require('__internal/grids/tree_list/m_core'), - null, - DevExpress.require('__internal/core/utils/m_dom'), - DevExpress.require('__internal/core/utils/m_common'), - DevExpress.require('__internal/core/utils/m_type'), - DevExpress.require('common/data/array_store'), - 'TreeList' - )); -} +window.treeListMocks = treeListMocks; + +export const setupTreeListModules = treeListMocks.setupTreeListModules; +export const MockDataController = treeListMocks.MockDataController; +export const MockEditingController = treeListMocks.MockEditingController; +export const MockSelectionController = treeListMocks.MockSelectionController; +export const MockColumnsController = treeListMocks.MockColumnsController; +export const MockTablePositionViewController = treeListMocks.MockTablePositionViewController; +export const MockGridDataSource = treeListMocks.MockGridDataSource; +export const getCells = treeListMocks.getCells; +export const MockColumnsSeparatorView = treeListMocks.MockColumnsSeparatorView; +export const MockTrackerView = treeListMocks.MockTrackerView; +export const MockDraggingPanel = treeListMocks.MockDraggingPanel; +export const TestDraggingHeader = treeListMocks.TestDraggingHeader; +export const generateItems = treeListMocks.generateItems; +export const generateNestedData = treeListMocks.generateNestedData; + +export default treeListMocks; diff --git a/packages/devextreme/testing/helpers/visibilityChangeMock.js b/packages/devextreme/testing/helpers/visibilityChangeMock.js new file mode 100644 index 000000000000..01ff2d64e372 --- /dev/null +++ b/packages/devextreme/testing/helpers/visibilityChangeMock.js @@ -0,0 +1,11 @@ +import * as visibilityChange from 'common/core/events/visibility_change'; + +import { spySeam, stubSeam } from './moduleSeam.js'; + +export function spyVisibilityEvent(name) { + return spySeam(visibilityChange, name); +} + +export function stubVisibilityEvent(name) { + return stubSeam(visibilityChange, name); +} diff --git a/packages/devextreme/testing/helpers/vizMocks.js b/packages/devextreme/testing/helpers/vizMocks.js index ae1a8310d41b..19d5b610f701 100644 --- a/packages/devextreme/testing/helpers/vizMocks.js +++ b/packages/devextreme/testing/helpers/vizMocks.js @@ -1,16 +1,16 @@ /* global currentAssert, currentTest, sinon */ import $ from 'jquery'; -import * as tooltipModule from 'viz/core/tooltip'; -import * as titleModule from 'viz/core/title'; +import tooltipModule from 'viz/core/tooltip'; +import titleModule from 'viz/core/title'; import legendModule from 'viz/components/legend'; import axisModule from 'viz/axes/base_axis'; -import * as pointModule from 'viz/series/points/base_point'; +import pointModule from 'viz/series/points/base_point'; import { Series } from 'viz/series/base_series'; -import * as loadingIndicatorModule from 'viz/core/loading_indicator'; -import * as exportMenuModule from 'viz/core/export'; +import loadingIndicatorModule from 'viz/core/loading_indicator'; +import exportMenuModule from 'viz/core/export'; import rendererModule from 'viz/core/renderers/renderer_default'; -import * as errors from 'viz/core/errors_warnings'; +import errors from 'viz/core/errors_warnings'; import * as baseWidgetUtils from '__internal/viz/core/base_widget.utils'; import * as typeUtils from 'core/utils/type'; @@ -377,7 +377,17 @@ const Point = stubClass(pointModule.Point); const Legend = stubClass(legendModule.Legend); const Title = stubClass(titleModule.Title); const Tooltip = stubClass(tooltipModule.Tooltip); -const Axis = stubClass(axisModule.Axis); +// ESM npm artifacts strip /// #DEBUG methods (removeDebug: true). +// Restore the ones gauges/charts call on mocks (kept in legacy CJS transpile). +const Axis = stubClass(axisModule.Axis, null, { + $extraFunctions: [ + 'shift', + '_getTickMarkPoints', + '_validateOverlappingMode', + '_getStep', + '_validateDisplayMode', + ], +}); const SeriesStub = stubClass(Series); export { diff --git a/packages/devextreme/testing/helpers/widgetsList.js b/packages/devextreme/testing/helpers/widgetsList.js index 39a55a632040..eaf7b2dc9221 100644 --- a/packages/devextreme/testing/helpers/widgetsList.js +++ b/packages/devextreme/testing/helpers/widgetsList.js @@ -1,87 +1,159 @@ +import Accordion from 'ui/accordion'; +import ActionSheet from 'ui/action_sheet'; +import Autocomplete from 'ui/autocomplete'; +import BarGauge from 'viz/bar_gauge'; +import Box from 'ui/box'; +import Bullet from 'viz/bullet'; +import Button from 'ui/button'; +import Calendar from 'ui/calendar'; +import Chart from 'viz/chart'; +import CheckBox from 'ui/check_box'; +import CircularGauge from 'viz/circular_gauge'; +import ColorBox from 'ui/color_box'; +import ContextMenu from 'ui/context_menu'; +import DataGrid from 'ui/data_grid'; +import DateBox from 'ui/date_box'; +import DateRangeBox from 'ui/date_range_box'; +import Drawer from 'ui/drawer'; +import DropDownBox from 'ui/drop_down_box'; +import FileManager from 'ui/file_manager'; +import FileUploader from 'ui/file_uploader'; +import FilterBuilder from 'ui/filter_builder'; +import Form from 'ui/form'; +import Funnel from 'viz/funnel'; +import Gallery from 'ui/gallery'; +import Gantt from 'ui/gantt'; +import HtmlEditor from 'ui/html_editor'; +import LinearGauge from 'viz/linear_gauge'; +import List from 'ui/list'; +import LoadIndicator from 'ui/load_indicator'; +import LoadPanel from 'ui/load_panel'; +import Lookup from 'ui/lookup'; +import Map from 'ui/map'; +import Menu from 'ui/menu'; +import MultiView from 'ui/multi_view'; +import NumberBox from 'ui/number_box'; +import PieChart from 'viz/pie_chart'; +import PivotGrid from 'ui/pivot_grid'; +import PivotGridFieldChooser from 'ui/pivot_grid_field_chooser'; +import PolarChart from 'viz/polar_chart'; +import Popover from 'ui/popover'; +import Popup from 'ui/popup'; +import ProgressBar from 'ui/progress_bar'; +import RangeSelector from 'viz/range_selector'; +import RangeSlider from 'ui/range_slider'; +import RadioGroup from 'ui/radio_group'; +import Resizable from 'ui/resizable'; +import ResponsiveBox from 'ui/responsive_box'; +import Sankey from 'viz/sankey'; +import Scheduler from 'ui/scheduler'; +import ScrollView from 'ui/scroll_view'; +import SelectBox from 'ui/select_box'; +import Slider from 'ui/slider'; +import Sparkline from 'viz/sparkline'; +import Switch from 'ui/switch'; +import TabPanel from 'ui/tab_panel'; +import Tabs from 'ui/tabs'; +import TagBox from 'ui/tag_box'; +import TextArea from 'ui/text_area'; +import TextBox from 'ui/text_box'; +import TileView from 'ui/tile_view'; +import Toast from 'ui/toast'; +import Toolbar from 'ui/toolbar'; +import Tooltip from 'ui/tooltip'; +import TreeList from 'ui/tree_list'; +import TreeMap from 'viz/tree_map'; +import TreeView from 'ui/tree_view'; +import ValidationGroup from 'ui/validation_group'; +import ValidationSummary from 'ui/validation_summary'; +import VectorMap from 'viz/vector_map'; +import DropDownButton from 'ui/drop_down_button'; +import DxDropDownEditor from 'ui/drop_down_editor/ui.drop_down_editor'; +import DxDropDownList from 'ui/drop_down_editor/ui.drop_down_list'; + const widgetsList = { - Accordion: require('ui/accordion'), - ActionSheet: require('ui/action_sheet'), - Autocomplete: require('ui/autocomplete'), - BarGauge: require('viz/bar_gauge'), - Box: require('ui/box'), - Bullet: require('viz/bullet'), - Button: require('ui/button'), - Calendar: require('ui/calendar'), - Chart: require('viz/chart'), - CheckBox: require('ui/check_box'), - CircularGauge: require('viz/circular_gauge'), - ColorBox: require('ui/color_box'), - ContextMenu: require('ui/context_menu'), - DataGrid: require('ui/data_grid'), - DateBox: require('ui/date_box'), - DateRangeBox: require('ui/date_range_box'), - Drawer: require('ui/drawer'), - DropDownBox: require('ui/drop_down_box'), - FileManager: require('ui/file_manager'), - FileUploader: require('ui/file_uploader'), - FilterBuilder: require('ui/filter_builder'), - Form: require('ui/form'), - Funnel: require('viz/funnel'), - Gallery: require('ui/gallery'), - Gantt: require('ui/gantt'), - HtmlEditor: require('ui/html_editor'), - LinearGauge: require('viz/linear_gauge'), - List: require('ui/list'), - LoadIndicator: require('ui/load_indicator'), - LoadPanel: require('ui/load_panel'), - Lookup: require('ui/lookup'), - Map: require('ui/map'), - Menu: require('ui/menu'), - MultiView: require('ui/multi_view'), - NumberBox: require('ui/number_box'), - PieChart: require('viz/pie_chart'), - PivotGrid: require('ui/pivot_grid'), - PivotGridFieldChooser: require('ui/pivot_grid_field_chooser'), - PolarChart: require('viz/polar_chart'), - Popover: require('ui/popover'), - Popup: require('ui/popup'), - ProgressBar: require('ui/progress_bar'), - RangeSelector: require('viz/range_selector'), - RangeSlider: require('ui/range_slider'), - RadioGroup: require('ui/radio_group'), - Resizable: require('ui/resizable'), - ResponsiveBox: require('ui/responsive_box'), - Sankey: require('viz/sankey'), - Scheduler: require('ui/scheduler'), - ScrollView: require('ui/scroll_view'), - SelectBox: require('ui/select_box'), - Slider: require('ui/slider'), - Sparkline: require('viz/sparkline'), - Switch: require('ui/switch'), - TabPanel: require('ui/tab_panel'), - Tabs: require('ui/tabs'), - TagBox: require('ui/tag_box'), - TextArea: require('ui/text_area'), - TextBox: require('ui/text_box'), - TileView: require('ui/tile_view'), - Toast: require('ui/toast'), - Toolbar: require('ui/toolbar'), - Tooltip: require('ui/tooltip'), - TreeList: require('ui/tree_list'), - TreeMap: require('viz/tree_map'), - TreeView: require('ui/tree_view'), - ValidationGroup: require('ui/validation_group'), - ValidationSummary: require('ui/validation_summary'), - VectorMap: require('viz/vector_map') + Accordion, + ActionSheet, + Autocomplete, + BarGauge, + Box, + Bullet, + Button, + Calendar, + Chart, + CheckBox, + CircularGauge, + ColorBox, + ContextMenu, + DataGrid, + DateBox, + DateRangeBox, + Drawer, + DropDownBox, + FileManager, + FileUploader, + FilterBuilder, + Form, + Funnel, + Gallery, + Gantt, + HtmlEditor, + LinearGauge, + List, + LoadIndicator, + LoadPanel, + Lookup, + Map, + Menu, + MultiView, + NumberBox, + PieChart, + PivotGrid, + PivotGridFieldChooser, + PolarChart, + Popover, + Popup, + ProgressBar, + RangeSelector, + RangeSlider, + RadioGroup, + Resizable, + ResponsiveBox, + Sankey, + Scheduler, + ScrollView, + SelectBox, + Slider, + Sparkline, + Switch, + TabPanel, + Tabs, + TagBox, + TextArea, + TextBox, + TileView, + Toast, + Toolbar, + Tooltip, + TreeList, + TreeMap, + TreeView, + ValidationGroup, + ValidationSummary, + VectorMap }; const dropDownEditorsList = { - dxAutocomplete: require('ui/autocomplete'), - dxColorBox: require('ui/color_box'), - dxDateBox: require('ui/date_box'), - dxDateRangeBox: require('ui/date_range_box'), - dxDropDownBox: require('ui/drop_down_box'), - dxDropDownButton: require('ui/drop_down_button'), - dxSelectBox: require('ui/select_box'), - dxTagBox: require('ui/tag_box'), - dxDropDownEditor: require('ui/drop_down_editor/ui.drop_down_editor'), - dxDropDownList: require('ui/drop_down_editor/ui.drop_down_list'), + dxAutocomplete: Autocomplete, + dxColorBox: ColorBox, + dxDateBox: DateBox, + dxDateRangeBox: DateRangeBox, + dxDropDownBox: DropDownBox, + dxDropDownButton: DropDownButton, + dxSelectBox: SelectBox, + dxTagBox: TagBox, + dxDropDownEditor: DxDropDownEditor, + dxDropDownList: DxDropDownList, }; -exports.widgetsList = widgetsList; -exports.dropDownEditorsList = dropDownEditorsList; +export { widgetsList, dropDownEditorsList }; diff --git a/packages/devextreme/testing/helpers/wrapRenovatedWidget.js b/packages/devextreme/testing/helpers/wrapRenovatedWidget.js index 318697106789..6de4bb42ab62 100644 --- a/packages/devextreme/testing/helpers/wrapRenovatedWidget.js +++ b/packages/devextreme/testing/helpers/wrapRenovatedWidget.js @@ -1,7 +1,7 @@ // eslint-disable-next-line spellcheck/spell-checker -const reRender = require('inferno').rerender; +import { rerender as reRender } from 'inferno'; -exports.wrapRenovatedWidget = function wrapRenovatedWidget(renovatedWidget) { +export function wrapRenovatedWidget(renovatedWidget) { class WrappedWidget extends renovatedWidget { callMethod(name, ...args) { const result = super[name](...args); @@ -28,4 +28,4 @@ exports.wrapRenovatedWidget = function wrapRenovatedWidget(renovatedWidget) { const result = WrappedWidget; result.IS_RENOVATED_WIDGET = true; return result; -}; +} diff --git a/packages/devextreme/testing/helpers/xmlHttpRequestMock.js b/packages/devextreme/testing/helpers/xmlHttpRequestMock.js index cc55e4440a93..604163d61f3f 100644 --- a/packages/devextreme/testing/helpers/xmlHttpRequestMock.js +++ b/packages/devextreme/testing/helpers/xmlHttpRequestMock.js @@ -1,4 +1,4 @@ -/* global $ */ +import $ from 'jquery'; const RealXMLHttpRequest = window.XMLHttpRequest; diff --git a/packages/devextreme/testing/runner/README.md b/packages/devextreme/testing/runner/README.md new file mode 100644 index 000000000000..b1ea2acfb29c --- /dev/null +++ b/packages/devextreme/testing/runner/README.md @@ -0,0 +1,114 @@ +# QUnit test runner (native ESM) + +Developer notes for the Node HTTP runner that serves QUnit suites with **native ESM + import maps** (no SystemJS). + +Related layout: + +| Path | Role | +| --- | --- | +| `testing/runner/lib/` | Server-side request handling, source rewrites, import-map build | +| `testing/helpers/esm-shims/` | Browser-side shim modules wired through the import map / static redirects | + +After changing TypeScript under `testing/runner/`, recompile (`tsc -p testing/runner/tsconfig.json`) and **restart** the process on port `20060` — templates and rewrite logic are loaded at process start. + +--- + +## `lib/static.ts` + +HTTP static file server for the QUnit runner. + +**Responsibilities:** + +- Resolve and serve workspace files (tests, helpers, artifacts, vendors) with correct content types and cache headers. +- Apply **serve-time transforms** so the browser receives valid ESM: + - QUnit tests/helpers → `cjsInterop.rewriteQunitTestHelperSource` + - `aspnet.js` UMD artifact → `cjsInterop.rewriteAspnetArtifactToEsm` + - Vendor / Globalize / Intl / VectorMap bundles → wrap as ESM modules + - JSON (`?esm-export=1`) → `export default …` +- Redirect the few artifact URLs that have a hand-written shim (themes) — see `handWrittenShims.ts`. + +This module is the integration point: almost every special-case rewrite for QUnit ESM loading goes through `tryServeStatic`. + +--- + +## `lib/cjsInterop.ts` + +Serve-time **CJS → ESM** source rewrites for QUnit tests, helpers, and bundle templates. + +`testing/tests/**` and `testing/helpers/**` are now fully native ESM (no `require()`/`module.exports`/AMD left) — the CJS/AMD rewrites below only still fire for `build/bundle-templates/**` (bundler-input sources, out of scope for the QUnit migration). CJS-style `import x from 'bare-specifier'` (bare default/named imports on modules with an imperfect export shape) is still common everywhere and always rewritten, except `jquery` — its shim has a real `export default $`, so it's excluded and loads natively. + +**What it does:** + +1. **`require('…')`** (bundle templates only) → hoisted `import * as __dxReq_N` plus `('default' in ns ? ns.default : { …ns })` at the call site (keeps explicit `default: null` for noJQuery/…; mutable shallow copy only when there is no default — needed when tests assign onto the module object). +2. **`module.exports` / `exports.*`** (bundle templates only) → wrap the file with a synthetic `module`/`exports` object and emit `export default` + named exports. +3. **Bare default / named imports** → namespace import + CJS default interop (`'default' in ns ? ns.default : …`, merge default object/function into named bindings when needed); `jquery` is excluded and passes through untouched. +4. **Plugin-style JSON** (`file.json!` / `file.json!json`, bundle templates only) → absolute URLs with `?esm-export=1`. +5. **`aspnet.js`** → dedicated UMD → ESM conversion (`rewriteAspnetArtifactToEsm`). + +`esm-shims/` files are **excluded** from this pipeline (`isQunitTestOrHelperPath`) — they are already real ESM. + +--- + +## Stubbing a module from a test + +Modules are served as plain ESM artifacts. A module namespace object is frozen, so +`sinon.stub(module, 'name')` and `module.name = fn` both throw — the exporting module +is the only thing that can reassign its own binding. + +The house pattern is a **`DEBUG_set_*` seam**: turn the export into a `let` and add a +setter inside a `/// #DEBUG` block, which `-c qunit` builds keep and production builds +strip. + +```ts +export let Renderer = function (options) { /* … */ }; + +/// #DEBUG +export function DEBUG_set_Renderer(value: typeof Renderer): void { + Renderer = value; +} +/// #ENDDEBUG +``` + +Product code that does `import { Renderer } from '…'` sees the new value, because ESM +named exports are live bindings. + +In tests, drive the seam through [`testing/helpers/moduleSeam.js`](../helpers/moduleSeam.js), +which re-attaches the `.restore()` that an anonymous `sinon.stub()` lacks: + +```js +import { stubSeam, spySeam } from '../../helpers/moduleSeam.js'; + +// was: sinon.stub(rendererModule, 'Renderer') +const stub = stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer'); +// … +stub.restore(); + +// was: rendererModule.Renderer = fn; +rendererModule.DEBUG_set_Renderer(fn); +``` + +**Import the barrel that has a default export.** `cjsInterop` rewrites a default import +from a bare specifier to `('default' in ns ? ns.default : { ...ns })`. A barrel with no +default (`viz/core/utils.js`, `viz/core/renderers/renderer.js`) therefore hands the test +a dead **copy** — seams installed elsewhere stay invisible. Use the `_default` barrel +(`viz/core/utils_default`, `viz/core/renderers/renderer_default`), which is +`import * as X; export default X`. + +Older sources use `exports.DEBUG_set_X = DEBUG_set_X` instead of `export function`; +`static.ts` rewrites that to a real ESM export at serve time, so both spellings work. + +--- + +## `testing/helpers/esm-shims/` + +Browser modules that the import map (and/or `static.ts` artifact redirects) substitute for real package / artifact specifiers during QUnit runs. + +**Why they exist:** + +- **Custom composition** — e.g. `themes.js`, which the import map and `static.ts` both point at. Stubbing is *not* a reason to add a shim; use a `DEBUG_set_*` seam instead. +- **Globals bridge** — e.g. `jquery.js` / `knockout.js` re-export the classic `<script>` globals after `noConflict()`. +- **CSS plugin imports** — suites still write `import 'fluent_blue_light.css!'`; `*.css.js` shims call `injectStylesheet.js` to append `<link>` tags. +- **Vendor / interop quirks** — thin adapters (`jspdf_autotable.js`, `tslib.js`, `zod.js`, …) when the stock ESM build is awkward for the runner. +- **Shared helpers** — `injectStylesheet.js` for theme CSS. + +Do **not** put product fixes in these shims — they are test-runner adapters only. diff --git a/packages/devextreme/testing/runner/index.ts b/packages/devextreme/testing/runner/index.ts index 81dd39cde514..6eb89ed08a00 100644 --- a/packages/devextreme/testing/runner/index.ts +++ b/packages/devextreme/testing/runner/index.ts @@ -82,6 +82,7 @@ const pages = createPagesRenderer({ contentWithCacheBuster, getCacheBuster, jsonString, + packageRoot: PACKAGE_ROOT, renderTemplate: templates.renderTemplate, }); const suitesService = createSuitesService({ @@ -100,7 +101,6 @@ const vectorMapService = createVectorMapService({ const staticFiles = createStaticFileService({ escapeHtml, rootDirectory: WORKSPACE_ROOT, - setNoCacheHeaders, setStaticCacheHeaders, }); @@ -121,8 +121,10 @@ function start(): void { }); }); + // Bind all interfaces for Docker/CI, but advertise localhost — browsers + // reject fetches to http://0.0.0.0/ (Chrome: "Failed to fetch … 0.0.0.0"). server.listen(QUNIT_PORT, '0.0.0.0', () => { - logger.writeLine(`QUnit runner server listens on http://0.0.0.0:${QUNIT_PORT}...`); + logger.writeLine(`QUnit runner server listens on http://localhost:${QUNIT_PORT}...`); }); } diff --git a/packages/devextreme/testing/runner/lib/cjsInterop.ts b/packages/devextreme/testing/runner/lib/cjsInterop.ts new file mode 100644 index 000000000000..caaed4d983da --- /dev/null +++ b/packages/devextreme/testing/runner/lib/cjsInterop.ts @@ -0,0 +1,486 @@ +/** + * Serve-time CJS → ESM helpers for QUnit tests/helpers under native ESM. + * + * 1) `import x from 'mod'` → namespace + `'default' in ns ? ns.default : {…ns}` + * (skipped for `jquery` — its shim has a real `export default $`) + * 2) `require(...)` → equivalent ESM imports (only fires for `build/bundle-templates/` + * sources now — `testing/tests/**`/`testing/helpers/**` are require()-free) + * 3) CJS `module.exports` / `exports.*` → `export default` + named exports + * (same: bundle-templates only) + */ + +const MIXED_DEFAULT_NAMED_RE = /import\s+([A-Za-z_$][\w$]*)\s*,\s*(\{[^}]*\})\s*from\s*('[^']+'|"[^"]+")/g; +const DEFAULT_ONLY_RE = /import\s+([A-Za-z_$][\w$]*)\s+from\s*('[^']+'|"[^"]+")/g; +const NAMED_ONLY_RE = /import\s*(\{[^}]*\})\s*from\s*('[^']+'|"[^"]+")/g; + +const SPEC = "('[^']+'|\"[^\"]+\")"; + +const ESM_ARTIFACT_ROOT = '/packages/devextreme/artifacts/transpiled-esm-npm/esm'; +const DX_NODE_MODULES = '/packages/devextreme/node_modules'; + +const BUNDLE_TEMPLATE_ROOTS = [ + 'packages/devextreme/build/bundle-templates/', + 'packages/devextreme/artifacts/transpiled/bundles/', + 'packages/devextreme/artifacts/transpiled-esm-npm/bundles/', +]; + +const QUNIT_REWRITE_ROOTS = [ + 'packages/devextreme/testing/tests/', + 'packages/devextreme/testing/helpers/', + ...BUNDLE_TEMPLATE_ROOTS, +]; + +let requireCounter = 0; +let importCounter = 0; + +function normalizePath(filePath: string): string { + return filePath.replace(/\\/g, '/'); +} + +function pathMatchesRoots(filePath: string | undefined, roots: string[]): boolean { + if (!filePath) { + return false; + } + const normalized = normalizePath(filePath); + return roots.some((root) => normalized.includes(`/${root}`) || normalized.startsWith(root)); +} + +function isBundleTemplatePath(sourcePath?: string): boolean { + return pathMatchesRoots(sourcePath, BUNDLE_TEMPLATE_ROOTS); +} + +function cjsDefaultExpr(ns: string): string { + return `('default' in ${ns} ? ${ns}.default : { ...${ns} })`; +} + +/** + * Merge namespace named exports with a default object/function export + * (gauge/widget ctors hang `_TESTS_*` statics on the default). + */ +function cjsMergedNamespaceExpr(ns: string): string { + return `({ ...${ns}, ...((typeof ${ns}.default === 'object' || typeof ${ns}.default === 'function') && ${ns}.default ? ${ns}.default : {}) })`; +} + +function nextRequireId(): string { + requireCounter += 1; + return `__dxReq_${requireCounter}`; +} + +function nextImportId(): string { + importCounter += 1; + return `__dxImp_${importCounter}`; +} + +function resetRewriteCounters(): void { + requireCounter = 0; + importCounter = 0; +} + +function skipQuotedString(source: string, start: number): number { + const quote = source[start]; + let i = start + 1; + while (i < source.length) { + const ch = source[i]; + if (ch === '\\') { + i += 2; + } else if (ch === quote) { + return i + 1; + } else { + i += 1; + } + } + return source.length; +} + +/** True when `offset` lies inside a block or line comment (strings are skipped). */ +function isOffsetInsideComment(source: string, offset: number): boolean { + let i = 0; + while (i < offset) { + if (source.startsWith('/*', i)) { + const end = source.indexOf('*/', i + 2); + if (end < 0 || offset < end + 2) { + return true; + } + i = end + 2; + } else if (source.startsWith('//', i)) { + const end = source.indexOf('\n', i); + const lineEnd = end < 0 ? source.length : end; + if (offset < lineEnd) { + return true; + } + i = lineEnd; + } else { + const ch = source[i]; + if (ch === '"' || ch === '\'' || ch === '`') { + i = skipQuotedString(source, i); + } else { + i += 1; + } + } + } + return false; +} + +/** + * Plugin-style json specs (`file.json!` / `file.json!json`) must not stay as + * bare specifiers under a package prefix map — browsers resolve + * `devextreme-cldr-data/fr.json!json` to a literal path with `!json` → 404. + */ +function normalizeJsonPluginSpecifier(spec: string): string | null { + const jsonBang = /^(.*\.json)!(?:json)?$/.exec(spec); + if (!jsonBang) { + return null; + } + + const jsonPath = jsonBang[1]; + if (jsonPath.startsWith('devextreme-cldr-data/') || jsonPath.startsWith('cldr-core/')) { + return `${DX_NODE_MODULES}/${jsonPath}?esm-export=1`; + } + if (jsonPath.startsWith('.') || jsonPath.startsWith('/')) { + return `${jsonPath}?esm-export=1`; + } + return `${ESM_ARTIFACT_ROOT}/${jsonPath}?esm-export=1`; +} + +function normalizeRequireSpecifierForEsm(specWithQuotes: string, sourcePath?: string): string { + const quote = specWithQuotes[0]; + const spec = specWithQuotes.slice(1, -1); + + const jsonUrl = normalizeJsonPluginSpecifier(spec); + if (jsonUrl) { + return `${quote}${jsonUrl}${quote}`; + } + + if (!isBundleTemplatePath(sourcePath)) { + return specWithQuotes; + } + + // Bundle template CJS requires are relative to bundler sources; browser ESM + // needs bare package-style aliases. + if (spec.startsWith('../')) { + return `${quote}${spec.replace(/^(\.\.\/)+/, '')}${quote}`; + } + + return specWithQuotes; +} + +function normalizeBundleTemplateRelativeEsmSpecifiers(source: string, sourcePath?: string): string { + if (!isBundleTemplatePath(sourcePath)) { + return source; + } + + return source.replace( + /((?:import|export)\s[^'"]*?\sfrom\s*|import\s*)(['"])([^'"]+)\2/g, + (_match, prefix: string, quote: string, spec: string) => { + if (!spec.startsWith('../')) { + return `${prefix}${quote}${spec}${quote}`; + } + return `${prefix}${quote}${spec.replace(/^(\.\.\/)+/, '')}${quote}`; + }, + ); +} + +/** + * Replace every static `require('…')` with a shared namespace alias and hoist + * `import * as` declarations. Skips requires inside comments (bundle templates + * keep optional widgets commented out). + */ +function rewriteRequiresToEsm(source: string, sourcePath?: string): string { + if (!/\brequire\s*\(/.test(source)) { + return source; + } + + const specToAlias = new Map<string, string>(); + const next = source.replace( + // SPEC already includes a capturing group — do not wrap it again or + // the replace callback's `offset` argument shifts. + new RegExp(`require\\s*\\(\\s*${SPEC}\\s*\\)(?:\\s*\\.\\s*default\\b)?`, 'g'), + (match, spec: string, offset: number) => { + if (isOffsetInsideComment(source, offset)) { + return match; + } + const normalizedSpec = normalizeRequireSpecifierForEsm(spec, sourcePath); + let alias = specToAlias.get(normalizedSpec); + if (!alias) { + alias = nextRequireId(); + specToAlias.set(normalizedSpec, alias); + } + // Prefer an explicit default (including null); otherwise a mutable + // shallow copy of the namespace. Bare `ns` is an immutable Module + // Namespace and breaks `DevExpress.events = require(…); events.click = …`. + return `(${cjsDefaultExpr(alias)})`; + }, + ); + + if (!specToAlias.size) { + return next; + } + + const importBlock = [...specToAlias.entries()] + .map(([spec, alias]) => `import * as ${alias} from ${spec};`) + .join('\n'); + + return `${importBlock}\n${next}`; +} + +function isBareSpecifier(specWithQuotes: string): boolean { + const spec = specWithQuotes.slice(1, -1); + return !spec.startsWith('.') && !spec.startsWith('/'); +} + +/** + * Convert an ESM named-import clause `{ a as b, c }` into an object + * destructuring pattern `{ a: b, c }` (import `as` is not valid there). + */ +function importClauseToDestructuring(namedClause: string): string { + const trimmedClause = namedClause.trim(); + if (!trimmedClause.startsWith('{') || !trimmedClause.endsWith('}')) { + return namedClause; + } + + const inner = trimmedClause.slice(1, -1); + const parts = inner.split(',').map((part) => part.trim()).filter(Boolean); + const converted = parts.map((part) => { + const asMatch = /^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(part); + if (asMatch) { + return `${asMatch[1]}: ${asMatch[2]}`; + } + return part; + }); + + return `{ ${converted.join(', ')} }`; +} + +function rewriteCjsStyleDefaultImports(source: string): string { + let next = source.replace( + MIXED_DEFAULT_NAMED_RE, + (match, name: string, named: string, spec: string) => { + if (!isBareSpecifier(spec)) { + return match; + } + const ns = `__dxCjs_${name}`; + const bindingPattern = importClauseToDestructuring(named); + // `let` — some QUnit suites reassign default imports (e.g. `$ = coreRenderer`). + return `import * as ${ns} from ${spec};` + + ` let ${name} = ${cjsDefaultExpr(ns)};` + + ` const ${bindingPattern} = ${cjsMergedNamespaceExpr(ns)}`; + }, + ); + + next = next.replace(DEFAULT_ONLY_RE, (match, name: string, spec: string) => { + if (!isBareSpecifier(spec)) { + return match; + } + const ns = `__dxCjs_${name}`; + return `import * as ${ns} from ${spec}; let ${name} = ${cjsDefaultExpr(ns)}`; + }); + + next = next.replace(NAMED_ONLY_RE, (match, named: string, spec: string) => { + if (!isBareSpecifier(spec)) { + return match; + } + const ns = nextImportId(); + const bindingPattern = importClauseToDestructuring(named); + return `import * as ${ns} from ${spec};` + + ` const ${bindingPattern} = ${cjsMergedNamespaceExpr(ns)}`; + }); + + return next; +} + +/** + * True for a single-line static import we can safely hoist. + * Incomplete multi-line openers (`import {`) must stay with their body lines. + */ +function isCompleteImportLine(line: string): boolean { + const trimmed = line.trim(); + if (!/^import\b/.test(trimmed) || /^import\s*\(/.test(trimmed)) { + return false; + } + if (/^import\s*['"][^'"]+['"]/.test(trimmed)) { + return true; + } + return /\bfrom\s*['"][^'"]+['"]/.test(trimmed); +} + +function hoistImportsFromSource(source: string): { imports: string[]; body: string } { + const normalized = source.replace(/;(?=\s*import\s)/g, ';\n'); + const imports: string[] = []; + const bodyLines: string[] = []; + + normalized.split('\n').forEach((line) => { + if (isCompleteImportLine(line)) { + imports.push(line.trim()); + } else { + bodyLines.push(line); + } + }); + + return { + imports, + body: bodyLines.join('\n').trim(), + }; +} + +/** Collect `exports.foo` / `module.exports.foo` assignment names for ESM named re-exports. */ +function collectCjsExportNames(source: string): string[] { + const names = new Set<string>(); + const patterns = [ + /(?:^|[^\w$.])exports\.([A-Za-z_$][\w$]*)\s*=/g, + /(?:^|[^\w$.])module\.exports\.([A-Za-z_$][\w$]*)\s*=/g, + /(?:^|[^\w$.])exports\[['"]([A-Za-z_$][\w$]*)['"]\]\s*=/g, + /(?:^|[^\w$.])module\.exports\[['"]([A-Za-z_$][\w$]*)['"]\]\s*=/g, + ]; + + patterns.forEach((pattern) => { + let match = pattern.exec(source); + while (match) { + const name = match[1]; + if (name !== 'default' && name !== '__esModule') { + names.add(name); + } + match = pattern.exec(source); + } + }); + + return [...names].sort(); +} + +function rewriteBundleTemplateModuleExports(source: string): string { + return source.replace( + /^\s*module\.exports\s*=\s*([\s\S]*?);\s*$/gm, + 'export default $1;', + ); +} + +function isLocallyDeclaredBinding(source: string, name: string): boolean { + const escaped = name.replace(/\$/g, '\\$'); + const directRe = new RegExp( + `(?:^|[\\s;{}])(?:(?:const|let|var)\\s+${escaped}\\b|function\\s+${escaped}\\b|class\\s+${escaped}\\b)`, + ); + if (directRe.test(source)) { + return true; + } + const destructuringRe = new RegExp( + `(?:const|let|var)\\s*\\{[^}]*\\b${escaped}\\b(?!\\s*:)[^}]*\\}`, + ); + return destructuringRe.test(source); +} + +function wrapCjsModuleExports(source: string, sourcePath?: string): string { + if (/^\s*export\s/m.test(source)) { + return source; + } + + const usesExports = /\bmodule\.exports\b/.test(source) + || /\bexports\./.test(source) + || /\bexports\[/.test(source); + + if (!usesExports) { + return source; + } + + if (isBundleTemplatePath(sourcePath)) { + return rewriteBundleTemplateModuleExports(source); + } + + // AMD / DevExpress.require helpers — leave untouched + if (/\bdefine\.amd\b/.test(source) || /\bDevExpress\.require\b/.test(source)) { + return source; + } + + // Named ESM exports must reflect `module.exports.name`. When a local binding + // already uses that name, alias to avoid collisions / wrong re-exports. + const namedExports = collectCjsExportNames(source) + .map((name) => { + if (isLocallyDeclaredBinding(source, name)) { + const alias = `__dxExp_${name}`; + return `const ${alias} = module.exports.${name};\nexport { ${alias} as ${name} };`; + } + return `export const ${name} = module.exports.${name};`; + }) + .join('\n'); + + return `const module = { exports: {} }; +let exports = module.exports; +${source} +export default module.exports; +${namedExports ? `${namedExports}\n` : ''}`; +} + +export function rewriteQunitTestHelperSource(source: string, sourcePath?: string): string { + resetRewriteCounters(); + let next = normalizeBundleTemplateRelativeEsmSpecifiers(source, sourcePath); + next = rewriteRequiresToEsm(next, sourcePath); + next = wrapCjsModuleExports(next, sourcePath); + next = rewriteCjsStyleDefaultImports(next); + const { imports, body } = hoistImportsFromSource(next); + if (!imports.length) { + return next; + } + return `${[...new Set(imports)].join('\n')}\n${body}\n`; +} + +/** + * Convert the UMD `aspnet.js` artifact into a native ESM module. + * Non-AMD branch expects a global `DevExpress`; under import maps that throws. + */ +export function rewriteAspnetArtifactToEsm(source: string, sourcePath?: string): string { + const normalized = normalizePath(sourcePath || ''); + if (normalized && !normalized.endsWith('/aspnet.js')) { + return source; + } + if (!/\bDevExpress\.aspnet\s*=/.test(source) || !/\bdefine\s*\(/.test(source)) { + return source; + } + if (/^\s*import\s/m.test(source)) { + return source; + } + + const factoryCall = /\)\s*\(\s*function\s*\(([^)]*)\)\s*\{/; + const match = factoryCall.exec(source); + if (!match) { + return source; + } + + const params = match[1]; + let body = source.slice(match.index + match[0].length); + body = body.replace(/\}\)\s*;?\s*$/, ''); + + return `import __dxAspnetJquery from 'jquery'; +import * as __dxAspnetTemplateEngineRegistry from './core/templates/template_engine_registry'; +import * as __dxAspnetTemplateBase from './core/templates/template_base'; +import * as __dxAspnetGuid from './core/guid'; +import * as __dxAspnetValidationEngine from './ui/validation_engine'; +import * as __dxAspnetIterator from './core/utils/iterator'; +import * as __dxAspnetDom from './core/utils/dom'; +import * as __dxAspnetString from './core/utils/string'; +import * as __dxAspnetAjax from './core/utils/ajax'; + +const __dxAspnetFactory = function(${params}) { +${body} +}; + +const __dxAspnet = __dxAspnetFactory( + __dxAspnetJquery, + __dxAspnetTemplateEngineRegistry.setTemplateEngine, + __dxAspnetTemplateBase.renderedCallbacks, + ${cjsDefaultExpr('__dxAspnetGuid')}, + ${cjsDefaultExpr('__dxAspnetValidationEngine')}, + __dxAspnetIterator, + __dxAspnetDom.extractTemplateMarkup, + __dxAspnetString.encodeHtml, + ${cjsDefaultExpr('__dxAspnetAjax')}, +); + +export default __dxAspnet; +`; +} + +export function isQunitTestOrHelperPath(relativePath: string): boolean { + const normalized = normalizePath(relativePath); + if (normalized.includes('/testing/helpers/esm-shims/')) { + return false; + } + return pathMatchesRoots(normalized, QUNIT_REWRITE_ROOTS); +} diff --git a/packages/devextreme/testing/runner/lib/handWrittenShims.ts b/packages/devextreme/testing/runner/lib/handWrittenShims.ts new file mode 100644 index 000000000000..89ed7f00811c --- /dev/null +++ b/packages/devextreme/testing/runner/lib/handWrittenShims.ts @@ -0,0 +1,23 @@ +const HAND_WRITTEN_ESM_SHIMS: readonly { esmPath: string; shimUrl: string }[] = [ + { + esmPath: '__internal/ui/themes.js', + shimUrl: '/packages/devextreme/testing/helpers/esm-shims/themes.js', + }, + { + esmPath: 'ui/themes.js', + shimUrl: '/packages/devextreme/testing/helpers/esm-shims/themes.js', + }, +]; + +const ESM_ARTIFACT_PREFIX = 'packages/devextreme/artifacts/transpiled-esm-npm/esm/'; + +export function findHandWrittenShim(relativeUrlPath: string): string | null { + const normalized = relativeUrlPath.replace(/\\/g, '/').replace(/^\/+/, ''); + const idx = normalized.indexOf(ESM_ARTIFACT_PREFIX); + const esmPath = idx >= 0 + ? normalized.slice(idx + ESM_ARTIFACT_PREFIX.length) + : normalized; + + const match = HAND_WRITTEN_ESM_SHIMS.find((entry) => entry.esmPath === esmPath); + return match?.shimUrl ?? null; +} diff --git a/packages/devextreme/testing/runner/lib/importMap.ts b/packages/devextreme/testing/runner/lib/importMap.ts new file mode 100644 index 000000000000..89c7716b8913 --- /dev/null +++ b/packages/devextreme/testing/runner/lib/importMap.ts @@ -0,0 +1,386 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +const ESM_ROOT = '/packages/devextreme/artifacts/transpiled-esm-npm/esm'; +const SHIMS = '/packages/devextreme/testing/helpers/esm-shims'; +const NODE_MODULES = '/packages/devextreme/node_modules'; + +function resolveEsmRootPath(): string { + const cwd = process.cwd(); + const candidates = [ + path.join(cwd, 'artifacts/transpiled-esm-npm/esm'), + path.join(cwd, 'packages/devextreme/artifacts/transpiled-esm-npm/esm'), + ]; + + const resolved = candidates.find((candidate) => fs.existsSync(candidate)); + return resolved ?? candidates[0]; +} + +const ESM_FS_ROOT = resolveEsmRootPath(); + +function resolveWorkspaceRoot(): string { + const cwd = process.cwd(); + if (fs.existsSync(path.join(cwd, 'packages', 'devextreme'))) { + return cwd; + } + return path.resolve(cwd, '../..'); +} + +/** + * Resolve a file from the workspace virtual store to a browser URL + * (packages that are not always hoisted into package node_modules). + */ +function resolveVirtualStorePackageUrl(packageName: string, fileName: string): string | null { + const workspaceRoot = resolveWorkspaceRoot(); + const storeDirs = [ + path.join(workspaceRoot, 'node_modules', '.pnpm'), + path.join(workspaceRoot, 'packages', 'devextreme', 'node_modules', '.pnpm'), + ]; + + for (const storeDir of storeDirs) { + if (!fs.existsSync(storeDir)) { + // skip missing store roots + } else { + const matches = fs.readdirSync(storeDir) + .filter((name) => name.startsWith(`${packageName}@`)) + .sort() + .reverse(); + + for (const match of matches) { + const filePath = path.join(storeDir, match, 'node_modules', packageName, fileName); + if (fs.existsSync(filePath)) { + const relative = path.relative(workspaceRoot, filePath).split(path.sep).join('/'); + return `/${relative}`; + } + } + } + } + + return null; +} + +export interface ImportMapOptions { + jqueryUrl: string; + cacheBuster: string; + /** Absolute FS path to the suite script (for scanning json! / css! imports). */ + suiteFilePath?: string; +} + +export interface BrowserImportMap { + imports: Record<string, string>; +} + +function withCacheBuster(url: string, cacheBuster: string): string { + if (!cacheBuster) { + return url; + } + + if (url.startsWith(ESM_ROOT)) { + return url; + } + + return `${url}${url.includes('?') ? '&' : '?'}${cacheBuster}`; +} + +/** + * Collect plugin-style bare imports (`*.json!`, `*.json!json`, `*.css!`, …) from suite source. + */ +export function collectPluginSpecifiers(suiteSource: string): string[] { + const found = new Set<string>(); + // `file.json!` / `file.json!json` (and css equivalents) + const re = /['"]([^'"]+\.(?:json|css)!(?:json|css)?)['"]/g; + let match = re.exec(suiteSource); + while (match) { + found.add(match[1]); + match = re.exec(suiteSource); + } + return [...found]; +} + +function collectRelativeImportPaths(source: string): string[] { + const found: string[] = []; + const re = /(?:import|export)\s+(?:[^'"\n;]+?\s+from\s+)?['"](\.[^'"]+)['"]|require\s*\(\s*['"](\.[^'"]+)['"]\s*\)/g; + let match = re.exec(source); + while (match) { + found.push(match[1] || match[2]); + match = re.exec(source); + } + return found; +} + +function resolveLocalSuiteModulePath(fromFile: string, relativeSpec: string): string | null { + const withoutQuery = relativeSpec.split('?')[0]; + const resolved = path.resolve(path.dirname(fromFile), withoutQuery); + const candidates = [ + resolved, + `${resolved}.js`, + path.join(resolved, 'index.js'), + ]; + const isExistingFile = (candidate: string): boolean => ( + fs.existsSync(candidate) && fs.statSync(candidate).isFile() + ); + return candidates.find(isExistingFile) ?? null; +} + +/** + * Suites often pull `*.json!` only through nested helpers (e.g. loadPanel tests). + * Walk relative local imports so those plugin specs land in the import map. + */ +export function collectPluginSpecifiersFromSuiteTree(suiteFilePath: string): string[] { + const found = new Set<string>(); + const queue = [suiteFilePath]; + const seen = new Set<string>(); + + while (queue.length) { + const filePath = queue.shift(); + if (filePath === undefined) { + break; + } + + const normalized = path.resolve(filePath); + if (!seen.has(normalized) && fs.existsSync(normalized)) { + seen.add(normalized); + + const source = fs.readFileSync(normalized, 'utf8'); + collectPluginSpecifiers(source).forEach((specifier) => found.add(specifier)); + + collectRelativeImportPaths(source).forEach((relativeSpec) => { + const nextPath = resolveLocalSuiteModulePath(normalized, relativeSpec); + if (nextPath) { + queue.push(nextPath); + } + }); + } + } + + return [...found]; +} + +function resolveJsonBangToUrl(specifier: string): string | null { + // e.g. localization/messages/ja.json! or cldr fr.json!json → JSON as ESM module + const jsonBang = /^(.*\.json)!(?:json)?$/.exec(specifier); + if (!jsonBang) { + return null; + } + + const withoutBang = jsonBang[1]; // keep .json + + // Vendor CLDR JSON (localization.globalize suites) + if ( + withoutBang.startsWith('devextreme-cldr-data/') + || withoutBang.startsWith('cldr-core/') + ) { + return `${NODE_MODULES}/${withoutBang}?esm-export=1`; + } + + return `${ESM_ROOT}/${withoutBang}?esm-export=1`; +} + +/** + * Suites that remap optional vendor packages to null stubs + * (missing-dependency error coverage). + * + * The stub suites are often wrappers (`*.missingModules.tests.js`) that + * `require()` / `import` `importQuill.tests.js` etc. — match against the + * whole local import tree, not only the entry suite path. + */ +function collectSuiteImportOverrides(suiteFilePath?: string): Record<string, string> { + if (!suiteFilePath) { + return {}; + } + + const helpers = '/packages/devextreme/testing/helpers'; + const overrides: Record<string, string> = {}; + + const queue = [suiteFilePath]; + const seen = new Set<string>(); + + while (queue.length) { + const filePath = queue.shift(); + if (filePath === undefined) { + break; + } + + const normalizedPath = path.resolve(filePath); + if (!seen.has(normalizedPath) && fs.existsSync(normalizedPath)) { + seen.add(normalizedPath); + + const normalized = normalizedPath.replace(/\\/g, '/'); + if (normalized.includes('/importGantt.tests.js')) { + overrides['devexpress-gantt'] = `${helpers}/noGantt.js`; + } + if (normalized.includes('/importQuill.tests.js')) { + overrides['devextreme-quill'] = `${helpers}/quillDependencies/noQuill.js`; + } + if (normalized.includes('/importDiagram.tests.js')) { + overrides['devexpress-diagram'] = `${helpers}/noDiagram.js`; + } + + const source = fs.readFileSync(normalizedPath, 'utf8'); + collectRelativeImportPaths(source).forEach((relativeSpec) => { + const nextPath = resolveLocalSuiteModulePath(normalizedPath, relativeSpec); + if (nextPath) { + queue.push(nextPath); + } + }); + } + } + + return overrides; +} + +/** + * Exact bare entries for package-root files (exporter, color, …). + */ +function collectPackageRootEntries(): Record<string, string> { + const entries: Record<string, string> = {}; + + if (!fs.existsSync(ESM_FS_ROOT)) { + return entries; + } + + fs.readdirSync(ESM_FS_ROOT, { withFileTypes: true }).forEach((entry) => { + if (entry.isFile() && entry.name.endsWith('.js')) { + const name = entry.name.slice(0, -3); + // Extensionless, like the `viz/` `core/` `common/` prefix maps: artifacts import + // root modules as `../../exporter`, and a second URL for the same file would give + // the browser a second module instance with its own copy of every binding. + const url = `${ESM_ROOT}/${name}`; + entries[name] = url; + // Suites often `require('aspnet.js')` / `require('color.js')` with the extension. + entries[entry.name] = url; + return; + } + + // Package folders with index.js (e.g. events → events/index.js), mapped + // extensionless for the same reason as the file branch above: artifacts + // import them as `../events`, and the server resolves the index itself. + if (entry.isDirectory()) { + const indexPath = path.join(ESM_FS_ROOT, entry.name, 'index.js'); + if (fs.existsSync(indexPath)) { + entries[entry.name] = `${ESM_ROOT}/${entry.name}`; + } + } + }); + + return entries; +} + +const BUNDLE_TEMPLATES_ROOT = '/packages/devextreme/build/bundle-templates/'; + +/** + * Builds a browser import map for QUnit native ESM. + */ +export function buildQunitImportMap({ + cacheBuster, + jqueryUrl, + suiteFilePath, +}: ImportMapOptions): BrowserImportMap { + const rawImports: Record<string, string> = { + // Package roots (trailing slash = prefix remap) + 'ui/': `${ESM_ROOT}/ui/`, + 'core/': `${ESM_ROOT}/core/`, + 'common/': `${ESM_ROOT}/common/`, + 'data/': `${ESM_ROOT}/data/`, + 'events/': `${ESM_ROOT}/events/`, + 'animation/': `${ESM_ROOT}/animation/`, + 'localization/': `${ESM_ROOT}/localization/`, + 'file_management/': `${ESM_ROOT}/file_management/`, + 'integration/': `${ESM_ROOT}/integration/`, + 'mobile/': `${ESM_ROOT}/mobile/`, + 'viz/': `${ESM_ROOT}/viz/`, + '__internal/': `${ESM_ROOT}/__internal/`, + 'renovation/': `${ESM_ROOT}/renovation/`, + 'bundles/': BUNDLE_TEMPLATES_ROOT, + 'bundles/dx.custom.js': '/packages/devextreme/artifacts/transpiled-esm-npm/bundles/dx.custom.js', + + // Exact package-root entries (exporter, color, localization, events, …) + ...collectPackageRootEntries(), + + jquery: jqueryUrl.includes('noJQuery') ? jqueryUrl : `${SHIMS}/jquery.js`, + + // Injected by babel transform-runtime (esm transpile). + // jspdf imports `@babel/runtime/helpers/<name>` (CJS); our artifacts use + // `helpers/esm/<name>`. Remap non-esm helper paths to the ESM builds, but + // keep an exact `helpers/esm/` prefix so existing imports stay correct. + '@babel/runtime/helpers/esm/': `${NODE_MODULES}/@babel/runtime/helpers/esm/`, + '@babel/runtime/helpers/': `${NODE_MODULES}/@babel/runtime/helpers/esm/`, + '@babel/runtime/': `${NODE_MODULES}/@babel/runtime/`, + + // Vendors (prefer ESM builds where available) + inferno: `${NODE_MODULES}/inferno/dist/index.dev.esm.js`, + 'inferno-hydrate': `${NODE_MODULES}/inferno-hydrate/dist/index.dev.esm.js`, + 'inferno-create-element': `${NODE_MODULES}/inferno-create-element/dist/index.dev.esm.js`, + '@preact/signals-core': `${NODE_MODULES}/@preact/signals-core/dist/signals-core.module.js`, + + // eslint-disable-next-line spellcheck/spell-checker + fflate: resolveVirtualStorePackageUrl('fflate', 'esm/browser.js') + ?? `${NODE_MODULES}/fflate/esm/browser.js`, + knockout: `${SHIMS}/knockout.js`, + rrule: `${NODE_MODULES}/rrule/dist/esm/index.js`, + // eslint-disable-next-line spellcheck/spell-checker + tslib: resolveVirtualStorePackageUrl('tslib', 'tslib.es6.mjs') ?? `${SHIMS}/tslib.js`, + jspdf: `${NODE_MODULES}/jspdf/dist/jspdf.es.min.js`, + 'jspdf-autotable': `${SHIMS}/jspdf_autotable.js`, + 'fast-png': resolveVirtualStorePackageUrl('fast-png', 'lib-esm/index.js') ?? `${NODE_MODULES}/fast-png/lib-esm/index.js`, + // eslint-disable-next-line spellcheck/spell-checker + iobuffer: resolveVirtualStorePackageUrl('iobuffer', 'lib-esm/IOBuffer.js') ?? `${NODE_MODULES}/iobuffer/lib-esm/IOBuffer.js`, + // eslint-disable-next-line spellcheck/spell-checker + pako: resolveVirtualStorePackageUrl('pako', 'dist/pako.esm.mjs') ?? `${NODE_MODULES}/pako/dist/pako.esm.mjs`, + // Plugins live under dist/globalize/*.js (not dist/*.js) + globalize: `${NODE_MODULES}/globalize/dist/globalize.js`, + 'globalize/': `${NODE_MODULES}/globalize/dist/globalize/`, + cldr: `${NODE_MODULES}/cldrjs/dist/cldr.js`, + 'cldr/': `${NODE_MODULES}/cldrjs/dist/cldr/`, + 'devextreme-cldr-data/': `${NODE_MODULES}/devextreme-cldr-data/`, + 'cldr-core/': `${NODE_MODULES}/cldr-core/`, + // Browser UMD build (Node `intl/index.js` uses `global` / `require`) + intl: `${NODE_MODULES}/intl/dist/Intl.complete.js`, + jszip: '/packages/devextreme/artifacts/js/jszip.js', + 'devextreme-exceljs-fork': '/packages/devextreme/artifacts/js/dx-exceljs-fork.js', + 'devextreme-quill': `${NODE_MODULES}/devextreme-quill/dist/dx-quill.js`, + 'devexpress-diagram': '/packages/devextreme/artifacts/js/dx-diagram.js', + 'devexpress-gantt': '/packages/devextreme/artifacts/js/dx-gantt.js', + + // css! plugin replacements + 'fluent_blue_light.css!': `${SHIMS}/fluent_blue_light.css.js`, + 'material_blue_light.css!': `${SHIMS}/material_blue_light.css.js`, + 'gantt.css!': `${SHIMS}/gantt.css.js`, + + // Hand-written shim: theme composition is custom. + 'ui/themes': `${SHIMS}/themes.js`, + '__internal/ui/themes': `${SHIMS}/themes.js`, + + // Stubs + zod: `${SHIMS}/zod.js`, + 'zod-to-json-schema': `${SHIMS}/zod-to-json-schema.js`, + + // Suite-specific missing-dependency overrides + ...collectSuiteImportOverrides(suiteFilePath), + }; + + if (suiteFilePath && fs.existsSync(suiteFilePath)) { + collectPluginSpecifiersFromSuiteTree(suiteFilePath).forEach((specifier) => { + if (/\.json!(?:json)?$/.test(specifier)) { + const url = resolveJsonBangToUrl(specifier); + if (url) { + rawImports[specifier] = url; + } + } + }); + } + + const imports: Record<string, string> = {}; + Object.entries(rawImports).forEach(([key, url]) => { + // Import-map package-prefix entries (`foo/`) must map to a URL ending + // with `/` per spec. Appending `?cache` breaks that shape and browsers + // treat such entries as blocked (`null`), causing CI-only resolution fails. + imports[key] = key.endsWith('/') ? url : withCacheBuster(url, cacheBuster); + }); + + return { imports }; +} + +export function getEsmModuleRoot(): string { + return ESM_ROOT; +} diff --git a/packages/devextreme/testing/runner/lib/pages.ts b/packages/devextreme/testing/runner/lib/pages.ts index 582bafab9de6..75719e2615b8 100644 --- a/packages/devextreme/testing/runner/lib/pages.ts +++ b/packages/devextreme/testing/runner/lib/pages.ts @@ -1,16 +1,20 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; + import { BaseRunProps, RunAllModel, RunSuiteModel, TemplateVars, } from './types'; - -interface SystemPackage { - main?: string; - defaultExtension?: string; -} +import { + buildQunitImportMap, + collectPluginSpecifiersFromSuiteTree, + getEsmModuleRoot, +} from './importMap'; interface PagesRendererDeps { contentWithCacheBuster: (contentPath: string, cacheBuster: string) => string; getCacheBuster: (searchParams: URLSearchParams) => string; jsonString: (value: unknown) => string; + packageRoot: string; renderTemplate: (templateName: string, vars?: TemplateVars) => string; } @@ -24,10 +28,14 @@ export interface PagesRenderer { ) => string; } +const ESM_NPM = 'transpiled-esm-npm/esm'; +const TESTING_BASE = '/packages/devextreme/testing/'; + export function createPagesRenderer({ contentWithCacheBuster, getCacheBuster, jsonString, + packageRoot, renderTemplate, }: PagesRendererDeps): PagesRenderer { function renderIndexPage(): string { @@ -71,44 +79,44 @@ export function createPagesRenderer({ || scriptVirtualPath.includes('Bundles') || scriptVirtualPath.includes('DevExpress.jquery'); - const cspPart = runProps.NoCsp ? '' : '-systemjs'; - const npmModule = `transpiled${cspPart}`; - const testingBasePath = runProps.NoCsp - ? '/packages/devextreme/testing/' - : '/packages/devextreme/artifacts/transpiled-testing/'; + const esmRootPath = path.join(packageRoot, 'artifacts', 'transpiled-esm-npm', 'esm'); + const esmReady = fs.existsSync(path.join(esmRootPath, 'integration', 'jquery.js')); + + if (!esmReady) { + return `<!DOCTYPE html><html><head><title>ESM artifacts missing +

QUnit ESM loader: artifacts missing

+

artifacts/transpiled-esm-npm/esm is not built.

+

pnpm run dev / build:dev should build ESM via build:transpile -c ci.

+

If artifacts were cleaned manually, rebuild:

+
pnpm nx run devextreme:build:qunit-esm
+

Then reload the suite.

+`; + } function getJQueryUrl(): string { if (isNoJQueryTest) { - return `${testingBasePath}helpers/noJQuery.js`; + return `${TESTING_BASE}helpers/noJQuery.js`; } return '/packages/devextreme/artifacts/js/jquery.js'; } - function getTestUrl(): string { - if (runProps.NoCsp) { - return scriptVirtualPath; - } - - return scriptVirtualPath.replace('/testing/', '/artifacts/transpiled-testing/'); - } - function getJQueryIntegrationImports(): string[] { const result: string[] = []; if (!isSelfSufficientTest) { if (runProps.NoJQuery || isNoJQueryTest || isServerSideTest) { - result.push(`${testingBasePath}helpers/jQueryEventsPatch.js`); - result.push(`${testingBasePath}helpers/argumentsValidator.js`); - result.push(`${testingBasePath}helpers/dataPatch.js`); - result.push(`/packages/devextreme/artifacts/${npmModule}/__internal/integration/jquery/component_registrator.js`); + result.push(`${TESTING_BASE}helpers/jQueryEventsPatch.js`); + result.push(`${TESTING_BASE}helpers/argumentsValidator.js`); + result.push(`${TESTING_BASE}helpers/dataPatch.js`); + result.push(`/packages/devextreme/artifacts/${ESM_NPM}/__internal/integration/jquery/component_registrator.js`); } else { - result.push(`/packages/devextreme/artifacts/${npmModule}/integration/jquery.js`); + result.push(`/packages/devextreme/artifacts/${ESM_NPM}/integration/jquery.js`); } } if (isServerSideTest) { - result.push(`${testingBasePath}helpers/ssrEmulator.js`); + result.push(`${TESTING_BASE}helpers/ssrEmulator.js`); } return result; @@ -120,121 +128,33 @@ export function createPagesRenderer({ const qunitJs = contentWithCacheBuster('/packages/devextreme/node_modules/qunit/qunit/qunit.js', cacheBuster); const qunitExtensionsJs = contentWithCacheBuster('/packages/devextreme/testing/helpers/qunitExtensions.js', cacheBuster); const jqueryJs = contentWithCacheBuster('/packages/devextreme/node_modules/jquery/dist/jquery.js', cacheBuster); + const knockoutJs = contentWithCacheBuster('/packages/devextreme/node_modules/knockout/build/output/knockout-latest.debug.js', cacheBuster); const sinonJs = contentWithCacheBuster('/packages/devextreme/node_modules/sinon/pkg/sinon.js', cacheBuster); - const systemJs = contentWithCacheBuster( - runProps.NoCsp - ? '/packages/devextreme/node_modules/systemjs/dist/system.js' - : '/packages/devextreme/node_modules/systemjs/dist/system-csp-production.js', - cacheBuster, - ); - - const cspMap: Record = !runProps.NoCsp - ? { - 'inferno-create-element': '/packages/devextreme/node_modules/inferno-create-element/dist/inferno-create-element.js', - intl: '/packages/devextreme/artifacts/js-systemjs/intl/index.js', - knockout: '/packages/devextreme/artifacts/js-systemjs/knockout.js', - css: '/packages/devextreme/artifacts/js-systemjs/css.js', - 'material_blue_light.css': '/packages/devextreme/artifacts/css-systemjs/dx.material.blue.light.css', - 'fluent_blue_light.css': '/packages/devextreme/artifacts/css-systemjs/dx.fluent.blue.light.css', - 'gantt.css': '/packages/devextreme/artifacts/css-systemjs/dx-gantt.css', - 'devextreme-cldr-data': '/packages/devextreme/artifacts/js-systemjs/devextreme-cldr-data', - 'cldr-core': '/packages/devextreme/artifacts/js-systemjs/cldr-core', - json: '/packages/devextreme/artifacts/js-systemjs/json.js', - '@preact/signals-core': '/packages/devextreme/artifacts/js-systemjs/preact-signals.js', - } - : { - 'devextreme-cldr-data': '/packages/devextreme/node_modules/devextreme-cldr-data', - 'cldr-core': '/packages/devextreme/node_modules/cldr-core', - '@preact/signals-core': '/packages/devextreme/node_modules/@preact/signals-core/dist/signals-core.js', - }; - const systemMap: Record = { - globalize: '/packages/devextreme/node_modules/globalize/dist/globalize', - intl: '/packages/devextreme/node_modules/intl/index.js', - cldr: '/packages/devextreme/node_modules/cldrjs/dist/cldr', - jquery: getJQueryUrl(), - knockout: '/packages/devextreme/node_modules/knockout/build/output/knockout-latest.debug.js', - jszip: '/packages/devextreme/artifacts/js/jszip.js', - underscore: '/packages/devextreme/node_modules/underscore/underscore-min.js', - '@@devextreme/vdom': '/packages/devextreme/node_modules/@devextreme/vdom', - 'devextreme-quill': '/packages/devextreme/node_modules/devextreme-quill/dist/dx-quill.js', - 'devexpress-diagram': '/packages/devextreme/artifacts/js/dx-diagram.js', - 'devexpress-gantt': '/packages/devextreme/artifacts/js/dx-gantt.js', - 'devextreme-exceljs-fork': '/packages/devextreme/node_modules/devextreme-exceljs-fork/dist/dx-exceljs-fork.js', - // eslint-disable-next-line @stylistic/quote-props - 'fflate': '/packages/devextreme/node_modules/fflate/esm/browser.js', - jspdf: '/packages/devextreme/node_modules/jspdf/dist/jspdf.umd.js', - 'jspdf-autotable': '/packages/devextreme/node_modules/jspdf-autotable/dist/jspdf.plugin.autotable.js', - rrule: '/packages/devextreme/node_modules/rrule/dist/es5/rrule.js', - inferno: '/packages/devextreme/node_modules/inferno/dist/inferno.js', - 'inferno-hydrate': '/packages/devextreme/node_modules/inferno-hydrate/dist/inferno-hydrate.js', - 'inferno-compat': '/packages/devextreme/node_modules/inferno-compat/dist/inferno-compat.js', - 'inferno-clone-vnode': '/packages/devextreme/node_modules/inferno-clone-vnode/dist/index.cjs.js', - 'inferno-create-element': '/packages/devextreme/node_modules/inferno-create-element/dist/index.cjs.js', - 'inferno-create-class': '/packages/devextreme/node_modules/inferno-create-class/dist/index.cjs.js', - 'inferno-extras': '/packages/devextreme/node_modules/inferno-extras/dist/index.cjs.js', - 'material_blue_light.css': '/packages/devextreme/artifacts/css/dx.material.blue.light.css', - 'fluent_blue_light.css': '/packages/devextreme/artifacts/css/dx.fluent.blue.light.css', - 'gantt.css': '/packages/devextreme/artifacts/css/dx-gantt.css', - css: '/packages/devextreme/node_modules/systemjs-plugin-css/css.js', - text: '/packages/devextreme/node_modules/systemjs-plugin-text/text.js', - json: '/packages/devextreme/node_modules/systemjs-plugin-json/json.js', - 'plugin-babel': '/packages/devextreme/node_modules/systemjs-plugin-babel/plugin-babel.js', - 'systemjs-babel-build': '/packages/devextreme/node_modules/systemjs-plugin-babel/systemjs-babel-browser.js', - // Provide minimal stubs as those packages aren't used in QUnit tests - zod: '/packages/devextreme/testing/helpers/stubs/zodStub.js', - 'zod-to-json-schema': '/packages/devextreme/testing/helpers/stubs/zodToJsonSchemaStub.js', - ...cspMap, - }; + const suiteFilePath = path.join( + packageRoot, + scriptVirtualPath.replace(/^\/packages\/devextreme\//, ''), + ); - const systemPackages: Record = { - '': { - defaultExtension: 'js', - }, - globalize: { - main: '../globalize.js', - defaultExtension: 'js', - }, - cldr: { - main: '../cldr.js', - defaultExtension: 'js', - }, - 'common/core/events/utils': { - main: 'index', - }, - 'events/utils': { - main: 'index', - }, - events: { - main: 'index', - }, - }; + const importMap = buildQunitImportMap({ + jqueryUrl: getJQueryUrl(), + cacheBuster, + suiteFilePath, + }); - const knockoutPath = '/packages/devextreme/node_modules/knockout/build/output/knockout-latest.debug.js'; + // Preload theme CSS before the suite graph evaluates. Scheduler (and others) + // call getThemeType() at module scope; sibling ESM imports run in parallel, + // so without this CSS may not be in the DOM yet and isMaterialBased freezes + // as false. + const themeCssImportPaths = fs.existsSync(suiteFilePath) + ? collectPluginSpecifiersFromSuiteTree(suiteFilePath) + .filter((specifier) => /\.css!(?:css)?$/.test(specifier)) + : []; - const systemConfig = { - baseURL: `/packages/devextreme/artifacts/${npmModule}`, - transpiler: 'plugin-babel', - map: systemMap, - packages: systemPackages, - packageConfigPaths: [ - '@@devextreme/*/package.json', - ], - meta: { - [knockoutPath]: { - format: 'global', - deps: ['jquery'], - exports: 'ko', - }, - '*.js': { - babelOptions: { - es2015: false, - }, - }, - }, - }; + const importMapScript = ``; - const integrationImportPaths = getJQueryIntegrationImports(); + // Restore CSP for default runs; `?nocsp` keeps the meta off for suites + // that branch on QUnit.urlParams['nocsp'] (Knockout, aspnet, …). const cspMetaTag = runProps.NoCsp ? '' : ` string; rootDirectory: string; - setNoCacheHeaders: (res: ServerResponse) => void; setStaticCacheHeaders: (res: ServerResponse, searchParams: URLSearchParams) => void; } @@ -18,53 +25,99 @@ export interface StaticFileService { ) => boolean; } +const CONTENT_TYPES: Readonly> = { + '.html': 'text/html; charset=utf-8', + '.htm': 'text/html; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.js': 'application/javascript; charset=utf-8', + '.mjs': 'application/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.xml': 'text/xml; charset=utf-8', + '.xsl': 'text/xml; charset=utf-8', + '.txt': 'text/plain; charset=utf-8', + '.md': 'text/plain; charset=utf-8', + '.log': 'text/plain; charset=utf-8', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.ico': 'image/x-icon', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.ttf': 'font/ttf', + '.eot': 'application/vnd.ms-fontobject', + '.map': 'application/json; charset=utf-8', + '.wasm': 'application/wasm', +}; + +const JS_CONTENT_TYPE = 'application/javascript; charset=utf-8'; +const ESM_ARTIFACT_MARKER = '/artifacts/transpiled-esm-npm/esm/'; + +function normalizeUrlPath(filePath: string): string { + return filePath.split(path.sep).join('/'); +} + function getContentType(filePath: string): string { - const ext = path.extname(filePath).toLowerCase(); - - switch (ext) { - case '.html': - case '.htm': - return 'text/html; charset=utf-8'; - case '.css': - return 'text/css; charset=utf-8'; - case '.js': - case '.mjs': - return 'application/javascript; charset=utf-8'; - case '.json': - return 'application/json; charset=utf-8'; - case '.xml': - case '.xsl': - return 'text/xml; charset=utf-8'; - case '.txt': - case '.md': - case '.log': - return 'text/plain; charset=utf-8'; - case '.svg': - return 'image/svg+xml'; - case '.png': - return 'image/png'; - case '.jpg': - case '.jpeg': - return 'image/jpeg'; - case '.gif': - return 'image/gif'; - case '.ico': - return 'image/x-icon'; - case '.woff': - return 'font/woff'; - case '.woff2': - return 'font/woff2'; - case '.ttf': - return 'font/ttf'; - case '.eot': - return 'application/vnd.ms-fontobject'; - case '.map': - return 'application/json; charset=utf-8'; - case '.wasm': - return 'application/wasm'; - default: - return 'application/octet-stream'; + return CONTENT_TYPES[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream'; +} + +function sendError(res: ServerResponse, statusCode: number, message: string): boolean { + // Always override any prior Cache-Control (e.g. DX_HTTP_CACHE year-long + // headers set before a transform/read failure). + applyNoCacheHeaders(res); + res.statusCode = statusCode; + res.setHeader('Content-Type', 'text/plain; charset=utf-8'); + res.end(message); + return true; +} + +function sendJsModuleBody(res: ServerResponse, body: string): boolean { + const buffer = Buffer.from(body, 'utf8'); + res.statusCode = 200; + res.setHeader('Content-Type', JS_CONTENT_TYPE); + res.setHeader('Content-Length', String(buffer.length)); + res.end(buffer); + return true; +} + +function sendTransformedJs( + res: ServerResponse, + filePath: string, + transform: (raw: string) => string, + errorMessage: string, +): boolean { + try { + return sendJsModuleBody(res, transform(fs.readFileSync(filePath, 'utf8'))); + } catch { + return sendError(res, 500, errorMessage); + } +} + +/** + * Native ESM requires resolvable URLs. Our transpiled ESM tree uses + * extensionless relative imports (`from './wrapper'`). Resolve those + * to `.js` / `/index.js` on disk so import maps can load artifacts. + * + * Prefer `name.js` over a sibling directory `name/` — otherwise imports like + * `../__internal/integration/jquery` resolve to a directory listing (HTML) + * and the browser reports "Failed to fetch dynamically imported module". + * + * Also: files like `ui.collection_widget.edit` have a dotted basename; + * `path.extname` returns `.edit`, so we must still try appending `.js`. + */ +function resolveStaticFilePath(filePath: string): string | null { + if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { + return filePath; + } + + for (const candidate of [`${filePath}.js`, `${filePath}.mjs`, path.join(filePath, 'index.js')]) { + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) { + return candidate; + } } + + return fs.existsSync(filePath) ? filePath : null; } function sendStaticFile(res: ServerResponse, filePath: string, fileSize: number): boolean { @@ -88,6 +141,314 @@ function sendStaticFile(res: ServerResponse, filePath: string, fileSize: number) return true; } +// --- Vendor / UMD → ESM wrappers ------------------------------------------------ + +const ESM_DEFAULT_FROM_CJS = `const __dxVendorExport = module.exports && module.exports.__esModule + && Object.prototype.hasOwnProperty.call(module.exports, 'default') + ? module.exports.default + : module.exports; +export default __dxVendorExport; +`; + +function forceVendorGlobalThis(source: string): string { + return source.replace(/}\(\s*this\s*,/g, '}(globalThis,'); +} + +function wrapVendorCjsBranch( + source: string, + options: { + preamble?: string; + requireShim?: string; + exportsInit?: string; + trailing?: string; + rewriteThis?: boolean; + } = {}, +): string { + const { + preamble = '', + requireShim = '', + exportsInit = '{}', + trailing = ESM_DEFAULT_FROM_CJS, + rewriteThis = true, + } = options; + + const vendorSource = rewriteThis ? forceVendorGlobalThis(source) : source; + + return [ + preamble, + preamble ? '\n' : '', + `const module = { exports: ${exportsInit} };\n`, + 'const exports = module.exports;\n', + 'var define;\n', + requireShim, + vendorSource, + '\n', + trailing, + ].join(''); +} + +/** + * `intl/dist/Intl.complete.js` appends locale data that expects a free + * `IntlPolyfill` binding from the UMD *browser* branch. Forcing CJS breaks + * that, so keep the global branch and re-export the polyfill. + */ +function wrapIntlVendorAsEsm(source: string): string { + return 'var define;\n' + + 'var IntlPolyfill;\n' + + `${source + .replace(/}\(this,/g, '}(globalThis,') + .replace(/e\.IntlPolyfill=r\(\)/g, 'e.IntlPolyfill=IntlPolyfill=r()')}\n` + + 'export default IntlPolyfill;\n'; +} + +/** + * Force the CJS branch of a UMD wrapper and re-export `module.exports` as default. + * Also emit synthetic named exports from the webpack entry module so + * `import Def, * as Ns from 'pkg'` gets CJS-style interop + * (needed by diagram.importer → `Ns.DiagramControl`). + */ +function collectWebpackEntryExportNames(source: string): string[] { + const entryMatch = /var __webpack_exports__ = __webpack_require__\((\d+)\);/.exec(source); + if (!entryMatch) { + return []; + } + + const entryId = entryMatch[1]; + const moduleStart = source.indexOf(`/***/ ${entryId}`); + if (moduleStart < 0) { + return []; + } + + const nextModule = source.indexOf('\n/***/ ', moduleStart + 1); + const moduleSource = nextModule < 0 + ? source.slice(moduleStart) + : source.slice(moduleStart, nextModule); + + const names = new Set(); + const definePropertyRe = /Object\.defineProperty\(\s*exports\s*,\s*["']([^"']+)["']/g; + let match = definePropertyRe.exec(moduleSource); + while (match) { + const name = match[1]; + if (name !== '__esModule' && name !== 'default' && /^[A-Za-z_$][\w$]*$/.test(name)) { + names.add(name); + } + match = definePropertyRe.exec(moduleSource); + } + + return [...names].sort(); +} + +function wrapWebpackVendorAsEsm(source: string): string { + const namedExports = collectWebpackEntryExportNames(source) + .map((name) => `export const ${name} = module.exports.${name};`) + .join('\n'); + + return wrapVendorCjsBranch(source, { + rewriteThis: false, + trailing: `${ESM_DEFAULT_FROM_CJS}${namedExports ? `${namedExports}\n` : ''}`, + }); +} + +/** globalize / cldrjs ship as UMD; native ESM needs a CJS-branch + require shim. */ +function wrapGlobalizeOrCldrAsEsm(source: string, relativeUrlPath: string): string { + const normalized = normalizeUrlPath(relativeUrlPath); + const isCldrMain = normalized.endsWith('/cldrjs/dist/cldr.js'); + const isCldrPlugin = /\/cldrjs\/dist\/cldr\/[^/]+\.js$/i.test(normalized); + const isGlobalizeMain = normalized.endsWith('/globalize/dist/globalize.js'); + const isGlobalizePlugin = normalized.includes('/globalize/dist/globalize/'); + const baseName = path.basename(normalized, '.js'); + const needsNumber = isGlobalizePlugin && (baseName === 'currency' || baseName === 'date'); + + const preamble: string[] = []; + if (isCldrPlugin) { + preamble.push('import __dxCldr from \'cldr\';'); + } else if (isGlobalizeMain || isGlobalizePlugin) { + preamble.push('import __dxCldr from \'cldr\';'); + preamble.push('import \'cldr/event\';'); + if (isGlobalizePlugin) { + preamble.push('import \'cldr/supplemental\';'); + preamble.push('import __dxGlobalize from \'globalize\';'); + if (needsNumber) { + // CJS factory skips `./number`; AMD/DevExtreme always load it first. + preamble.push('import \'./number.js\';'); + } + } + } + + const requireShim = isCldrMain + ? 'function require(id) { throw new Error(\'Unexpected require in cldr: \' + id); }\n' + : [ + 'function require(id) {\n', + ' if (id === \'cldrjs\' || id === \'cldr\' || id === \'../cldr\') {\n', + ' return __dxCldr;\n', + ' }\n', + ' if (id === \'../globalize\' || id === \'globalize\') {\n', + ' return __dxGlobalize;\n', + ' }\n', + ' throw new Error(\'Unhandled require in globalize/cldr UMD: \' + id);\n', + '}\n', + ].join(''); + + return wrapVendorCjsBranch(source, { + preamble: preamble.join('\n'), + requireShim, + }); +} + +/** + * Vector map geo data UMD: CJS writes into `exports`, browser branch expects + * bare `DevExpress`. Under ESM imports hoist above suite setup, so create the + * global sources bag and point `module.exports` at the same object. + */ +function wrapVectorMapDataAsEsm(source: string): string { + return wrapVendorCjsBranch(source, { + preamble: [ + 'globalThis.DevExpress = globalThis.DevExpress || {};', + 'globalThis.DevExpress.viz = globalThis.DevExpress.viz || {};', + 'globalThis.DevExpress.viz.map = globalThis.DevExpress.viz.map || {};', + 'globalThis.DevExpress.viz.map.sources = globalThis.DevExpress.viz.map.sources || {};', + ].join('\n'), + exportsInit: 'globalThis.DevExpress.viz.map.sources', + trailing: 'export default module.exports;\n', + }); +} + +/** `dx.vectormaputils.js` is UMD (`exports.parse = …`); tests do `import { parse }`. */ +function wrapVectorMapUtilsAsEsm(source: string): string { + return wrapVendorCjsBranch(source, { + rewriteThis: false, + trailing: 'export default module.exports;\nexport const parse = module.exports.parse;\n', + }); +} + +type VendorWrapper = (source: string, relativeUrlPath: string) => string; + +function resolveVendorEsmWrapper(relativeUrlPath: string): VendorWrapper | null { + const normalized = normalizeUrlPath(relativeUrlPath); + + if ( + normalized.endsWith('/intl/dist/Intl.complete.js') + || normalized.endsWith('/intl/dist/Intl.js') + ) { + return (source) => wrapIntlVendorAsEsm(source); + } + + if ( + normalized.endsWith('/globalize/dist/globalize.js') + || normalized.includes('/globalize/dist/globalize/') + || normalized.endsWith('/cldrjs/dist/cldr.js') + || /\/cldrjs\/dist\/cldr\/[^/]+\.js$/i.test(normalized) + ) { + return wrapGlobalizeOrCldrAsEsm; + } + + if (/\/artifacts\/js\/vectormap-data\/[^/]+\.js$/i.test(normalized)) { + return (source) => wrapVectorMapDataAsEsm(source); + } + + if (/\/artifacts\/js\/vectormap-utils\/dx\.vectormaputils\.js$/i.test(normalized)) { + return (source) => wrapVectorMapUtilsAsEsm(source); + } + + if ( + normalized.endsWith('/devextreme-quill/dist/dx-quill.js') + || normalized.endsWith('/artifacts/js/dx-diagram.js') + || normalized.endsWith('/artifacts/js/dx-gantt.js') + || normalized.endsWith('/artifacts/js/dx-exceljs-fork.js') + || normalized.endsWith('/artifacts/js/jszip.js') + ) { + return (source) => wrapWebpackVendorAsEsm(source); + } + + return null; +} + +/** Serve JSON as `export default …` for native ESM (`*.json!` replacement). */ +function sendJsonAsEsmModule(res: ServerResponse, filePath: string): boolean { + return sendTransformedJs( + res, + filePath, + (raw) => { + JSON.parse(raw); + return `export default ${raw};\n`; + }, + 'Failed to export JSON as ESM module', + ); +} + +// --- Mutable artifact facades --------------------------------------------------- + +function sendShimModule(res: ServerResponse, shimUrl: string): boolean { + // Serve a re-export at the artifact URL so relative library imports and + // bare import-map entries share the same shim module graph. + return sendJsModuleBody( + res, + `export * from '${shimUrl}';\nexport { default } from '${shimUrl}';\n`, + ); +} + +// --- ESM artifact tweaks -------------------------------------------------------- + +/** + * Convert leftover `exports.foo = …` (from #DEBUG / dual CJS-ESM sources) + * into native ESM exports so the browser does not throw "exports is not defined". + */ +function rewriteLegacyCjsExportsInEsmArtifact(source: string): string { + if (!/\bexports\./.test(source)) { + return source; + } + + return source + .replace( + /^exports\.([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*);?\s*$/gm, + (_match, exportName: string, valueName: string) => (exportName === valueName + ? `export { ${exportName} };` + : `export { ${valueName} as ${exportName} };`), + ) + .replace( + /^exports\.([A-Za-z_$][\w$]*)\s*=\s*function\s*\(/gm, + 'export function $1(', + ) + .replace( + /^exports\.([A-Za-z_$][\w$]*)\s*=\s*async\s+function\s*\(/gm, + 'export async function $1(', + ); +} + +/** + * ESM npm artifacts are built with removeDebug:true, which strips QUnit-only + * hooks. Re-attach the ones still present as locals in the compiled module. + * + * When debug is kept (`-c qunit`), some sources still emit CJS `exports.*` + * assignments that throw under native ESM — rewrite those to ESM exports. + */ +function restoreEsmDebugTestHooks(relativeUrlPath: string, source: string): string { + const normalized = normalizeUrlPath(relativeUrlPath); + if (!normalized.includes(ESM_ARTIFACT_MARKER)) { + return source; + } + + return rewriteLegacyCjsExportsInEsmArtifact(source); +} + +function sendEsmArtifactJs( + res: ServerResponse, + filePath: string, + relativeUrlPath: string, +): boolean { + try { + const raw = fs.readFileSync(filePath, 'utf8'); + let body = restoreEsmDebugTestHooks(relativeUrlPath, raw); + body = rewriteAspnetArtifactToEsm(body, relativeUrlPath); + if (body === raw) { + return sendStaticFile(res, filePath, fs.statSync(filePath).size); + } + return sendJsModuleBody(res, body); + } catch { + return sendError(res, 500, 'Failed to serve ESM artifact'); + } +} + function sendDirectoryListing( res: ServerResponse, requestPath: string, @@ -141,7 +502,6 @@ ${items.join('\n')} export function createStaticFileService({ escapeHtml, rootDirectory, - setNoCacheHeaders, setStaticCacheHeaders, }: StaticFileServiceDeps): StaticFileService { function tryServeStatic( @@ -156,30 +516,77 @@ export function createStaticFileService({ const relativeToRoot = path.relative(rootDirectory, filePath); if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) { - setNoCacheHeaders(res); - res.statusCode = 403; - res.setHeader('Content-Type', 'text/plain; charset=utf-8'); - res.end('Forbidden'); - return true; + return sendError(res, 403, 'Forbidden'); } - if (!fs.existsSync(filePath)) { + const resolvedFilePath = resolveStaticFilePath(filePath); + if (!resolvedFilePath) { return false; } setStaticCacheHeaders(res, searchParams); - const stat = fs.statSync(filePath); + const stat = fs.statSync(resolvedFilePath); if (stat.isDirectory()) { - return sendDirectoryListing(res, pathname, filePath, escapeHtml); + return sendDirectoryListing(res, pathname, resolvedFilePath, escapeHtml); } - if (stat.isFile()) { - return sendStaticFile(res, filePath, stat.size); + if (!stat.isFile()) { + return false; + } + + if (searchParams.has('esm-export') && path.extname(resolvedFilePath).toLowerCase() === '.json') { + return sendJsonAsEsmModule(res, resolvedFilePath); + } + + // Native ESM resolves relative imports against the request URL, not the + // on-disk file. Redirect extensionless URLs to the canonical file URL. + const resolvedUrlPath = `/${normalizeUrlPath(path.relative(rootDirectory, resolvedFilePath))}`; + if (resolvedUrlPath !== normalizedPath) { + const query = searchParams.toString(); + res.statusCode = 302; + res.setHeader('Location', query ? `${resolvedUrlPath}?${query}` : resolvedUrlPath); + res.end(); + return true; + } + + const relativeUrlPath = normalizeUrlPath(relativeToRoot); + const isJs = path.extname(resolvedFilePath).toLowerCase() === '.js'; + + if (isJs && isQunitTestOrHelperPath(relativeUrlPath)) { + return sendTransformedJs( + res, + resolvedFilePath, + (raw) => rewriteQunitTestHelperSource(raw, relativeUrlPath), + 'Failed to rewrite CJS-style test/helper module', + ); + } + + if (!searchParams.has('dx-original')) { + const shimUrl = findHandWrittenShim(relativeUrlPath); + if (shimUrl) { + return sendShimModule(res, shimUrl); + } + } + + if (isJs) { + const vendorWrapper = resolveVendorEsmWrapper(relativeUrlPath); + if (vendorWrapper) { + return sendTransformedJs( + res, + resolvedFilePath, + (raw) => vendorWrapper(raw, relativeUrlPath), + 'Failed to wrap vendor bundle as ESM', + ); + } + + if (relativeUrlPath.includes(ESM_ARTIFACT_MARKER)) { + return sendEsmArtifactJs(res, resolvedFilePath, relativeUrlPath); + } } - return false; + return sendStaticFile(res, resolvedFilePath, stat.size); } return { diff --git a/packages/devextreme/testing/runner/templates/run-suite.template.html b/packages/devextreme/testing/runner/templates/run-suite.template.html index a7660dce96c2..d206af4ba123 100644 --- a/packages/devextreme/testing/runner/templates/run-suite.template.html +++ b/packages/devextreme/testing/runner/templates/run-suite.template.html @@ -99,9 +99,9 @@ - + - + + + {{{IMPORT_MAP_SCRIPT}}} +
- + diff --git a/packages/devextreme/testing/systemjs-builder.js b/packages/devextreme/testing/systemjs-builder.js deleted file mode 100644 index 940b87d249de..000000000000 --- a/packages/devextreme/testing/systemjs-builder.js +++ /dev/null @@ -1,272 +0,0 @@ -const path = require('path'); -const fs = require('fs'); -const babel = require('@babel/core'); -const parseArguments = require('minimist'); - - -const root = path.join(__dirname, '..'); -const transpilePath = path.join(root, '/artifacts/transpiled'); - -const getFileList = (dirName) => { - let files = []; - const items = fs.readdirSync(dirName, { withFileTypes: true }); - - // eslint-disable-next-line no-restricted-syntax - for(const item of items) { - if(item.isDirectory()) { - files = [...files, ...getFileList(path.join(dirName, item.name))]; - } else if( - item.name.endsWith('.js') || - (item.name.endsWith('.json') && !item.name.includes('tsconfig') && !item.name.includes('__meta')) - ) { - files.push(path.join(dirName, item.name)); - } - } - - return files; -}; - -const writeFileSync = (destPath, file) => { - const destDir = path.dirname(destPath); - if(!fs.existsSync(destDir)) { - fs.mkdirSync(destDir, { recursive: true }); - } - - fs.writeFileSync(destPath, file); -}; - -const buildAmdModule = (body) => ` -define(function(require, exports, module) { - ${body} -}); -`; - -const transpileCommonJSFile = (source, pathToFile) => { - const [pre, post] = path.extname(pathToFile) === '.json' - ? ['module.exports = ', ';'] - : ['', '']; - - writeFileSync( - pathToFile, - buildAmdModule( - `${pre}${source}${post}`.replace(/(\n|\r)/g, '$1 ') - ) - ); -}; - -const buildJsonModule = (body) => ` -define(function(require, exports, module) { - module.exports = ${body}; -}); -`; - -const buildSystemJSModule = (body, pre = '') => ` -SystemJS.register([], function(exports) { - ${pre} - - return { - setters: [], - execute: function() { - ${body} - } - }; -}); -`; - -const transpileFile = async(sourcePath, targetPath) => { - const code = fs.readFileSync(sourcePath) - .toString() - .replaceAll('/packages/devextreme/testing/helpers/wrapRenovatedWidget.js', '/packages/devextreme/artifacts/transpiled-testing/helpers/wrapRenovatedWidget.js') - .replaceAll(path.normalize('/testing/helpers/'), path.normalize('/artifacts/transpiled-testing/helpers/')) - // TODO see packages/devextreme/testing/tests/DevExpress.viz.vectorMap.utils/tests.js - // import { parse } from '../../../artifacts/js/vectormap-utils/dx.vectormaputils.js'; - // This used to work because the runner cwd was the same as the devextreme root folder - // remove next 3 lines after fix - .replaceAll( - path.normalize('../../../artifacts/js/vectormap-utils/dx.vectormaputils.js'), - path.normalize('../../../../artifacts/js/vectormap-utils/dx.vectormaputils.js')); - - if(sourcePath.includes('testing/helpers/includeThemesLinks.js')) { - writeFileSync(targetPath, buildSystemJSModule('', code.replaceAll('\n', ' '))); - return; - } - - if( - /(^|\s)System(JS)?\.register/gm.test(code) || - /(^|\s)define\(/gm.test(code) || - sourcePath.includes('helpers/forMap') - ) { - writeFileSync(targetPath, code); - } else if(/(\(|\s|^)require\(/.test(code) || /(module\.)?exports(\.\w+)?\s?=/.test(code)) { - transpileCommonJSFile(code, targetPath); - } else if(sourcePath.endsWith('.json')) { - writeFileSync(targetPath, buildJsonModule(code)); - } else { - await transpileWithBabel(code, targetPath); - } -}; - -const transpileModules = async() => { - await Promise.all( - getFileList(transpilePath).map((filePath) => { - return transpileFile( - filePath, - filePath.replace(path.normalize('/transpiled'), path.normalize('/transpiled-systemjs')), - ); - }) - ); -}; - -const buildCssAsSystemModule = (name, filePath) => ` -System.register('${filePath}', [], false, function() {}); -(function() { - if (typeof document == 'undefined') return; - var link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = '/packages/devextreme/${filePath}'; - link.setAttribute('data-theme', '${name}'); - document.getElementsByTagName('head')[0].appendChild(link); -})(); -`; - -const transpileCss = async() => { - const cssList = [ - ['artifacts/css/dx.light.css', 'generic.light'], - ['artifacts/css/dx.material.blue.light.css', 'material.blue.light'], - ['artifacts/css/dx.fluent.blue.light.css', 'fluent.blue.light'], - ['artifacts/css/dx-gantt.css', 'gantt'], - ]; - - // eslint-disable-next-line no-restricted-syntax - for(const [cssFile, styleName] of cssList) { - const destPath = path.join(root, cssFile.replace('css', 'css-systemjs')); - - writeFileSync(destPath, buildCssAsSystemModule(styleName, cssFile)); - } -}; - -const transpileWithBabel = async(sourceCode, destPath) => { - const { code } = await babel.transform(sourceCode, { - compact: false, - plugins: ['@babel/plugin-transform-modules-systemjs'], - sourceMaps: true, - }); - - writeFileSync(destPath, code); -}; - -const transpileIntl = async() => { - const listIntlFiles = [ - { - filePath: require.resolve('intl/lib/core.js'), - destPath: path.join(root, 'artifacts/js-systemjs/intl/intl.js'), - }, - { - filePath: require.resolve('intl/locale-data/complete.js'), - destPath: path.join(root, 'artifacts/js-systemjs/intl/intl.complete.js'), - }, - ]; - - await Promise.all(listIntlFiles.map(({ filePath, destPath }) => { - const code = fs.readFileSync(filePath).toString(); - - writeFileSync( - destPath, - buildAmdModule( - code.replace('IntlPolyfill', 'require("./intl.js")') - ) - ); - })); - - const intlIndex = ` - define(function(require, exports, module) { - window.IntlPolyfill = require('./intl.js'); - - require('./intl.complete.js'); - - if (!window.Intl) { - window.Intl = window.IntlPolyfill; - window.IntlPolyfill.__applyLocaleSensitivePrototypes(); - } - - module.exports = window.IntlPolyfill; - }); - `; - - writeFileSync(path.join(root, 'artifacts/js-systemjs/intl/index.js'), intlIndex); -}; - -const transpileJsVendors = async() => { - const pluginsList = [ - { - filePath: require.resolve('systemjs-plugin-css/css.js'), - destPath: path.join(root, 'artifacts/js-systemjs/css.js'), - }, - { - filePath: require.resolve('systemjs-plugin-json/json.js'), - destPath: path.join(root, 'artifacts/js-systemjs/json.js'), - }, - ]; - - await Promise.all( - pluginsList.map(({ filePath, destPath }) => { - const code = fs.readFileSync(filePath).toString(); - - return writeFileSync( - destPath, - buildSystemJSModule( - '', - code.replaceAll('module.exports', 'exports') - ) - ); - }), - ); - - await transpileIntl(); - - await transpileFile( - require.resolve('knockout/build/output/knockout-latest.debug.js'), - path.join(root, 'artifacts/js-systemjs/knockout.js') - ); - await transpileFile( - path.join(root, 'node_modules/@preact/signals-core/dist/signals-core.js'), - path.join(root, 'artifacts/js-systemjs/preact-signals.js') - ); - - - [].concat( - getFileList(path.join(root, 'node_modules/devextreme-cldr-data')), - getFileList(path.join(root, 'node_modules/cldr-core/supplemental')) - ) - .filter(filePath => filePath.endsWith('.json')) - .forEach((filePath) => { - transpileFile(filePath, filePath.replace(path.normalize('/node_modules'), path.normalize('/artifacts/js-systemjs'))); - }); -}; - -const transpileTesting = async() => { - const contentList = getFileList(path.join(root, 'testing/content')); - const helpersList = getFileList(path.join(root, 'testing/helpers')); - const testsList = getFileList(path.join(root, 'testing/tests')); - - [].concat(contentList, helpersList, testsList) - .forEach((filePath) => { - transpileFile(filePath, filePath.replace(path.normalize('/testing/'), path.normalize('/artifacts/transpiled-testing/'))); - }); -}; - -(async() => { - - const { transpile } = parseArguments(process.argv); - - switch(transpile) { - case 'modules': - return await transpileModules(); - case 'testing': - return await transpileTesting(); - case 'css': - return await transpileCss(); - case 'js-vendors': - return await transpileJsVendors(); - } -})(); diff --git a/packages/devextreme/testing/tests/Bundles/bundlesParts/animation.tests.js b/packages/devextreme/testing/tests/Bundles/bundlesParts/animation.tests.js index 4901738147fe..adb9bbba3336 100644 --- a/packages/devextreme/testing/tests/Bundles/bundlesParts/animation.tests.js +++ b/packages/devextreme/testing/tests/Bundles/bundlesParts/animation.tests.js @@ -1,4 +1,4 @@ -const $ = require('jquery'); +import $ from 'jquery'; QUnit.test('animation', function(assert) { diff --git a/packages/devextreme/testing/tests/Bundles/bundlesParts/core.tests.js b/packages/devextreme/testing/tests/Bundles/bundlesParts/core.tests.js index b572f50372b9..a2869b26c482 100644 --- a/packages/devextreme/testing/tests/Bundles/bundlesParts/core.tests.js +++ b/packages/devextreme/testing/tests/Bundles/bundlesParts/core.tests.js @@ -1,4 +1,4 @@ -const $ = require('jquery'); +import $ from 'jquery'; QUnit.test('core', function(assert) { diff --git a/packages/devextreme/testing/tests/Bundles/bundlesParts/data.odata.tests.js b/packages/devextreme/testing/tests/Bundles/bundlesParts/data.odata.tests.js index 58a2a5e54678..41385002be19 100644 --- a/packages/devextreme/testing/tests/Bundles/bundlesParts/data.odata.tests.js +++ b/packages/devextreme/testing/tests/Bundles/bundlesParts/data.odata.tests.js @@ -1,4 +1,4 @@ -const $ = require('jquery'); +import $ from 'jquery'; QUnit.test('data.odata', function(assert) { diff --git a/packages/devextreme/testing/tests/Bundles/bundlesParts/events.tests.js b/packages/devextreme/testing/tests/Bundles/bundlesParts/events.tests.js index a4634362de59..b1c6fa2b2e61 100644 --- a/packages/devextreme/testing/tests/Bundles/bundlesParts/events.tests.js +++ b/packages/devextreme/testing/tests/Bundles/bundlesParts/events.tests.js @@ -1,5 +1,5 @@ -const $ = require('jquery'); -const special = require('../../../helpers/eventHelper.js').special; +import $ from 'jquery'; +import { special } from '../../../helpers/eventHelper.js'; QUnit.test('events', function(assert) { diff --git a/packages/devextreme/testing/tests/Bundles/bundlesParts/widgets-base.tests.js b/packages/devextreme/testing/tests/Bundles/bundlesParts/widgets-base.tests.js index 302526274c8a..f60e0e5ce790 100644 --- a/packages/devextreme/testing/tests/Bundles/bundlesParts/widgets-base.tests.js +++ b/packages/devextreme/testing/tests/Bundles/bundlesParts/widgets-base.tests.js @@ -1,4 +1,4 @@ -const $ = require('jquery'); +import $ from 'jquery'; QUnit.test('widgets-base', function(assert) { diff --git a/packages/devextreme/testing/tests/Bundles/bundlesParts/widgets-web.tests.js b/packages/devextreme/testing/tests/Bundles/bundlesParts/widgets-web.tests.js index ed84590a1d82..76875c74e79c 100644 --- a/packages/devextreme/testing/tests/Bundles/bundlesParts/widgets-web.tests.js +++ b/packages/devextreme/testing/tests/Bundles/bundlesParts/widgets-web.tests.js @@ -1,4 +1,4 @@ -const $ = require('jquery'); +import $ from 'jquery'; QUnit.test('widgets-web', function(assert) { diff --git a/packages/devextreme/testing/tests/Bundles/dx.all.tests.js b/packages/devextreme/testing/tests/Bundles/dx.all.tests.js index 004aaa59610a..26109431b59c 100644 --- a/packages/devextreme/testing/tests/Bundles/dx.all.tests.js +++ b/packages/devextreme/testing/tests/Bundles/dx.all.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); +import $ from 'jquery'; -require('bundles/dx.all.js'); +import 'bundles/dx.all.js'; QUnit.test('DevExpress namespaces', function(assert) { const namespaces = [ @@ -19,10 +19,10 @@ QUnit.test('DevExpress namespaces', function(assert) { assert.ok(DevExpress.utils.readyCallbacks, 'readyCallbacks namespace'); }); -require('./bundlesParts/core.tests.js'); -require('./bundlesParts/events.tests.js'); -require('./bundlesParts/data.tests.js'); -require('./bundlesParts/data.odata.tests.js'); -require('./bundlesParts/animation.tests.js'); -require('./bundlesParts/widgets-base.tests.js'); -require('./bundlesParts/widgets-web.tests.js'); +import './bundlesParts/core.tests.js'; +import './bundlesParts/events.tests.js'; +import './bundlesParts/data.tests.js'; +import './bundlesParts/data.odata.tests.js'; +import './bundlesParts/animation.tests.js'; +import './bundlesParts/widgets-base.tests.js'; +import './bundlesParts/widgets-web.tests.js'; diff --git a/packages/devextreme/testing/tests/Bundles/dx.custom.tests.js b/packages/devextreme/testing/tests/Bundles/dx.custom.tests.js index cfdd52f6ed78..81ad2c77a395 100644 --- a/packages/devextreme/testing/tests/Bundles/dx.custom.tests.js +++ b/packages/devextreme/testing/tests/Bundles/dx.custom.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); +import $ from 'jquery'; -require('bundles/dx.custom.js'); +import 'bundles/dx.custom.js'; QUnit.test('DevExpress namespaces', function(assert) { const namespaces = [ @@ -16,10 +16,10 @@ QUnit.test('DevExpress namespaces', function(assert) { }); }); -require('./bundlesParts/core.tests.js'); -require('./bundlesParts/events.tests.js'); -require('./bundlesParts/data.tests.js'); -require('./bundlesParts/data.odata.tests.js'); -require('./bundlesParts/animation.tests.js'); -require('./bundlesParts/widgets-base.tests.js'); -require('./bundlesParts/widgets-web.tests.js'); +import './bundlesParts/core.tests.js'; +import './bundlesParts/events.tests.js'; +import './bundlesParts/data.tests.js'; +import './bundlesParts/data.odata.tests.js'; +import './bundlesParts/animation.tests.js'; +import './bundlesParts/widgets-base.tests.js'; +import './bundlesParts/widgets-web.tests.js'; diff --git a/packages/devextreme/testing/tests/Bundles/dx.viz.tests.js b/packages/devextreme/testing/tests/Bundles/dx.viz.tests.js index b579be2030f6..88d61c7f24cf 100644 --- a/packages/devextreme/testing/tests/Bundles/dx.viz.tests.js +++ b/packages/devextreme/testing/tests/Bundles/dx.viz.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); +import $ from 'jquery'; -require('bundles/dx.viz.js'); +import 'bundles/dx.viz.js'; QUnit.test('DevExpress namespaces', function(assert) { const namespaces = [ @@ -16,8 +16,8 @@ QUnit.test('DevExpress namespaces', function(assert) { }); }); -require('./bundlesParts/core.tests.js'); -require('./bundlesParts/events.tests.js'); -require('./bundlesParts/data.tests.js'); -require('./bundlesParts/data.odata.tests.js'); -require('./bundlesParts/animation.tests.js'); +import './bundlesParts/core.tests.js'; +import './bundlesParts/events.tests.js'; +import './bundlesParts/data.tests.js'; +import './bundlesParts/data.odata.tests.js'; +import './bundlesParts/animation.tests.js'; diff --git a/packages/devextreme/testing/tests/Bundles/dx.web.tests.js b/packages/devextreme/testing/tests/Bundles/dx.web.tests.js index b96815164317..c8ef7037c130 100644 --- a/packages/devextreme/testing/tests/Bundles/dx.web.tests.js +++ b/packages/devextreme/testing/tests/Bundles/dx.web.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); +import $ from 'jquery'; -require('bundles/dx.web.js'); +import 'bundles/dx.web.js'; QUnit.test('DevExpress namespaces', function(assert) { const namespaces = [ @@ -23,10 +23,10 @@ QUnit.test('DevExpress namespaces', function(assert) { }); }); -require('./bundlesParts/core.tests.js'); -require('./bundlesParts/events.tests.js'); -require('./bundlesParts/data.tests.js'); -require('./bundlesParts/data.odata.tests.js'); -require('./bundlesParts/animation.tests.js'); -require('./bundlesParts/widgets-base.tests.js'); -require('./bundlesParts/widgets-web.tests.js'); +import './bundlesParts/core.tests.js'; +import './bundlesParts/events.tests.js'; +import './bundlesParts/data.tests.js'; +import './bundlesParts/data.odata.tests.js'; +import './bundlesParts/animation.tests.js'; +import './bundlesParts/widgets-base.tests.js'; +import './bundlesParts/widgets-web.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.animation/fx.tests.js b/packages/devextreme/testing/tests/DevExpress.animation/fx.tests.js index 68e939713614..5b25cee2a986 100644 --- a/packages/devextreme/testing/tests/DevExpress.animation/fx.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.animation/fx.tests.js @@ -4,6 +4,7 @@ import eventsEngine from 'common/core/events/core/events_engine'; import fx from 'common/core/animation/fx'; import translator from 'common/core/animation/translator'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import positionUtils from 'common/core/animation/position'; import support from '__internal/core/utils/m_support'; @@ -75,7 +76,7 @@ QUnit.module('frame transitions', { this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { return setTimeout(callback, 1); }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.animation/position.tests.js b/packages/devextreme/testing/tests/DevExpress.animation/position.tests.js index 84f4b67bd58c..752e74173c64 100644 --- a/packages/devextreme/testing/tests/DevExpress.animation/position.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.animation/position.tests.js @@ -1,7 +1,9 @@ import $ from 'jquery'; import positionUtils from 'common/core/animation/position'; import translator from '__internal/common/core/animation/translatorModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import browser from 'core/utils/browser'; +import devices from '__internal/core/m_devices'; import fixtures from '../../helpers/positionFixtures.js'; import { implementationsMap } from 'core/utils/size'; import { getWindow } from 'core/utils/window.js'; @@ -942,23 +944,30 @@ const testCollision = (name, fixtureName, params, expectedHorzDist, expectedVert assert.deepEqual(positionUtils.offset($event), { left: 100, top: 200 }, 'position.offset() is correct'); }); - QUnit.test('position should return window.innerHeight if window.outerHeight < window.innerHeight', function(assert) { + QUnit.test('position should measure the window by its client height whatever window.outerHeight reports', function(assert) { const $what = $('#what').height(300); const initialInnerHeight = window.innerHeight; const initialOuterHeight = window.outerHeight; + const heightStub = sinon.stub(implementationsMap, 'getHeight').returns(1000); + try { window.innerHeight = 500; - window.outerHeight = 200; - const resultPosition = setupPosition($what, { - of: $(window) - }); + // 0 is what the browser reports before the first frame, a smaller value comes from a zoomed out page + [0, 200].forEach((outerHeight) => { + window.outerHeight = outerHeight; + + const resultPosition = setupPosition($what, { + of: $(window) + }); - assert.roughEqual(resultPosition.v.location, 100, 50, 'vertical location is correct'); + assert.roughEqual(resultPosition.v.location, 350, 50, `vertical location is correct, outerHeight is ${outerHeight}`); + }); } finally { window.innerHeight = initialInnerHeight; window.outerHeight = initialOuterHeight; + heightStub.restore(); } }); @@ -1018,9 +1027,89 @@ const testCollision = (name, fixtureName, params, expectedHorzDist, expectedVert } }); + QUnit.test('position should measure the window by its client height on desktop Safari', function(assert) { + const $what = $('#what').height(300); + const initialInnerHeight = window.innerHeight; + const initialSafari = browser.safari; + + const heightStub = sinon.stub(implementationsMap, 'getHeight').returns(1000); + const devicesStub = sinon.stub(devices, 'real').returns({ deviceType: 'desktop', platform: 'generic' }); + + try { + window.innerHeight = 500; + browser.safari = true; + + const resultPosition = setupPosition($what, { + of: $(window) + }); + + assert.roughEqual(resultPosition.v.location, 350, 50, 'vertical location is correct'); + } finally { + window.innerHeight = initialInnerHeight; + browser.safari = initialSafari; + heightStub.restore(); + devicesStub.restore(); + } + }); + + QUnit.test('position should measure the window by window.innerHeight on iOS whatever the browser is', function(assert) { + const $what = $('#what').height(300); + const initialInnerHeight = window.innerHeight; + const initialSafari = browser.safari; + + const heightStub = sinon.stub(implementationsMap, 'getHeight').returns(1000); + const devicesStub = sinon.stub(devices, 'real').returns({ deviceType: 'desktop', platform: 'ios' }); + + try { + window.innerHeight = 500; + + [true, false].forEach((isSafari) => { + browser.safari = isSafari; + + const resultPosition = setupPosition($what, { + of: $(window) + }); + + assert.roughEqual(resultPosition.v.location, 100, 50, `vertical location is correct, browser.safari is ${isSafari}`); + }); + } finally { + window.innerHeight = initialInnerHeight; + browser.safari = initialSafari; + heightStub.restore(); + devicesStub.restore(); + } + }); + + QUnit.test('position should measure the window by its client width whatever window.outerWidth reports', function(assert) { + const $what = $('#what').width(300); + const initialInnerWidth = window.innerWidth; + const initialOuterWidth = window.outerWidth; + + const widthStub = sinon.stub(implementationsMap, 'getWidth').returns(1000); + + try { + window.innerWidth = 500; + + // 0 is what the browser reports before the first frame, a smaller value comes from a zoomed out page + [0, 200].forEach((outerWidth) => { + window.outerWidth = outerWidth; + + const resultPosition = setupPosition($what, { + of: $(window) + }); + + assert.roughEqual(resultPosition.h.location, 350, 50, `horizontal location is correct, outerWidth is ${outerWidth}`); + }); + } finally { + window.innerWidth = initialInnerWidth; + window.outerWidth = initialOuterWidth; + widthStub.restore(); + } + }); + // T664522 QUnit.test('setup should call resetPosition with finishTransition argument', function(assert) { - const resetPositionStub = sinon.stub(translator, 'resetPosition').callsFake(($element, finishTransition) => { + const resetPositionStub = stubSeam(translator, 'resetPosition', 'DEBUG_set_resetPosition').callsFake(($element, finishTransition) => { assert.equal(finishTransition, true, 'finishTransition is true'); }); diff --git a/packages/devextreme/testing/tests/DevExpress.animation/translator.tests.js b/packages/devextreme/testing/tests/DevExpress.animation/translator.tests.js index 795307a74539..d2e1bdd5e861 100644 --- a/packages/devextreme/testing/tests/DevExpress.animation/translator.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.animation/translator.tests.js @@ -1,7 +1,8 @@ -const $ = require('jquery'); -const translator = require('common/core/animation/translator'); -const styleUtils = require('core/utils/style'); -const transformStyle = styleUtils.styleProp('transform'); +import $ from 'jquery'; +import translator from 'common/core/animation/translator'; +import { styleProp } from 'core/utils/style'; + +const transformStyle = styleProp('transform'); QUnit.module('translator', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet.tests.js b/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet.tests.js index c61b8e313b3c..6d2fdbc87a94 100644 --- a/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet.tests.js @@ -1,39 +1,28 @@ -(function(factory) { - if(typeof define === 'function' && define.amd) { - define(function(require, exports, module) { - require('integration/jquery'), - require('ui/button'); - require('ui/check_box'); - require('ui/drop_down_button'); - require('ui/form'); - require('ui/popup'); - require('ui/select_box'); - require('ui/text_box'); - require('ui/toolbar'); - require('ui/validator'); - require('ui/validation_summary'); - - const aspnet = require('aspnet'); - window.DevExpress = { aspnet: aspnet }; // for DevExpress.aspnet.createComponent in templates - - module.exports = factory( - require('jquery'), - require('core/templates/template_engine_registry').setTemplateEngine, - aspnet, - function() { return require('ui/widget/ui.errors'); }, - function() { return require('../../helpers/ajaxMock.js'); } - ); - }); - } else { - factory( - window.jQuery, - DevExpress.setTemplateEngine, - DevExpress.aspnet, - function() { return window.DevExpress_ui_widget_errors; }, - function() { return window.ajaxMock; } - ); - } -}(function($, setTemplateEngine, aspnet, errorsAccessor, ajaxMockAccessor) { +import 'integration/jquery'; +import 'ui/button'; +import 'ui/check_box'; +import 'ui/drop_down_button'; +import 'ui/form'; +import 'ui/popup'; +import 'ui/select_box'; +import 'ui/text_box'; +import 'ui/toolbar'; +import 'ui/validator'; +import 'ui/validation_summary'; + +import $ from 'jquery'; +import { setTemplateEngine } from 'core/templates/template_engine_registry'; +import aspnetModule from 'aspnet'; +import errorsModule from 'ui/widget/ui.errors'; +import ajaxMock from '../../helpers/ajaxMock.js'; + +// Templates call DevExpress.aspnet.createComponent / renderComponent. +// MVC-style templates also expect global `$` (runner calls jQuery.noConflict()). +window.DevExpress = window.DevExpress || {}; +window.DevExpress.aspnet = aspnetModule; +window.$ = $; + +(function($, setTemplateEngine, aspnet, errorsAccessor, ajaxMockAccessor) { if(QUnit.urlParams['nojquery']) { return; @@ -696,4 +685,4 @@ }); }); -})); +})($, setTemplateEngine, aspnetModule, function() { return errorsModule; }, function() { return ajaxMock; }); diff --git a/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet_bundled.tests.js b/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet_bundled.tests.js index 3b91756424b0..9677bb02ef5e 100644 --- a/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet_bundled.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.aspnet/aspnet_bundled.tests.js @@ -1,7 +1,8 @@ -define(function(require) { - window.DevExpress_ui_widget_errors = require('ui/widget/ui.errors'); - window.ajaxMock = require('../../helpers/ajaxMock.js'); - require('bundles/dx.web.js'); - require('aspnet.js'); - require('./aspnet.tests.js'); -}); +import DevExpress_ui_widget_errors from 'ui/widget/ui.errors'; +import ajaxMock from '../../helpers/ajaxMock.js'; +import 'bundles/dx.web.js'; +import 'aspnet.js'; +import './aspnet.tests.js'; + +window.DevExpress_ui_widget_errors = DevExpress_ui_widget_errors; +window.ajaxMock = ajaxMock; diff --git a/packages/devextreme/testing/tests/DevExpress.common/charts.tests.js b/packages/devextreme/testing/tests/DevExpress.common/charts.tests.js index 8c3260a4ca9b..70458d3829cb 100644 --- a/packages/devextreme/testing/tests/DevExpress.common/charts.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.common/charts.tests.js @@ -1,16 +1,19 @@ import { registerPattern, registerGradient } from 'common/charts'; import graphicObjects from '__internal/common/charts'; -import utils from 'viz/core/utils_default'; +function clearGraphicObjects() { + const objects = graphicObjects.getGraphicObjects(); + Object.keys(objects).forEach((key) => { + delete objects[key]; + }); +} QUnit.module('Graphic objects', { beforeEach: function() { - this.getNextDefsStub = sinon.stub(utils, 'getNextDefsSvgId'); - this.getNextDefsStub.onCall(0).returns('DevExpressId_1'); - this.getNextDefsStub.onCall(1).returns('DevExpressId_2'); + clearGraphicObjects(); }, afterEach: function() { - this.getNextDefsStub.restore(); + clearGraphicObjects(); } }); @@ -18,12 +21,11 @@ QUnit.test('should register pattern', function(assert) { const id_1 = registerPattern({ key: 'test_key_1' }); const id_2 = registerPattern({ key: 'test_key_2' }); - assert.equal(this.getNextDefsStub.callCount, 2); - assert.equal(id_1, 'DevExpressId_1'); - assert.equal(id_2, 'DevExpressId_2'); + assert.ok(/^DevExpress_\d+$/.test(id_1), 'id has expected format'); + assert.notEqual(id_1, id_2, 'ids are unique'); assert.deepEqual(graphicObjects.getGraphicObjects(), { - 'DevExpressId_1': { key: 'test_key_1', type: 'pattern' }, - 'DevExpressId_2': { key: 'test_key_2', type: 'pattern' } + [id_1]: { key: 'test_key_1', type: 'pattern' }, + [id_2]: { key: 'test_key_2', type: 'pattern' } }); }); @@ -31,11 +33,10 @@ QUnit.test('should register gradient', function(assert) { const id_1 = registerGradient('gradient_type', { key: 'test_key_1' }); const id_2 = registerGradient('gradient_type', { key: 'test_key_2' }); - assert.equal(this.getNextDefsStub.callCount, 2); - assert.equal(id_1, 'DevExpressId_1'); - assert.equal(id_2, 'DevExpressId_2'); + assert.ok(/^DevExpress_\d+$/.test(id_1), 'id has expected format'); + assert.notEqual(id_1, id_2, 'ids are unique'); assert.deepEqual(graphicObjects.getGraphicObjects(), { - 'DevExpressId_1': { key: 'test_key_1', type: 'gradient_type' }, - 'DevExpressId_2': { key: 'test_key_2', type: 'gradient_type' } + [id_1]: { key: 'test_key_1', type: 'gradient_type' }, + [id_2]: { key: 'test_key_2', type: 'gradient_type' } }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.core/config_bundled.tests.js b/packages/devextreme/testing/tests/DevExpress.core/config_bundled.tests.js index 7800023e6c96..e7cdd80db419 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/config_bundled.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/config_bundled.tests.js @@ -3,14 +3,14 @@ const useJQuery = !QUnit.urlParams['nojquery']; window.DevExpress = window.DevExpress || {}; window.DevExpress.config = { useJQuery: useJQuery }; -define(function(require) { - QUnit.test = QUnit.urlParams['nocsp'] ? QUnit.test : QUnit.skip; - require('bundles/dx.all.js'); +// Must stay dynamic: static imports hoist above the config assignment. +await import('bundles/dx.all.js'); - QUnit.module('config.useJQuery'); +QUnit.test = QUnit.urlParams['nocsp'] ? QUnit.test : QUnit.skip; - QUnit.test('config value useJQuery with jQuery in window', function(assert) { - const config = DevExpress.config; - assert.equal(config().useJQuery, useJQuery); - }); +QUnit.module('config.useJQuery'); + +QUnit.test('config value useJQuery with jQuery in window', function(assert) { + const config = DevExpress.config; + assert.equal(config().useJQuery, useJQuery); }); diff --git a/packages/devextreme/testing/tests/DevExpress.core/config_bundled_nojquery.tests.js b/packages/devextreme/testing/tests/DevExpress.core/config_bundled_nojquery.tests.js index b239f8f42fc9..8425f89264bd 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/config_bundled_nojquery.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/config_bundled_nojquery.tests.js @@ -1,11 +1,10 @@ -define(function(require) { - QUnit.test = QUnit.urlParams['nocsp'] ? QUnit.test : QUnit.skip; - require('bundles/dx.all.js'); +import 'bundles/dx.all.js'; - QUnit.module('config.useJQuery'); +QUnit.test = QUnit.urlParams['nocsp'] ? QUnit.test : QUnit.skip; - QUnit.test('config value useJQuery should be false if jquery is not included', function(assert) { - const config = DevExpress.config; - assert.equal(!!config().useJQuery, false); - }); +QUnit.module('config.useJQuery'); + +QUnit.test('config value useJQuery should be false if jquery is not included', function(assert) { + const config = DevExpress.config; + assert.equal(!!config().useJQuery, false); }); diff --git a/packages/devextreme/testing/tests/DevExpress.core/config_nojquery.tests.js b/packages/devextreme/testing/tests/DevExpress.core/config_nojquery.tests.js index 72c3071ea903..6b822ccfe13f 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/config_nojquery.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/config_nojquery.tests.js @@ -1,4 +1,4 @@ -const config = require('core/config'); +import config from 'core/config'; QUnit.module('config.useJQuery'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/domAdapter.tests.js b/packages/devextreme/testing/tests/DevExpress.core/domAdapter.tests.js index 20220591e88f..4ecb6f729423 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/domAdapter.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/domAdapter.tests.js @@ -1,4 +1,4 @@ -const domAdapter = require('core/dom_adapter'); +import domAdapter from 'core/dom_adapter'; QUnit.module('DOM Adapter', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.core/elementData.tests.js b/packages/devextreme/testing/tests/DevExpress.core/elementData.tests.js index 613c39d0ff37..b570f85b7d76 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/elementData.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/elementData.tests.js @@ -1,4 +1,4 @@ -const dataUtils = require('core/element_data'); +import * as dataUtils from 'core/element_data'; QUnit.module('Data'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/guid.tests.js b/packages/devextreme/testing/tests/DevExpress.core/guid.tests.js index a1084e1afab6..d34fc88c539a 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/guid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/guid.tests.js @@ -1,4 +1,4 @@ -const Guid = require('core/guid'); +import Guid from 'core/guid'; QUnit.module('Guid'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/namespace.tests.js b/packages/devextreme/testing/tests/DevExpress.core/namespace.tests.js index ff21a6088e2b..43f157d18be8 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/namespace.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/namespace.tests.js @@ -1,7 +1,7 @@ -const testGlobalExports = require('../../helpers/publicModulesHelper.js'); -const { version } = require('core/version'); +import testGlobalExports from '../../helpers/publicModulesHelper.js'; +import { version } from 'core/version'; -require('bundles/modules/core'); +import 'bundles/modules/core'; testGlobalExports({ 'DevExpress': DevExpress diff --git a/packages/devextreme/testing/tests/DevExpress.core/renderer.tests.js b/packages/devextreme/testing/tests/DevExpress.core/renderer.tests.js index 81e4982aa60e..20fef36c5dd6 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/renderer.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/renderer.tests.js @@ -1,5 +1,5 @@ -const { setHeight, setWidth, implementationsMap } = require('core/utils/size'); -const renderer = require('core/renderer'); +import { setHeight, setWidth, implementationsMap } from 'core/utils/size'; +import renderer from 'core/renderer'; QUnit.module('renderer'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.ajax.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.ajax.tests.js index e5cffcd95c9f..2ddc16fb90c3 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.ajax.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.ajax.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); -const ajax = require('core/utils/ajax'); -const compareVersion = require('core/utils/version').compare; +import $ from 'jquery'; +import ajax from 'core/utils/ajax'; +import { compare as compareVersion } from 'core/utils/version'; QUnit.test = QUnit.urlParams['nocsp'] ? QUnit.test : QUnit.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.animationFrame.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.animationFrame.tests.js index 4fcc57b708d0..cb656949677c 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.animationFrame.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.animationFrame.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); -const animationFrame = require('common/core/animation/frame'); -const coreUtilsType = require('core/utils/type'); +import $ from 'jquery'; +import * as animationFrame from 'common/core/animation/frame'; +import * as coreUtilsType from 'core/utils/type'; QUnit.module('animation frame'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.browser.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.browser.tests.js index 3b130918c3e8..73f57f1e2e9e 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.browser.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.browser.tests.js @@ -1,4 +1,4 @@ -const browser = require('core/utils/browser'); +import browser from 'core/utils/browser'; const userAgents = { webkit: 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Custom/43.0.2357.124', diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.callOnce.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.callOnce.tests.js index 40d37d5e9842..25f6af0c10ec 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.callOnce.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.callOnce.tests.js @@ -1,4 +1,4 @@ -const callOnce = require('core/utils/call_once'); +import callOnce from 'core/utils/call_once'; QUnit.module('callOnce'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.callbacks.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.callbacks.tests.js index 655765914043..7979d8f50a12 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.callbacks.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.callbacks.tests.js @@ -1,4 +1,4 @@ -const Callbacks = require('core/utils/callbacks'); +import Callbacks from 'core/utils/callbacks'; QUnit.module('Methods', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.date.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.date.tests.js index 33b63f0f13cc..57094138f274 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.date.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.date.tests.js @@ -1,4 +1,4 @@ -const dateUtils = require('core/utils/date'); +import dateUtils from 'core/utils/date'; const WEEK_DAYS = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.date_parser.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.date_parser.tests.js index 0f362cd3cf90..2adb57a35cb8 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.date_parser.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.date_parser.tests.js @@ -1,5 +1,5 @@ -const dateSerialization = require('core/utils/date_serialization'); -const config = require('core/config'); +import dateSerialization from 'core/utils/date_serialization'; +import config from 'core/config'; QUnit.module('Default DX Formats'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.date_serialization.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.date_serialization.tests.js index 52caaf89815c..1ef84b2d5312 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.date_serialization.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.date_serialization.tests.js @@ -1,5 +1,5 @@ -const dateSerialization = require('core/utils/date_serialization'); -const config = require('core/config'); +import dateSerialization from 'core/utils/date_serialization'; +import config from 'core/config'; QUnit.module('date serialization tests', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.deferred.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.deferred.tests.js index 00e03da93ee7..59d3b42bb705 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.deferred.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.deferred.tests.js @@ -1,12 +1,9 @@ -define(function(require) { - const isFunction = require('core/utils/type').isFunction; - const deferredUtils = require('core/utils/deferred'); - const Deferred = deferredUtils.Deferred; +import { isFunction } from 'core/utils/type'; +import * as deferredUtils from 'core/utils/deferred'; - if(!QUnit.urlParams['nojquery']) { - return; - } +const { Deferred } = deferredUtils; +if(QUnit.urlParams['nojquery']) { QUnit.module('when'); QUnit.test('when should be resolved synchronously', function(assert) { @@ -491,4 +488,4 @@ define(function(require) { deferred.resolve(1); }); -}); +} diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.dependencyInjector.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.dependencyInjector.tests.js index e0038433ce04..0c128e2bba9c 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.dependencyInjector.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.dependencyInjector.tests.js @@ -1,4 +1,4 @@ -const dependencyInjector = require('core/utils/dependency_injector'); +import dependencyInjector from 'core/utils/dependency_injector'; QUnit.module('dependencyInjector'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.icon.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.icon.tests.js index 2956a9598ff1..c61c0e8a06b9 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.icon.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.icon.tests.js @@ -1,4 +1,7 @@ import { getImageSourceType, getImageContainer } from 'core/utils/icon'; +import { getImageAriaLabel } from '__internal/core/utils/m_icon'; +import localization from 'localization'; +import ja from 'localization/messages/ja.json!'; const { module: testModule, test } = QUnit; @@ -144,3 +147,36 @@ testModule('icon utils', { }); }); }); + +testModule('getImageAriaLabel', () => { + [ + { source: 'close', expected: 'Close', description: 'a dxIcon with a localized message' }, + { source: 'iconName', expected: 'iconName', description: 'a dxIcon without a localized message' }, + { source: 'fa fa-home', expected: 'fa fa-home', description: 'a font icon' }, + { source: '/path/file.png', expected: 'file', description: 'a path to an image' }, + { source: 'https://example.com/path/file.png', expected: undefined, description: 'an image URL' }, + { source: 'www.example.com/file.png', expected: undefined, description: 'an image URL without a protocol' }, + { source: 'data:image/png;base64,qwerty', expected: undefined, description: 'a base64 image' }, + { source: 'Svg title', expected: 'Svg title', description: 'an svg with a title' }, + { source: '', expected: undefined, description: 'an svg with an empty title' }, + { source: '', expected: undefined, description: 'an svg without a title' }, + { source: '', expected: undefined, description: 'an empty string' }, + ].forEach(({ source, expected, description }) => { + test(`should return ${JSON.stringify(expected) || 'undefined'} for ${description}`, function(assert) { + assert.strictEqual(getImageAriaLabel(source), expected); + }); + }); + + test('should localize the dxIcon name with the current locale', function(assert) { + const defaultLocale = localization.locale(); + + try { + localization.loadMessages(ja); + localization.locale('ja'); + + assert.strictEqual(getImageAriaLabel('close'), '閉じる'); + } finally { + localization.locale(defaultLocale); + } + }); +}); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.inflector.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.inflector.tests.js index d89fb09febc9..d3821edba406 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.inflector.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.inflector.tests.js @@ -1,4 +1,4 @@ -const inflector = require('core/utils/inflector'); +import * as inflector from 'core/utils/inflector'; QUnit.module('inflector'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.math.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.math.tests.js index 08bad03e0d61..a6a12f9e637e 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.math.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.math.tests.js @@ -1,4 +1,4 @@ -const mathUtils = require('core/utils/math'); +import * as mathUtils from 'core/utils/math'; const adjust = mathUtils.adjust; QUnit.test('fitIntoRange', function(assert) { diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.object.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.object.tests.js index ee2de0cafc59..8626f70f98e6 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.object.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.object.tests.js @@ -1,4 +1,4 @@ -const objectUtils = require('core/utils/object'); +import * as objectUtils from 'core/utils/object'; QUnit.test('orderEach', function(assert) { const checkOrderEach = function(mapKeys, keys) { diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.queue.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.queue.tests.js index e45b3e012d1e..4b9152f6ab6f 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.queue.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.queue.tests.js @@ -1,5 +1,5 @@ -const $ = require('jquery'); -const queueUtils = require('core/utils/queue'); +import $ from 'jquery'; +import * as queueUtils from 'core/utils/queue'; QUnit.module('enqueue'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.ready_callbacks.js b/packages/devextreme/testing/tests/DevExpress.core/utils.ready_callbacks.js index d34014597f44..d5b31ae3320e 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.ready_callbacks.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.ready_callbacks.js @@ -1,4 +1,4 @@ -const readyCallbacks = require('core/utils/ready_callbacks'); +import readyCallbacks from 'core/utils/ready_callbacks'; QUnit.module('readyCallbacks injection', { afterEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.size.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.size.tests.js index 9869807eb0b3..1fd14a2d9351 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.size.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.size.tests.js @@ -1,5 +1,8 @@ import $ from 'jquery'; -import sizeUtils, { getHeight, getWidth, getInnerHeight, getInnerWidth, getOuterHeight, getOuterWidth } from 'core/utils/size'; +import { + getHeight, getWidth, getInnerHeight, getInnerWidth, getOuterHeight, getOuterWidth, + getElementBoxParams, addOffsetToMaxHeight, addOffsetToMinHeight, getVerticalOffsets, getVisibleHeight, +} from 'core/utils/size'; import browser from 'core/utils/browser'; const testStyles = [ @@ -268,13 +271,13 @@ QUnit.test('element in parent with fixed size', function(assert) { const computedStyles = window.getComputedStyle(element); - assert.deepEqual(sizeUtils.getElementBoxParams('width', computedStyles), { + assert.deepEqual(getElementBoxParams('width', computedStyles), { border: 2, margin: 12, padding: 8 }, 'element borders, paddings and margins were computed correctly'); - assert.deepEqual(sizeUtils.getElementBoxParams('height', computedStyles), { + assert.deepEqual(getElementBoxParams('height', computedStyles), { border: 2, margin: 10, padding: 6 @@ -297,7 +300,7 @@ QUnit.module('calculate height', { QUnit.test('check addOffsetToMaxHeight', function(assert) { const checkFunc = ({ value, offset, container }, expected) => { - assert.strictEqual(sizeUtils.addOffsetToMaxHeight(value, offset, container), expected); + assert.strictEqual(addOffsetToMaxHeight(value, offset, container), expected); }; checkFunc({ value: 300, offset: 0, container: null }, 300); @@ -310,13 +313,13 @@ QUnit.test('check addOffsetToMaxHeight', function(assert) { checkFunc({ value: 'auto', offset: 0, container: null }, 'auto'); checkFunc({ value: null, offset: -50, container: null }, 'none'); - assert.roughEqual(sizeUtils.addOffsetToMaxHeight('50%', -20, window), windowHeight / 2 - 20, 1, 'string value in percent'); - assert.roughEqual(sizeUtils.addOffsetToMaxHeight('50%', -20, this.container), 30, 1, 'string value in percent with specific container'); + assert.roughEqual(addOffsetToMaxHeight('50%', -20, window), windowHeight / 2 - 20, 1, 'string value in percent'); + assert.roughEqual(addOffsetToMaxHeight('50%', -20, this.container), 30, 1, 'string value in percent with specific container'); }); QUnit.test('check addOffsetToMinHeight', function(assert) { const checkFunc = ({ value, offset, container }, expected) => { - assert.strictEqual(sizeUtils.addOffsetToMinHeight(value, offset, container), expected); + assert.strictEqual(addOffsetToMinHeight(value, offset, container), expected); }; checkFunc({ value: 300, offset: 0, container: null }, 300); @@ -329,21 +332,21 @@ QUnit.test('check addOffsetToMinHeight', function(assert) { checkFunc({ value: 'auto', offset: 0, container: null }, 'auto'); checkFunc({ value: null, offset: -50, container: null }, 0); - assert.roughEqual(sizeUtils.addOffsetToMinHeight('50%', -20, window), windowHeight / 2 - 20, 1, 'string value in percent'); - assert.roughEqual(sizeUtils.addOffsetToMaxHeight('50%', -20, this.container), 30, 1, 'string value in percent with specific container'); + assert.roughEqual(addOffsetToMinHeight('50%', -20, window), windowHeight / 2 - 20, 1, 'string value in percent'); + assert.roughEqual(addOffsetToMaxHeight('50%', -20, this.container), 30, 1, 'string value in percent with specific container'); }); QUnit.test('check getVerticalOffsets', function(assert) { - assert.strictEqual(sizeUtils.getVerticalOffsets(null), 0, 'no element'); - assert.strictEqual(sizeUtils.getVerticalOffsets(this.container), 20, 'container paddings'); - assert.strictEqual(sizeUtils.getVerticalOffsets(this.container, true), 30, 'include margins'); - assert.strictEqual(sizeUtils.getVerticalOffsets(this.invisibleElement), 10, 'invisible element paddings'); + assert.strictEqual(getVerticalOffsets(null), 0, 'no element'); + assert.strictEqual(getVerticalOffsets(this.container), 20, 'container paddings'); + assert.strictEqual(getVerticalOffsets(this.container, true), 30, 'include margins'); + assert.strictEqual(getVerticalOffsets(this.invisibleElement), 10, 'invisible element paddings'); }); QUnit.test('check getVisibleHeight', function(assert) { - assert.strictEqual(sizeUtils.getVerticalOffsets(null), 0, 'no element'); - assert.strictEqual(sizeUtils.getVisibleHeight(this.container), 100, 'container height'); - assert.strictEqual(sizeUtils.getVisibleHeight(this.invisibleElement), 0, 'invisible element height'); + assert.strictEqual(getVerticalOffsets(null), 0, 'no element'); + assert.strictEqual(getVisibleHeight(this.container), 100, 'container height'); + assert.strictEqual(getVisibleHeight(this.invisibleElement), 0, 'invisible element height'); }); QUnit.test('height for element with transform', function(assert) { diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.string.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.string.tests.js index 7d741058029f..dd40da14f95f 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.string.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.string.tests.js @@ -1,4 +1,4 @@ -const stringUtils = require('core/utils/string'); +import * as stringUtils from 'core/utils/string'; QUnit.module('String utils'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.topOverlay.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.topOverlay.tests.js index e166fe4a77b8..4a16594e8e4b 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.topOverlay.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.topOverlay.tests.js @@ -1,5 +1,5 @@ -const hideTopOverlay = require('common/core/environment/hide_top_overlay'); -const hideTopOverlayCallback = require('common/core/environment/hide_callback').hideCallback; +import hideTopOverlay from 'common/core/environment/hide_top_overlay'; +import { hideCallback as hideTopOverlayCallback } from 'common/core/environment/hide_callback'; QUnit.module('top overlay util'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.type.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.type.tests.js index 64fc102e8fbf..f9b83d4d1f72 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.type.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.type.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const typeUtils = require('core/utils/type'); -const Deferred = require('core/utils/deferred').Deferred; -const renderer = require('core/renderer'); -const eventsEngine = require('common/core/events/core/events_engine'); +import $ from 'jquery'; +import * as typeUtils from 'core/utils/type'; +import { Deferred } from 'core/utils/deferred'; +import renderer from 'core/renderer'; +import eventsEngine from 'common/core/events/core/events_engine'; QUnit.module('Type checking'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.variableWrapper.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.variableWrapper.tests.js index 6826b0d54b22..2d501deaa2bd 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.variableWrapper.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.variableWrapper.tests.js @@ -1,5 +1,5 @@ -const variableWrapper = require('core/utils/variable_wrapper'); -const { logger } = require('core/utils/console'); +import variableWrapper from 'core/utils/variable_wrapper'; +import { logger } from 'core/utils/console'; QUnit.test('Base wrapper methods', function(assert) { assert.strictEqual(variableWrapper.isWrapped(3), false, 'isWrapped method'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.version.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.version.tests.js index 52ac8ad732c5..7d92557fef71 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.version.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.version.tests.js @@ -1,4 +1,4 @@ -const compare = require('core/utils/version').compare; +import { compare } from 'core/utils/version'; QUnit.module('version'); diff --git a/packages/devextreme/testing/tests/DevExpress.core/utils.viewPort.tests.js b/packages/devextreme/testing/tests/DevExpress.core/utils.viewPort.tests.js index d221fc4188d7..2764eb8ea107 100644 --- a/packages/devextreme/testing/tests/DevExpress.core/utils.viewPort.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.core/utils.viewPort.tests.js @@ -1,5 +1,5 @@ -const $ = require('jquery'); -const viewPortUtils = require('core/utils/view_port'); +import $ from 'jquery'; +import * as viewPortUtils from 'core/utils/view_port'; const viewPort = viewPortUtils.value; const viewPortChanged = viewPortUtils.changeCallback; diff --git a/packages/devextreme/testing/tests/DevExpress.data/odataCommonOData.tests.js b/packages/devextreme/testing/tests/DevExpress.data/odataCommonOData.tests.js index 65714c62c3a7..59857ba336d3 100644 --- a/packages/devextreme/testing/tests/DevExpress.data/odataCommonOData.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.data/odataCommonOData.tests.js @@ -1,8 +1,9 @@ -const $ = require('jquery'); -const converters = require('common/data/odata/utils').keyConverters; -const interpretJsonFormat = require('common/data/odata/utils').OData__internals.interpretJsonFormat; -const Guid = require('core/guid'); -const typeUtils = require('core/utils/type'); +import $ from 'jquery'; +import { keyConverters as converters, OData__internals } from 'common/data/odata/utils'; +import Guid from 'core/guid'; +import { isNumeric } from 'core/utils/type'; + +const interpretJsonFormat = OData__internals.interpretJsonFormat; QUnit.module('OData 2'); QUnit.test('key converters', function(assert) { @@ -83,10 +84,10 @@ QUnit.test('count', function(assert) { const t4 = interpretJsonFormat(a4, 'success'); assert.equal(t1.count, 3); - assert.ok(typeUtils.isNumeric(t1.count)); + assert.ok(isNumeric(t1.count)); assert.equal(t2.count, 3); - assert.ok(typeUtils.isNumeric(t2.count)); + assert.ok(isNumeric(t2.count)); assert.ok(!t3.count); assert.ok(!t4.count); @@ -173,10 +174,10 @@ QUnit.test('count', function(assert) { const t4 = interpretJsonFormat(a4, 'success'); assert.equal(t1.count, 3); - assert.ok(typeUtils.isNumeric(t1.count)); + assert.ok(isNumeric(t1.count)); assert.equal(t2.count, 3); - assert.ok(typeUtils.isNumeric(t2.count)); + assert.ok(isNumeric(t2.count)); assert.ok(!t3.count); assert.ok(!t4.count); diff --git a/packages/devextreme/testing/tests/DevExpress.data/storeCustom.tests.js b/packages/devextreme/testing/tests/DevExpress.data/storeCustom.tests.js index 77d783ec7864..9df83eb8ee6e 100644 --- a/packages/devextreme/testing/tests/DevExpress.data/storeCustom.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.data/storeCustom.tests.js @@ -1,8 +1,10 @@ -const $ = require('jquery'); -const CustomStore = require('common/data/custom_store').CustomStore; -const { isLoadResultObject, isGroupItemsArray, isItemsArray } = require('common/data/custom_store'); -const processRequestResultLock = require('common/data/utils').processRequestResultLock; -const config = require('core/config'); +import $ from 'jquery'; +import { CustomStore, isLoadResultObject, isGroupItemsArray, isItemsArray } from 'common/data/custom_store'; +import { processRequestResultLock } from 'common/data/utils'; +import config from 'core/config'; +import ErrorHandlingHelper from '../../helpers/data.errorHandlingHelper.js'; +import ajaxMock from '../../helpers/ajaxMock.js'; + const ERRORS = { INVALID_RETURN: 'E4012', MISSING_USER_FUNC: 'E4011', @@ -10,8 +12,6 @@ const ERRORS = { QUERY_NOT_SUPPORTED: 'E4010', REQUEST_ERROR: 'E4013' }; -const ErrorHandlingHelper = require('../../helpers/data.errorHandlingHelper.js'); -const ajaxMock = require('../../helpers/ajaxMock.js'); QUnit.testDone(function() { ajaxMock.clear(); diff --git a/packages/devextreme/testing/tests/DevExpress.data/storeCustom_loadModeRaw.tests.js b/packages/devextreme/testing/tests/DevExpress.data/storeCustom_loadModeRaw.tests.js index 1d77cb003870..dfe93222a34e 100644 --- a/packages/devextreme/testing/tests/DevExpress.data/storeCustom_loadModeRaw.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.data/storeCustom_loadModeRaw.tests.js @@ -1,5 +1,5 @@ -const CustomStore = require('common/data/custom_store').CustomStore; -const ErrorHandlingHelper = require('../../helpers/data.errorHandlingHelper.js'); +import { CustomStore } from 'common/data/custom_store'; +import ErrorHandlingHelper from '../../helpers/data.errorHandlingHelper.js'; const RAW = 'raw'; diff --git a/packages/devextreme/testing/tests/DevExpress.data/storeLocal.tests.js b/packages/devextreme/testing/tests/DevExpress.data/storeLocal.tests.js index 84d9f5ef2c2c..98f2a6ce1387 100644 --- a/packages/devextreme/testing/tests/DevExpress.data/storeLocal.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.data/storeLocal.tests.js @@ -1,4 +1,4 @@ -const LocalStore = require('common/data/local_store'); +import LocalStore from 'common/data/local_store'; const TEST_NAME = '65DFE188-D178-11E1-A097-51216288709B'; const DX_LOCALSTORAGE_ITEM_NAME = 'dx-data-localStore-' + TEST_NAME; diff --git a/packages/devextreme/testing/tests/DevExpress.exporter/pdfCreator.tests.js b/packages/devextreme/testing/tests/DevExpress.exporter/pdfCreator.tests.js index 0019aba4340f..455daabecc4f 100644 --- a/packages/devextreme/testing/tests/DevExpress.exporter/pdfCreator.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.exporter/pdfCreator.tests.js @@ -1,10 +1,12 @@ -const $ = require('jquery'); -const version = require('core/version').version; -const getData = require('exporter').pdf.getData; -const pdfCreator = require('__internal/exporter/pdf_creator').__tests; -const isFunction = require('core/utils/type').isFunction; -const imageCreator = require('__internal/exporter/image_creator').imageCreator; -const getWindow = require('core/utils/window').getWindow; +import $ from 'jquery'; +import { version } from 'core/version'; +import { pdf } from 'exporter'; +import { __tests as pdfCreator } from '__internal/exporter/pdf_creator'; +import { isFunction } from 'core/utils/type'; +import { imageCreator } from '__internal/exporter/image_creator'; +import { getWindow } from 'core/utils/window'; + +const getData = pdf.getData; const window = getWindow(); const ASN_DATE_REGEX = /CreationDate\s\(D:([0-9]+)Z([0-9]+)'([0-9]+)'/; diff --git a/packages/devextreme/testing/tests/DevExpress.exporter/svgCreator.tests.js b/packages/devextreme/testing/tests/DevExpress.exporter/svgCreator.tests.js index 07af2a88a0a1..f52588a6dc00 100644 --- a/packages/devextreme/testing/tests/DevExpress.exporter/svgCreator.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.exporter/svgCreator.tests.js @@ -1,8 +1,10 @@ -const $ = require('jquery'); -const isFunction = require('core/utils/type').isFunction; -const exporter = require('exporter').svg; +import $ from 'jquery'; +import { isFunction } from 'core/utils/type'; +import { svg } from 'exporter'; +import * as svgUtils from 'core/utils/svg'; + +const exporter = svg; const svgCreator = exporter.creator; -const svgUtils = require('core/utils/svg'); function setupCanvasStub() { // Blob diff --git a/packages/devextreme/testing/tests/DevExpress.jquery/bundled.tests.js b/packages/devextreme/testing/tests/DevExpress.jquery/bundled.tests.js index 0fc86f98a8e9..26ba47daecfb 100644 --- a/packages/devextreme/testing/tests/DevExpress.jquery/bundled.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.jquery/bundled.tests.js @@ -1,18 +1,13 @@ -define(function(require) { - if(QUnit.urlParams['nojquery']) { - return; - } - - const $ = require('jquery'); - - require('bundles/dx.all.js'); - +import $ from 'jquery'; +import 'integration/jquery'; +import dxButton from 'ui/button'; +if(!QUnit.urlParams['nojquery']) { QUnit.module('jquery integration'); QUnit.test('renderer uses correct strategy', function(assert) { const node = document.createElement('div'); - const element = new DevExpress.ui.dxButton(node).element(); + const element = new dxButton(node).element(); assert.ok(element instanceof window.jQuery); }); @@ -22,4 +17,4 @@ define(function(require) { assert.equal(typeof $element.dxButton, 'function'); }); -}); +} diff --git a/packages/devextreme/testing/tests/DevExpress.jquery/selectors.tests.js b/packages/devextreme/testing/tests/DevExpress.jquery/selectors.tests.js index 1fa633d8d503..3e03a4f8c55f 100644 --- a/packages/devextreme/testing/tests/DevExpress.jquery/selectors.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.jquery/selectors.tests.js @@ -1,10 +1,7 @@ -define(function(require) { - const $ = require('jquery'); - - if(QUnit.urlParams['nojquery']) { - return; - } +import $ from 'jquery'; +import selectors from '__internal/core/utils/m_selectors'; +if(!QUnit.urlParams['nojquery']) { QUnit.testStart(function() { const markup = `
@@ -49,8 +46,6 @@ define(function(require) { $('#qunit-fixture').html(markup); }); - const selectors = require('__internal/core/utils/m_selectors'); - QUnit.test('focusable', function(assert) { const focusableContainer = $('.focusable'); focusableContainer.each(function(index, item) { @@ -78,4 +73,4 @@ define(function(require) { assert.ok(!$(item).is(selectors.tabbable)); }); }); -}); +} diff --git a/packages/devextreme/testing/tests/DevExpress.jquery/template.tests.js b/packages/devextreme/testing/tests/DevExpress.jquery/template.tests.js index c072d222b479..c36c17a61804 100644 --- a/packages/devextreme/testing/tests/DevExpress.jquery/template.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.jquery/template.tests.js @@ -1,134 +1,126 @@ -SystemJS.config({ - map: { - 'jqueryify': SystemJS.map.jquery +import $ from 'jquery'; +import { Template } from 'core/templates/template'; +import { setTemplateEngine } from 'core/templates/template_engine_registry'; +import errors from 'core/errors'; + +QUnit.module('custom template rendering', { + beforeEach: function() { + this.originalLog = errors.log; + }, + afterEach: function() { + errors.log = this.originalLog; } }); -define(function(require) { - const $ = require('jquery'); - const Template = require('core/templates/template').Template; - const setTemplateEngine = require('core/templates/template_engine_registry').setTemplateEngine; - const errors = require('core/errors'); - - QUnit.module('custom template rendering', { - beforeEach: function() { - this.originalLog = errors.log; - }, - afterEach: function() { - errors.log = this.originalLog; +QUnit.module('user template engine'); + +const customUserTemplate = { + compile: function(element) { + element = $(element); + if(element[0].nodeName.toLowerCase() !== 'script') { + element = $('
').append(element); } - }); - QUnit.module('user template engine'); + const text = element.html(); - const customUserTemplate = { - compile: function(element) { - element = $(element); - if(element[0].nodeName.toLowerCase() !== 'script') { - element = $('
').append(element); + return text.split('$'); + }, + render: function(template, data, index) { + let i; + const result = template.slice(0); + for(i = 0; i < template.length; i++) { + if(template[i] in data) { + result[i] = data[template[i]]; } - - const text = element.html(); - - return text.split('$'); - }, - render: function(template, data, index) { - let i; - const result = template.slice(0); - for(i = 0; i < template.length; i++) { - if(template[i] in data) { - result[i] = data[template[i]]; - } - if(template[i] === '@index') { - result[i] = index; - } + if(template[i] === '@index') { + result[i] = index; } - return result.join(''); } - }; + return result.join(''); + } +}; - QUnit.test('custom user template engine for div template', function(assert) { - setTemplateEngine(customUserTemplate); +QUnit.test('custom user template engine for div template', function(assert) { + setTemplateEngine(customUserTemplate); - const template = new Template($('
$text$
')); - const container = $('
'); + const template = new Template($('
$text$
')); + const container = $('
'); - template.render({ model: { text: 123 }, container: container }); + template.render({ model: { text: 123 }, container: container }); - assert.equal(container.children().length, 1); - assert.equal(container.children().text(), '123'); - }); + assert.equal(container.children().length, 1); + assert.equal(container.children().text(), '123'); +}); - QUnit.test('custom user template engine for script template', function(assert) { - setTemplateEngine(customUserTemplate); +QUnit.test('custom user template engine for script template', function(assert) { + setTemplateEngine(customUserTemplate); - const template = new Template($('')); - const container = $('
'); + const template = new Template($('')); + const container = $('
'); - template.render({ model: { text: 123 }, container: container }); + template.render({ model: { text: 123 }, container: container }); - assert.equal(container.children('b').length, 1); - assert.equal(container.text().replace('\r\n', ''), 'Text: 123'); - }); + assert.equal(container.children('b').length, 1); + assert.equal(container.text().replace('\r\n', ''), 'Text: 123'); +}); - QUnit.test('custom user template engine has access to item index', function(assert) { - setTemplateEngine(customUserTemplate); +QUnit.test('custom user template engine has access to item index', function(assert) { + setTemplateEngine(customUserTemplate); - const template = new Template($('
$text$, ($@index$)
')); - const container = $('
'); + const template = new Template($('
$text$, ($@index$)
')); + const container = $('
'); - template.render({ model: { text: 123 }, container: container, index: 1 }); + template.render({ model: { text: 123 }, container: container, index: 1 }); - assert.equal(container.children().text(), '123, (1)'); - }); + assert.equal(container.children().text(), '123, (1)'); +}); - QUnit.test('removing div template from document on creation', function(assert) { - setTemplateEngine(customUserTemplate); +QUnit.test('removing div template from document on creation', function(assert) { + setTemplateEngine(customUserTemplate); - const template = new Template($('
$text$
')); - const container = $('
'); + const template = new Template($('
$text$
')); + const container = $('
'); - template.render({ model: { text: 123 }, container: container }); + template.render({ model: { text: 123 }, container: container }); - assert.equal(container.children().length, 1); - assert.equal(container.children().text(), '123'); - }); + assert.equal(container.children().length, 1); + assert.equal(container.children().text(), '123'); +}); - QUnit.test('template render result', function(assert) { - setTemplateEngine(customUserTemplate); +QUnit.test('template render result', function(assert) { + setTemplateEngine(customUserTemplate); - const template = new Template($('
$text$
')); - const container = $('
'); + const template = new Template($('
$text$
')); + const container = $('
'); - let result = template.render({ model: { text: 123 }, container: container }); + let result = template.render({ model: { text: 123 }, container: container }); - result = $(result); + result = $(result); - assert.equal(result.length, 1); - assert.equal(result[0].tagName.toLowerCase(), 'div'); - assert.equal(result.text(), '123'); - }); + assert.equal(result.length, 1); + assert.equal(result[0].tagName.toLowerCase(), 'div'); + assert.equal(result.text(), '123'); +}); - QUnit.module('default template engine', { - beforeEach: function() { - setTemplateEngine('default'); - } - }); +QUnit.module('default template engine', { + beforeEach: function() { + setTemplateEngine('default'); + } +}); - QUnit.test('default template engine should clone element', function(assert) { - const $element = $('
123
'); - const template = new Template($element); - const $result = template.render({ model: null, container: $('
') }); +QUnit.test('default template engine should clone element', function(assert) { + const $element = $('
123
'); + const template = new Template($element); + const $result = template.render({ model: null, container: $('
') }); - assert.notEqual($result[0], $element[0]); - }); + assert.notEqual($result[0], $element[0]); +}); - QUnit.test('default template engine should preserve element for transcluded templates', function(assert) { - const $element = $('
123
'); - const template = new Template($element); - const $result = template.render({ model: null, container: $('
'), transclude: true }); +QUnit.test('default template engine should preserve element for transcluded templates', function(assert) { + const $element = $('
123
'); + const template = new Template($element); + const $result = template.render({ model: null, container: $('
'), transclude: true }); - assert.equal($result[0], $element[0]); - }); + assert.equal($result[0], $element[0]); }); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/accordion.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/accordion.tests.js index 1aa8ec8d966b..73fb8de9fa96 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/accordion.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/accordion.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/accordion'); -require('integration/knockout'); +import 'ui/accordion'; +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('accordion'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/actionSheet.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/actionSheet.tests.js index 915242f80339..7f9b0f8be278 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/actionSheet.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/actionSheet.tests.js @@ -1,9 +1,9 @@ -const $ = require('jquery'); -const noop = require('core/utils/common').noop; -const ko = require('knockout'); +import $ from 'jquery'; +import { noop } from 'core/utils/common'; +import ko from 'knockout'; -require('ui/action_sheet'); -require('integration/knockout'); +import 'ui/action_sheet'; +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('actionSheet'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/autocomplete.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/autocomplete.tests.js index 75e68a92ef42..051095d87f9e 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/autocomplete.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/autocomplete.tests.js @@ -1,9 +1,9 @@ -const $ = require('jquery'); -const keyboardMock = require('../../helpers/keyboardMock.js'); -const ko = require('knockout'); -const Autocomplete = require('ui/autocomplete'); +import $ from 'jquery'; +import keyboardMock from '../../helpers/keyboardMock.js'; +import ko from 'knockout'; +import Autocomplete from 'ui/autocomplete'; -require('integration/knockout'); +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('autocomplete'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/box.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/box.tests.js index dcf294de0542..895ea354435f 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/box.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/box.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/box'); -require('integration/knockout'); +import 'ui/box'; +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('box'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/calendar.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/calendar.tests.js index 67b304370855..36c48366dbfa 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/calendar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/calendar.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('integration/knockout'); -require('ui/calendar'); +import 'integration/knockout'; +import 'ui/calendar'; if(QUnit.urlParams['nocsp']) { QUnit.module('calendar'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/cleanNode.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/cleanNode.tests.js index 09d033a2cb0f..18cfac3f7b09 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/cleanNode.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/cleanNode.tests.js @@ -1,8 +1,8 @@ -require('integration/knockout'); +import 'integration/knockout'; -const $ = require('jquery'); -const ko = require('knockout'); -const dataUtils = require('core/element_data'); +import $ from 'jquery'; +import ko from 'knockout'; +import * as dataUtils from 'core/element_data'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/collectionWidget.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/collectionWidget.tests.js index 85fc6c1a9364..782302d79e69 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/collectionWidget.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/collectionWidget.tests.js @@ -1,10 +1,10 @@ -const $ = require('jquery'); -const ko = require('knockout'); -const registerComponent = require('core/component_registrator'); -const CollectionWidget = require('ui/collection/ui.collection_widget.edit'); -const executeAsyncMock = require('../../helpers/executeAsyncMock.js'); +import $ from 'jquery'; +import ko from 'knockout'; +import registerComponent from 'core/component_registrator'; +import CollectionWidget from 'ui/collection/ui.collection_widget.edit'; +import executeAsyncMock from '../../helpers/executeAsyncMock.js'; -require('integration/knockout'); +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/componentRegistration.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/componentRegistration.tests.js index f50865a4f32a..34b6cf81b199 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/componentRegistration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/componentRegistration.tests.js @@ -1,17 +1,17 @@ -const $ = require('jquery'); -const noop = require('core/utils/common').noop; -const ko = require('knockout'); -const registerComponent = require('core/component_registrator'); -const DOMComponent = require('core/dom_component'); -const Widget = require('ui/widget/ui.widget'); -const KoTemplate = require('__internal/integration/knockout/template').KoTemplate; -const CollectionWidget = require('ui/collection/ui.collection_widget.edit'); -const config = require('core/config'); -const dataUtils = require('core/element_data'); - -require('ui/select_box'); -require('ui/lookup'); -require('integration/knockout'); +import $ from 'jquery'; +import { noop } from 'core/utils/common'; +import ko from 'knockout'; +import registerComponent from 'core/component_registrator'; +import DOMComponent from 'core/dom_component'; +import Widget from 'ui/widget/ui.widget'; +import { KoTemplate } from '__internal/integration/knockout/template'; +import CollectionWidget from 'ui/collection/ui.collection_widget.edit'; +import config from 'core/config'; +import * as dataUtils from 'core/element_data'; + +import 'ui/select_box'; +import 'ui/lookup'; +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/contextMenu.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/contextMenu.tests.js index 2e00952a03ae..007675bd338a 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/contextMenu.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/contextMenu.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/context_menu'); -require('integration/knockout'); +import 'ui/context_menu'; +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('contextMenu'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/draggable.test.js b/packages/devextreme/testing/tests/DevExpress.knockout/draggable.test.js index 158144782ee3..3fd767fb6891 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/draggable.test.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/draggable.test.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/draggable'); -require('integration/knockout'); +import 'ui/draggable'; +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('draggable'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/dropDownEditor.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/dropDownEditor.tests.js index 71c25bda8a73..b37dce285d97 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/dropDownEditor.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/dropDownEditor.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('integration/knockout'); -require('ui/drop_down_editor/ui.drop_down_editor'); +import 'integration/knockout'; +import 'ui/drop_down_editor/ui.drop_down_editor'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/eventRegistration.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/eventRegistration.tests.js index d361180f9fd4..9f374cff9ec4 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/eventRegistration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/eventRegistration.tests.js @@ -1,13 +1,13 @@ -const $ = require('jquery'); -const ko = require('knockout'); -const registerEvent = require('common/core/events/core/event_registrator'); -const dragEvents = require('common/core/events/drag'); -const clickEvent = require('common/core/events/click'); -const holdEvent = require('common/core/events/hold'); -const pointerEvents = require('common/core/events/pointer'); -const swipeEvents = require('common/core/events/swipe'); - -require('integration/knockout'); +import $ from 'jquery'; +import ko from 'knockout'; +import registerEvent from 'common/core/events/core/event_registrator'; +import * as dragEvents from 'common/core/events/drag'; +import * as clickEvent from 'common/core/events/click'; +import holdEvent from 'common/core/events/hold'; +import pointerEvents from 'common/core/events/pointer'; +import * as swipeEvents from 'common/core/events/swipe'; + +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/fieldset_bundled.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/fieldset_bundled.tests.js index fca674de95b1..a06d1f04ed40 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/fieldset_bundled.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/fieldset_bundled.tests.js @@ -1,12 +1,12 @@ -const $ = require('jquery'); -const ko = require('knockout'); -const browser = require('core/utils/browser'); -const devices = require('core/devices'); - -require('fluent_blue_light.css!'); -require('../../helpers/executeAsyncMock.js'); -require('integration/knockout'); -require('bundles/modules/parts/widgets-web'); +import $ from 'jquery'; +import ko from 'knockout'; +import browser from 'core/utils/browser'; +import devices from 'core/devices'; + +import 'fluent_blue_light.css!'; +import '../../helpers/executeAsyncMock.js'; +import 'integration/knockout'; +import 'bundles/modules/parts/widgets-web'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/list.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/list.tests.js index ccaef0f8ac54..e714b13b60fa 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/list.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/list.tests.js @@ -1,13 +1,13 @@ // eslint-disable-next-line spellcheck/spell-checker -const { rerender } = require('inferno'); -const $ = require('jquery'); -const ko = require('knockout'); -const executeAsyncMock = require('../../helpers/executeAsyncMock.js'); +import { rerender } from 'inferno'; +import $ from 'jquery'; +import ko from 'knockout'; +import executeAsyncMock from '../../helpers/executeAsyncMock.js'; -require('ui/list'); -require('integration/knockout'); +import 'ui/list'; +import 'integration/knockout'; -require('fluent_blue_light.css!'); +import 'fluent_blue_light.css!'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/loadPanel.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/loadPanel.tests.js index fd1a488e1d02..1c0bac242b33 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/loadPanel.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/loadPanel.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/load_panel'); -require('integration/knockout'); +import 'ui/load_panel'; +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/lookup.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/lookup.tests.js index dce97a7ad480..e8a4e4f54572 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/lookup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/lookup.tests.js @@ -1,10 +1,10 @@ -const $ = require('jquery'); -const fx = require('common/core/animation/fx'); -const executeAsyncMock = require('../../helpers/executeAsyncMock.js'); -const ko = require('knockout'); +import $ from 'jquery'; +import fx from 'common/core/animation/fx'; +import executeAsyncMock from '../../helpers/executeAsyncMock.js'; +import ko from 'knockout'; -require('ui/lookup'); -require('integration/knockout'); +import 'ui/lookup'; +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/objectUtils.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/objectUtils.tests.js index 957c8333967d..40473d14344f 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/objectUtils.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/objectUtils.tests.js @@ -1,8 +1,8 @@ -const ko = require('knockout'); -const variableWrapper = require('core/utils/variable_wrapper'); -const objectUtils = require('core/utils/object'); +import ko from 'knockout'; +import variableWrapper from 'core/utils/variable_wrapper'; +import * as objectUtils from 'core/utils/object'; -require('integration/knockout'); +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('objectUtils'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/overlay.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/overlay.tests.js index 43e88500b53c..589945083c9d 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/overlay.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/overlay.tests.js @@ -1,12 +1,12 @@ -const $ = require('jquery'); -const fx = require('common/core/animation/fx'); -const ko = require('knockout'); +import $ from 'jquery'; +import fx from 'common/core/animation/fx'; +import ko from 'knockout'; -require('ui/overlay/ui.overlay'); -require('ui/slider'); -require('integration/knockout'); +import 'ui/overlay/ui.overlay'; +import 'ui/slider'; +import 'integration/knockout'; -require('fluent_blue_light.css!'); +import 'fluent_blue_light.css!'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/pivotGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/pivotGrid.tests.js index 371579a4c8f6..8bba7db3198d 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/pivotGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/pivotGrid.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/pivot_grid/ui.pivot_grid'); -require('integration/knockout'); +import 'ui/pivot_grid/ui.pivot_grid'; +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/popup.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/popup.tests.js index 7674a1d2da9b..786a39b98e49 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/popup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/popup.tests.js @@ -1,11 +1,11 @@ -const $ = require('jquery'); -const viewPort = require('core/utils/view_port').value; -const devices = require('core/devices'); -const themes = require('ui/themes'); -const ko = require('knockout'); - -require('ui/popup'); -require('integration/knockout'); +import $ from 'jquery'; +import { value as viewPort } from 'core/utils/view_port'; +import devices from 'core/devices'; +import themes from 'ui/themes'; +import ko from 'knockout'; + +import 'ui/popup'; +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/scheduler.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/scheduler.tests.js index 9e4e250dd210..7c7d4a27cbb3 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/scheduler.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/scheduler.tests.js @@ -1,11 +1,11 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('integration/knockout'); -require('ui/scheduler'); -const { waitAsync } = require('../../helpers/scheduler/waitForAsync.js'); +import 'integration/knockout'; +import 'ui/scheduler'; +import { waitAsync } from '../../helpers/scheduler/waitForAsync.js'; -require('fluent_blue_light.css!'); +import 'fluent_blue_light.css!'; if(QUnit.urlParams['nocsp']) { QUnit.module('scheduler'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/selectBox.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/selectBox.tests.js index e1ef4e07e3a6..ebbcd6d86678 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/selectBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/selectBox.tests.js @@ -1,9 +1,9 @@ -const $ = require('jquery'); -const SelectBox = require('ui/select_box'); -const fx = require('common/core/animation/fx'); -const ko = require('knockout'); +import $ from 'jquery'; +import SelectBox from 'ui/select_box'; +import fx from 'common/core/animation/fx'; +import ko from 'knockout'; -require('integration/knockout'); +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/sortable.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/sortable.tests.js index d8ed65f0ad23..2a957de9f147 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/sortable.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/sortable.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/sortable'); -require('integration/knockout'); +import 'ui/sortable'; +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('sortable'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/tabs.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/tabs.tests.js index 04b7d4ecb257..6f0e24c9e0c5 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/tabs.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/tabs.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/tabs'); -require('integration/knockout'); +import 'ui/tabs'; +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('tabs'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/tagBox.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/tagBox.tests.js index 0669839de5d5..a3da1992fc6c 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/tagBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/tagBox.tests.js @@ -1,9 +1,9 @@ -const $ = require('jquery'); -const TagBox = require('ui/tag_box'); -const fx = require('common/core/animation/fx'); -const ko = require('knockout'); +import $ from 'jquery'; +import TagBox from 'ui/tag_box'; +import fx from 'common/core/animation/fx'; +import ko from 'knockout'; -require('integration/knockout'); +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/template.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/template.tests.js index d1c93959a99b..0a0e61558a83 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/template.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/template.tests.js @@ -1,7 +1,7 @@ -const $ = require('jquery'); -const noop = require('core/utils/common').noop; -const ko = require('knockout'); -const KoTemplate = require('__internal/integration/knockout/template').KoTemplate; +import $ from 'jquery'; +import { noop } from 'core/utils/common'; +import ko from 'knockout'; +import { KoTemplate } from '__internal/integration/knockout/template'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/toolbar.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/toolbar.tests.js index c062021fd46d..feed2c66c09f 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/toolbar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/toolbar.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/toolbar'); -require('integration/knockout'); +import 'ui/toolbar'; +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/treeList.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/treeList.tests.js index c373b4e03a6c..cd28b27a44cb 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/treeList.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/treeList.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const ko = require('knockout'); +import $ from 'jquery'; +import ko from 'knockout'; -require('ui/tree_list'); -require('integration/knockout'); +import 'ui/tree_list'; +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('treeList'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/treeView.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/treeView.tests.js index f36d202feda9..e315f3c49d4e 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/treeView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/treeView.tests.js @@ -1,10 +1,10 @@ -const $ = require('jquery'); -const noop = require('core/utils/common').noop; -const ko = require('knockout'); +import $ from 'jquery'; +import { noop } from 'core/utils/common'; +import ko from 'knockout'; -require('ui/button'); -require('ui/tree_view'); -require('integration/knockout'); +import 'ui/button'; +import 'ui/tree_view'; +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('treeView'); diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/validationGroup.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/validationGroup.tests.js index 53552dd61d28..03cafdd745ad 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/validationGroup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/validationGroup.tests.js @@ -1,11 +1,11 @@ -const $ = require('jquery'); -const ko = require('knockout'); -const ValidationEngine = require('ui/validation_engine'); - -require('ui/text_box'); -require('ui/validation_group'); -require('ui/validator'); -require('integration/knockout'); +import $ from 'jquery'; +import ko from 'knockout'; +import ValidationEngine from 'ui/validation_engine'; + +import 'ui/text_box'; +import 'ui/validation_group'; +import 'ui/validator'; +import 'integration/knockout'; const moduleWithoutCsp = QUnit.urlParams['nocsp'] ? QUnit.module : QUnit.module.skip; diff --git a/packages/devextreme/testing/tests/DevExpress.knockout/variableWrapperUtils.tests.js b/packages/devextreme/testing/tests/DevExpress.knockout/variableWrapperUtils.tests.js index a43c88e1ab7f..bc793dc7d40c 100644 --- a/packages/devextreme/testing/tests/DevExpress.knockout/variableWrapperUtils.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.knockout/variableWrapperUtils.tests.js @@ -1,8 +1,8 @@ -const ko = require('knockout'); -const variableWrapper = require('core/utils/variable_wrapper'); -const { logger } = require('core/utils/console'); +import ko from 'knockout'; +import variableWrapper from 'core/utils/variable_wrapper'; +import { logger } from 'core/utils/console'; -require('integration/knockout'); +import 'integration/knockout'; if(QUnit.urlParams['nocsp']) { QUnit.module('variableWrapperUtils'); diff --git a/packages/devextreme/testing/tests/DevExpress.localization/ldml.tests.js b/packages/devextreme/testing/tests/DevExpress.localization/ldml.tests.js index b96f360e315f..c368509ea446 100644 --- a/packages/devextreme/testing/tests/DevExpress.localization/ldml.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.localization/ldml.tests.js @@ -1,17 +1,15 @@ -require('../../helpers/noIntl.js'); -const getNumberFormatter = require('common/core/localization/ldml/number').getFormatter; -const getNumberFormat = require('common/core/localization/ldml/number').getFormat; -const getDateParser = require('common/core/localization/ldml/date.parser').getParser; -const getRegExpInfo = require('common/core/localization/ldml/date.parser').getRegExpInfo; -const getDateFormatter = require('common/core/localization/ldml/date.formatter').getFormatter; -const getDateFormat = require('common/core/localization/ldml/date.format').getFormat; -const defaultDateNames = require('common/core/localization/default_date_names'); -const numberLocalization = require('common/core/localization/number'); -const dateLocalization = require('common/core/localization/date'); -const extend = require('core/utils/extend').extend; -const console = require('core/utils/console').logger; - -require('common/core/localization/currency'); +import '../../helpers/noIntl.js'; +import { getFormatter as getNumberFormatter, getFormat as getNumberFormat } from 'common/core/localization/ldml/number'; +import { getParser as getDateParser, getRegExpInfo } from 'common/core/localization/ldml/date.parser'; +import { getFormatter as getDateFormatter } from 'common/core/localization/ldml/date.formatter'; +import { getFormat as getDateFormat } from 'common/core/localization/ldml/date.format'; +import defaultDateNames from 'common/core/localization/default_date_names'; +import numberLocalization from 'common/core/localization/number'; +import dateLocalization from 'common/core/localization/date'; +import { extend } from 'core/utils/extend'; +import { logger as console } from 'core/utils/console'; + +import 'common/core/localization/currency'; const dateParts = extend({}, defaultDateNames, { getPeriodNames: function() { @@ -406,14 +404,14 @@ QUnit.module('number formatter', () => { assert.deepEqual(regExpInfo.patterns, [ 'HH', '\' h \'', 'mm' ]); - // eslint-disable-next-line no-useless-escape + assert.deepEqual(regExpInfo.regexp, /^(2[0-3]|1[0-9]|0?[0-9])(\ h\ )([1-5][0-9]|0?[0-9])$/i); regExpInfo = getRegExpInfo('HH:mm', parts); assert.deepEqual(regExpInfo.patterns, [ 'HH', ':', 'mm' ]); - // eslint-disable-next-line no-useless-escape + assert.deepEqual(regExpInfo.regexp, /^(2[0-3]|1[0-9]|0?[0-9])(h|:)([1-5][0-9]|0?[0-9])$/i); parts.getTimeSeparator = function() { @@ -423,7 +421,7 @@ QUnit.module('number formatter', () => { assert.deepEqual(regExpInfo.patterns, [ 'HH', ':', 'mm' ]); - // eslint-disable-next-line no-useless-escape + assert.deepEqual(regExpInfo.regexp, /^(2[0-3]|1[0-9]|0?[0-9])(\[\.\]|:)([1-5][0-9]|0?[0-9])$/i); }); diff --git a/packages/devextreme/testing/tests/DevExpress.localization/localization.custom.tests.js b/packages/devextreme/testing/tests/DevExpress.localization/localization.custom.tests.js index c3079b762b39..254848e54d1f 100644 --- a/packages/devextreme/testing/tests/DevExpress.localization/localization.custom.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.localization/localization.custom.tests.js @@ -1,5 +1,5 @@ -const numberLocalization = require('common/core/localization/number'); -const dateLocalization = require('common/core/localization/date'); +import numberLocalization from 'common/core/localization/number'; +import dateLocalization from 'common/core/localization/date'; QUnit.module('Custom date names', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.tests.js b/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.tests.js index d028a628baf6..02a4ec2b0a06 100644 --- a/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.tests.js @@ -1,957 +1,933 @@ -SystemJS.config({ - meta: { - './localization.base.tests.js': { - deps: [ - 'common/core/localization/globalize/core', - 'common/core/localization/globalize/number', - 'common/core/localization/globalize/currency', - 'common/core/localization/globalize/date', - 'common/core/localization/globalize/message' - ] - } - }, - packages: { - 'globalize': { - meta: { - '../globalize.js': { - deps: ['cldr/unresolved'] - } - } - } - } -}); +import 'common/core/localization/globalize/core'; +import 'common/core/localization/globalize/number'; +import 'common/core/localization/globalize/currency'; +import 'common/core/localization/globalize/date'; +import 'common/core/localization/globalize/message'; -define(function(require, exports, module) { - const cldrData = [ - require('devextreme-cldr-data/ar.json!json'), - require('devextreme-cldr-data/ru.json!json'), - require('devextreme-cldr-data/de.json!json'), - require('devextreme-cldr-data/da.json!json') - ]; +import { generateDate as generateExpectedDate } from '../../helpers/dateHelper.js'; - require('common/core/localization/globalize/core'); - require('common/core/localization/globalize/number'); - require('common/core/localization/globalize/currency'); - require('common/core/localization/globalize/date'); - require('common/core/localization/globalize/message'); +import $ from 'jquery'; +import Globalize from 'globalize'; +import numberLocalization from 'common/core/localization/number'; +import dateLocalization from 'common/core/localization/date'; +import messageLocalization from 'common/core/localization/message'; +import config from 'core/config'; - const generateExpectedDate = require('../../helpers/dateHelper.js').generateDate; +import ExcelJSLocalizationFormatTests from '../DevExpress.exporter/exceljsParts/exceljs.format.tests.js'; - const $ = require('jquery'); - const Globalize = require('globalize'); - const numberLocalization = require('common/core/localization/number'); - const dateLocalization = require('common/core/localization/date'); - const messageLocalization = require('common/core/localization/message'); - const config = require('core/config'); +import likelySubtags from 'cldr-core/supplemental/likelySubtags.json!'; - const ExcelJSLocalizationFormatTests = require('../DevExpress.exporter/exceljsParts/exceljs.format.tests.js'); +import { noop } from 'core/utils/common'; +import formatHelper from 'format_helper'; +import browser from 'core/utils/browser'; +import dateUtils from 'core/utils/date'; - const likelySubtags = require('cldr-core/supplemental/likelySubtags.json!'); - Globalize.load(likelySubtags); +import sharedTests from './sharedParts/localization.shared.js'; - cldrData.forEach(localeCldrData => { - Globalize.load(localeCldrData); - }); +import ar from 'devextreme-cldr-data/ar.json!json'; +import ru from 'devextreme-cldr-data/ru.json!json'; +import de from 'devextreme-cldr-data/de.json!json'; +import da from 'devextreme-cldr-data/da.json!json'; - const NBSP = String.fromCharCode(160); - const RUB = String.fromCharCode(8381); +const cldrData = [ar, ru, de, da]; - const noop = require('core/utils/common').noop; - const formatHelper = require('format_helper'); - const browser = require('core/utils/browser'); - const dateUtils = require('core/utils/date'); +Globalize.load(likelySubtags); - const sharedTests = require('./sharedParts/localization.shared.js').default; +cldrData.forEach(localeCldrData => { + Globalize.load(localeCldrData); +}); - const NEGATIVE_NUMBERS = [-4.645, -35.855]; - const ROUNDING_CORRECTION = { - '-4.64': '-4.65', - '-35.85': '-35.86' - }; +const NBSP = String.fromCharCode(160); +const RUB = String.fromCharCode(8381); + +const NEGATIVE_NUMBERS = [-4.645, -35.855]; +const ROUNDING_CORRECTION = { + '-4.64': '-4.65', + '-35.85': '-35.86' +}; + +function isIosWithMSKTimeZone() { + const isIos = navigator.userAgent.indexOf('Mac OS X') > -1 && browser['webkit']; + const hasMSKTimeZone = new Date().toString().indexOf('MSK') > -1; + + return isIos && hasMSKTimeZone; +} + +QUnit.module('Globalize common', { + before: function() { + numberLocalization.inject({ + format: function(value, format) { + // NOTE: Globalizejs implementation of negative number rounding differs from Intl. + // https://github.com/globalizejs/globalize/issues/884 + // If the fractional portion is exactly 0.5 and the argument is negative, + // the argument is rounded to the next integer in the positive direction + let result = this.callBase.apply(this, arguments); + if(NEGATIVE_NUMBERS.indexOf(value) !== -1 && format.type === 'fixedPoint' && format.precision === 2 && !!ROUNDING_CORRECTION[result]) { + result = ROUNDING_CORRECTION[result]; + } + return result; + } + }); + } +}, function() { - function isIosWithMSKTimeZone() { - const isIos = navigator.userAgent.indexOf('Mac OS X') > -1 && browser['webkit']; - const hasMSKTimeZone = new Date().toString().indexOf('MSK') > -1; + QUnit.test('engine', function(assert) { + assert.equal(numberLocalization.engine(), 'globalize'); + assert.equal(dateLocalization.engine(), 'globalize'); + assert.equal(messageLocalization.engine(), 'globalize'); + }); + + sharedTests(); +}); - return isIos && hasMSKTimeZone; +QUnit.module('Localization date (ru)', { + beforeEach: function() { + Globalize.locale('ru'); + }, + afterEach: function() { + Globalize.locale('en'); } +}, () => { + QUnit.test('getFormatParts', function(assert) { + assert.equal(dateLocalization.getFormatParts('dayofweek').length, 0); + assert.equal(dateLocalization.getFormatParts('shortdate').join(' '), 'day month year'); + assert.equal(dateLocalization.getFormatParts('longDateLongTime').join(' '), 'day month year hours minutes seconds'); + assert.equal(dateLocalization.getFormatParts('d - M - y, hh:mm:ss [SSS]').join(' '), 'day month year hours minutes seconds milliseconds'); + assert.equal(dateLocalization.getFormatParts('ah:mm').join(' '), 'hours minutes'); // T460693 + }); - QUnit.module('Globalize common', { - before: function() { - numberLocalization.inject({ - format: function(value, format) { - // NOTE: Globalizejs implementation of negative number rounding differs from Intl. - // https://github.com/globalizejs/globalize/issues/884 - // If the fractional portion is exactly 0.5 and the argument is negative, - // the argument is rounded to the next integer in the positive direction - let result = this.callBase.apply(this, arguments); - if(NEGATIVE_NUMBERS.indexOf(value) !== -1 && format.type === 'fixedPoint' && format.precision === 2 && !!ROUNDING_CORRECTION[result]) { - result = ROUNDING_CORRECTION[result]; - } - return result; - } - }); - } - }, function() { + QUnit.test('getMonthNames', function(assert) { + assert.deepEqual(dateLocalization.getMonthNames(), + ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], + 'Array of month names'); + }); - QUnit.test('engine', function(assert) { - assert.equal(numberLocalization.engine(), 'globalize'); - assert.equal(dateLocalization.engine(), 'globalize'); - assert.equal(messageLocalization.engine(), 'globalize'); - }); + QUnit.test('getMonthNames with specified type', function(assert) { + assert.deepEqual(dateLocalization.getMonthNames('wide', 'format'), + ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'], + 'Array of month names'); + }); - sharedTests(); + QUnit.test('getMonthNames with type=\'standalone\'', function(assert) { + assert.deepEqual(dateLocalization.getMonthNames('wide', 'standalone'), + ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], + 'Array of month names'); }); - QUnit.module('Localization date (ru)', { - beforeEach: function() { - Globalize.locale('ru'); - }, - afterEach: function() { - Globalize.locale('en'); - } - }, () => { - QUnit.test('getFormatParts', function(assert) { - assert.equal(dateLocalization.getFormatParts('dayofweek').length, 0); - assert.equal(dateLocalization.getFormatParts('shortdate').join(' '), 'day month year'); - assert.equal(dateLocalization.getFormatParts('longDateLongTime').join(' '), 'day month year hours minutes seconds'); - assert.equal(dateLocalization.getFormatParts('d - M - y, hh:mm:ss [SSS]').join(' '), 'day month year hours minutes seconds milliseconds'); - assert.equal(dateLocalization.getFormatParts('ah:mm').join(' '), 'hours minutes'); // T460693 - }); + QUnit.test('getPeriodNames', function(assert) { + assert.deepEqual(dateLocalization.getPeriodNames(), ['AM', 'PM'], 'Array of period names'); - QUnit.test('getMonthNames', function(assert) { - assert.deepEqual(dateLocalization.getMonthNames(), - ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], - 'Array of month names'); - }); + Globalize.locale('ar'); - QUnit.test('getMonthNames with specified type', function(assert) { - assert.deepEqual(dateLocalization.getMonthNames('wide', 'format'), - ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'], - 'Array of month names'); - }); + [null, 'abbreviated', 'wide', 'narrow'].forEach(format => { + ['format', null].forEach(type => { + const expect = ([null, 'wide'].includes(format) && type == null) ? ['صباحًا', 'مساءً'] : ['ص', 'م']; - QUnit.test('getMonthNames with type=\'standalone\'', function(assert) { - assert.deepEqual(dateLocalization.getMonthNames('wide', 'standalone'), - ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'], - 'Array of month names'); + assert.deepEqual(dateLocalization.getPeriodNames(format, type), expect, 'Array of correct period names'); + }); }); + }); - QUnit.test('getPeriodNames', function(assert) { - assert.deepEqual(dateLocalization.getPeriodNames(), ['AM', 'PM'], 'Array of period names'); - - Globalize.locale('ar'); + QUnit.test('getDayNames', function(assert) { + assert.deepEqual(dateLocalization.getDayNames(), + ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'], + 'Array of day names'); + }); - [null, 'abbreviated', 'wide', 'narrow'].forEach(format => { - ['format', null].forEach(type => { - const expect = ([null, 'wide'].includes(format) && type == null) ? ['صباحًا', 'مساءً'] : ['ص', 'م']; + QUnit.test('getTimeSeparator', function(assert) { + assert.equal(dateLocalization.getTimeSeparator(), ':'); + }); - assert.deepEqual(dateLocalization.getPeriodNames(format, type), expect, 'Array of correct period names'); - }); - }); + QUnit.test('format', function(assert) { + const expectedValues = { + 'day': '2', + 'dayofweek': 'понедельник', + 'hour': '03', + 'longdate': 'понедельник, 2 марта 2015 г.', + 'longdatelongtime': 'понедельник, 2 марта 2015 г., 03:04:05', + 'longtime': '03:04:05', + 'millisecond': '006', + 'minute': '04', + 'month': 'март', + 'monthandday': '2 марта', + 'monthandyear': 'март 2015 г.', + 'quarter': '1-й кв.', + 'quarterandyear': '1-й кв. 2015 г.', + 'second': '05', + 'shortdate': '02.03.2015', + 'shortdateshorttime': '02.03.2015, 03:04', + 'shorttime': '03:04', + 'shortyear': '15', + 'year': '2015', + + 'datetime-local': '2015-03-02T03:04:05', + 'yyyy MMMM d': '2015 марта 2', + 'ss SSS': '05 006' + }; + const date = new Date(2015, 2, 2, 3, 4, 5); + + date.setMilliseconds(6); + + $.each(expectedValues, function(format, expectedValue) { + assert.equal(dateLocalization.format(date, format), expectedValue, format + ' format'); }); - QUnit.test('getDayNames', function(assert) { - assert.deepEqual(dateLocalization.getDayNames(), - ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'], - 'Array of day names'); - }); - - QUnit.test('getTimeSeparator', function(assert) { - assert.equal(dateLocalization.getTimeSeparator(), ':'); - }); - - QUnit.test('format', function(assert) { - const expectedValues = { - 'day': '2', - 'dayofweek': 'понедельник', - 'hour': '03', - 'longdate': 'понедельник, 2 марта 2015 г.', - 'longdatelongtime': 'понедельник, 2 марта 2015 г., 03:04:05', - 'longtime': '03:04:05', - 'millisecond': '006', - 'minute': '04', - 'month': 'март', - 'monthandday': '2 марта', - 'monthandyear': 'март 2015 г.', - 'quarter': '1-й кв.', - 'quarterandyear': '1-й кв. 2015 г.', - 'second': '05', - 'shortdate': '02.03.2015', - 'shortdateshorttime': '02.03.2015, 03:04', - 'shorttime': '03:04', - 'shortyear': '15', - 'year': '2015', - - 'datetime-local': '2015-03-02T03:04:05', - 'yyyy MMMM d': '2015 марта 2', - 'ss SSS': '05 006' - }; - const date = new Date(2015, 2, 2, 3, 4, 5); - - date.setMilliseconds(6); - - $.each(expectedValues, function(format, expectedValue) { - assert.equal(dateLocalization.format(date, format), expectedValue, format + ' format'); - }); + assert.equal(dateLocalization.format(date), String(new Date(2015, 2, 2, 3, 4, 5)), 'without format'); + assert.notOk(dateLocalization.format(), 'without date'); + }); - assert.equal(dateLocalization.format(date), String(new Date(2015, 2, 2, 3, 4, 5)), 'without format'); - assert.notOk(dateLocalization.format(), 'without date'); - }); + QUnit.test('format cache for different locales', function(assert) { + const originalLocale = Globalize.locale().locale; + const date = new Date(2015, 2, 2, 3, 4, 5); + try { + Globalize.locale('en'); + assert.equal(dateLocalization.format(date, 'month'), 'March'); + } finally { + Globalize.locale(originalLocale); + assert.equal(dateLocalization.format(date, 'month'), 'март'); + } + }); - QUnit.test('format cache for different locales', function(assert) { - const originalLocale = Globalize.locale().locale; - const date = new Date(2015, 2, 2, 3, 4, 5); - try { - Globalize.locale('en'); - assert.equal(dateLocalization.format(date, 'month'), 'March'); - } finally { - Globalize.locale(originalLocale); - assert.equal(dateLocalization.format(date, 'month'), 'март'); + QUnit.test('parse', function(assert) { + const assertData = { + 'day': { + text: '2', + expectedConfig: { day: 2 } + }, + 'hour': { + text: '03', + expectedConfig: { hours: 3 } + }, + 'longdate': { + text: 'понедельник, 2 марта 2015 г.', + expected: new Date(2015, 2, 2) + }, + 'longdatelongtime': { + text: 'понедельник, 2 марта 2015 г., 3:04:05', + expected: new Date(2015, 2, 2, 3, 4, 5) + }, + 'longtime': { + text: '3:04:05', + expectedConfig: { hours: 3, minutes: 4, seconds: 5 } + }, + 'minute': { + text: '04', + expectedConfig: { minutes: 4 } + }, + 'month': { + text: 'март', + expectedConfig: { month: 2, day: 1 } + }, + 'monthandday': { + text: '2 марта', + expectedConfig: { month: 2, day: 2 } + }, + 'monthandyear': { + text: 'март 2015 г.', + expected: new Date(2015, 2, 1) + }, + 'second': { + text: '05', + expectedConfig: { seconds: 5 } + }, + 'shortdate': { + text: '02.03.2015', + expected: new Date(2015, 2, 2) + }, + 'shortdateshorttime': { + text: '02.03.2015, 3:04', + expected: new Date(2015, 2, 2, 3, 4) + }, + 'shorttime': { + text: '3:04', + expectedConfig: { hours: 3, minutes: 4 } + }, + 'shortyear': { + text: '15', + expected: new Date(2015, 0, 1) + }, + 'year': { + text: '2015', + expected: new Date(2015, 0, 1) + }, + + 'datetime-local': { + text: '2015-03-02T03:04:05', + expected: new Date(2015, 2, 2, 3, 4, 5) + }, + 'yyyy MMMM d': { + text: '2015 марта 2', + expected: new Date(2015, 2, 2) } + }; + + $.each(assertData, function(format, data) { + assert.equal(dateLocalization.parse(data.text, format), String(data.expected || generateExpectedDate(data.expectedConfig)), format + ' format'); }); - QUnit.test('parse', function(assert) { - const assertData = { - 'day': { - text: '2', - expectedConfig: { day: 2 } - }, - 'hour': { - text: '03', - expectedConfig: { hours: 3 } - }, - 'longdate': { - text: 'понедельник, 2 марта 2015 г.', - expected: new Date(2015, 2, 2) - }, - 'longdatelongtime': { - text: 'понедельник, 2 марта 2015 г., 3:04:05', - expected: new Date(2015, 2, 2, 3, 4, 5) - }, - 'longtime': { - text: '3:04:05', - expectedConfig: { hours: 3, minutes: 4, seconds: 5 } - }, - 'minute': { - text: '04', - expectedConfig: { minutes: 4 } - }, - 'month': { - text: 'март', - expectedConfig: { month: 2, day: 1 } - }, - 'monthandday': { - text: '2 марта', - expectedConfig: { month: 2, day: 2 } - }, - 'monthandyear': { - text: 'март 2015 г.', - expected: new Date(2015, 2, 1) - }, - 'second': { - text: '05', - expectedConfig: { seconds: 5 } - }, - 'shortdate': { - text: '02.03.2015', - expected: new Date(2015, 2, 2) - }, - 'shortdateshorttime': { - text: '02.03.2015, 3:04', - expected: new Date(2015, 2, 2, 3, 4) - }, - 'shorttime': { - text: '3:04', - expectedConfig: { hours: 3, minutes: 4 } - }, - 'shortyear': { - text: '15', - expected: new Date(2015, 0, 1) - }, - 'year': { - text: '2015', - expected: new Date(2015, 0, 1) - }, - - 'datetime-local': { - text: '2015-03-02T03:04:05', - expected: new Date(2015, 2, 2, 3, 4, 5) - }, - 'yyyy MMMM d': { - text: '2015 марта 2', - expected: new Date(2015, 2, 2) - } - }; + assert.equal(dateLocalization.parse('550', 'millisecond').getMilliseconds(), 550, 'millisecond format'); + assert.equal(dateLocalization.parse('550', 'SSS').getMilliseconds(), 550, 'millisecond format'); - $.each(assertData, function(format, data) { - assert.equal(dateLocalization.parse(data.text, format), String(data.expected || generateExpectedDate(data.expectedConfig)), format + ' format'); - }); + assert.equal(dateLocalization.parse(dateLocalization.format(new Date(), 'shortDate')), String(generateExpectedDate({ hours: 0 })), 'without format'); + assert.notOk(dateLocalization.parse(), 'without date'); - assert.equal(dateLocalization.parse('550', 'millisecond').getMilliseconds(), 550, 'millisecond format'); - assert.equal(dateLocalization.parse('550', 'SSS').getMilliseconds(), 550, 'millisecond format'); + assert.equal(dateLocalization.parse(Globalize.formatDate(new Date(), { date: 'short' }), { date: 'short' }), String(generateExpectedDate({ hours: 0 })), 'globalize format'); + }); - assert.equal(dateLocalization.parse(dateLocalization.format(new Date(), 'shortDate')), String(generateExpectedDate({ hours: 0 })), 'without format'); - assert.notOk(dateLocalization.parse(), 'without date'); + QUnit.test('firstDayOfWeekIndex', function(assert) { + assert.equal(dateLocalization.firstDayOfWeekIndex(), 1); + }); +}); - assert.equal(dateLocalization.parse(Globalize.formatDate(new Date(), { date: 'short' }), { date: 'short' }), String(generateExpectedDate({ hours: 0 })), 'globalize format'); - }); +QUnit.module('Custom format types', () => { + QUnit.test('format: { time: \'medium\' }', function(assert) { + assert.equal(dateLocalization.format(new Date(2015, 1, 2, 3, 4, 5, 6), { time: 'medium' }), '3:04:05 AM', 'with object format'); + }); - QUnit.test('firstDayOfWeekIndex', function(assert) { - assert.equal(dateLocalization.firstDayOfWeekIndex(), 1); - }); + QUnit.test('Parse custom format', function(assert) { + const expected = new Date(2010, 2, 2).toString(); + assert.equal(dateLocalization.parse('20100302', 'yyyyMMdd'), expected, 'Format \'yyyyMMdd\' parse ok'); + assert.equal(dateLocalization.parse('02mar10', 'dMyyyy'), expected, 'Format \'dMyyyy\' parse ok'); }); +}); - QUnit.module('Custom format types', () => { - QUnit.test('format: { time: \'medium\' }', function(assert) { - assert.equal(dateLocalization.format(new Date(2015, 1, 2, 3, 4, 5, 6), { time: 'medium' }), '3:04:05 AM', 'with object format'); +QUnit.module('Localization message (custom locales)', { + beforeEach: function() { + messageLocalization.load({ + 'en': { + addedKey: 'testValue', + hello: 'Hello, {0} {1}' + } + }); + } +}, () => { + QUnit.test('Fallback to neutral culture', function(assert) { + const originalLocale = Globalize.locale().locale; + + messageLocalization.load({ + 'ru': { + TestBack: 'Back ru', + TestCancel: 'Cancel ru' + } }); - QUnit.test('Parse custom format', function(assert) { - const expected = new Date(2010, 2, 2).toString(); - assert.equal(dateLocalization.parse('20100302', 'yyyyMMdd'), expected, 'Format \'yyyyMMdd\' parse ok'); - assert.equal(dateLocalization.parse('02mar10', 'dMyyyy'), expected, 'Format \'dMyyyy\' parse ok'); + messageLocalization.load({ + 'ru-RU': { + TestCancel: 'Cancel ru-RU' + } }); + + try { + Globalize.locale('ru-RU'); + + assert.equal(messageLocalization.format('TestBack'), 'Back ru'); + assert.equal(messageLocalization.format('TestCancel'), 'Cancel ru-RU'); + } finally { + Globalize.locale(originalLocale); + } }); - QUnit.module('Localization message (custom locales)', { - beforeEach: function() { - messageLocalization.load({ - 'en': { - addedKey: 'testValue', - hello: 'Hello, {0} {1}' - } - }); + + QUnit.test('Fallback to default (en) culture', function(assert) { + const originalLocale = Globalize.locale().locale; + try { + Globalize.locale('ru'); + + assert.equal(messageLocalization.format('OK'), 'OK'); + assert.equal(messageLocalization.getFormatter('OK')(), 'OK'); + } finally { + Globalize.locale(originalLocale); } - }, () => { - QUnit.test('Fallback to neutral culture', function(assert) { - const originalLocale = Globalize.locale().locale; + }); - messageLocalization.load({ - 'ru': { - TestBack: 'Back ru', - TestCancel: 'Cancel ru' - } - }); + QUnit.test('Extended culture with empty string value (T271323)', function(assert) { + const originalLocale = Globalize.locale().locale; - messageLocalization.load({ - 'ru-RU': { - TestCancel: 'Cancel ru-RU' + Globalize.load({ + 'supplemental': { + 'likelySubtags': { + 'zh': 'zh-Hans-CN' } - }); - - try { - Globalize.locale('ru-RU'); + } + }); - assert.equal(messageLocalization.format('TestBack'), 'Back ru'); - assert.equal(messageLocalization.format('TestCancel'), 'Cancel ru-RU'); - } finally { - Globalize.locale(originalLocale); + messageLocalization.load({ + 'zh-CN': { + addedKey: '' } }); + try { + Globalize.locale('zh-CN'); - QUnit.test('Fallback to default (en) culture', function(assert) { - const originalLocale = Globalize.locale().locale; - try { - Globalize.locale('ru'); + assert.equal(messageLocalization.localizeString('@addedKey'), 'testValue', 'Default culture value'); + } finally { + Globalize.locale(originalLocale); + } + }); - assert.equal(messageLocalization.format('OK'), 'OK'); - assert.equal(messageLocalization.getFormatter('OK')(), 'OK'); - } finally { - Globalize.locale(originalLocale); + QUnit.test('localizeString by custom locale (T383089)', function(assert) { + messageLocalization.load({ + 'ru': { + 'ruAddedKey': 'ruValue' } }); + Globalize.locale('ru'); + const localized = messageLocalization.localizeString('@ruAddedKey @@ruAddedKey @'); + assert.equal(localized, 'ruValue @ruAddedKey @'); - QUnit.test('Extended culture with empty string value (T271323)', function(assert) { - const originalLocale = Globalize.locale().locale; + Globalize.locale('en'); + }); - Globalize.load({ - 'supplemental': { - 'likelySubtags': { - 'zh': 'zh-Hans-CN' - } - } - }); + QUnit.test('Empty message', function(assert) { + Globalize.loadMessages({ + 'en': { + 'empty': '' + } + }); - messageLocalization.load({ - 'zh-CN': { - addedKey: '' - } - }); + assert.equal(messageLocalization.format('empty'), ''); + }); - try { - Globalize.locale('zh-CN'); + QUnit.test('DX messages can be customized', function(assert) { + assert.equal(messageLocalization.format('dxCollectionWidget-noDataText'), 'No data to display'); - assert.equal(messageLocalization.localizeString('@addedKey'), 'testValue', 'Default culture value'); - } finally { - Globalize.locale(originalLocale); + Globalize.loadMessages({ + 'en': { + 'dxCollectionWidget-noDataText': 'Custom caption' } }); - QUnit.test('localizeString by custom locale (T383089)', function(assert) { - messageLocalization.load({ - 'ru': { - 'ruAddedKey': 'ruValue' - } - }); - Globalize.locale('ru'); - const localized = messageLocalization.localizeString('@ruAddedKey @@ruAddedKey @'); - assert.equal(localized, 'ruValue @ruAddedKey @'); + assert.equal(messageLocalization.format('dxCollectionWidget-noDataText'), 'Custom caption'); + }); - Globalize.locale('en'); + QUnit.test('getDictionary ru', function(assert) { + messageLocalization.load({ + 'ru': { + 'freshRuAddedKey': 'ruValue' + } }); + Globalize.locale('ru'); - QUnit.test('Empty message', function(assert) { - Globalize.loadMessages({ - 'en': { - 'empty': '' - } - }); + messageLocalization.localizeString('@unknownKey'); + messageLocalization.localizeString('@ruAddedKey'); - assert.equal(messageLocalization.format('empty'), ''); - }); + assert.equal(messageLocalization.getDictionary()['freshRuAddedKey'], 'ruValue'); + assert.equal(messageLocalization.getDictionary(true)['freshRuAddedKey'], undefined); + assert.equal(messageLocalization.getDictionary()['unknownKey'], 'Unknown key'); + assert.equal(messageLocalization.getDictionary(true)['unknownKey'], 'Unknown key'); - QUnit.test('DX messages can be customized', function(assert) { - assert.equal(messageLocalization.format('dxCollectionWidget-noDataText'), 'No data to display'); + Globalize.locale('en'); + }); +}); - Globalize.loadMessages({ - 'en': { - 'dxCollectionWidget-noDataText': 'Custom caption' - } - }); +QUnit.module('Localization globalizeNumber', () => { + QUnit.test('format', function(assert) { + assert.equal(numberLocalization.format(1.2), '1.2'); + assert.equal(numberLocalization.format(12), '12'); + assert.equal(numberLocalization.format(2, { minimumIntegerDigits: 2 }), '02'); + assert.equal(numberLocalization.format(12, { minimumIntegerDigits: 2 }), '12'); + assert.equal(numberLocalization.format(2, { minimumIntegerDigits: 3 }), '002'); + assert.equal(numberLocalization.format(12, { minimumIntegerDigits: 3 }), '012'); + assert.equal(numberLocalization.format(123, { minimumIntegerDigits: 3 }), '123'); + }); +}); - assert.equal(messageLocalization.format('dxCollectionWidget-noDataText'), 'Custom caption'); - }); +QUnit.module('Localization currency with Globalize', () => { - QUnit.test('getDictionary ru', function(assert) { - messageLocalization.load({ - 'ru': { - 'freshRuAddedKey': 'ruValue' - } - }); - Globalize.locale('ru'); + QUnit.test('format currency default after global config change', function(assert) { + const originalDefaultCurrency = config().defaultCurrency; - messageLocalization.localizeString('@unknownKey'); - messageLocalization.localizeString('@ruAddedKey'); + assert.equal(numberLocalization.format(1.2, { currency: 'default' }), '$1.20'); - assert.equal(messageLocalization.getDictionary()['freshRuAddedKey'], 'ruValue'); - assert.equal(messageLocalization.getDictionary(true)['freshRuAddedKey'], undefined); - assert.equal(messageLocalization.getDictionary()['unknownKey'], 'Unknown key'); - assert.equal(messageLocalization.getDictionary(true)['unknownKey'], 'Unknown key'); + config({ defaultCurrency: 'EUR' }); + assert.equal(numberLocalization.format(12, { currency: 'default' }), '€12.00'); + + config({ defaultCurrency: originalDefaultCurrency }); + assert.equal(numberLocalization.format(1.2, { currency: 'default' }), '$1.20'); - Globalize.locale('en'); - }); }); - QUnit.module('Localization globalizeNumber', () => { - QUnit.test('format', function(assert) { - assert.equal(numberLocalization.format(1.2), '1.2'); - assert.equal(numberLocalization.format(12), '12'); - assert.equal(numberLocalization.format(2, { minimumIntegerDigits: 2 }), '02'); - assert.equal(numberLocalization.format(12, { minimumIntegerDigits: 2 }), '12'); - assert.equal(numberLocalization.format(2, { minimumIntegerDigits: 3 }), '002'); - assert.equal(numberLocalization.format(12, { minimumIntegerDigits: 3 }), '012'); - assert.equal(numberLocalization.format(123, { minimumIntegerDigits: 3 }), '123'); - }); + QUnit.test('format', function(assert) { + assert.equal(numberLocalization.format(1.2, { currency: 'default' }), '$1.20'); + assert.equal(numberLocalization.format(12, { currency: 'default' }), '$12.00'); + assert.equal(numberLocalization.format(1, { minimumIntegerDigits: 2, minimumFractionDigits: 0, currency: 'default' }), '$01'); + assert.equal(numberLocalization.format(1, { minimumIntegerDigits: 2, minimumFractionDigits: 0, currency: 'RUB' }), 'RUB' + NBSP + '01'); }); - QUnit.module('Localization currency with Globalize', () => { + QUnit.test('format currency with sign/style (T1076906)', function(assert) { + assert.equal(numberLocalization.format(-1.2, { currency: 'default', style: 'accounting' }), '($1.20)'); + assert.equal(numberLocalization.format(-1.2, { type: 'currency', useCurrencyAccountingStyle: true }), '($1)'); + assert.equal(numberLocalization.format(-12, { currency: 'default', style: 'symbol' }), '-$12.00'); + assert.equal(numberLocalization.format(-12, { type: 'currency', useCurrencyAccountingStyle: false }), '-$12'); + }); - QUnit.test('format currency default after global config change', function(assert) { - const originalDefaultCurrency = config().defaultCurrency; + QUnit.test('format currency & power in RU locale', function(assert) { + Globalize.locale('ru'); + assert.equal(numberLocalization.format(0, { type: 'currency thousands', currency: undefined, precision: 0 }), '0K' + NBSP + '$'); + assert.equal(numberLocalization.format(0, { type: 'currency thousands', currency: 'CSK', precision: 2 }), '0,00K' + NBSP + 'CSK'); + assert.equal(numberLocalization.format(2e+5, { type: 'currency thousands', precision: 0 }), '200K' + NBSP + '$'); + Globalize.locale('en'); + }); - assert.equal(numberLocalization.format(1.2, { currency: 'default' }), '$1.20'); + QUnit.test('getOpenXmlCurrencyFormat: check conversion for some cultures (T835933)', function(assert) { + try { + Globalize.locale('en'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat(undefined), '$#,##0{0}_);\\($#,##0{0}\\)'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('USD'), '$#,##0{0}_);\\($#,##0{0}\\)'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('RUB'), '\\R\\U\\B#,##0{0}_);\\(\\R\\U\\B#,##0{0}\\)'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('CNY'), '\\C\\N\\¥#,##0{0}_);\\(\\C\\N\\¥#,##0{0}\\)'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('NOK'), '\\N\\O\\K#,##0{0}_);\\(\\N\\O\\K#,##0{0}\\)'); + + Globalize.locale('en-ru'); // switch to parent if there are no settings for the passed culture + assert.equal(numberLocalization.getOpenXmlCurrencyFormat(undefined), '$#,##0{0}_);\\($#,##0{0}\\)'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('USD'), '$#,##0{0}_);\\($#,##0{0}\\)'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('RUB'), '\\R\\U\\B#,##0{0}_);\\(\\R\\U\\B#,##0{0}\\)'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('CNY'), '\\C\\N\\¥#,##0{0}_);\\(\\C\\N\\¥#,##0{0}\\)'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('NOK'), '\\N\\O\\K#,##0{0}_);\\(\\N\\O\\K#,##0{0}\\)'); - config({ defaultCurrency: 'EUR' }); - assert.equal(numberLocalization.format(12, { currency: 'default' }), '€12.00'); + Globalize.locale('ru'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat(undefined), '#,##0{0}\xA0$'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('USD'), '#,##0{0}\xA0$'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('RUB'), '#,##0{0}\xA0\\₽'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('CNY'), '#,##0{0}\xA0\\C\\N\\¥'); + assert.equal(numberLocalization.getOpenXmlCurrencyFormat('NOK'), '#,##0{0}\xA0\\N\\O\\K'); + } finally { + Globalize.locale('en'); + } + }); - config({ defaultCurrency: originalDefaultCurrency }); - assert.equal(numberLocalization.format(1.2, { currency: 'default' }), '$1.20'); + QUnit.test('getDecimalSeparator and getThousandsSeparator in RU locale', function(assert) { + Globalize.locale('ru'); + assert.equal(numberLocalization.getDecimalSeparator(), ','); + assert.equal(numberLocalization.getThousandsSeparator(), '\xa0'); + Globalize.locale('en'); + }); - }); + QUnit.test('getCurrencySymbol and config.defaultCurrency', function(assert) { + const originalDefaultCurrency = config().defaultCurrency; - QUnit.test('format', function(assert) { - assert.equal(numberLocalization.format(1.2, { currency: 'default' }), '$1.20'); - assert.equal(numberLocalization.format(12, { currency: 'default' }), '$12.00'); - assert.equal(numberLocalization.format(1, { minimumIntegerDigits: 2, minimumFractionDigits: 0, currency: 'default' }), '$01'); - assert.equal(numberLocalization.format(1, { minimumIntegerDigits: 2, minimumFractionDigits: 0, currency: 'RUB' }), 'RUB' + NBSP + '01'); - }); + try { + assert.equal(numberLocalization.getCurrencySymbol().symbol, '$'); - QUnit.test('format currency with sign/style (T1076906)', function(assert) { - assert.equal(numberLocalization.format(-1.2, { currency: 'default', style: 'accounting' }), '($1.20)'); - assert.equal(numberLocalization.format(-1.2, { type: 'currency', useCurrencyAccountingStyle: true }), '($1)'); - assert.equal(numberLocalization.format(-12, { currency: 'default', style: 'symbol' }), '-$12.00'); - assert.equal(numberLocalization.format(-12, { type: 'currency', useCurrencyAccountingStyle: false }), '-$12'); - }); + config({ + defaultCurrency: 'EUR' + }); - QUnit.test('format currency & power in RU locale', function(assert) { - Globalize.locale('ru'); - assert.equal(numberLocalization.format(0, { type: 'currency thousands', currency: undefined, precision: 0 }), '0K' + NBSP + '$'); - assert.equal(numberLocalization.format(0, { type: 'currency thousands', currency: 'CSK', precision: 2 }), '0,00K' + NBSP + 'CSK'); - assert.equal(numberLocalization.format(2e+5, { type: 'currency thousands', precision: 0 }), '200K' + NBSP + '$'); - Globalize.locale('en'); - }); + assert.equal(numberLocalization.getCurrencySymbol().symbol, '€'); + } finally { + config({ + defaultCurrency: originalDefaultCurrency + }); + } + }); +}); - QUnit.test('getOpenXmlCurrencyFormat: check conversion for some cultures (T835933)', function(assert) { - try { - Globalize.locale('en'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat(undefined), '$#,##0{0}_);\\($#,##0{0}\\)'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('USD'), '$#,##0{0}_);\\($#,##0{0}\\)'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('RUB'), '\\R\\U\\B#,##0{0}_);\\(\\R\\U\\B#,##0{0}\\)'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('CNY'), '\\C\\N\\¥#,##0{0}_);\\(\\C\\N\\¥#,##0{0}\\)'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('NOK'), '\\N\\O\\K#,##0{0}_);\\(\\N\\O\\K#,##0{0}\\)'); - - Globalize.locale('en-ru'); // switch to parent if there are no settings for the passed culture - assert.equal(numberLocalization.getOpenXmlCurrencyFormat(undefined), '$#,##0{0}_);\\($#,##0{0}\\)'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('USD'), '$#,##0{0}_);\\($#,##0{0}\\)'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('RUB'), '\\R\\U\\B#,##0{0}_);\\(\\R\\U\\B#,##0{0}\\)'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('CNY'), '\\C\\N\\¥#,##0{0}_);\\(\\C\\N\\¥#,##0{0}\\)'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('NOK'), '\\N\\O\\K#,##0{0}_);\\(\\N\\O\\K#,##0{0}\\)'); - - Globalize.locale('ru'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat(undefined), '#,##0{0}\xA0$'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('USD'), '#,##0{0}\xA0$'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('RUB'), '#,##0{0}\xA0\\₽'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('CNY'), '#,##0{0}\xA0\\C\\N\\¥'); - assert.equal(numberLocalization.getOpenXmlCurrencyFormat('NOK'), '#,##0{0}\xA0\\N\\O\\K'); - } finally { - Globalize.locale('en'); - } - }); +QUnit.module('Exceljs format', () => { + ExcelJSLocalizationFormatTests.runCurrencyTests([ + { value: 'USD', expected: '$#,##0_);\\($#,##0\\)' }, + { value: 'RUB', expected: '\\R\\U\\B#,##0_);\\(\\R\\U\\B#,##0\\)' }, + { value: 'JPY', expected: '\\¥#,##0_);\\(\\¥#,##0\\)' }, + { value: 'KPW', expected: '\\K\\P\\W#,##0_);\\(\\K\\P\\W#,##0\\)' }, + { value: 'LBP', expected: '\\L\\B\\P#,##0_);\\(\\L\\B\\P#,##0\\)' }, + { value: 'SEK', expected: '\\S\\E\\K#,##0_);\\(\\S\\E\\K#,##0\\)' } + ]); + + ExcelJSLocalizationFormatTests.runPivotGridCurrencyTests([ + { value: 'USD', expected: '$#,##0_);\\($#,##0\\)' }, + { value: 'RUB', expected: '\\R\\U\\B#,##0_);\\(\\R\\U\\B#,##0\\)' }, + { value: 'JPY', expected: '\\¥#,##0_);\\(\\¥#,##0\\)' }, + { value: 'KPW', expected: '\\K\\P\\W#,##0_);\\(\\K\\P\\W#,##0\\)' }, + { value: 'LBP', expected: '\\L\\B\\P#,##0_);\\(\\L\\B\\P#,##0\\)' }, + { value: 'SEK', expected: '\\S\\E\\K#,##0_);\\(\\S\\E\\K#,##0\\)' } + ]); +}); - QUnit.test('getDecimalSeparator and getThousandsSeparator in RU locale', function(assert) { - Globalize.locale('ru'); - assert.equal(numberLocalization.getDecimalSeparator(), ','); - assert.equal(numberLocalization.getThousandsSeparator(), '\xa0'); - Globalize.locale('en'); +QUnit.module('Format helper', () => { + QUnit.module('Numeric and dateTime formats', { + beforeEach: function() { + this.testDate = new Date(2010, 2, 5, 12, 13, 33, 0); + } + }, () => { + QUnit.test('Currency numeric formats', function(assert) { + assert.equal(formatHelper.format(1204, 'currency'), '$1,204'); + assert.equal(formatHelper.format(1204, { type: 'cuRRency', precision: 2 }), '$1,204.00'); + assert.equal(formatHelper.format(-1204, { type: 'currency', precision: 2 }), '($1,204.00)'); }); - QUnit.test('getCurrencySymbol and config.defaultCurrency', function(assert) { - const originalDefaultCurrency = config().defaultCurrency; + QUnit.test('currency RUB large number format with different locales', function(assert) { + const currentCultureName = Globalize.locale().locale; - try { - assert.equal(numberLocalization.getCurrencySymbol().symbol, '$'); + assert.equal(formatHelper.format(1.204, { type: 'currency', precision: 2, currency: 'RUB' }), 'RUB' + NBSP + '1.20'); - config({ - defaultCurrency: 'EUR' - }); - - assert.equal(numberLocalization.getCurrencySymbol().symbol, '€'); + Globalize.locale('ru'); + try { + assert.equal(formatHelper.format(1.204, { type: 'currency', precision: 2, currency: 'RUB' }), '1,20' + NBSP + RUB); } finally { - config({ - defaultCurrency: originalDefaultCurrency - }); + Globalize.locale(currentCultureName); } }); - }); - - QUnit.module('Exceljs format', () => { - ExcelJSLocalizationFormatTests.default.runCurrencyTests([ - { value: 'USD', expected: '$#,##0_);\\($#,##0\\)' }, - { value: 'RUB', expected: '\\R\\U\\B#,##0_);\\(\\R\\U\\B#,##0\\)' }, - { value: 'JPY', expected: '\\¥#,##0_);\\(\\¥#,##0\\)' }, - { value: 'KPW', expected: '\\K\\P\\W#,##0_);\\(\\K\\P\\W#,##0\\)' }, - { value: 'LBP', expected: '\\L\\B\\P#,##0_);\\(\\L\\B\\P#,##0\\)' }, - { value: 'SEK', expected: '\\S\\E\\K#,##0_);\\(\\S\\E\\K#,##0\\)' } - ]); - - ExcelJSLocalizationFormatTests.default.runPivotGridCurrencyTests([ - { value: 'USD', expected: '$#,##0_);\\($#,##0\\)' }, - { value: 'RUB', expected: '\\R\\U\\B#,##0_);\\(\\R\\U\\B#,##0\\)' }, - { value: 'JPY', expected: '\\¥#,##0_);\\(\\¥#,##0\\)' }, - { value: 'KPW', expected: '\\K\\P\\W#,##0_);\\(\\K\\P\\W#,##0\\)' }, - { value: 'LBP', expected: '\\L\\B\\P#,##0_);\\(\\L\\B\\P#,##0\\)' }, - { value: 'SEK', expected: '\\S\\E\\K#,##0_);\\(\\S\\E\\K#,##0\\)' } - ]); - }); - - QUnit.module('Format helper', () => { - QUnit.module('Numeric and dateTime formats', { - beforeEach: function() { - this.testDate = new Date(2010, 2, 5, 12, 13, 33, 0); - } - }, () => { - QUnit.test('Currency numeric formats', function(assert) { - assert.equal(formatHelper.format(1204, 'currency'), '$1,204'); - assert.equal(formatHelper.format(1204, { type: 'cuRRency', precision: 2 }), '$1,204.00'); - assert.equal(formatHelper.format(-1204, { type: 'currency', precision: 2 }), '($1,204.00)'); - }); - - QUnit.test('currency RUB large number format with different locales', function(assert) { - const currentCultureName = Globalize.locale().locale; - assert.equal(formatHelper.format(1.204, { type: 'currency', precision: 2, currency: 'RUB' }), 'RUB' + NBSP + '1.20'); + QUnit.test('Fixed point numeric formats', function(assert) { + assert.equal(formatHelper.format(23.04059872, { type: 'fIxedPoint', precision: 4 }), '23.0406'); + }); - Globalize.locale('ru'); - try { - assert.equal(formatHelper.format(1.204, { type: 'currency', precision: 2, currency: 'RUB' }), '1,20' + NBSP + RUB); - } finally { - Globalize.locale(currentCultureName); - } - }); + QUnit.test('Percent numeric formats', function(assert) { + assert.equal(formatHelper.format(0.45, 'percEnt'), '45%'); + assert.equal(formatHelper.format(0.45, { type: 'peRcent', precision: 2 }), '45.00%'); + }); - QUnit.test('Fixed point numeric formats', function(assert) { - assert.equal(formatHelper.format(23.04059872, { type: 'fIxedPoint', precision: 4 }), '23.0406'); - }); + QUnit.test('Decimal numeric formats', function(assert) { + assert.equal(formatHelper.format(437, 'decimAl'), '437'); + assert.equal(formatHelper.format(437, { type: 'deCimal', precision: 5 }), '00437'); + }); - QUnit.test('Percent numeric formats', function(assert) { - assert.equal(formatHelper.format(0.45, 'percEnt'), '45%'); - assert.equal(formatHelper.format(0.45, { type: 'peRcent', precision: 2 }), '45.00%'); - }); + QUnit.test('Long date format', function(assert) { + assert.equal(formatHelper.format(this.testDate, 'LONGDate'), 'Friday, March 5, 2010'); + }); - QUnit.test('Decimal numeric formats', function(assert) { - assert.equal(formatHelper.format(437, 'decimAl'), '437'); - assert.equal(formatHelper.format(437, { type: 'deCimal', precision: 5 }), '00437'); - }); + QUnit.test('Long time format', function(assert) { + assert.equal(formatHelper.format(this.testDate, 'longTIME'), '12:13:33 PM'); + }); + QUnit.test('Month and day format', function(assert) { + assert.equal(formatHelper.format(this.testDate, 'monthAndDAY'), 'March 5'); + }); - QUnit.test('Long date format', function(assert) { - assert.equal(formatHelper.format(this.testDate, 'LONGDate'), 'Friday, March 5, 2010'); - }); + QUnit.test('Month and year format', function(assert) { + assert.equal(formatHelper.format(this.testDate, 'MONTHAndYear'), 'March 2010'); + }); - QUnit.test('Long time format', function(assert) { - assert.equal(formatHelper.format(this.testDate, 'longTIME'), '12:13:33 PM'); - }); - QUnit.test('Month and day format', function(assert) { - assert.equal(formatHelper.format(this.testDate, 'monthAndDAY'), 'March 5'); - }); + QUnit.test('Short date format', function(assert) { + assert.equal(formatHelper.format(this.testDate, 'shoRTDate'), '3/5/2010'); + }); - QUnit.test('Month and year format', function(assert) { - assert.equal(formatHelper.format(this.testDate, 'MONTHAndYear'), 'March 2010'); - }); + QUnit.test('Short time format', function(assert) { + assert.equal(formatHelper.format(this.testDate, 'shoRTTime'), '12:13 PM'); + }); - QUnit.test('Short date format', function(assert) { - assert.equal(formatHelper.format(this.testDate, 'shoRTDate'), '3/5/2010'); - }); + QUnit.test('Custom date time format', function(assert) { + assert.equal(formatHelper.format(this.testDate, 'dd MMM yy'), '05 Mar 10'); + }); - QUnit.test('Short time format', function(assert) { - assert.equal(formatHelper.format(this.testDate, 'shoRTTime'), '12:13 PM'); - }); + QUnit.test('LongDateLongTime', function(assert) { + assert.equal(formatHelper.format(this.testDate, 'LONGDATELongTime'), 'Friday, March 5, 2010, 12:13:33 PM'); + }); - QUnit.test('Custom date time format', function(assert) { - assert.equal(formatHelper.format(this.testDate, 'dd MMM yy'), '05 Mar 10'); - }); + QUnit.test('ShortDateShortTime', function(assert) { + assert.equal(formatHelper.format(this.testDate, 'shortDATESHORTTime'), '3/5/2010, 12:13 PM'); + }); - QUnit.test('LongDateLongTime', function(assert) { - assert.equal(formatHelper.format(this.testDate, 'LONGDATELongTime'), 'Friday, March 5, 2010, 12:13:33 PM'); - }); + QUnit.test('Invalid format parameters', function(assert) { + assert.equal(formatHelper.format('test', 'percent'), 'test'); + assert.equal(formatHelper.format(12, 12), 12); + }); - QUnit.test('ShortDateShortTime', function(assert) { - assert.equal(formatHelper.format(this.testDate, 'shortDATESHORTTime'), '3/5/2010, 12:13 PM'); - }); + QUnit.test('Quarter and year', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QUarterAndYear'), 'Q1 2005'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'dd MMM yy, Q'), '01 Jan 05, 1'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qq, dd MMM yy'), '01, 01 Jan 05'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'q, yy'), '1, 05'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qq, yy'), '01, 05'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QQ, yyyy MM'), '01, 2005 01'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qqq, yyyy'), 'Q1, 2005'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qqqq, yyyy'), '1st quarter, 2005'); + }); - QUnit.test('Invalid format parameters', function(assert) { - assert.equal(formatHelper.format('test', 'percent'), 'test'); - assert.equal(formatHelper.format(12, 12), 12); - }); + // B218108 + QUnit.test('Custom quarter format', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'q'), '1', 'quarter format - q'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'Q'), '1', 'quarter format - Q'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qq'), '01', 'quarter format - qq'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QQ'), '01', 'quarter format - QQ'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qqq'), 'Q1', 'quarter format - qqq'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QQQ'), 'Q1', 'quarter format - QQQ'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qqqq'), '1st quarter', 'quarter format - qqqq'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QQQQ'), '1st quarter', 'quarter format - QQQQ'); + + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'q MMM QQ'), '1 Jan 01', 'quarter format - q MMM QQ'); + }); - QUnit.test('Quarter and year', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QUarterAndYear'), 'Q1 2005'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'dd MMM yy, Q'), '01 Jan 05, 1'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qq, dd MMM yy'), '01, 01 Jan 05'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'q, yy'), '1, 05'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qq, yy'), '01, 05'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QQ, yyyy MM'), '01, 2005 01'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qqq, yyyy'), 'Q1, 2005'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qqqq, yyyy'), '1st quarter, 2005'); - }); + QUnit.test('Quarters for any months', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'quartERAndYear'), 'Q1 2005'); + assert.equal(formatHelper.format(new Date(2005, 1, 1), 'quartERAndYear'), 'Q1 2005'); + assert.equal(formatHelper.format(new Date(2005, 2, 1), 'quartERAndYear'), 'Q1 2005'); - // B218108 - QUnit.test('Custom quarter format', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'q'), '1', 'quarter format - q'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'Q'), '1', 'quarter format - Q'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qq'), '01', 'quarter format - qq'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QQ'), '01', 'quarter format - QQ'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qqq'), 'Q1', 'quarter format - qqq'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QQQ'), 'Q1', 'quarter format - QQQ'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'qqqq'), '1st quarter', 'quarter format - qqqq'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'QQQQ'), '1st quarter', 'quarter format - QQQQ'); - - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'q MMM QQ'), '1 Jan 01', 'quarter format - q MMM QQ'); - }); + assert.equal(formatHelper.format(new Date(2005, 3, 1), 'quarterANDYear'), 'Q2 2005'); + assert.equal(formatHelper.format(new Date(2005, 4, 1), 'quarterANDYear'), 'Q2 2005'); + assert.equal(formatHelper.format(new Date(2005, 5, 1), 'quarterANDYear'), 'Q2 2005'); - QUnit.test('Quarters for any months', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'quartERAndYear'), 'Q1 2005'); - assert.equal(formatHelper.format(new Date(2005, 1, 1), 'quartERAndYear'), 'Q1 2005'); - assert.equal(formatHelper.format(new Date(2005, 2, 1), 'quartERAndYear'), 'Q1 2005'); + assert.equal(formatHelper.format(new Date(2005, 6, 1), 'qUARterAndYear'), 'Q3 2005'); + assert.equal(formatHelper.format(new Date(2005, 7, 1), 'qUARterAndYear'), 'Q3 2005'); + assert.equal(formatHelper.format(new Date(2005, 8, 1), 'qUARterAndYear'), 'Q3 2005'); - assert.equal(formatHelper.format(new Date(2005, 3, 1), 'quarterANDYear'), 'Q2 2005'); - assert.equal(formatHelper.format(new Date(2005, 4, 1), 'quarterANDYear'), 'Q2 2005'); - assert.equal(formatHelper.format(new Date(2005, 5, 1), 'quarterANDYear'), 'Q2 2005'); + assert.equal(formatHelper.format(new Date(2005, 9, 1), 'qUARterAndYear'), 'Q4 2005'); + assert.equal(formatHelper.format(new Date(2005, 10, 1), 'qUARterAndYear'), 'Q4 2005'); + assert.equal(formatHelper.format(new Date(2005, 11, 1), 'qUARterAndYear'), 'Q4 2005'); + }); - assert.equal(formatHelper.format(new Date(2005, 6, 1), 'qUARterAndYear'), 'Q3 2005'); - assert.equal(formatHelper.format(new Date(2005, 7, 1), 'qUARterAndYear'), 'Q3 2005'); - assert.equal(formatHelper.format(new Date(2005, 8, 1), 'qUARterAndYear'), 'Q3 2005'); + QUnit.test('Choose call format method by the value type', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'loNGDate'), 'Saturday, January 1, 2005'); + assert.equal(formatHelper.format(new Date(2005, 0, 1), 'SHORTDate'), '1/1/2005'); + assert.equal(formatHelper.format(12.098, { type: 'fixEDPoint', precision: 2 }), '12.10'); + assert.equal(formatHelper.format(12.098, { type: 'cuRRency', precision: 1 }), '$12.1'); + assert.equal(formatHelper.format('InvalidValue'), 'InvalidValue'); + }); - assert.equal(formatHelper.format(new Date(2005, 9, 1), 'qUARterAndYear'), 'Q4 2005'); - assert.equal(formatHelper.format(new Date(2005, 10, 1), 'qUARterAndYear'), 'Q4 2005'); - assert.equal(formatHelper.format(new Date(2005, 11, 1), 'qUARterAndYear'), 'Q4 2005'); - }); + QUnit.test('Millisecond date time interval format', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 1, 10, 33, 20, 237), 'millisecond'), '237'); + assert.equal(formatHelper.format(new Date(2005, 0, 1, 10, 33, 20, 569), 'millisecond'), '569'); + }); - QUnit.test('Choose call format method by the value type', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'loNGDate'), 'Saturday, January 1, 2005'); - assert.equal(formatHelper.format(new Date(2005, 0, 1), 'SHORTDate'), '1/1/2005'); - assert.equal(formatHelper.format(12.098, { type: 'fixEDPoint', precision: 2 }), '12.10'); - assert.equal(formatHelper.format(12.098, { type: 'cuRRency', precision: 1 }), '$12.1'); - assert.equal(formatHelper.format('InvalidValue'), 'InvalidValue'); - }); + QUnit.test('Day date time interval format', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'day'), '16'); + assert.equal(formatHelper.format(new Date(2005, 0, 30, 19, 23, 56, 237), 'day'), '30'); + }); - QUnit.test('Millisecond date time interval format', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 1, 10, 33, 20, 237), 'millisecond'), '237'); - assert.equal(formatHelper.format(new Date(2005, 0, 1, 10, 33, 20, 569), 'millisecond'), '569'); - }); + QUnit.test('Month date time interval format', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'month'), 'January'); + assert.equal(formatHelper.format(new Date(2005, 9, 27, 19, 23, 56, 237), 'month'), 'October'); + }); - QUnit.test('Day date time interval format', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'day'), '16'); - assert.equal(formatHelper.format(new Date(2005, 0, 30, 19, 23, 56, 237), 'day'), '30'); - }); + QUnit.test('Quarter date time interval format', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'quarter'), 'Q1'); + assert.equal(formatHelper.format(new Date(2005, 9, 27, 19, 23, 56, 237), 'quarter'), 'Q4'); + }); - QUnit.test('Month date time interval format', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'month'), 'January'); - assert.equal(formatHelper.format(new Date(2005, 9, 27, 19, 23, 56, 237), 'month'), 'October'); - }); + QUnit.test('Year date time interval format', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'year'), '2005'); + assert.equal(formatHelper.format(new Date(2009, 9, 27, 19, 23, 56, 237), 'year'), '2009'); + }); - QUnit.test('Quarter date time interval format', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'quarter'), 'Q1'); - assert.equal(formatHelper.format(new Date(2005, 9, 27, 19, 23, 56, 237), 'quarter'), 'Q4'); - }); + QUnit.test('Short Year date time interval format', function(assert) { + assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'shortyear'), '05'); + assert.equal(formatHelper.format(new Date(2009, 9, 27, 19, 23, 56, 237), 'shortyear'), '09'); + }); - QUnit.test('Year date time interval format', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'year'), '2005'); - assert.equal(formatHelper.format(new Date(2009, 9, 27, 19, 23, 56, 237), 'year'), '2009'); - }); + // This condition is added because of Safari bug 15434904 + if(!isIosWithMSKTimeZone()) { + QUnit.test('getDateMarkerFormat for second range', function(assert) { + const date1 = new Date(2010, 0, 1, 2, 23, 33); + const date2 = new Date(date1.getTime()); - QUnit.test('Short Year date time interval format', function(assert) { - assert.equal(formatHelper.format(new Date(2005, 0, 16, 10, 33, 20, 237), 'shortyear'), '05'); - assert.equal(formatHelper.format(new Date(2009, 9, 27, 19, 23, 56, 237), 'shortyear'), '09'); + date2.setMilliseconds(3000); + const format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); + assert.equal(formatHelper.format(date2, format), '2:23:36 AM'); }); - // This condition is added because of Safari bug 15434904 - if(!isIosWithMSKTimeZone()) { - QUnit.test('getDateMarkerFormat for second range', function(assert) { - const date1 = new Date(2010, 0, 1, 2, 23, 33); - const date2 = new Date(date1.getTime()); - - date2.setMilliseconds(3000); - const format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), '2:23:36 AM'); - }); - - QUnit.test('getDateMarkerFormat for minute range', function(assert) { - const date1 = new Date(2010, 0, 1, 2, 23, 33, 990); - let date2 = new Date(date1.getTime()); - let format; - - date2.setSeconds(63); - format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), '2:24:03 AM'); - - date2 = new Date(date1.getTime()); - date2.setMinutes(25); - format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), '2:25 AM'); - }); - - QUnit.test('getDateMarkerFormat for hour range', function(assert) { - const date1 = new Date(2010, 0, 1, 2, 23, 33, 990); - let date2 = new Date(date1.getTime()); - let format; - - date2.setSeconds(30000); - format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), '10:43:00 AM'); - - date2 = new Date(date1.getTime()); - date2.setHours(4); - format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), '4:23 AM'); - }); - } - - QUnit.test('getDateMarkerFormat for day range', function(assert) { - const date1 = new Date(2010, 0, 29, 12, 23, 33, 990); + QUnit.test('getDateMarkerFormat for minute range', function(assert) { + const date1 = new Date(2010, 0, 1, 2, 23, 33, 990); let date2 = new Date(date1.getTime()); let format; - // day and time - date2 = new Date(date1.getTime()); - date2.setMinutes(1000); + date2.setSeconds(63); format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), 'Saturday, 30 4:40 AM'); + assert.equal(formatHelper.format(date2, format), '2:24:03 AM'); - // day date2 = new Date(date1.getTime()); - date2.setDate(30); + date2.setMinutes(25); format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), 'Saturday, 30'); + assert.equal(formatHelper.format(date2, format), '2:25 AM'); }); - QUnit.test('getDateMarkerFormat for month range', function(assert) { - const date1 = new Date(2010, 10, 29, 12, 23, 33, 990); + QUnit.test('getDateMarkerFormat for hour range', function(assert) { + const date1 = new Date(2010, 0, 1, 2, 23, 33, 990); let date2 = new Date(date1.getTime()); let format; - // month, day and time - date2.setHours(74); - format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), 'December 2 2:23 AM'); - - // year, month, day and time - date2 = new Date(date1.getTime()); - date2.setFullYear(2011); - date2.setMonth(11); - date2.setHours(74); - format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), '1/1/2012 2:23 AM'); - - // month and day - date2 = new Date(date1.getTime()); - date2.setDate(32); + date2.setSeconds(30000); format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), 'December 2'); + assert.equal(formatHelper.format(date2, format), '10:43:00 AM'); - // month date2 = new Date(date1.getTime()); - date2.setMonth(11); + date2.setHours(4); format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), 'December'); + assert.equal(formatHelper.format(date2, format), '4:23 AM'); }); + } - QUnit.test('getDateMarkerFormat for year range', function(assert) { - const date1 = new Date(2010, 10, 29, 12, 23, 33, 990); - const date2 = new Date(date1.getTime()); + QUnit.test('getDateMarkerFormat for day range', function(assert) { + const date1 = new Date(2010, 0, 29, 12, 23, 33, 990); + let date2 = new Date(date1.getTime()); + let format; + + // day and time + date2 = new Date(date1.getTime()); + date2.setMinutes(1000); + format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); + assert.equal(formatHelper.format(date2, format), 'Saturday, 30 4:40 AM'); + + // day + date2 = new Date(date1.getTime()); + date2.setDate(30); + format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); + assert.equal(formatHelper.format(date2, format), 'Saturday, 30'); + }); - // year - date2.setFullYear(2031); - const format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); - assert.equal(formatHelper.format(date2, format), '2031'); - }); + QUnit.test('getDateMarkerFormat for month range', function(assert) { + const date1 = new Date(2010, 10, 29, 12, 23, 33, 990); + let date2 = new Date(date1.getTime()); + let format; + + // month, day and time + date2.setHours(74); + format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); + assert.equal(formatHelper.format(date2, format), 'December 2 2:23 AM'); + + // year, month, day and time + date2 = new Date(date1.getTime()); + date2.setFullYear(2011); + date2.setMonth(11); + date2.setHours(74); + format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); + assert.equal(formatHelper.format(date2, format), '1/1/2012 2:23 AM'); + + // month and day + date2 = new Date(date1.getTime()); + date2.setDate(32); + format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); + assert.equal(formatHelper.format(date2, format), 'December 2'); + + // month + date2 = new Date(date1.getTime()); + date2.setMonth(11); + format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); + assert.equal(formatHelper.format(date2, format), 'December'); + }); - // B217749 - QUnit.test('value is null or undefined', function(assert) { - assert.strictEqual(formatHelper.format(null, ''), ''); - assert.strictEqual(formatHelper.format(undefined, ''), ''); - assert.strictEqual(formatHelper.format('test', ''), 'test'); - }); + QUnit.test('getDateMarkerFormat for year range', function(assert) { + const date1 = new Date(2010, 10, 29, 12, 23, 33, 990); + const date2 = new Date(date1.getTime()); - QUnit.test('large number auto format negative numbers', function(assert) { - assert.strictEqual(formatHelper.format(-123, 'fixedPoint largeNumber'), '-123'); - assert.strictEqual(formatHelper.format(-1230, 'fixedPoint largeNumber'), '-1K'); - assert.strictEqual(formatHelper.format(-12300000, 'fixedPoint largeNumber'), '-12M'); - }); + // year + date2.setFullYear(2031); + const format = formatHelper.getDateFormatByDifferences(dateUtils.getDatesDifferences(date1, date2)); + assert.equal(formatHelper.format(date2, format), '2031'); + }); - QUnit.test('large number auto format precision', function(assert) { - assert.strictEqual(formatHelper.format(0.01, 'fixedPoint LARGENumber'), '0'); - assert.strictEqual(formatHelper.format(10.23, 'fixedPoint largeNumber'), '10'); - assert.strictEqual(formatHelper.format(123, { type: 'fixedPoint largeNumber', precision: 1 }), '123.0'); - assert.strictEqual(formatHelper.format(12345, { type: 'fixedPoint largeNUMBER', precision: 2 }), '12.35K'); - assert.strictEqual(formatHelper.format(12345, { type: 'fixedPoint largeNumber', precision: 5 }), '12.34500K'); - }); + // B217749 + QUnit.test('value is null or undefined', function(assert) { + assert.strictEqual(formatHelper.format(null, ''), ''); + assert.strictEqual(formatHelper.format(undefined, ''), ''); + assert.strictEqual(formatHelper.format('test', ''), 'test'); + }); - QUnit.test('large number auto format small numbers', function(assert) { - assert.strictEqual(formatHelper.format(0.01, { type: 'fixedPoint largeNumber', precision: 2 }), '0.01'); - assert.strictEqual(formatHelper.format(999, { type: 'fixedPoint largeNumber', precision: 2 }), '999.00'); - assert.strictEqual(formatHelper.format(999.9, { type: 'fixedPoint largeNumber', precision: 0 }), '1,000'); - assert.strictEqual(formatHelper.format(1000, { type: 'fixedPoint largeNumber', precision: 0 }), '1K'); - }); + QUnit.test('large number auto format negative numbers', function(assert) { + assert.strictEqual(formatHelper.format(-123, 'fixedPoint largeNumber'), '-123'); + assert.strictEqual(formatHelper.format(-1230, 'fixedPoint largeNumber'), '-1K'); + assert.strictEqual(formatHelper.format(-12300000, 'fixedPoint largeNumber'), '-12M'); + }); - QUnit.test('large number auto format powers', function(assert) { - assert.strictEqual(formatHelper.format(1234.56, { type: 'fixedPoint largeNumber', precision: 2 }), '1.23K'); - assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint largeNumber', precision: 2 }), '12.35K'); - assert.strictEqual(formatHelper.format(123400000, { type: 'fixedPoint largeNumber', precision: 2 }), '123.40M'); - assert.strictEqual(formatHelper.format(1234000000, { type: 'fixedPoint largeNumber', precision: 2 }), '1.23B'); - assert.strictEqual(formatHelper.format(12340000000000, { type: 'fixedPoint largeNumber', precision: 2 }), '12.34T'); - assert.strictEqual(formatHelper.format(12340000000000000, { type: 'fixedPoint largeNumber', precision: 2 }), '12,340.00T'); - }); + QUnit.test('large number auto format precision', function(assert) { + assert.strictEqual(formatHelper.format(0.01, 'fixedPoint LARGENumber'), '0'); + assert.strictEqual(formatHelper.format(10.23, 'fixedPoint largeNumber'), '10'); + assert.strictEqual(formatHelper.format(123, { type: 'fixedPoint largeNumber', precision: 1 }), '123.0'); + assert.strictEqual(formatHelper.format(12345, { type: 'fixedPoint largeNUMBER', precision: 2 }), '12.35K'); + assert.strictEqual(formatHelper.format(12345, { type: 'fixedPoint largeNumber', precision: 5 }), '12.34500K'); + }); - QUnit.test('large number format powers', function(assert) { - assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint', precision: 2 }), '12,345.67'); - assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint largeNumber', precision: 2 }), '12.35K'); - assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint thousands', precision: 2 }), '12.35K'); - assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint miLLions', precision: 3 }), '0.012M'); - assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint biLLions', precision: 7 }), '0.0000123B'); - assert.strictEqual(formatHelper.format(12345670, { type: 'fixedPoint triLLions', precision: 7 }), '0.0000123T'); - }); + QUnit.test('large number auto format small numbers', function(assert) { + assert.strictEqual(formatHelper.format(0.01, { type: 'fixedPoint largeNumber', precision: 2 }), '0.01'); + assert.strictEqual(formatHelper.format(999, { type: 'fixedPoint largeNumber', precision: 2 }), '999.00'); + assert.strictEqual(formatHelper.format(999.9, { type: 'fixedPoint largeNumber', precision: 0 }), '1,000'); + assert.strictEqual(formatHelper.format(1000, { type: 'fixedPoint largeNumber', precision: 0 }), '1K'); + }); - QUnit.test('currency large number format', function(assert) { - assert.strictEqual(formatHelper.format(12345.67, { type: 'currency largeNumber', precision: 2 }), '$12.35K'); - assert.strictEqual(formatHelper.format(12345.67, { type: 'currency thoUSands', precision: 2 }), '$12.35K'); - assert.strictEqual(formatHelper.format(12345.67, { type: 'currency miLLions', precision: 3 }), '$0.012M'); - }); + QUnit.test('large number auto format powers', function(assert) { + assert.strictEqual(formatHelper.format(1234.56, { type: 'fixedPoint largeNumber', precision: 2 }), '1.23K'); + assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint largeNumber', precision: 2 }), '12.35K'); + assert.strictEqual(formatHelper.format(123400000, { type: 'fixedPoint largeNumber', precision: 2 }), '123.40M'); + assert.strictEqual(formatHelper.format(1234000000, { type: 'fixedPoint largeNumber', precision: 2 }), '1.23B'); + assert.strictEqual(formatHelper.format(12340000000000, { type: 'fixedPoint largeNumber', precision: 2 }), '12.34T'); + assert.strictEqual(formatHelper.format(12340000000000000, { type: 'fixedPoint largeNumber', precision: 2 }), '12,340.00T'); + }); - QUnit.test('large number format without number type', function(assert) { - assert.strictEqual(formatHelper.format(12345.67, { type: 'largeNumber', precision: 2 }), '12.35K'); - assert.strictEqual(formatHelper.format(12345.67, { type: 'thousands', precision: 2 }), '12.35K'); - assert.strictEqual(formatHelper.format(12345.67, { type: 'millions', precision: 3 }), '0.012M'); - }); + QUnit.test('large number format powers', function(assert) { + assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint', precision: 2 }), '12,345.67'); + assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint largeNumber', precision: 2 }), '12.35K'); + assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint thousands', precision: 2 }), '12.35K'); + assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint miLLions', precision: 3 }), '0.012M'); + assert.strictEqual(formatHelper.format(12345.67, { type: 'fixedPoint biLLions', precision: 7 }), '0.0000123B'); + assert.strictEqual(formatHelper.format(12345670, { type: 'fixedPoint triLLions', precision: 7 }), '0.0000123T'); + }); - QUnit.test('Empty format for number', function(assert) { - assert.equal(formatHelper.format(1204, ''), '1204'); - assert.equal(formatHelper.format(12.04, ''), '12.04'); - }); + QUnit.test('currency large number format', function(assert) { + assert.strictEqual(formatHelper.format(12345.67, { type: 'currency largeNumber', precision: 2 }), '$12.35K'); + assert.strictEqual(formatHelper.format(12345.67, { type: 'currency thoUSands', precision: 2 }), '$12.35K'); + assert.strictEqual(formatHelper.format(12345.67, { type: 'currency miLLions', precision: 3 }), '$0.012M'); + }); - QUnit.test('exponential number type pow', function(assert) { - assert.strictEqual(formatHelper.format(5, { type: 'exponEntial', precision: 2 }), '5.00E+0'); - assert.strictEqual(formatHelper.format(0.0081, { type: 'exponential', precision: 2 }), '8.10E-3'); - assert.strictEqual(formatHelper.format(-12345.67, { type: 'exponential', precision: 2 }), '-1.23E+4'); - assert.strictEqual(formatHelper.format(500000001, { type: 'exponential', precision: 2 }), '5.00E+8'); - assert.strictEqual(formatHelper.format(1.56662165464E+99, { type: 'exponential', precision: 2 }), '1.57E+99'); - assert.strictEqual(formatHelper.format(1.56662165464E-99, { type: 'exponential', precision: 2 }), '1.57E-99'); - }); + QUnit.test('large number format without number type', function(assert) { + assert.strictEqual(formatHelper.format(12345.67, { type: 'largeNumber', precision: 2 }), '12.35K'); + assert.strictEqual(formatHelper.format(12345.67, { type: 'thousands', precision: 2 }), '12.35K'); + assert.strictEqual(formatHelper.format(12345.67, { type: 'millions', precision: 3 }), '0.012M'); + }); - QUnit.test('exponential number type precision', function(assert) { - assert.strictEqual(formatHelper.format(1234, 'exponential'), '1.2E+3'); - assert.strictEqual(formatHelper.format(5, { type: 'exponential', precision: 0 }), '5E+0'); - assert.strictEqual(formatHelper.format(0.0081, { type: 'exponential', precision: 1 }), '8.1E-3'); - assert.strictEqual(formatHelper.format(-12345.67, { type: 'exponential', precision: 2 }), '-1.23E+4'); - assert.strictEqual(formatHelper.format(500000001, { type: 'exponential', precision: 3 }), '5.000E+8'); - assert.strictEqual(formatHelper.format(-123456789, { type: 'exponential', precision: 8 }), '-1.23456789E+8'); - }); + QUnit.test('Empty format for number', function(assert) { + assert.equal(formatHelper.format(1204, ''), '1204'); + assert.equal(formatHelper.format(12.04, ''), '12.04'); + }); - QUnit.test('exponential number type round', function(assert) { - assert.strictEqual(formatHelper.format(0.00999, { type: 'exponential', precision: 0 }), '1E-2'); - assert.strictEqual(formatHelper.format(0.00999, { type: 'exponential', precision: 2 }), '9.99E-3'); - assert.strictEqual(formatHelper.format(999, { type: 'exponential', precision: 1 }), '1.0E+3'); - }); + QUnit.test('exponential number type pow', function(assert) { + assert.strictEqual(formatHelper.format(5, { type: 'exponEntial', precision: 2 }), '5.00E+0'); + assert.strictEqual(formatHelper.format(0.0081, { type: 'exponential', precision: 2 }), '8.10E-3'); + assert.strictEqual(formatHelper.format(-12345.67, { type: 'exponential', precision: 2 }), '-1.23E+4'); + assert.strictEqual(formatHelper.format(500000001, { type: 'exponential', precision: 2 }), '5.00E+8'); + assert.strictEqual(formatHelper.format(1.56662165464E+99, { type: 'exponential', precision: 2 }), '1.57E+99'); + assert.strictEqual(formatHelper.format(1.56662165464E-99, { type: 'exponential', precision: 2 }), '1.57E-99'); + }); + QUnit.test('exponential number type precision', function(assert) { + assert.strictEqual(formatHelper.format(1234, 'exponential'), '1.2E+3'); + assert.strictEqual(formatHelper.format(5, { type: 'exponential', precision: 0 }), '5E+0'); + assert.strictEqual(formatHelper.format(0.0081, { type: 'exponential', precision: 1 }), '8.1E-3'); + assert.strictEqual(formatHelper.format(-12345.67, { type: 'exponential', precision: 2 }), '-1.23E+4'); + assert.strictEqual(formatHelper.format(500000001, { type: 'exponential', precision: 3 }), '5.000E+8'); + assert.strictEqual(formatHelper.format(-123456789, { type: 'exponential', precision: 8 }), '-1.23456789E+8'); + }); - QUnit.test('exponential number type positive and negative number', function(assert) { - assert.strictEqual(formatHelper.format(0, { type: 'exponential', precision: 2 }), '0.00E+0'); - assert.strictEqual(formatHelper.format(1234, { type: 'exponential', precision: 2 }), '1.23E+3'); - assert.strictEqual(formatHelper.format(-1234, { type: 'exponential', precision: 2 }), '-1.23E+3'); - }); + QUnit.test('exponential number type round', function(assert) { + assert.strictEqual(formatHelper.format(0.00999, { type: 'exponential', precision: 0 }), '1E-2'); + assert.strictEqual(formatHelper.format(0.00999, { type: 'exponential', precision: 2 }), '9.99E-3'); + assert.strictEqual(formatHelper.format(999, { type: 'exponential', precision: 1 }), '1.0E+3'); + }); + + QUnit.test('exponential number type positive and negative number', function(assert) { + assert.strictEqual(formatHelper.format(0, { type: 'exponential', precision: 2 }), '0.00E+0'); + assert.strictEqual(formatHelper.format(1234, { type: 'exponential', precision: 2 }), '1.23E+3'); + assert.strictEqual(formatHelper.format(-1234, { type: 'exponential', precision: 2 }), '-1.23E+3'); }); - QUnit.module('formatNumberEx', () => { - QUnit.test('not execute formatNumberEx for non-number value', function(assert) { - assert.equal(formatHelper.format('test string', { format: 'currency' }), 'test string'); - }); + }); - QUnit.test('not execute formatNumberEx for infinite value', function(assert) { - assert.equal(formatHelper.format(Infinity, { format: 'fixedPoint' }), Infinity.toString()); - assert.equal(formatHelper.format(-Infinity, { format: 'fixedPoint' }), (-Infinity).toString()); - }); + QUnit.module('formatNumberEx', () => { + QUnit.test('not execute formatNumberEx for non-number value', function(assert) { + assert.equal(formatHelper.format('test string', { format: 'currency' }), 'test string'); + }); - QUnit.test('not execute formatNumberEx for NaN value', function(assert) { - assert.equal(formatHelper.format(NaN, { format: 'fixedPoint' }), NaN.toString()); - }); + QUnit.test('not execute formatNumberEx for infinite value', function(assert) { + assert.equal(formatHelper.format(Infinity, { format: 'fixedPoint' }), Infinity.toString()); + assert.equal(formatHelper.format(-Infinity, { format: 'fixedPoint' }), (-Infinity).toString()); + }); - QUnit.test('Case insensitive currency', function(assert) { - assert.equal(formatHelper.format(1204, 'currency'), '$1,204'); - assert.equal(formatHelper.format(1204, { type: 'cuRrency', precision: 2 }), '$1,204.00'); - }); + QUnit.test('not execute formatNumberEx for NaN value', function(assert) { + assert.equal(formatHelper.format(NaN, { format: 'fixedPoint' }), NaN.toString()); + }); + + QUnit.test('Case insensitive currency', function(assert) { + assert.equal(formatHelper.format(1204, 'currency'), '$1,204'); + assert.equal(formatHelper.format(1204, { type: 'cuRrency', precision: 2 }), '$1,204.00'); + }); - QUnit.test('not execute formatNumberEx for string w with set format', function(assert) { - /* eslint-disable no-extend-native */ + QUnit.test('not execute formatNumberEx for string w with set format', function(assert) { + /* eslint-disable no-extend-native */ - String.prototype.format = noop; + String.prototype.format = noop; - assert.equal(formatHelper.format(123, 'currency'), '$123'); + assert.equal(formatHelper.format(123, 'currency'), '$123'); - // cleanup - delete String.prototype.format; - }); + // cleanup + delete String.prototype.format; }); }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.widgets.tests.js b/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.widgets.tests.js index 29056fc61eed..d0488ddaab72 100644 --- a/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.widgets.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.localization/localization.globalize.widgets.tests.js @@ -1,13 +1,27 @@ -const likelySubtags = require('cldr-core/supplemental/likelySubtags.json!'); -const numberingSystems = require('cldr-core/supplemental/numberingSystems.json!'); -const Globalize = require('globalize'); +import likelySubtags from 'cldr-core/supplemental/likelySubtags.json!'; +import numberingSystems from 'cldr-core/supplemental/numberingSystems.json!'; +import Globalize from 'globalize'; -const cldrData = [ - require('devextreme-cldr-data/fa.json!json'), - require('devextreme-cldr-data/mr.json!json'), - require('devextreme-cldr-data/ar.json!json'), - require('devextreme-cldr-data/de.json!json'), -]; +import fa from 'devextreme-cldr-data/fa.json!json'; +import mr from 'devextreme-cldr-data/mr.json!json'; +import ar from 'devextreme-cldr-data/ar.json!json'; +import de from 'devextreme-cldr-data/de.json!json'; + +import 'common/core/localization/globalize/core'; +import 'common/core/localization/globalize/number'; +import 'common/core/localization/globalize/currency'; +import 'common/core/localization/globalize/date'; +import 'common/core/localization/globalize/message'; + +import $ from 'jquery'; +import dateLocalization from 'common/core/localization/date'; + +import 'ui/date_box'; +import 'viz/chart'; + +import * as ExcelExport from '__internal/exporter/exceljs/export_format'; + +const cldrData = [fa, mr, ar, de]; Globalize.load(likelySubtags); Globalize.load(numberingSystems); @@ -16,20 +30,6 @@ cldrData.forEach(localeCldrData => { Globalize.load(localeCldrData); }); -require('common/core/localization/globalize/core'); -require('common/core/localization/globalize/number'); -require('common/core/localization/globalize/currency'); -require('common/core/localization/globalize/date'); -require('common/core/localization/globalize/message'); - -const $ = require('jquery'); -const dateLocalization = require('common/core/localization/date'); - -require('ui/date_box'); -require('viz/chart'); - -const ExcelExport = require('__internal/exporter/exceljs/export_format'); - const TEXTEDITOR_INPUT_SELECTOR = '.dx-texteditor-input'; const DATEVIEW_ITEM_SELECTOR = '.dx-dateview-item'; const DATEVIEW_ROLLER_DAY_SELECTOR = '.dx-dateviewroller-day'; diff --git a/packages/devextreme/testing/tests/DevExpress.localization/localization.messages.test.js b/packages/devextreme/testing/tests/DevExpress.localization/localization.messages.test.js index 6f4570399b3b..12845c890d5a 100644 --- a/packages/devextreme/testing/tests/DevExpress.localization/localization.messages.test.js +++ b/packages/devextreme/testing/tests/DevExpress.localization/localization.messages.test.js @@ -1,18 +1,18 @@ -const localization = require('localization'); -const dictionaries = {}; - -dictionaries['zh-tw'] = require('localization/messages/zh-tw.json!'); +import { loadMessages, locale, formatMessage } from 'localization'; +// eslint-disable-next-line spellcheck/spell-checker +import zhTwMessages from 'localization/messages/zh-tw.json!'; QUnit.module('Locale messages of DevExtreme', { }, () => { QUnit.test('test zh-TW locale format message', function(assert) { try { - localization.loadMessages(dictionaries['zh-tw']); - localization.locale('zh-TW'); - assert.equal(localization.formatMessage('Yes'), '是'); + // eslint-disable-next-line spellcheck/spell-checker + loadMessages(zhTwMessages); + locale('zh-TW'); + assert.equal(formatMessage('Yes'), '是'); } finally { - localization.locale('en'); + locale('en'); } }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.localization/validation.tests.js b/packages/devextreme/testing/tests/DevExpress.localization/validation.tests.js index 623796d1dd67..4b12138141f5 100644 --- a/packages/devextreme/testing/tests/DevExpress.localization/validation.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.localization/validation.tests.js @@ -1,22 +1,22 @@ -require('common/core/localization/globalize/core'); -require('common/core/localization/globalize/number'); -require('common/core/localization/globalize/currency'); -require('common/core/localization/globalize/date'); -require('common/core/localization/globalize/message'); -const cldrData = [ - require('devextreme-cldr-data/fr.json!json') -]; +import 'common/core/localization/globalize/core'; +import 'common/core/localization/globalize/number'; +import 'common/core/localization/globalize/currency'; +import 'common/core/localization/globalize/date'; +import 'common/core/localization/globalize/message'; -const ValidationEngine = require('ui/validation_engine'); -const Globalize = require('globalize'); -const localization = require('localization'); -const fr = require('localization/messages/fr.json!'); +import ValidationEngine from 'ui/validation_engine'; +import Globalize from 'globalize'; +import { loadMessages } from 'localization'; +import fr from 'localization/messages/fr.json!'; +import frCldr from 'devextreme-cldr-data/fr.json!json'; + +const cldrData = [frCldr]; cldrData.forEach(localeCldrData => { Globalize.load(localeCldrData); }); -localization.loadMessages(fr); +loadMessages(fr); QUnit.module('culture-specific validation', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/autocomplete.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/autocomplete.tests.js index 96bbb0814273..1465ec1041b3 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/autocomplete.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/autocomplete.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/autocomplete.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/autocomplete.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/calendar.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/calendar.tests.js index e66ea9a02d22..2e12f7ce1934 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/calendar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/calendar.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/calendar.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/calendar.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/calendarView.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/calendarView.tests.js index 379d0c3a2bde..1df0b685f22d 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/calendarView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/calendarView.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/calendarView.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/calendarView.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/checkbox.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/checkbox.tests.js index c1b0d6e44add..b5fc2724d7ee 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/checkbox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/checkbox.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/checkbox.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/checkbox.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/colorBox.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/colorBox.tests.js index 3bc387fd14fe..982ff39cba8b 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/colorBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/colorBox.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/colorBox.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/colorBox.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/colorView.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/colorView.tests.js index c201e83bda6d..f583b2c317b0 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/colorView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/colorView.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/colorView.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/colorView.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/dataGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/dataGrid.tests.js index ec74c8ebdb22..08524be4e186 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/dataGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/dataGrid.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.dataGrid/dataGrid.markup.tests.js'); +import '../DevExpress.ui.widgets.dataGrid/dataGrid.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/datebox.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/datebox.tests.js index 83b3da7dae74..1abfc6833567 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/datebox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/datebox.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/datebox.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/datebox.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/dropDownBox.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/dropDownBox.tests.js index 7f0b377d0e1c..0a924c4d3ddd 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/dropDownBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/dropDownBox.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/dropDownBox.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/dropDownBox.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/dropDownEditor.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/dropDownEditor.tests.js index db2ecf9228a4..75ed630f0be3 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/dropDownEditor.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/dropDownEditor.tests.js @@ -1,6 +1,6 @@ -require('../DevExpress.ui.widgets.editors/dropDownEditor.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/dropDownEditor.markup.tests.js'; -const $ = require('jquery'); +import $ from 'jquery'; QUnit.testStart(function() { const markup = diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/editor.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/editor.tests.js index 44c141bfad1c..35d3c16459a1 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/editor.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/editor.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/editor.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/editor.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/fieldChooser.js b/packages/devextreme/testing/tests/DevExpress.serverSide/fieldChooser.js index 6dcfd49d807f..e149cec5a68a 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/fieldChooser.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/fieldChooser.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.pivotGrid/fieldChooser.markup.tests.js'); +import '../DevExpress.ui.widgets.pivotGrid/fieldChooser.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/fileManager.test.js b/packages/devextreme/testing/tests/DevExpress.serverSide/fileManager.test.js index 99cab6583dd4..3279f15e81fc 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/fileManager.test.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/fileManager.test.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets/fileManagerParts/markup.tests.js'); +import '../DevExpress.ui.widgets/fileManagerParts/markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/fileUploader.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/fileUploader.tests.js index f4589a68023c..2bec0f8ddd17 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/fileUploader.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/fileUploader.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/fileUploader.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/fileUploader.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/filterBuilder.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/filterBuilder.tests.js index 8a0776fe2cf4..6ef6b145c922 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/filterBuilder.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/filterBuilder.tests.js @@ -1,4 +1,4 @@ -const $ = require('jquery'); +import $ from 'jquery'; QUnit.testStart(function() { $('#qunit-fixture').html('
'); @@ -6,4 +6,4 @@ QUnit.testStart(function() { QUnit.module('Filter Builder markup'); -require('../DevExpress.ui.widgets/filterBuilderParts/markupTests.js'); +import '../DevExpress.ui.widgets/filterBuilderParts/markupTests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/gantt.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/gantt.tests.js index 1be9953591fc..a729ff53a995 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/gantt.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/gantt.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets/gantt.markup.tests.js'); +import '../DevExpress.ui.widgets/gantt.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/localization.intl.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/localization.intl.tests.js index e7d178cf6c91..b76ef02a5a17 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/localization.intl.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/localization.intl.tests.js @@ -1 +1 @@ -require('../DevExpress.localization/localization.intl.tests.js'); +import '../DevExpress.localization/localization.intl.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/lookup.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/lookup.tests.js index ad9f60381275..2117e3c204f0 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/lookup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/lookup.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/lookup.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/lookup.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/numberBox.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/numberBox.tests.js index 19ef38e94b32..a0a3b0669b1e 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/numberBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/numberBox.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/numberBox.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/numberBox.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/overlay.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/overlay.tests.js index e522acc25f38..dcb97e2244d4 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/overlay.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/overlay.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); +import $ from 'jquery'; -require('ui/overlay/ui.overlay'); +import 'ui/overlay/ui.overlay'; QUnit.testStart(function() { const markup = '
'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/pivotGrid.test.js b/packages/devextreme/testing/tests/DevExpress.serverSide/pivotGrid.test.js index f77edd20916f..28c2b5db5f05 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/pivotGrid.test.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/pivotGrid.test.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.pivotGrid/pivotGrid.markup.tests.js'); +import '../DevExpress.ui.widgets.pivotGrid/pivotGrid.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/progressBar.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/progressBar.tests.js index eb4872b71ad4..02ef76eea59b 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/progressBar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/progressBar.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets/progressBar.markup.tests.js'); +import '../DevExpress.ui.widgets/progressBar.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/rangeSlider.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/rangeSlider.tests.js index 79c5b0289c8f..394299a66e6d 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/rangeSlider.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/rangeSlider.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/rangeSlider.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/rangeSlider.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/selectBox.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/selectBox.tests.js index ecc17d5906c9..2182ec5303da 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/selectBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/selectBox.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/selectBox.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/selectBox.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/slider.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/slider.tests.js index a668a821e87f..ee9f7e4166fd 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/slider.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/slider.tests.js @@ -1,4 +1,4 @@ -const $ = require('jquery'); +import $ from 'jquery'; const SLIDER_HANDLE_CLASS = 'dx-slider-handle'; const TOOLTIP_CLASS = 'dx-tooltip'; @@ -27,5 +27,5 @@ QUnit.test('there is no tooltip in markup on server', function(assert) { assert.notOk($tooltip.length); }); -require('../DevExpress.ui.widgets.editors/slider.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/slider.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/switch.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/switch.tests.js index b91ca3f996d2..87f85ba12381 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/switch.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/switch.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/switch.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/switch.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/tagBox.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/tagBox.tests.js index f710d02b08a8..329ed5c3ec78 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/tagBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/tagBox.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/tagBox.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/tagBox.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/textArea.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/textArea.tests.js index cf7894744cf6..a6a89cfc7dbd 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/textArea.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/textArea.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/textArea.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/textArea.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/textBox.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/textBox.tests.js index 6a78b3b280f3..1dda1bebd5ff 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/textBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/textBox.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/textbox.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/textbox.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/textEditor.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/textEditor.tests.js index 830fe319edde..15a76356118b 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/textEditor.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/textEditor.tests.js @@ -4,4 +4,4 @@ QUnit.testStart(function() { document.getElementById('qunit-fixture').innerHTML = markup; }); -require('../DevExpress.ui.widgets.editors/textEditorParts/markup.tests.js'); +import '../DevExpress.ui.widgets.editors/textEditorParts/markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/trackBar.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/trackBar.tests.js index f461dea2be50..b5424648c678 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/trackBar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/trackBar.tests.js @@ -1,8 +1,8 @@ -require('../DevExpress.ui.widgets.editors/trackBar.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/trackBar.markup.tests.js'; -const $ = require('jquery'); +import $ from 'jquery'; -require('ui/track_bar'); +import 'ui/track_bar'; QUnit.testStart(function() { const markup = diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/treeList.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/treeList.tests.js index a12733657c3a..c8711b637d5f 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/treeList.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/treeList.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.treeList/treeList.markup.tests.js'); +import '../DevExpress.ui.widgets.treeList/treeList.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/utils.ready_callbacks.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/utils.ready_callbacks.tests.js index 31683ada9350..a9d6c21a0d4f 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/utils.ready_callbacks.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/utils.ready_callbacks.tests.js @@ -1,4 +1,4 @@ -const readyCallbacks = require('core/utils/ready_callbacks'); +import readyCallbacks from 'core/utils/ready_callbacks'; QUnit.module('readyCallbacks injection', { afterEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/validationGroup.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/validationGroup.tests.js index add3678c14f1..6bea69aa6175 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/validationGroup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/validationGroup.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/validationGroup.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/validationGroup.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/validationSummary.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/validationSummary.tests.js index b1a3df75a59e..9ba926ac4497 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/validationSummary.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/validationSummary.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/validationSummary.markup.tests.js'); +import '../DevExpress.ui.widgets.editors/validationSummary.markup.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/validator.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/validator.tests.js index d6eadca67b34..a6305d78403c 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/validator.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/validator.tests.js @@ -1 +1 @@ -require('../DevExpress.ui.widgets.editors/validator.tests.js'); +import '../DevExpress.ui.widgets.editors/validator.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.serverSide/widgetsCreation.tests.js b/packages/devextreme/testing/tests/DevExpress.serverSide/widgetsCreation.tests.js index 86030b7adb10..090386d3d9ff 100644 --- a/packages/devextreme/testing/tests/DevExpress.serverSide/widgetsCreation.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.serverSide/widgetsCreation.tests.js @@ -1,6 +1,6 @@ -const widgets = require('../../helpers/widgetsList.js').widgetsList; +import { widgetsList as widgets } from '../../helpers/widgetsList.js'; -const DataSource = require('common/data/data_source'); +import DataSource from 'common/data/data_source'; QUnit.module('Widget creation', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/click.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/click.tests.js index b1a60379837d..d7faf25dfb2d 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/click.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/click.tests.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import { noop } from 'core/utils/common'; import clickEvent from 'common/core/events/click'; +import { removeEvent } from 'common/core/events/remove'; import domUtils from '__internal/core/utils/m_dom'; import support from '__internal/core/utils/m_support'; import devices from '__internal/core/m_devices'; @@ -425,3 +426,52 @@ QUnit.test('dxclick should not be fired twice when \'click\' is triggered from i pointer.start().down().up(); $(document).off('dxclick', $.noop); }); + +QUnit.test('foreign dxremove handler on the previously clicked node should survive a dxclick on another node (5025)', function(assert) { + let clickedNodeClickCount = 0; + let otherNodeClickCount = 0; + let foreignHandlerCallCount = 0; + + const $clicked = $('#first').on('dxclick', function() { + clickedNodeClickCount++; + }); + const $other = $('#second').on('dxclick', function() { + otherNodeClickCount++; + }); + + nativePointerMock($clicked).start().click(); + + $clicked.on(removeEvent, function() { + foreignHandlerCallCount++; + }); + + nativePointerMock($other).start().click(); + + $clicked.triggerHandler({ type: removeEvent }); + + assert.equal(clickedNodeClickCount, 1, 'dxclick fired on the clicked node'); + assert.equal(otherNodeClickCount, 1, 'dxclick fired on the other node'); + assert.equal(foreignHandlerCallCount, 1, `foreign ${removeEvent} handler is still subscribed`); +}); + +QUnit.test('foreign dxremove handler should survive a second dxclick on the same node', function(assert) { + let clickCount = 0; + let foreignHandlerCallCount = 0; + + const $clicked = $('#first').on('dxclick', function() { + clickCount++; + }); + + nativePointerMock($clicked).start().click(); + + $clicked.on(removeEvent, function() { + foreignHandlerCallCount++; + }); + + nativePointerMock($clicked).start().click(); + + $clicked.triggerHandler({ type: removeEvent }); + + assert.equal(clickCount, 2, 'dxclick fired twice on the same node'); + assert.equal(foreignHandlerCallCount, 1, `foreign ${removeEvent} handler is still subscribed`); +}); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/contextmenu.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/contextmenu.tests.js index b2fbf79af553..cc79ce03817d 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/contextmenu.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/contextmenu.tests.js @@ -1,9 +1,9 @@ -const $ = require('jquery'); -const noop = require('core/utils/common').noop; -const devices = require('core/devices'); -const support = require('core/utils/support'); -const holdEvent = require('common/core/events/hold'); -const contextMenuEvent = require('common/core/events/contextmenu'); +import $ from 'jquery'; +import { noop } from 'core/utils/common'; +import devices from 'core/devices'; +import * as support from 'core/utils/support'; +import holdEvent from 'common/core/events/hold'; +import * as contextMenuEvent from 'common/core/events/contextmenu'; QUnit.testStart(function() { const markup = diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/dblclick.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/dblclick.tests.js index acbeecedcecb..71464819b371 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/dblclick.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/dblclick.tests.js @@ -1,7 +1,7 @@ -const $ = require('jquery'); -const dblclickEvent = require('common/core/events/dblclick'); -const { dblClick } = require('__internal/events/dblclick'); -const pointerMock = require('../../helpers/pointerMock.js'); +import $ from 'jquery'; +import dblclickEvent from 'common/core/events/dblclick'; +import { dblClick } from '__internal/events/dblclick'; +import pointerMock from '../../helpers/pointerMock.js'; QUnit.testStart(function() { const markup = diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/drag.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/drag.tests.js index 182e9b32063c..cdcad45ebffa 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/drag.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/drag.tests.js @@ -1,10 +1,10 @@ -const $ = require('jquery'); -const noop = require('core/utils/common').noop; -const dragEvents = require('common/core/events/drag'); -const support = require('core/utils/support'); -const GestureEmitter = require('common/core/events/gesture/emitter.gesture'); +import $ from 'jquery'; +import { noop } from 'core/utils/common'; +import * as dragEvents from 'common/core/events/drag'; +import * as support from 'core/utils/support'; +import GestureEmitter from 'common/core/events/gesture/emitter.gesture'; const dropTargets = dragEvents.dropTargets; -const pointerMock = require('../../helpers/pointerMock.js'); +import pointerMock from '../../helpers/pointerMock.js'; $('#qunit-fixture').addClass('qunit-fixture-visible'); QUnit.testStart(function() { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/events.utils.nodesDisposing.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/events.utils.nodesDisposing.tests.js index c59021aa41f3..b42d196ee5ec 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/events.utils.nodesDisposing.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/events.utils.nodesDisposing.tests.js @@ -31,7 +31,7 @@ QUnit.test('should clean elementDataMap when using subscribeNodesDisposing and u ? afterSubscribeElementData[removeEvent].handleObjects.length : 0; - unsubscribeNodesDisposing(clickEvent, subscriptionData.callback, subscriptionData.nodes); + unsubscribeNodesDisposing(clickEvent, subscriptionData.onceCallback, subscriptionData.nodes); const finalElementData = eventsEngine.elementDataMap.get(document); const afterUnsubscribeHandleObjectsCount = finalElementData && finalElementData[removeEvent] diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/feedback.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/feedback.tests.js index 6458f0852da3..ee54e87596ba 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/feedback.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/feedback.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const noop = require('core/utils/common').noop; -const devices = require('core/devices'); -const feedbackEvents = require('common/core/events/core/emitter.feedback'); -const pointerMock = require('../../helpers/pointerMock.js'); +import $ from 'jquery'; +import { noop } from 'core/utils/common'; +import devices from 'core/devices'; +import * as feedbackEvents from 'common/core/events/core/emitter.feedback'; +import pointerMock from '../../helpers/pointerMock.js'; QUnit.testStart(function() { const markup = diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/hold.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/hold.tests.js index 78a4db9961b1..a00284ee8a34 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/hold.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/hold.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); -const holdEvent = require('common/core/events/hold'); -const pointerMock = require('../../helpers/pointerMock.js'); +import $ from 'jquery'; +import holdEvent from 'common/core/events/hold'; +import pointerMock from '../../helpers/pointerMock.js'; QUnit.testStart(function() { const markup = diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/hover.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/hover.tests.js index a358fb58f39d..791e7fc26416 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/hover.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/hover.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); -const devices = require('core/devices'); -const hoverEvents = require('common/core/events/hover'); +import $ from 'jquery'; +import devices from 'core/devices'; +import * as hoverEvents from 'common/core/events/hover'; QUnit.testStart(function() { const markup = diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/pointer.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/pointer.tests.js index 93b47fdba064..fb85cd963e47 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/pointer.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/pointer.tests.js @@ -1,4 +1,4 @@ -const $ = require('jquery'); +import $ from 'jquery'; QUnit.testStart(function() { const markup = @@ -9,8 +9,8 @@ QUnit.testStart(function() { $('#qunit-fixture').html(markup); }); -require('./pointerParts/baseTests.js'); -require('./pointerParts/mouseTests.js'); -require('./pointerParts/touchTests.js'); -require('./pointerParts/mouseAndTouchTests.js'); -require('./pointerParts/strategySelectionTests.js'); +import './pointerParts/baseTests.js'; +import './pointerParts/mouseTests.js'; +import './pointerParts/touchTests.js'; +import './pointerParts/mouseAndTouchTests.js'; +import './pointerParts/strategySelectionTests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/baseTests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/baseTests.js index af476b785267..fdfb40692a7c 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/baseTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/baseTests.js @@ -1,9 +1,9 @@ -const $ = require('jquery'); -const noop = require('core/utils/common').noop; -const BaseStrategy = require('common/core/events/pointer/base'); -const registerEvent = require('common/core/events/core/event_registrator'); -const typeUtils = require('core/utils/type'); -const special = require('../../../helpers/eventHelper.js').special; +import $ from 'jquery'; +import { noop } from 'core/utils/common'; +import BaseStrategy from 'common/core/events/pointer/base'; +import registerEvent from 'common/core/events/core/event_registrator'; +import * as typeUtils from 'core/utils/type'; +import { special } from '../../../helpers/eventHelper.js'; const BubbledTestEventMap = { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/mouseAndTouchTests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/mouseAndTouchTests.js index ac065b0129b6..9f407cde1721 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/mouseAndTouchTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/mouseAndTouchTests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const MouseAndTouchStrategy = require('common/core/events/pointer/mouse_and_touch'); -const registerEvent = require('common/core/events/core/event_registrator'); -const nativePointerMock = require('../../../helpers/nativePointerMock.js'); -const special = require('../../../helpers/eventHelper.js').special; +import $ from 'jquery'; +import MouseAndTouchStrategy from 'common/core/events/pointer/mouse_and_touch'; +import registerEvent from 'common/core/events/core/event_registrator'; +import nativePointerMock from '../../../helpers/nativePointerMock.js'; +import { special } from '../../../helpers/eventHelper.js'; QUnit.module('mouse and touch events', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/mouseTests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/mouseTests.js index 8733017ed2d0..2b69eafcfbec 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/mouseTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/mouseTests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const MouseStrategy = require('common/core/events/pointer/mouse'); -const registerEvent = require('common/core/events/core/event_registrator'); -const nativePointerMock = require('../../../helpers/nativePointerMock.js'); -const special = require('../../../helpers/eventHelper.js').special; +import $ from 'jquery'; +import MouseStrategy from 'common/core/events/pointer/mouse'; +import registerEvent from 'common/core/events/core/event_registrator'; +import nativePointerMock from '../../../helpers/nativePointerMock.js'; +import { special } from '../../../helpers/eventHelper.js'; QUnit.module('mouse events', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/touchTests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/touchTests.js index 78cf41582d81..8cc336fa4fb2 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/touchTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/pointerParts/touchTests.js @@ -1,10 +1,10 @@ -const $ = require('jquery'); -const TouchStrategy = require('common/core/events/pointer/touch'); -const registerEvent = require('common/core/events/core/event_registrator'); -const nativePointerMock = require('../../../helpers/nativePointerMock.js'); -const noop = require('core/utils/common').noop; -const special = require('../../../helpers/eventHelper.js').special; -const eventsEngine = require('common/core/events/core/events_engine'); +import $ from 'jquery'; +import TouchStrategy from 'common/core/events/pointer/touch'; +import registerEvent from 'common/core/events/core/event_registrator'; +import nativePointerMock from '../../../helpers/nativePointerMock.js'; +import { noop } from 'core/utils/common'; +import { special } from '../../../helpers/eventHelper.js'; +import eventsEngine from 'common/core/events/core/events_engine'; QUnit.module('touch events', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/scroll.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/scroll.tests.js index d80ac13750a1..f4738fd8e668 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/scroll.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/scroll.tests.js @@ -1,12 +1,12 @@ -const $ = require('jquery'); -const noop = require('core/utils/common').noop; -const scrollEvents = require('common/core/events/gesture/emitter.gesture.scroll'); -const GestureEmitter = require('common/core/events/gesture/emitter.gesture'); -const eventUtils = require('common/core/events/utils/index'); -const devices = require('core/devices'); -const compareVersions = require('core/utils/version').compare; -const animationFrame = require('common/core/animation/frame'); -const pointerMock = require('../../helpers/pointerMock.js'); +import $ from 'jquery'; +import { noop } from 'core/utils/common'; +import scrollEvents from 'common/core/events/gesture/emitter.gesture.scroll'; +import GestureEmitter from 'common/core/events/gesture/emitter.gesture'; +import * as eventUtils from 'common/core/events/utils/index'; +import devices from 'core/devices'; +import { compare as compareVersions } from 'core/utils/version'; +import * as animationFrame from 'common/core/animation/frame'; +import pointerMock from '../../helpers/pointerMock.js'; QUnit.testStart(function() { const markup = diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/transformation.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/transformation.tests.js index a0e83e63a5a6..e4422f6b6700 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/transformation.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/transformation.tests.js @@ -1,5 +1,5 @@ -const $ = require('jquery'); -const transformEvent = require('common/core/events/transform'); +import $ from 'jquery'; +import * as transformEvent from 'common/core/events/transform'; $('#qunit-fixture').addClass('qunit-fixture-visible'); QUnit.testStart(function() { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.events/wheel.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.events/wheel.tests.js index f2f68c750db5..57f4ef469f58 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.events/wheel.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.events/wheel.tests.js @@ -1,6 +1,6 @@ -const $ = require('jquery'); -const wheelEvent = require('common/core/events/core/wheel'); -const nativePointerMock = require('../../helpers/nativePointerMock.js'); +import $ from 'jquery'; +import * as wheelEvent from 'common/core/events/core/wheel'; +import nativePointerMock from '../../helpers/nativePointerMock.js'; QUnit.testStart(function() { const markup = diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/adaptiveColumns.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/adaptiveColumns.tests.js index 7159ed187fb5..3ec0cf098d5a 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/adaptiveColumns.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/adaptiveColumns.tests.js @@ -105,19 +105,22 @@ QUnit.module('AdaptiveColumns', { if(name === 'width' || name === 'height') { ++cssInvokeCounter; } + return cssFunc.apply(this, arguments); }; - // arrange, act - $('.dx-datagrid').width(200); - setupDataGrid(this); - this.rowsView.render($('#container')); - this.resizingController.updateDimensions(); - this.clock.tick(10); - - // assert - assert.equal(cssInvokeCounter, 0, 'no $.css() invokes for width/height CSS properties'); - - renderer.fn.css = cssFunc; + try { + // arrange, act + $('.dx-datagrid').width(200); + setupDataGrid(this); + this.rowsView.render($('#container')); + this.resizingController.updateDimensions(); + this.clock.tick(10); + + // assert + assert.equal(cssInvokeCounter, 0, 'no $.css() invokes for width/height CSS properties'); + } finally { + renderer.fn.css = cssFunc; + } }); // T516888 diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsResizingReorderingModule.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsResizingReorderingModule.tests.js index ea8ba47f77f1..045056dce0f9 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsResizingReorderingModule.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/columnsResizingReorderingModule.tests.js @@ -968,6 +968,13 @@ QUnit.module('Columns resizing', { ] }), + dataSource: { + key: noop, + store: noop, + pageCount: () => 1, + totalCount: () => 0 + }, + columnsResizer: { isResizing: () => true }, diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js index c2fc39385b72..246015e62d13 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataController.tests.js @@ -213,7 +213,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod ]; const dataSource = createDataSource(array, { key: 'id' }); - this.dataController.pushed.add(pushedSpy); + this.dataSourceController.pushed.add(pushedSpy); // assert assert.strictEqual(pushedSpy.callCount, 0, 'the pushed callback was not called'); @@ -239,9 +239,9 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod ]; let dataSource = createDataSource(array, { key: 'id' }); - this.dataController.dataPushedHandlerProxy = dataPushedHandlerSpy; + this.dataSourceController.dataPushedHandlerProxy = dataPushedHandlerSpy; this.dataController.setDataSource(dataSource); - dataSource = this.dataController.dataSource(); + dataSource = this.dataSourceController.getAdapter(); dataSource.load(); // assert @@ -319,7 +319,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.option('dataSource', []); - const dataSource = this.dataController.dataSource()._dataSource; + const dataSource = this.dataSourceController.getDataSource(); this.dataController.changed.add(function(args) { changedCount++; @@ -344,7 +344,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // assert assert.ok(dataSource._disposed, 'dataSource is disposed'); - assert.strictEqual(this.dataController._dataSource, null, 'dataSourceAdapter is removed'); + assert.strictEqual(this.dataSourceController.getAdapter(), null, 'dataSourceAdapter is removed'); }); // T697860 @@ -383,7 +383,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod dataSource.load(); // act - this.dataController.setDataSource(null); + this.dataController._disposeDataSource(); // assert assert.strictEqual(loadingChangedSpy.callCount, 2, 'loadingChanged call count'); @@ -503,7 +503,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: true, desc: false }, { selector: 'age', isExpanded: true, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: true, desc: false }, { selector: 'age', isExpanded: true, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.equal(this.dataController.items()[1].data.key, 30); assert.equal(changedCallCount, 1, 'changed called one time'); // T122785 @@ -535,7 +535,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: false, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: false, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.deepEqual(this.dataController.items()[0].data.collapsedItems, [{ name: 'Alex', age: 30 }]); assert.equal(this.dataController.items()[0].data.items, null); @@ -577,7 +577,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod assert.equal(loadingCount, 1, 'loading called one time'); // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: true, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: true, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.equal(this.dataController.items()[0].data.items.length, 1); @@ -590,7 +590,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod assert.equal(changedCallCount, 1, 'changed called one time'); // T372049 assert.equal(loadingCount, 1, 'loading is not called on collapseAll'); - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: false, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: false, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.strictEqual(this.dataController.items()[0].data.items, null); }); @@ -632,7 +632,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod assert.deepEqual(loadingArgs[0].group, [{ selector: 'name', isExpanded: true, desc: false }], 'loading group arg'); // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: true, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: true, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.equal(this.dataController.items()[0].data.items.length, 1); @@ -645,7 +645,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod assert.equal(changedCallCount, 1, 'changed called one time after collapseAll'); // T372049 assert.equal(loadingArgs.length, 1, 'loading is not called after collapseAll'); - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'name', isExpanded: false, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'name', isExpanded: false, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'Alex'); assert.strictEqual(this.dataController.items()[0].data.items, null); }); @@ -873,8 +873,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act this.dataController.resetDataSource(); - const dataIndexGetter = this.dataController._dataSource.getDataIndexGetter(); - this.dataController._dataSource.getDataIndexGetter(); + const dataIndexGetter = this.dataSourceController.getDataIndexGetter(); assert.deepEqual(this.dataController._columnsController.getSortDataSourceParameters(), [{ desc: false, selector: dataIndexGetter }], 'Sort parameters'); // assert assert.equal(dataSource.items()[0].name, 'Alex', 'Item0'); @@ -1210,7 +1209,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act this.dataController.resetDataSource(); - assert.equal(this.dataController.pageCount(), 2); + assert.equal(this.dataSourceController.pageCount(), 2); this.dataController.getPageIndexByKey('Bob').done(function(pageIndex) { assert.equal(pageIndex, 1); }); @@ -1235,7 +1234,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act this.dataController.resetDataSource(); - assert.equal(this.dataController.pageCount(), 4); + assert.equal(this.dataSourceController.pageCount(), 4); this.dataController.getPageIndexByKey('Bob').done(function(pageIndex) { assert.equal(pageIndex, 3); }); @@ -1262,10 +1261,11 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act const dataController = this.dataController; + const dataSourceController = this.dataSourceController; dataController.resetDataSource(); // assert dataController.getPageIndexByKey({ name: 'Bob', age: 24 }).done(function(pageIndex) { - assert.equal(dataController.pageCount(), 5); + assert.equal(dataSourceController.pageCount(), 5); assert.equal(pageIndex, 3); }); }); @@ -1293,11 +1293,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act const dataController = this.dataController; + const dataSourceController = this.dataSourceController; dataController.resetDataSource(); // assert dataController.getGlobalRowIndexByKey('Mark').done(function(globalRowIndex) { ++foundRowCount; - assert.equal(dataController.pageCount(), 4, 'Page count'); + assert.equal(dataSourceController.pageCount(), 4, 'Page count'); assert.equal(globalRowIndex, 5, 'Mark'); }); dataController.getGlobalRowIndexByKey('Alex').done(function(globalRowIndex) { @@ -1377,11 +1378,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act const dataController = this.dataController; + const dataSourceController = this.dataSourceController; dataController.resetDataSource(); // assert dataController.getGlobalRowIndexByKey(1).done(globalRowIndex => { - assert.equal(dataController.pageCount(), 3, 'Page count'); + assert.equal(dataSourceController.pageCount(), 3, 'Page count'); assert.equal(globalRowIndex, 1, 'globalRowIndex'); done(); }); @@ -1413,11 +1415,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act const dataController = this.dataController; + const dataSourceController = this.dataSourceController; dataController.resetDataSource(); // assert dataController.getGlobalRowIndexByKey('Sad').done(function(globalRowIndex) { ++foundRowCount; - assert.equal(dataController.pageCount(), 5, 'Page count'); + assert.equal(dataSourceController.pageCount(), 5, 'Page count'); assert.equal(globalRowIndex, 4, 'Sad'); }); dataController.getGlobalRowIndexByKey('Alex').done(function(globalRowIndex) { @@ -1481,11 +1484,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act const dataController = this.dataController; + const dataSourceController = this.dataSourceController; dataController.resetDataSource(); // assert dataController.getGlobalRowIndexByKey('Sad').done(function(globalRowIndex) { ++foundRowCount; - assert.equal(dataController.pageCount(), 5, 'Page count'); + assert.equal(dataSourceController.pageCount(), 5, 'Page count'); assert.equal(globalRowIndex, 4, 'Sad'); }); dataController.getGlobalRowIndexByKey('Alex').done(function(globalRowIndex) { @@ -1549,11 +1553,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act const dataController = this.dataController; + const dataSourceController = this.dataSourceController; dataController.resetDataSource(); // assert dataController.getGlobalRowIndexByKey('Sad').done(function(globalRowIndex) { ++foundRowCount; - assert.equal(dataController.pageCount(), 3, 'Page count'); + assert.equal(dataSourceController.pageCount(), 3, 'Page count'); assert.equal(globalRowIndex, 2, 'Sad'); }); dataController.getGlobalRowIndexByKey('Alex').done(function(globalRowIndex) { @@ -1616,11 +1621,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act const dataController = this.dataController; + const dataSourceController = this.dataSourceController; dataController.resetDataSource(); // assert dataController.getGlobalRowIndexByKey('Alex').done(function(globalRowIndex) { ++foundRowCount; - assert.equal(dataController.pageCount(), 5, 'Page count'); + assert.equal(dataSourceController.pageCount(), 5, 'Page count'); assert.equal(globalRowIndex, 2, 'Alex'); }); dataController.getGlobalRowIndexByKey('Bob').done(function(globalRowIndex) { @@ -1684,11 +1690,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act const dataController = this.dataController; + const dataSourceController = this.dataSourceController; dataController.resetDataSource(); // assert dataController.getGlobalRowIndexByKey('Alex').done(function(globalRowIndex) { ++foundRowCount; - assert.equal(dataController.pageCount(), 5, 'Page count'); + assert.equal(dataSourceController.pageCount(), 5, 'Page count'); assert.equal(globalRowIndex, 2, 'Alex'); }); dataController.getGlobalRowIndexByKey('Bob').done(function(globalRowIndex) { @@ -1756,11 +1763,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod // act const dataController = this.dataController; + const dataSourceController = this.dataSourceController; dataController.resetDataSource(); // assert dataController.getGlobalRowIndexByKey('Alex').done(function(globalRowIndex) { ++foundRowCount; - assert.equal(dataController.pageCount(), 2, 'Page count'); + assert.equal(dataSourceController.pageCount(), 2, 'Page count'); assert.equal(globalRowIndex, 0, 'Alex'); }); dataController.getGlobalRowIndexByKey('Bob').done(function(globalRowIndex) { @@ -1955,7 +1963,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'team', isExpanded: true, desc: false }]); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'team', isExpanded: true, desc: false }]); assert.equal(this.dataController.items()[0].data.key, 'internal'); assert.equal(this.dataController.items()[1].data.name, 'Bob'); }); @@ -1998,13 +2006,13 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.setDataSource(dataSource); dataSource.load(); - assert.ok(this.dataController.dataSource().sort(), 'sort parameters'); + assert.ok(this.dataSourceController.getAdapter().sort(), 'sort parameters'); // act this.columnsController.changeSortOrder(0, 'none'); // assert - assert.ok(!this.dataController.dataSource().sort(), 'no sort parameters'); + assert.ok(!this.dataSourceController.getAdapter().sort(), 'no sort parameters'); assert.deepEqual(this.dataController.items()[0].values, ['Alex', 30]); assert.deepEqual(this.dataController.items()[1].values, ['Dan', 25]); @@ -2028,7 +2036,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.setDataSource(dataSource); dataSource.load(); - assert.ok(this.dataController._dataSource.group()); + assert.ok(this.dataSourceController.getAdapter().group()); assert.equal(this.dataController.items()[0].rowType, 'group'); }); @@ -2080,8 +2088,8 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod }); // assert - assert.deepEqual(this.dataController._dataSource.sort(), [{ selector: 'age', desc: false }], 'sort from user state is applied by dataField'); - assert.deepEqual(this.dataController._dataSource.group(), null, 'group from user state not applied because dataSource not has name column'); + assert.deepEqual(this.dataSourceController.getAdapter().sort(), [{ selector: 'age', desc: false }], 'sort from user state is applied by dataField'); + assert.deepEqual(this.dataSourceController.getAdapter().group(), null, 'group from user state not applied because dataSource not has name column'); assert.deepEqual(this.dataController.items().length, 3); assert.deepEqual(this.dataController.items()[0].values, [20]); assert.deepEqual(this.dataController.items()[1].values, [25]); @@ -2099,12 +2107,12 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.setDataSource(dataSource); dataSource.load(); - assert.ok(this.dataController.dataSource().group()); + assert.ok(this.dataSourceController.getAdapter().group()); // act this.columnsController.moveColumn(0, 0, 'group', 'header'); // assert - assert.ok(!this.dataController.dataSource().group()); + assert.ok(!this.dataSourceController.getAdapter().group()); assert.equal(this.dataController.items()[0].rowType, 'data'); }); @@ -2710,7 +2718,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), {}, 'remote operations set correct'); + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), {}, 'remote operations set correct'); }); QUnit.test('Enable all remote operations if groupPaging is true', function(assert) { @@ -2728,7 +2736,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), { + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), { filtering: true, sorting: true, paging: true, @@ -2755,7 +2763,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), { + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), { filtering: true, sorting: false, paging: true, @@ -2778,7 +2786,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), {}, 'remote operations set correct'); + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), {}, 'remote operations set correct'); }); QUnit.test('Set remoteOperations option to true', function(assert) { @@ -2795,7 +2803,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.resetDataSource(); // assert - assert.deepEqual(this.dataController.dataSource().remoteOperations(), { filtering: true, sorting: true, paging: true, grouping: true, summary: true }, 'remote operations set correct'); + assert.deepEqual(this.dataSourceController.getAdapter().remoteOperations(), { filtering: true, sorting: true, paging: true, grouping: true, summary: true }, 'remote operations set correct'); }); // T541798 @@ -2849,8 +2857,8 @@ QUnit.module('No dataSource', { beforeEach: setupModule, afterEach: teardownModu QUnit.test('getters', function(assert) { assert.strictEqual(this.dataController.items().length, 0); - assert.strictEqual(this.dataController.totalItemsCount(), 0); - assert.strictEqual(this.dataController.pageCount(), 1); + assert.strictEqual(this.dataSourceController.totalItemsCount(), 0); + assert.strictEqual(this.dataSourceController.pageCount(), 1); assert.strictEqual(this.dataController.pageIndex(), 0); assert.strictEqual(this.dataController.pageSize(), 0); assert.strictEqual(this.dataController.isLoading(), false); @@ -3075,10 +3083,10 @@ QUnit.module('Paging', { beforeEach: setupPagingModule, afterEach: teardownPagin this.dataSource.load(); assert.equal(this.dataController.items().length, 5); - assert.equal(this.dataController.totalCount(), 7); - assert.equal(this.dataController.pageCount(), 2); + assert.equal(this.dataSourceController.totalCount(), 7); + assert.equal(this.dataSourceController.pageCount(), 2); assert.equal(this.dataController.pageIndex(), 0); - assert.ok(this.dataController.hasKnownLastPage()); + assert.ok(this.dataSourceController.hasKnownLastPage()); assert.deepEqual(this.dataController.items()[0].values, ['Alex', 215]); assert.deepEqual(this.dataController.items()[0].data, { name: 'Alex', pay: 215 }); }); @@ -3099,8 +3107,8 @@ QUnit.module('Paging', { beforeEach: setupPagingModule, afterEach: teardownPagin // assert assert.equal(changedCount, 1); assert.equal(this.dataController.items().length, 1); - assert.equal(this.dataController.totalCount(), 1); - assert.equal(this.dataController.pageCount(), 1); + assert.equal(this.dataSourceController.totalCount(), 1); + assert.equal(this.dataSourceController.pageCount(), 1); assert.equal(this.dataController.pageIndex(), 0); assert.deepEqual(this.dataController.items()[0].values, ['Dan3', 153]); }); @@ -3189,7 +3197,7 @@ QUnit.module('Paging', { beforeEach: setupPagingModule, afterEach: teardownPagin // assert assert.equal(this.dataController.items().length, 3); - assert.equal(this.dataController.pageCount(), 3); + assert.equal(this.dataSourceController.pageCount(), 3); assert.equal(this.dataController.pageIndex(), 1); assert.deepEqual(this.dataController.items()[0].values, ['Dan3', 153]); @@ -3208,14 +3216,14 @@ QUnit.module('Paging', { beforeEach: setupPagingModule, afterEach: teardownPagin // assert assert.equal(this.dataController.pageSize(), 2); - assert.equal(this.dataController.pageCount(), 4); + assert.equal(this.dataSourceController.pageCount(), 4); // act this.dataController.optionChanged({ name: 'dataSource' }); // assert assert.equal(this.dataController.pageSize(), 2); - assert.equal(this.dataController.pageCount(), 4); + assert.equal(this.dataSourceController.pageCount(), 4); assert.equal(this.option('paging.pageSize'), 2); }); @@ -3231,14 +3239,14 @@ QUnit.module('Paging', { beforeEach: setupPagingModule, afterEach: teardownPagin // assert assert.equal(this.dataController.pageIndex(), 1); - assert.equal(this.dataController.pageCount(), 2); + assert.equal(this.dataSourceController.pageCount(), 2); // act this.dataController.optionChanged({ name: 'dataSource' }); // assert assert.equal(this.dataController.pageIndex(), 1); - assert.equal(this.dataController.pageCount(), 2); + assert.equal(this.dataSourceController.pageCount(), 2); assert.equal(this.option('paging.pageIndex'), 1); }); @@ -3320,8 +3328,8 @@ QUnit.module('Paging', { beforeEach: setupPagingModule, afterEach: teardownPagin this.dataSource.reload(true); assert.equal(this.dataController.pageIndex(), 0); - assert.equal(this.dataController.totalCount(), 11); - assert.equal(this.dataController.pageCount(), 3); + assert.equal(this.dataSourceController.totalCount(), 11); + assert.equal(this.dataSourceController.pageCount(), 3); assert.equal(changedCount, 1, 'changed raise after reload'); }); @@ -3337,9 +3345,9 @@ QUnit.module('Paging', { beforeEach: setupPagingModule, afterEach: teardownPagin this.dataController.resetDataSource(); assert.equal(this.dataController.pageIndex(), 1); - assert.equal(this.dataController.pageCount(), 3); - assert.ok(this.dataController.hasKnownLastPage()); - assert.equal(this.dataController.totalCount(), 7); + assert.equal(this.dataSourceController.pageCount(), 3); + assert.ok(this.dataSourceController.hasKnownLastPage()); + assert.equal(this.dataSourceController.totalCount(), 7); assert.equal(this.dataController.items().length, 3); assert.deepEqual(this.dataController.items()[0].values, ['Dan3', 153]); }); @@ -3539,7 +3547,7 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft QUnit.test('virtual items on end when last page size less than half page size', function(assert) { const dataController = this.dataController; - dataController.store().insert({ id: 1001, value: 'value1001' }); + this.dataSourceController.store().insert({ id: 1001, value: 'value1001' }); dataController.refresh(); dataController.setViewportItemIndex(999); @@ -3560,10 +3568,10 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft // T866890 QUnit.test('virtual items on end when last page size less than viewport size', function(assert) { const dataController = this.dataController; - dataController.store().insert({ id: 1001 }); - dataController.store().insert({ id: 1002 }); - dataController.store().insert({ id: 1003 }); - dataController.store().insert({ id: 1004 }); + this.dataSourceController.store().insert({ id: 1001 }); + this.dataSourceController.store().insert({ id: 1002 }); + this.dataSourceController.store().insert({ id: 1003 }); + this.dataSourceController.store().insert({ id: 1004 }); dataController.refresh(); dataController.viewportSize(3); @@ -3579,7 +3587,7 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft // B233350 QUnit.test('virtual items on end when visibleRowsCount < pageSize and last page size greater than half page size', function(assert) { const dataController = this.dataController; - dataController._dataSource.store().remove(999); + this.dataSourceController.store().remove(999); dataController.refresh(); dataController.setViewportItemIndex(998); assert.strictEqual(this.dataController.pageIndex(), 49); @@ -3607,7 +3615,7 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft }); QUnit.test('getAllRowsCount for virtual scrolling', function(assert) { - assert.strictEqual(this.dataController.totalItemsCount(), 1000); + assert.strictEqual(this.dataSourceController.totalItemsCount(), 1000); }); // T308521 @@ -3616,7 +3624,7 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft const that = this; let countCallChanged = 0; let countCallDataSourceChanged = 0; - const dataSource = that.dataController.dataSource(); + const dataSource = that.dataSourceController.getAdapter(); that.dataController.changed.add(function() { that.columnOption('id', 'sortOrder', 'desc'); @@ -3642,7 +3650,7 @@ QUnit.module('Virtual scrolling', { beforeEach: setupVirtualScrollingModule, aft const firstRow = rows[0]; // act - this.dataController.dataSource().changed.fire({ changeType: 'pageIndex' }); + this.dataSourceController.getAdapter().changed.fire({ changeType: 'pageIndex' }); // assert assert.strictEqual(this.getVisibleRows(), rows, 'rows are not changed'); @@ -3683,7 +3691,7 @@ const setupVirtualRenderingModule = function() { this.dataController.viewportItemSize(10); this.dataController.viewportSize(10); - this.dataController._dataSource._renderTime = 50; + this.dataSourceController.getAdapter()._renderTime = 50; this.clock.tick(10); @@ -3959,7 +3967,7 @@ QUnit.module('Virtual rendering', { beforeEach: setupVirtualRenderingModule, aft const rowsScrollController = this.dataController._rowsScrollController; const defaultItemSize = rowsScrollController.getItemSize(); - const bottomPosition = (this.dataController.totalItemsCount() - this.dataController.viewportSize()) * defaultItemSize; + const bottomPosition = (this.dataSourceController.totalItemsCount() - this.dataController.viewportSize()) * defaultItemSize; // act this.dataController.setViewportPosition(bottomPosition); @@ -3971,7 +3979,7 @@ QUnit.module('Virtual rendering', { beforeEach: setupVirtualRenderingModule, aft assert.deepEqual(this.dataController.items()[itemCount - 2].key, ['value99']); assert.strictEqual(this.dataController.items()[itemCount - 1].key, 99); assert.strictEqual(this.dataController.pageIndex(), 0); - assert.strictEqual(this.dataController.itemsCount(), 100); + assert.strictEqual(this.dataSourceController.getAdapter().itemsCount(), 100); }); QUnit.test('scroll to previous render page', function(assert) { @@ -4088,7 +4096,7 @@ QUnit.module('Virtual rendering', { beforeEach: setupVirtualRenderingModule, aft // assert assert.strictEqual(this.dataController.items().length, 0, 'item count'); - assert.strictEqual(this.dataController.pageCount(), 1, 'page count'); + assert.strictEqual(this.dataSourceController.pageCount(), 1, 'page count'); // act this.option('searchPanel.text', ''); @@ -4096,7 +4104,7 @@ QUnit.module('Virtual rendering', { beforeEach: setupVirtualRenderingModule, aft // assert assert.strictEqual(this.dataController.items().length, 10, 'item count'); - assert.strictEqual(this.dataController.pageCount(), 5, 'page count'); + assert.strictEqual(this.dataSourceController.pageCount(), 5, 'page count'); }); QUnit.test('addRow > scroll to far > scroll back', function(assert) { @@ -4679,7 +4687,7 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { // assert assert.deepEqual(this.getDataItems(), items); - assert.equal(dataController.itemsCount(), 5); + assert.equal(this.dataSourceController.getAdapter().itemsCount(), 5); assert.ok(dataController.isLoaded()); assert.ok(!dataController.isLoading(), 'loading completed'); assert.ok(!isLoadingByEvent, 'loading completed'); @@ -4708,7 +4716,7 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { // assert assert.deepEqual(this.getDataItems(), TEN_NUMBERS); - assert.equal(dataController.itemsCount(), 10); + assert.equal(this.dataSourceController.getAdapter().itemsCount(), 10); assert.ok(dataController.isLoaded()); assert.ok(!dataController.isLoading(), 'loading completed'); assert.ok(!isLoadingByEvent, 'loading completed'); @@ -4965,19 +4973,19 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { this.dataController.viewportSize(15); // assert - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 1, 'initial load page count'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 1, 'initial load page count'); assert.strictEqual(this.dataController.items().length, 10, 'initial visible items count'); // act this.dataController.setViewportPosition(500); this.clock.tick(10); const visibleItems = this.dataController.items(); - const loadedItems = this.dataController.dataSource().items(); + const loadedItems = this.dataSourceController.getAdapter().items(); // assert assert.deepEqual(this.dataController.getLoadPageParams(), { pageIndex: 2, loadPageCount: 4, skipForCurrentPage: 5 }, 'load page params after scrolling'); assert.deepEqual(this.dataController.pageIndex(), 2, 'page index after scrolling'); - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 4, 'load page count after scrolling'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 4, 'load page count after scrolling'); assert.equal(loadedItems.length, 40, 'loaded items count'); assert.deepEqual(loadedItems[0], { id: 21, name: 'Name 21' }, 'first loaded item'); assert.deepEqual(loadedItems[39], { id: 60, name: 'Name 60' }, 'last loaded item'); @@ -5150,7 +5158,7 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { pageSize: 10 }); - const viewportSizeSpy = sinon.spy(this.dataController.dataSource(), 'viewportSize'); + const viewportSizeSpy = sinon.spy(this.dataSourceController.getAdapter(), 'viewportSize'); try { this.dataController.viewportSize(); @@ -5177,7 +5185,7 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { pageSize: 10 }); - const viewportItemSizeSpy = sinon.spy(this.dataController.dataSource(), 'viewportItemSize'); + const viewportItemSizeSpy = sinon.spy(this.dataSourceController.getAdapter(), 'viewportItemSize'); try { this.dataController.viewportItemSize(); @@ -5204,7 +5212,7 @@ QUnit.module('Virtual scrolling (ScrollingDataSource)', { pageSize: 10 }); - const setContentItemSizesSpy = sinon.spy(this.dataController.dataSource(), 'setContentItemSizes'); + const setContentItemSizesSpy = sinon.spy(this.dataSourceController.getAdapter(), 'setContentItemSizes'); try { this.dataController.setContentItemSizes([30]); @@ -5452,7 +5460,7 @@ QUnit.module('Virtual scrolling preload', { }, () => { QUnit.test('New mode. One page should be loaded on start', function(assert) { // arrange - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 1, 'initial load page count'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 1, 'initial load page count'); assert.strictEqual(this.getVisibleRows().length, 20, 'initial visible items count'); }); @@ -5460,7 +5468,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5477,7 +5485,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5494,7 +5502,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5511,7 +5519,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5527,7 +5535,7 @@ QUnit.module('Virtual scrolling preload', { // act this.dataController.setViewportPosition(1000); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5544,7 +5552,7 @@ QUnit.module('Virtual scrolling preload', { this.dataController.setViewportPosition(1000); this.dataController.setViewportPosition(999); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5562,7 +5570,7 @@ QUnit.module('Virtual scrolling preload', { this.dataController.setViewportPosition(1000); this.dataController.setViewportPosition(999); const visibleRows = this.getVisibleRows(); - const dataSourceAdapter = this.dataController.dataSource(); + const dataSourceAdapter = this.dataSourceController.getAdapter(); const loadedItems = dataSourceAdapter.items(); // assert @@ -5683,7 +5691,7 @@ QUnit.module('Infinite scrolling', { // assert assert.strictEqual(this.dataController.pageIndex(), 0); - assert.strictEqual(this.dataController.hasKnownLastPage(), false); + assert.strictEqual(this.dataSourceController.hasKnownLastPage(), false); assert.strictEqual(this.dataController.items().length, 20); }); @@ -5693,14 +5701,14 @@ QUnit.module('Infinite scrolling', { // assert assert.strictEqual(this.dataController.pageIndex(), 1); - assert.strictEqual(this.dataController.hasKnownLastPage(), false); + assert.strictEqual(this.dataSourceController.hasKnownLastPage(), false); assert.strictEqual(this.dataController.items().length, 40); }); // T193217 QUnit.test('setViewportItemIndex to end current page several times load next page one time', function(assert) { let loadingCount = 0; - this.dataController._dataSource.customizeStoreLoadOptions.add(function() { + this.dataSourceController.getAdapter().customizeStoreLoadOptions.add(function() { loadingCount++; }); // act @@ -5713,7 +5721,7 @@ QUnit.module('Infinite scrolling', { // assert assert.strictEqual(loadingCount, 1); assert.strictEqual(this.dataController.pageIndex(), 1); - assert.strictEqual(this.dataController.hasKnownLastPage(), false); + assert.strictEqual(this.dataSourceController.hasKnownLastPage(), false); assert.strictEqual(this.dataController.items().length, 40); }); @@ -5725,7 +5733,7 @@ QUnit.module('Infinite scrolling', { // assert assert.strictEqual(this.dataController.pageIndex(), 1); - assert.strictEqual(this.dataController.hasKnownLastPage(), false); + assert.strictEqual(this.dataSourceController.hasKnownLastPage(), false); assert.strictEqual(this.dataController.items().length, 40); }); @@ -5737,7 +5745,7 @@ QUnit.module('Infinite scrolling', { // assert assert.strictEqual(this.dataController.pageIndex(), 2); - assert.strictEqual(this.dataController.hasKnownLastPage(), true); + assert.strictEqual(this.dataSourceController.hasKnownLastPage(), true); assert.strictEqual(this.dataController.items().length, 50); }); @@ -5765,7 +5773,7 @@ QUnit.module('Infinite scrolling', { // assert assert.strictEqual(this.dataController.pageIndex(), 0); - assert.strictEqual(this.dataController.hasKnownLastPage(), false); + assert.strictEqual(this.dataSourceController.hasKnownLastPage(), false); assert.strictEqual(this.dataController.items().length, 20); }); @@ -6068,19 +6076,19 @@ QUnit.module('Infinite scrolling (ScrollingDataSource)', { this.dataController.viewportSize(15); // assert - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 1, 'initial load page count'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 1, 'initial load page count'); assert.strictEqual(this.dataController.items().length, 10, 'initial visible items count'); // act this.dataController.setViewportPosition(500); this.clock.tick(10); const visibleItems = this.dataController.items(); - const loadedItems = this.dataController.dataSource().items(); + const loadedItems = this.dataSourceController.getAdapter().items(); // assert assert.deepEqual(this.dataController.getLoadPageParams(), { pageIndex: 2, loadPageCount: 4, skipForCurrentPage: 5 }, 'load page params after scrolling'); assert.deepEqual(this.dataController.pageIndex(), 2, 'page index after scrolling'); - assert.strictEqual(this.dataController.dataSource().loadPageCount(), 4, 'load page count after scrolling'); + assert.strictEqual(this.dataSourceController.getAdapter().loadPageCount(), 4, 'load page count after scrolling'); assert.equal(loadedItems.length, 40, 'loaded items count'); assert.deepEqual(loadedItems[0], { id: 21, name: 'Name 21' }, 'first loaded item'); assert.deepEqual(loadedItems[39], { id: 60, name: 'Name 60' }, 'last loaded item'); @@ -7746,7 +7754,6 @@ QUnit.module('Filtering', { remoteOperations: { filtering: true } }); - this.dataController.isSharedDataSource = true; this.dataController.setDataSource(this.dataSource); let loadingCount = 0; @@ -7846,7 +7853,7 @@ QUnit.module('Filtering', { columns: [{ dataField: 'name', selectedFilterOperation: 'contains', filterValue: 'Al' }, 'age'] }); - this.dataController._dataSource.changed.add(function() { + this.dataSourceController.getAdapter().changed.add(function() { dataSourceChanged = true; }); @@ -7868,7 +7875,7 @@ QUnit.module('Filtering', { columns: [{ dataField: 'name', selectedFilterOperation: 'contains', filterValue: 'Al' }, 'age'] }); - this.dataController._dataSource.changed.add(function() { + this.dataSourceController.getAdapter().changed.add(function() { dataSourceChanged = true; }); @@ -7890,7 +7897,7 @@ QUnit.module('Filtering', { columns: [{ dataField: 'name', selectedFilterOperation: 'contains' }, 'age'] }); - this.dataController._dataSource.changed.add(function() { + this.dataSourceController.getAdapter().changed.add(function() { dataSourceChanged = true; }); @@ -8097,7 +8104,7 @@ QUnit.module('Filtering', { this.dataSource = undefined; // assert - assert.notOk(this.dataController.getDataSource(), 'no dataSource'); + assert.notOk(this.dataSourceController.getDataSource(), 'no dataSource'); // act this.dataController.clearFilter(); @@ -9336,8 +9343,8 @@ QUnit.module('Remote Grouping', { assert.strictEqual(storeLoadOptions.take, undefined, 'no take option'); assert.deepEqual(storeLoadOptions.group, [{ selector: 'name', desc: false, isExpanded: false }], 'group option'); - assert.equal(this.dataController.totalCount(), -1, 'totalCount'); - assert.equal(this.dataController.pageCount(), 1, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), -1, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 1, 'pageCount'); }); // T366766 @@ -9381,8 +9388,8 @@ QUnit.module('Remote Grouping', { assert.strictEqual(storeLoadOptions.take, undefined, 'no take option'); assert.deepEqual(storeLoadOptions.group, [{ selector: 'name', desc: false, isExpanded: false }], 'group option'); - assert.equal(this.dataController.totalCount(), -1, 'totalCount'); - assert.equal(this.dataController.pageCount(), 1, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), -1, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 1, 'pageCount'); }); // T317797 @@ -9421,10 +9428,10 @@ QUnit.module('Remote Grouping', { assert.strictEqual(storeLoadOptions.take, 2, 'take option'); assert.strictEqual(storeLoadOptions.requireTotalCount, true, 'requireTotalCount option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 10, 'totalCount'); - assert.equal(this.dataController.hasKnownLastPage(), true, 'hasKnownLastPage'); + assert.equal(this.dataSourceController.totalCount(), 10, 'totalCount'); + assert.equal(this.dataSourceController.hasKnownLastPage(), true, 'hasKnownLastPage'); assert.equal(this.dataController.items().length, 2, 'items count'); - assert.equal(this.dataController.pageCount(), 5, 'pageCount'); + assert.equal(this.dataSourceController.pageCount(), 5, 'pageCount'); }); // T318309 @@ -9451,8 +9458,8 @@ QUnit.module('Remote Grouping', { this.clock.tick(10); // assert - assert.equal(this.dataController.totalCount(), 2, 'totalCount'); - assert.equal(this.dataController.pageCount(), 1, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), 2, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 1, 'pageCount'); assert.deepEqual(this.dataController.items()[0].rowType, 'group', 'item 1 rowType'); assert.deepEqual(this.dataController.items()[0].key, ['1980/10/15'], 'item 1 key'); assert.deepEqual(this.dataController.items()[0].values, [new Date('1980/10/15')], 'item 1 values'); @@ -9607,7 +9614,7 @@ QUnit.module('Summary', { assert.strictEqual(storeLoadOptions.take, 2, 'take option'); assert.deepEqual(storeLoadOptions.totalSummary, [{ selector: 'age', summaryType: 'custom' }], 'totalSummary option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 10, 'totalCount'); + assert.equal(this.dataSourceController.totalCount(), 10, 'totalCount'); assert.deepEqual(this.dataController.footerItems(), [{ rowType: 'totalFooter', summaryCells: [[], [{ value: 3, @@ -9703,7 +9710,7 @@ QUnit.module('Summary', { assert.strictEqual(storeLoadOptions.take, 2, 'take option'); assert.deepEqual(storeLoadOptions.totalSummary, [{ selector: 'age', summaryType: 'custom' }], 'totalSummary option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 10, 'totalCount'); + assert.equal(this.dataSourceController.totalCount(), 10, 'totalCount'); assert.deepEqual(this.dataController.footerItems(), [{ rowType: 'totalFooter', summaryCells: [[], [{ value: 3, @@ -9785,7 +9792,7 @@ QUnit.module('Summary', { assert.strictEqual(storeLoadOptions.take, 2, 'take option'); assert.deepEqual(storeLoadOptions.totalSummary, [{ selector: 'age', summaryType: 'min' }], 'summary totalItems option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 10, 'totalCount'); + assert.equal(this.dataSourceController.totalCount(), 10, 'totalCount'); assert.deepEqual(this.dataController.footerItems(), [{ rowType: 'totalFooter', summaryCells: [[], [{ column: 'age', @@ -9847,8 +9854,8 @@ QUnit.module('Summary', { assert.deepEqual(storeLoadOptions.totalSummary, [{ selector: 'age', summaryType: 'min' }], 'summary totalItems option'); assert.deepEqual(storeLoadOptions.groupSummary, undefined, 'summary groupItems option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 4, 'totalCount'); - assert.equal(this.dataController.pageCount(), 4, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), 4, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 4, 'pageCount'); assert.deepEqual(this.dataController.items().length, 2); assert.deepEqual(this.dataController.items()[0].data, { key: 'Alex', @@ -9920,8 +9927,8 @@ QUnit.module('Summary', { assert.deepEqual(storeLoadOptions.totalSummary, [{ selector: 'age', summaryType: 'min' }], 'summary totalItems option'); assert.deepEqual(storeLoadOptions.groupSummary, undefined, 'summary groupItems option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 4, 'totalCount'); - assert.equal(this.dataController.pageCount(), 4, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), 4, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 4, 'pageCount'); assert.deepEqual(this.dataController.items().length, 2); assert.deepEqual(this.dataController.items()[0].data, { key: 'Alex', @@ -9988,8 +9995,8 @@ QUnit.module('Summary', { assert.deepEqual(storeLoadOptions.totalSummary, [{ selector: 'age', summaryType: 'min' }], 'summary totalItems option'); assert.deepEqual(storeLoadOptions.groupSummary, undefined, 'summary groupItems option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 10, 'totalCount'); - assert.equal(this.dataController.pageCount(), 5, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), 10, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 5, 'pageCount'); assert.deepEqual(this.dataController.items().length, 3); assert.deepEqual(this.dataController.items()[0].data, { key: 'Alex', @@ -10047,7 +10054,7 @@ QUnit.module('Summary', { assert.strictEqual(storeLoadOptions.take, 2, 'take option'); assert.deepEqual(storeLoadOptions.totalSummary, [{ selector: 'age', summaryType: 'min' }, { selector: 'date', summaryType: 'max' }], 'summary totalItems option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 10, 'totalCount'); + assert.equal(this.dataSourceController.totalCount(), 10, 'totalCount'); assert.deepEqual(this.dataController.footerItems(), [{ rowType: 'totalFooter', summaryCells: [[], [{ value: 3, @@ -10109,8 +10116,8 @@ QUnit.module('Summary', { assert.deepEqual(storeLoadOptions.totalSummary, [{ selector: 'age', summaryType: 'min' }], 'summary totalItems option'); assert.deepEqual(storeLoadOptions.groupSummary, [{ selector: 'age', summaryType: 'count' }], 'summary groupItems option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 2, 'totalCount'); - assert.equal(this.dataController.pageCount(), 1, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), 2, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 1, 'pageCount'); assert.deepEqual(this.dataController.items()[0].summaryCells, [[], [{ column: 'age', columnCaption: 'Age', @@ -10174,8 +10181,8 @@ QUnit.module('Summary', { assert.deepEqual(storeLoadOptions.totalSummary, [{ selector: 'age', summaryType: 'min' }], 'summary totalItems option'); assert.deepEqual(storeLoadOptions.groupSummary, [{ selector: 'age', summaryType: 'count' }], 'summary groupItems option'); assert.ok(!this.dataController.isLoading()); - assert.equal(this.dataController.totalCount(), 2, 'totalCount'); - assert.equal(this.dataController.pageCount(), 1, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), 2, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 1, 'pageCount'); assert.deepEqual(this.dataController.items()[0].summaryCells, [[], [{ column: 'age', columnCaption: 'Age', @@ -10243,8 +10250,8 @@ QUnit.module('Summary', { assert.strictEqual(storeLoadOptions.take, undefined, 'no take option'); assert.deepEqual(storeLoadOptions.filter, [['group', '=', 'Group1'], 'or', ['group', '=', 'Group0']], 'filter option'); assert.deepEqual(storeLoadOptions.sort, [{ 'desc': false, 'isExpanded': true, 'selector': 'group' }], 'sort option'); - assert.equal(this.dataController.totalCount(), 3, 'totalCount'); - assert.equal(this.dataController.pageCount(), 2, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), 3, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 2, 'pageCount'); const items = this.dataController.items(); assert.equal(items.length, 4, 'item count'); assert.deepEqual(items[0].key, ['Group1'], 'item 0'); @@ -10369,8 +10376,8 @@ QUnit.module('Summary', { [[['group1', '=', 'Group1'], 'and', ['group2', '=', 'Group1_0']], 'or', [['group1', '=', 'Group0'], 'and', ['group2', '=', 'Group0_0']]], 'filter option'); assert.deepEqual(storeLoadOptions.sort, [{ 'desc': false, 'isExpanded': true, 'selector': 'group1' }, { 'desc': false, 'isExpanded': true, 'selector': 'group2' }], 'sort option'); - assert.equal(this.dataController.totalCount(), 3, 'totalCount'); - assert.equal(this.dataController.pageCount(), 2, 'pageCount'); + assert.equal(this.dataSourceController.totalCount(), 3, 'totalCount'); + assert.equal(this.dataSourceController.pageCount(), 2, 'pageCount'); const items = this.dataController.items(); assert.equal(items.length, 6, 'item count'); assert.deepEqual(items[0].key, ['Group1'], 'item 0'); @@ -12981,7 +12988,7 @@ QUnit.module('Partial update', { QUnit.test('update one row', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13013,7 +13020,7 @@ QUnit.module('Partial update', { QUnit.test('update several rows', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13050,7 +13057,7 @@ QUnit.module('Partial update', { QUnit.test('update with incorrect rowIndices', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13085,7 +13092,7 @@ QUnit.module('Partial update', { // T266696 QUnit.test('insert item with editing of the first item', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); let changedArgs; this.dataController.changed.add(function(args) { @@ -13113,7 +13120,7 @@ QUnit.module('Partial update', { QUnit.test('insert one item', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13150,7 +13157,7 @@ QUnit.module('Partial update', { QUnit.test('update and insert', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13220,7 +13227,7 @@ QUnit.module('Partial update', { QUnit.test('remove one item', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13256,7 +13263,7 @@ QUnit.module('Partial update', { QUnit.test('update and remove', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13293,7 +13300,7 @@ QUnit.module('Partial update', { // T186415 QUnit.test('remove and update next row', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -13329,7 +13336,7 @@ QUnit.module('Partial update', { QUnit.test('insert and remove', function(assert) { this.setupModules(); - const dataSourceItems = this.dataController.dataSource().items(); + const dataSourceItems = this.dataSourceController.getAdapter().items(); const oldItems = this.dataController.items().slice(0); let changedArgs; @@ -14249,8 +14256,8 @@ QUnit.module('Using DataSource instance', { // assert assert.ok(!this.dataSource.filter(), 'no filter'); - assert.equal(this.dataController.itemsCount(), 3); - assert.equal(this.dataController.totalCount(), 3); + assert.equal(this.dataSourceController.getAdapter().itemsCount(), 3); + assert.equal(this.dataSourceController.totalCount(), 3); // act this.dataSource.filter(['field1', '=', 2]); @@ -14261,8 +14268,8 @@ QUnit.module('Using DataSource instance', { const filter = this.dataSource.filter(); assert.deepEqual(this.dataSource.filter(), [filter[0], '=', 2], 'changed filter'); assert.equal(this.dataController.items()[0].data.field3, 6); - assert.equal(this.dataController.itemsCount(), 2); - assert.equal(this.dataController.totalCount(), 2); + assert.equal(this.dataSourceController.getAdapter().itemsCount(), 2); + assert.equal(this.dataSourceController.totalCount(), 2); }); QUnit.test('change group', function(assert) { @@ -14297,8 +14304,8 @@ QUnit.module('Using DataSource instance', { assert.strictEqual(this.columnOption(2, 'groupIndex'), undefined); assert.deepEqual(changes, ['columns', 'data']); - assert.equal(this.dataController.itemsCount(), 5); - assert.equal(this.dataController.totalItemsCount(), 8); + assert.equal(this.dataSourceController.getAdapter().itemsCount(), 5); + assert.equal(this.dataSourceController.totalItemsCount(), 8); assert.equal(this.dataController.items()[0].rowType, 'group'); assert.equal(this.dataController.items()[1].rowType, 'data'); assert.equal(this.dataController.items()[1].data.field3, 3); @@ -14339,8 +14346,8 @@ QUnit.module('Using DataSource instance', { assert.strictEqual(this.columnOption(2, 'sortOrder'), 'desc'); assert.deepEqual(changes, ['columns', 'data']); - assert.equal(this.dataController.itemsCount(), 5); - assert.equal(this.dataController.totalItemsCount(), 5); + assert.equal(this.dataSourceController.getAdapter().itemsCount(), 5); + assert.equal(this.dataSourceController.totalItemsCount(), 5); assert.equal(this.dataController.items()[0].data.field3, 7); assert.equal(this.dataController.items()[4].data.field3, 3); }); @@ -14375,7 +14382,7 @@ QUnit.module('Using DataSource instance', { this.clock.tick(10); // assert - assert.equal(this.dataController.totalItemsCount(), 3); + assert.equal(this.dataSourceController.totalItemsCount(), 3); assert.equal(this.dataController.items().length, 3); // act @@ -14383,7 +14390,7 @@ QUnit.module('Using DataSource instance', { this.clock.tick(10); // assert - assert.equal(this.dataController.totalItemsCount(), 5); + assert.equal(this.dataSourceController.totalItemsCount(), 5); assert.equal(this.dataController.items().length, 5); const spy = sinon.spy(); @@ -14394,7 +14401,7 @@ QUnit.module('Using DataSource instance', { this.clock.tick(10); // assert - assert.equal(this.dataController.totalItemsCount(), 3); + assert.equal(this.dataSourceController.totalItemsCount(), 3); assert.equal(this.dataController.items().length, 3); assert.equal(this.dataController.pageIndex(), 0); assert.equal(this.dataSource.pageIndex(), 0); @@ -14412,7 +14419,7 @@ QUnit.module('Using DataSource instance', { this.clock.tick(10); assert.equal(this.columnsController.getGroupColumns().length, 1, 'grouped columns count'); - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'field1', desc: false, isExpanded: true }], 'dataSource group when autoExpandAll true'); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'field1', desc: false, isExpanded: true }], 'dataSource group when autoExpandAll true'); // act this.option('grouping.autoExpandAll', false); @@ -14421,8 +14428,8 @@ QUnit.module('Using DataSource instance', { // assert assert.equal(this.columnsController.getGroupColumns().length, 1, 'grouped columns count'); - assert.deepEqual(this.dataController._dataSource.group(), [{ selector: 'field1', desc: false, isExpanded: false }], 'dataSource group when autoExpandAll false'); - assert.equal(this.dataController.totalItemsCount(), 2); + assert.deepEqual(this.dataSourceController.getAdapter().group(), [{ selector: 'field1', desc: false, isExpanded: false }], 'dataSource group when autoExpandAll false'); + assert.equal(this.dataSourceController.totalItemsCount(), 2); assert.equal(this.dataController.items().length, 2); }); @@ -14443,8 +14450,8 @@ QUnit.module('Using DataSource instance', { // assert assert.equal(this.dataController.items().length, 3, 'items count'); - assert.equal(this.dataController.totalCount(), 5, 'total count'); - assert.equal(this.dataController.pageCount(), 2, 'page count'); + assert.equal(this.dataSourceController.totalCount(), 5, 'total count'); + assert.equal(this.dataSourceController.pageCount(), 2, 'page count'); }); // T752955 @@ -14459,7 +14466,7 @@ QUnit.module('Using DataSource instance', { try { // act this.dataSource.dispose(); - this.dataController.dataSource().dispose(true); + this.dataSourceController.getAdapter().dispose(true); // assert assert.ok(true, 'No exception'); @@ -14585,7 +14592,7 @@ QUnit.module('Exporting', { // assert assert.deepEqual(this.dataController.items().length, 3, 'items count'); assert.deepEqual(this.dataController.pageSize(), 3, 'pageSize'); - assert.deepEqual(this.dataController.pageCount(), 2, 'pageCount'); + assert.deepEqual(this.dataSourceController.pageCount(), 2, 'pageCount'); assert.deepEqual(changedCallCount, 0, 'changed call count'); @@ -14633,7 +14640,7 @@ QUnit.module('Exporting', { // assert assert.deepEqual(this.dataController.items().length, 2, 'items count'); assert.deepEqual(this.dataController.pageSize(), 3, 'pageSize'); - assert.deepEqual(this.dataController.pageCount(), 1, 'pageCount'); + assert.deepEqual(this.dataSourceController.pageCount(), 1, 'pageCount'); assert.deepEqual(changedCallCount, 0, 'changed call count'); @@ -14672,7 +14679,7 @@ QUnit.module('Exporting', { this.clock.tick(10); - assert.deepEqual(this.dataController.pageCount(), 4, 'pageCount'); + assert.deepEqual(this.dataSourceController.pageCount(), 4, 'pageCount'); this.dataController.changed.add(function() { @@ -14690,7 +14697,7 @@ QUnit.module('Exporting', { assert.deepEqual(this.dataController.items().length, 4, 'items count'); assert.deepEqual(this.dataController.pageSize(), 3, 'pageSize'); // T240474 - assert.deepEqual(this.dataController.pageCount(), 4, 'pageCount'); + assert.deepEqual(this.dataSourceController.pageCount(), 4, 'pageCount'); assert.deepEqual(changedCallCount, 0, 'changed call count'); @@ -14744,7 +14751,7 @@ QUnit.module('Exporting', { this.clock.tick(10); - assert.deepEqual(this.dataController.pageCount(), 1, 'pageCount'); + assert.deepEqual(this.dataSourceController.pageCount(), 1, 'pageCount'); this.dataController.changed.add(function() { @@ -14823,7 +14830,7 @@ QUnit.module('Exporting', { this.clock.tick(10); - assert.deepEqual(this.dataController.pageCount(), 1, 'pageCount'); + assert.deepEqual(this.dataSourceController.pageCount(), 1, 'pageCount'); this.dataController.changed.add(function() { @@ -14913,7 +14920,7 @@ QUnit.module('Exporting', { this.clock.tick(10); - assert.deepEqual(this.dataController.pageCount(), 1, 'pageCount'); + assert.deepEqual(this.dataSourceController.pageCount(), 1, 'pageCount'); this.dataController.changed.add(function() { @@ -15203,7 +15210,7 @@ QUnit.module('Exporting', { // assert assert.deepEqual(this.dataController.items().length, 3, 'items count'); assert.deepEqual(this.dataController.pageSize(), 3, 'pageSize'); - assert.deepEqual(this.dataController.pageCount(), 2, 'pageCount'); + assert.deepEqual(this.dataSourceController.pageCount(), 2, 'pageCount'); assert.deepEqual(changedCallCount, 0, 'changed call count'); @@ -15252,7 +15259,7 @@ QUnit.module('Exporting', { // assert assert.deepEqual(this.dataController.items().length, 2, 'items count'); assert.deepEqual(this.dataController.pageSize(), 3, 'pageSize'); - assert.deepEqual(this.dataController.pageCount(), 1, 'pageCount'); + assert.deepEqual(this.dataSourceController.pageCount(), 1, 'pageCount'); assert.deepEqual(changedCallCount, 0, 'changed call count'); @@ -15439,7 +15446,7 @@ QUnit.module('onOptionChanged', { that.option.restore(); sinon.stub(that, 'option').callsFake(function(optionName, value) { if(optionName === 'paging.pageSize' && value === 3) { - pageSize = that.dataController.dataSource().pageSize(); + pageSize = that.dataSourceController.getAdapter().pageSize(); } if(optionName === 'editing.changes') { return that.options.editing.changes; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js index 49bc940d87f1..56755d970cf8 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataGrid.tests.js @@ -1577,7 +1577,7 @@ QUnit.module('Assign options', baseModuleConfig, () => { // assert assert.ok(dataGrid.getController('data').isEmpty(), 'no data'); - assert.ok(!dataGrid.getController('data').dataSource(), 'no dataSource'); + assert.ok(!dataGrid.getController('dataSource').getAdapter(), 'no dataSource'); assert.strictEqual(dataGrid.getController('data')._cachedProcessedItems, null, 'cached processed items are cleared'); // T1045202 assert.strictEqual(dataGrid.getController('columns')._dataSource, null, 'no dataSource inside columnsController'); // T1045202 assert.equal(dataGrid.getController('data').items().length, 0, 'items count'); @@ -1963,7 +1963,7 @@ QUnit.module('Assign options', baseModuleConfig, () => { }); dataGrid.selectRows({ a: 1111, b: 222 }); - assert.deepEqual(dataGrid.getController('data').pageCount(), 2, 'pages count'); + assert.deepEqual(dataGrid.getController('dataSource').pageCount(), 2, 'pages count'); assert.deepEqual(dataGrid.getController('data').items().length, 3, 'items count'); assert.ok(dataGrid.getView('pagerView').isVisible(), 'pager visibility'); @@ -1971,7 +1971,7 @@ QUnit.module('Assign options', baseModuleConfig, () => { dataGrid.option('paging.enabled', false); // assert - assert.deepEqual(dataGrid.getController('data').pageCount(), 1, 'pages count when paging disabled'); + assert.deepEqual(dataGrid.getController('dataSource').pageCount(), 1, 'pages count when paging disabled'); assert.deepEqual(dataGrid.getController('data').items().length, 5, 'items count when paging disabled'); assert.ok(!dataGrid.getView('pagerView').isVisible(), 'pager visibility when paging disabled'); }); @@ -2101,7 +2101,7 @@ QUnit.module('Assign options', baseModuleConfig, () => { }); // assert - const dataSource = dataGrid.getController('data').dataSource(); + const dataSource = dataGrid.getController('dataSource').getAdapter(); assert.ok(!errorMessage, 'No error messages'); assert.ok(dataSource, 'dataSource assigned'); assert.ok(dataSource.requireTotalCount(), 'requireTotalCount assigned'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js index d83fa3a157c7..eccc30888fb8 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/dataSource.tests.js @@ -31,7 +31,7 @@ const createDataSource = function(options) { setupDataGridModules(dataGridStub, ['data', 'columns']); - const dataSourceAdapter = dataGridStub.dataController._createDataSourceAdapter(dataSource); + const dataSourceAdapter = dataGridStub.dataSourceController.createAdapter(dataSource); const origItems = dataSourceAdapter.items; const processItems = function(items) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/editing.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/editing.tests.js index bd01b78bb8c3..aece23220c57 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/editing.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/editing.tests.js @@ -3638,7 +3638,7 @@ QUnit.module('Editing with real dataController', { // arrange const that = this; const rowsView = this.rowsView; - const originalInsert = that.dataController.store().insert; + const originalInsert = that.dataSourceController.store().insert; const testElement = $('#container'); $.extend(that.options.editing, { @@ -3649,7 +3649,7 @@ QUnit.module('Editing with real dataController', { } }); - that.dataController.store().insert = function(values) { + that.dataSourceController.store().insert = function(values) { // assert assert.deepEqual(values, { Test1: 'test1', @@ -3691,7 +3691,7 @@ QUnit.module('Editing with real dataController', { assert.equal(testElement.find('input').length, 0, 'not has inputs'); // arrange - that.dataController.store().insert = originalInsert; + that.dataSourceController.store().insert = originalInsert; that.option('onRowInserting', function(params) { params.cancel = true; }); @@ -4163,7 +4163,7 @@ QUnit.module('Editing with real dataController', { // arrange const that = this; const rowsView = that.rowsView; - const originalUpdate = that.dataController.store().update; + const originalUpdate = that.dataSourceController.store().update; const testElement = $('#container'); $.extend(that.options.editing, { @@ -4174,7 +4174,7 @@ QUnit.module('Editing with real dataController', { } }); - that.dataController.store().update = function(key, values) { + that.dataSourceController.store().update = function(key, values) { // assert assert.deepEqual(values, { age: 15, @@ -4247,7 +4247,7 @@ QUnit.module('Editing with real dataController', { assert.equal(testElement.find('.dx-cell-modified').length, 0, 'no element with class name dx-cell-modified'); // arrange - that.dataController.store().update = originalUpdate; + that.dataSourceController.store().update = originalUpdate; that.option('onRowUpdating', function(params) { params.cancel = true; }); @@ -4295,7 +4295,7 @@ QUnit.module('Editing with real dataController', { } }); - sinon.spy(that.dataController.store(), 'update'); + sinon.spy(that.dataSourceController.store(), 'update'); const cancelDeferred = $.Deferred(); @@ -4326,14 +4326,14 @@ QUnit.module('Editing with real dataController', { that.saveEditData(); // assert - assert.ok(!that.dataController.store().update.called, 'update is not called'); + assert.ok(!that.dataSourceController.store().update.called, 'update is not called'); // act cancelDeferred.resolve(); // assert - assert.equal(that.dataController.store().update.callCount, 1, 'update called one time'); - assert.deepEqual(that.dataController.store().update.lastCall.args, [that.array[0], { name: 'Test1', room: 666 }], 'update args'); + assert.equal(that.dataSourceController.store().update.callCount, 1, 'update called one time'); + assert.deepEqual(that.dataSourceController.store().update.lastCall.args, [that.array[0], { name: 'Test1', room: 666 }], 'update args'); }); QUnit.test('Update cell when edit mode batch and cancel in onRowUpdating is Promise and resolved', function(assert) { @@ -4350,7 +4350,7 @@ QUnit.module('Editing with real dataController', { } }); - sinon.spy(that.dataController.store(), 'update'); + sinon.spy(that.dataSourceController.store(), 'update'); let resolve; const cancelPromise = new Promise(function(onResolve) { @@ -4384,10 +4384,10 @@ QUnit.module('Editing with real dataController', { that.saveEditData(); // assert - assert.ok(!that.dataController.store().update.called, 'update is not called'); + assert.ok(!that.dataSourceController.store().update.called, 'update is not called'); cancelPromise.then(function() { - assert.equal(that.dataController.store().update.callCount, 1, 'update called one time'); - assert.deepEqual(that.dataController.store().update.lastCall.args, [that.array[0], { name: 'Test1', room: 666 }], 'update args'); + assert.equal(that.dataSourceController.store().update.callCount, 1, 'update called one time'); + assert.deepEqual(that.dataSourceController.store().update.lastCall.args, [that.array[0], { name: 'Test1', room: 666 }], 'update args'); }); // act @@ -4410,7 +4410,7 @@ QUnit.module('Editing with real dataController', { } }); - sinon.spy(that.dataController.store(), 'update'); + sinon.spy(that.dataSourceController.store(), 'update'); const dataErrors = []; @@ -4446,13 +4446,13 @@ QUnit.module('Editing with real dataController', { that.saveEditData(); // assert - assert.ok(!that.dataController.store().update.called, 'update is not called'); + assert.ok(!that.dataSourceController.store().update.called, 'update is not called'); // act cancelDeferred.resolve(true); // assert - assert.ok(!that.dataController.store().update.called, 'update is not called'); + assert.ok(!that.dataSourceController.store().update.called, 'update is not called'); assert.ok(!dataErrors.length, 'no data errors'); }); @@ -4470,7 +4470,7 @@ QUnit.module('Editing with real dataController', { } }); - sinon.spy(that.dataController.store(), 'update'); + sinon.spy(that.dataSourceController.store(), 'update'); const cancelDeferred = $.Deferred(); @@ -4507,7 +4507,7 @@ QUnit.module('Editing with real dataController', { cancelDeferred.reject('Test Error Message'); // assert - assert.ok(!that.dataController.store().update.called, 'update is not called'); + assert.ok(!that.dataSourceController.store().update.called, 'update is not called'); assert.deepEqual(dataErrors.length, 1, 'data errors count'); assert.ok(dataErrors[0] instanceof Error, 'error has Error type'); @@ -5827,10 +5827,10 @@ QUnit.module('Editing with real dataController', { return testElement; }; - const oldUpdate = that.dataController.store().update; + const oldUpdate = that.dataSourceController.store().update; const updateDeferred = $.Deferred(); - that.dataController.store().update = function() { + that.dataSourceController.store().update = function() { updateCallCount++; oldUpdate.apply(this, arguments); return updateDeferred; @@ -12850,7 +12850,7 @@ QUnit.module('Editing with validation', { }, 'lastName'] }); - this.dataController.store().update = function() { + this.dataSourceController.store().update = function() { return $.Deferred().reject('Test Error'); }; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/filterSync.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/filterSync.tests.js index 8e41eed858c5..c79faaef52bc 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/filterSync.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/filterSync.tests.js @@ -289,7 +289,7 @@ QUnit.module('Sync with FilterValue', { this.dataController.clearFilter(); assert.equal(this.option('filterValue'), null); - assert.equal(this.dataController.getDataSource().filter(), null); + assert.equal(this.dataSourceController.getDataSource().filter(), null); }); // T659816 @@ -306,7 +306,7 @@ QUnit.module('Sync with FilterValue', { this.dataController.clearFilter('filterValue'); assert.equal(this.option('filterValue'), null); - assert.deepEqual(this.dataController.getDataSource().filter(), dataSourceFilter); + assert.deepEqual(this.dataSourceController.getDataSource().filter(), dataSourceFilter); }); // T639390 diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js index 1ff1c6b8d421..ac80de2240c1 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.integration.tests.js @@ -1286,7 +1286,7 @@ QUnit.module('Initialization', baseModuleConfig, () => { }); const rowsView = dataGrid.getView('rowsView'); const $lastRow = rowsView.getRow(2); - const dataSource = dataGrid.getController('data').dataSource(); + const dataSource = dataGrid.getController('dataSource').getAdapter(); sinon.spy(dataSource, 'load'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.tests.js index 4b93ae71c30e..e49b3f3ae333 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/focus.tests.js @@ -4571,8 +4571,7 @@ QUnit.module('Focused row', getModuleConfig(true), () => { this.setupModule(); - this.getController('data').store = function() { - }; + this.getController('dataSource').disposeAdapter(); this.getController('data').dataErrorOccurred.add(function(e) { dataErrors.push(e); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/gridView.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/gridView.tests.js index c613ebdb395d..50e953df54df 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/gridView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/gridView.tests.js @@ -1,5 +1,5 @@ import devices from '__internal/core/m_devices'; -import visibilityChange from 'common/core/events/visibility_change'; +import * as visibilityChange from 'common/core/events/visibility_change'; import 'fluent_blue_light.css!'; import $ from 'jquery'; import 'ui/data_grid'; @@ -913,23 +913,29 @@ QUnit.module('Grid view', { this.createGridView(this.defaultOptions); - visibilityChange.triggerShownEvent = function() { + const triggerShownEventInitial = visibilityChange.triggerShownEvent; + + visibilityChange.DEBUG_set_triggerShownEvent(function() { isShownEventTriggered = true; - }; + }); this.resizingController.component._fireContentReadyAction = function() { isContentReadyCalled = true; }; - // act - this.resizingController._initPostRenderHandlers(); - this.resizingController._refreshSizesHandler({ - changeType: 'updateSelection', - }); + try { + // act + this.resizingController._initPostRenderHandlers(); + this.resizingController._refreshSizesHandler({ + changeType: 'updateSelection', + }); - // assert - assert.ok(!isShownEventTriggered, 'shown event'); - assert.ok(!isContentReadyCalled, 'content ready'); + // assert + assert.ok(!isShownEventTriggered, 'shown event'); + assert.ok(!isContentReadyCalled, 'content ready'); + } finally { + visibilityChange.DEBUG_set_triggerShownEvent(triggerShownEventInitial); + } }); QUnit.test('Render scrollable when there is max height (T427967)', function(assert) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerFilter.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerFilter.tests.js index 9924268baf7d..3c193e4058ed 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerFilter.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/headerFilter.tests.js @@ -5,7 +5,6 @@ import gridCoreUtils from '__internal/grids/grid_core/m_utils'; import $ from 'jquery'; import ArrayStore from 'common/data/array_store'; -import { noop } from 'core/utils/common'; import ODataStore from 'common/data/odata/store'; import devices from '__internal/core/m_devices'; import { DataSource } from 'common/data/data_source/data_source'; @@ -536,10 +535,10 @@ QUnit.module('Header Filter', { const that = this; const testElement = $('#container'); + that.generateItems(5); that.setupDataGrid(); - that.dataController.dataSource = noop; - + that.dataSourceController.disposeAdapter(); that.columnHeadersView.render(testElement); that.headerFilterView.render(testElement); @@ -4085,7 +4084,7 @@ QUnit.module('Header Filter with real columnsController', { that.setupDataGrid(); const column = that.columnsController.getVisibleColumns()[0]; - that.dataController.store().on('loading', function(options) { + that.dataSourceController.store().on('loading', function(options) { loadOptions = options; }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/keyboardNavigation.accessibility.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/keyboardNavigation.accessibility.tests.js index 0ec9ea62580c..bd25a02825a4 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/keyboardNavigation.accessibility.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/keyboardNavigation.accessibility.tests.js @@ -373,14 +373,14 @@ QUnit.module('Keyboard navigation accessibility', { headersWrapper.getHeaderItem(0, 0).focus(); // assert - assert.notOk(this.getController('data').getDataSource().sort(), 'Sorting'); + assert.notOk(this.getController('dataSource').getDataSource().sort(), 'Sorting'); // act fireKeyDown(headersWrapper.getHeaderItem(0, 0), 'Enter'); this.clock.tick(10); // assert - assert.deepEqual(this.getController('data').getDataSource().sort(), [{ selector: 'name', desc: false }], 'Sorting'); + assert.deepEqual(this.getController('dataSource').getDataSource().sort(), [{ selector: 'name', desc: false }], 'Sorting'); assert.equal(keyDownFiresCount, 1, 'keyDownFiresCount'); // act @@ -388,7 +388,7 @@ QUnit.module('Keyboard navigation accessibility', { this.clock.tick(10); // assert - assert.deepEqual(this.getController('data').getDataSource().sort(), [{ selector: 'name', desc: true }], 'Sorting'); + assert.deepEqual(this.getController('dataSource').getDataSource().sort(), [{ selector: 'name', desc: true }], 'Sorting'); assert.equal(keyDownFiresCount, 2, 'keyDownFiresCount'); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/keyboardNavigation.keyboardKeys.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/keyboardNavigation.keyboardKeys.tests.js index 791e37b4728b..e0a87d92c437 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/keyboardNavigation.keyboardKeys.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/keyboardNavigation.keyboardKeys.tests.js @@ -1810,14 +1810,12 @@ QUnit.module('Keyboard keys', { return $container; }; - this.dataController.store = function() { - return { - key: function() { }, - update: function(key, values) { - isStoreUpdated = true; - return $.Deferred().resolve(key, values); - } - }; + this.dataControllerOptions.store = { + key: function() { }, + update: function(key, values) { + isStoreUpdated = true; + return $.Deferred().resolve(key, values); + } }; // act diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/pagerView.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/pagerView.tests.js index fb87a2844f8e..30dd86c9ac15 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/pagerView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/pagerView.tests.js @@ -411,7 +411,7 @@ QUnit.module('Pager', { const isVisible = pagerView.isVisible(); // assert - assert.equal(this.dataController.pageCount(), 2); + assert.equal(this.dataSourceController.pageCount(), 2); assert.ok(isVisible); assert.equal(pagerView.element().dxPagination('instance').option('pagesNavigatorVisible'), 'auto', 'pagesNavigatorVisible'); }); @@ -426,7 +426,7 @@ QUnit.module('Pager', { const isVisible = pagerView.isVisible(); // assert - assert.equal(this.dataController.pageCount(), 1); + assert.equal(this.dataSourceController.pageCount(), 1); assert.ok(!isVisible); }); @@ -443,8 +443,8 @@ QUnit.module('Pager', { const isVisible = pagerView.isVisible(); // assert - assert.ok(!this.dataController.hasKnownLastPage()); - assert.equal(this.dataController.pageCount(), 1); + assert.ok(!this.dataSourceController.hasKnownLastPage()); + assert.equal(this.dataSourceController.pageCount(), 1); assert.ok(isVisible); }); @@ -459,7 +459,7 @@ QUnit.module('Pager', { const isVisible = pagerView.isVisible(); // assert - assert.equal(this.dataController.pageCount(), 1); + assert.equal(this.dataSourceController.pageCount(), 1); assert.ok(isVisible); assert.equal(pagerView.element().dxPagination('instance').option('pagesNavigatorVisible'), true, 'pagesNavigatorVisible'); }); @@ -476,7 +476,7 @@ QUnit.module('Pager', { const isVisible = pagerView.isVisible(); // assert - assert.equal(this.dataController.pageCount(), 2); + assert.equal(this.dataSourceController.pageCount(), 2); assert.ok(!isVisible); assert.equal(dataUtils.data(pagerView.element().get(0), 'dxPager'), undefined, 'pager instance'); }); @@ -494,7 +494,7 @@ QUnit.module('Pager', { const isVisible = pagerView.isVisible(); // assert - assert.equal(this.dataController.pageCount(), 1); + assert.equal(this.dataSourceController.pageCount(), 1); assert.ok(!isVisible); }); @@ -511,7 +511,7 @@ QUnit.module('Pager', { const isVisible = pagerView.isVisible(); // assert - assert.equal(this.dataController.pageCount(), 1); + assert.equal(this.dataSourceController.pageCount(), 1); assert.deepEqual(this.dataController.getPageSizes(), []); assert.ok(!isVisible); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/rowsView.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/rowsView.tests.js index 8273c90ba416..07cf8ca0d518 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/rowsView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/rowsView.tests.js @@ -19,7 +19,6 @@ import { setupDataGridModules, MockDataController, MockColumnsController, MockSe import { findShadowHostOrDocument } from '../../helpers/dataGridHelper.js'; import numberLocalization from 'common/core/localization/number'; import virtualScrollingCore from '__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling_core'; -import ODataStore from 'common/data/odata/store'; import ArrayStore from 'common/data/array_store'; const expandCellTemplate = gridCoreUtils.getExpandCellTemplate(); @@ -941,14 +940,18 @@ QUnit.module('Rows view', { { data: { name: 'aaaüaaa' }, values: ['aaaüaaa'] }, { data: { name: 'aaaaaaü' }, values: ['aaaaaaü'] }, { data: { name: 'üaaaaaa' }, values: ['üaaaaaa'] }]; - const dataController = new MockDataController({ items: rows }); - dataController.getDataSource = () => ({ loadOptions: () => ({ - langParams: { - collatorOptions: { - sensitivity: 'base' - }, + const dataController = new MockDataController({ + items: rows, + dataSource: { + loadOptions: () => ({ + langParams: { + collatorOptions: { + sensitivity: 'base' + }, + } + }) } - }) }); + }); const rowsView = this.createRowsView(this.items, dataController, columns); const testElement = $('#container'); @@ -980,11 +983,6 @@ QUnit.module('Rows view', { const dataController = new MockDataController({ items: rows }); const rowsView = this.createRowsView(this.items, dataController, columns); const testElement = $('#container'); - const store = new ODataStore({ url: 'test.org' }); - - dataController.store = function() { - return store; - }; // act this.options.searchPanel = { @@ -1014,11 +1012,6 @@ QUnit.module('Rows view', { const dataController = new MockDataController({ items: rows }); const rowsView = this.createRowsView(this.items, dataController, columns); const testElement = $('#container'); - const store = new ODataStore({ url: 'test.org' }); - - dataController.store = function() { - return store; - }; // act this.options.searchPanel = { @@ -4660,7 +4653,7 @@ QUnit.module('Rows view with real dataController and columnController', { that.rowsView.resize(); // assert - assert.equal(that.dataController.pageCount(), 3, 'page count = 3'); + assert.equal(that.dataSourceController.pageCount(), 3, 'page count = 3'); assert.ok(!that.rowsView._hasHeight, 'not has height'); assert.ok(that.rowsView._rowHeight > 0, 'row height > 0'); assert.equal(Math.round(getHeight(that.rowsView._getFreeSpaceRowElements())), Math.round(that.rowsView._rowHeight * 2), 'height free space row'); @@ -4686,7 +4679,7 @@ QUnit.module('Rows view with real dataController and columnController', { that.rowsView.resize(); // assert - assert.equal(that.dataController.pageCount(), 3, 'page count = 3'); + assert.equal(that.dataSourceController.pageCount(), 3, 'page count = 3'); assert.ok(!that.rowsView._hasHeight, 'not has height'); assert.ok(that.rowsView._rowHeight > 0, 'row height > 0'); assert.equal(getHeight(that.rowsView._getFreeSpaceRowElements()), 0, 'no height free space row'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js index ad017bce1050..b698e348456f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/selection.tests.js @@ -490,7 +490,7 @@ QUnit.module('Selection', { beforeEach: setupSelectionModule, afterEach: teardow // T135244 QUnit.test('set selectedRows. Not Loading data then no selected rows', function(assert) { let loadingCount = 0; - this.dataController.store().on('loading', function() { + this.dataSourceController.store().on('loading', function() { loadingCount++; }); @@ -1002,7 +1002,7 @@ QUnit.module('Selection', { beforeEach: setupSelectionModule, afterEach: teardow this.dataController.optionChanged({ name: 'dataSource' }); this.clock.tick(10); - this.dataController.store().on('loading', function(options) { + this.dataSourceController.store().on('loading', function(options) { loadOptions = options; }); @@ -1344,7 +1344,7 @@ QUnit.module('Selection', { beforeEach: setupSelectionModule, afterEach: teardow this.dataController.resetDataSource(); let loadingCount = 0; - this.dataController.store().on('loading', function() { + this.dataSourceController.store().on('loading', function() { loadingCount++; }); @@ -2120,7 +2120,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec allowSelectAll: true } }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); this.selectionController.selectRows([2]); @@ -2146,7 +2146,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec allowSelectAll: true }, }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); // act this.selectionController.selectRows([1]); @@ -2177,7 +2177,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec allowSelectAll: true } }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); // act this.selectionController.selectAll(); @@ -2206,7 +2206,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec mode: 'multiple', } }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); sinon.spy(this.selectionController, '_updateSelectedOnPush'); sinon.spy(this.selectionController, 'getSelectedRowsData'); @@ -2227,7 +2227,7 @@ QUnit.module('Multiple selection. DataSource with key', { beforeEach: setupSelec deferred: true } }); - const store = this.dataController.dataSource().store(); + const store = this.dataSourceController.store(); sinon.spy(this.selectionController, '_updateSelectedOnPush'); sinon.spy(this.selectionController, 'getSelectedRowsData'); @@ -2599,7 +2599,7 @@ QUnit.module('Selection SelectAllMode', { // act $.each(this.selectionController.getSelectedRowKeys(), function(index, key) { - that.dataController.store().remove(key); + that.dataSourceController.store().remove(key); }); this.dataController.refresh(); @@ -2607,7 +2607,7 @@ QUnit.module('Selection SelectAllMode', { // assert assert.equal(this.array.length, 0, 'array length'); assert.equal(this.dataController.items().length, 0, 'items count'); - assert.equal(this.dataController.totalCount(), 0, 'totalCount'); + assert.equal(this.dataSourceController.totalCount(), 0, 'totalCount'); }); QUnit.test('Select All for multiple selection change page', function(assert) { @@ -3192,7 +3192,7 @@ QUnit.module('Selection when grouping', { this.dataController.pageIndex(1); this.selectionController.changeItemSelection(1); - assert.ok(this.dataController._dataSource.items()[0].isContinuation); + assert.ok(this.dataSourceController.getAdapter().items()[0].isContinuation); assert.ok(!this.dataController.items()[0].isSelected); assert.ok(this.dataController.items()[1].isSelected); assert.ok(!this.dataController.items()[2].isSelected); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/virtualScrolling.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/virtualScrolling.integration.tests.js index ff4057a2e1ca..9f2bb020d851 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/virtualScrolling.integration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.dataGrid/virtualScrolling.integration.tests.js @@ -3242,7 +3242,7 @@ QUnit.module('Virtual Scrolling', baseModuleConfig, () => { this.clock.tick(10); // assert - assert.deepEqual(dataGrid.getController('data').pageCount(), 2, 'pages count'); + assert.deepEqual(dataGrid.getController('dataSource').pageCount(), 2, 'pages count'); assert.deepEqual(dataGrid.getController('data').items().length, 5, 'items count'); assert.ok(!dataGrid.getView('pagerView').isVisible(), 'pager visibility'); }); @@ -6916,7 +6916,7 @@ QUnit.module('Infinite Scrolling', baseModuleConfig, () => { this.clock.tick(10); // assert assert.ok(dataGrid.getController('data').viewportSize() > 0); - assert.ok(!dataGrid.getController('data').dataSource().requireTotalCount()); + assert.ok(!dataGrid.getController('dataSource').getAdapter().requireTotalCount()); }); QUnit.test('New mode. Load panel should not be displayed at the bottom when all items are loaded', function(assert) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.mask.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.mask.tests.js index 69b3b7bd9bbb..a58a2ede028e 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.mask.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/datebox.mask.tests.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import { renderDateParts, getDatePartIndexByPosition } from '__internal/ui/date_box/date_box.mask.parts'; import dateParser from '__internal/core/localization/ldml/dateParserModule'; +import { spySeam } from '../../helpers/moduleSeam.js'; import dateLocalization from 'common/core/localization/date'; import localization from 'localization'; import { noop } from 'core/utils/common'; @@ -1761,7 +1762,7 @@ module('Options changed', setupModule, () => { }); test('performance - value change should not lead to recreate regexp and format pattern', function(assert) { - const regExpInfo = sinon.spy(dateParser, 'getRegExpInfo'); + const regExpInfo = spySeam(dateParser, 'getRegExpInfo', 'DEBUG_set_getRegExpInfo'); this.instance.option('displayFormat', 'dd.MM'); assert.strictEqual(regExpInfo.callCount, 1, 'regexpInfo should be called when format changed'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownBox.tests.js index c0b2ad29a24d..3c4059e5a0f7 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownBox.tests.js @@ -3,7 +3,7 @@ import renderer from 'core/renderer'; import keyboardMock from '../../helpers/keyboardMock.js'; import fx from 'common/core/animation/fx'; import DropDownBox from 'ui/drop_down_box'; -import typeUtils, { isRenderer } from 'core/utils/type'; +import { isRenderer, isFunction } from 'core/utils/type'; import config from 'core/config'; import devices from '__internal/core/m_devices'; import { normalizeKeyName } from 'common/core/events/utils/index'; @@ -789,7 +789,7 @@ QUnit.module('popup options', moduleConfig, () => { contentTemplate: () => $content }); - assert.ok(typeUtils.isFunction(instance.option('dropDownOptions.hideOnParentScroll'))); + assert.ok(isFunction(instance.option('dropDownOptions.hideOnParentScroll'))); }); [true, false].forEach((isMac) => { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js index 1176c8994059..472cd7a1c70d 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/lookup.tests.js @@ -3969,6 +3969,62 @@ if(devices.real().deviceType === 'desktop') { }); } +QUnit.module('screen reader announcements (T1334729)', { + beforeEach: function() { + fx.off = true; + this.clock = sinon.useFakeTimers(); + + this.instance = $('#lookup').dxLookup({ + dataSource: [1, 2, 3], + searchTimeout: 0, + 'dropDownOptions.animation': null, + opened: true + }).dxLookup('instance'); + + this.search = (value) => { + this.instance._searchBox.option('value', value); + this.clock.tick(0); + }; + + this.getA11yStatus = () => $(`.${LIST_CLASS}`).children('[role="status"]'); + this.getItemsAnnouncement = (itemsCount) => `${messageLocalization.format('dxList-listAriaLabel')}: ${itemsCount}`; + }, + afterEach: function() { + this.clock.restore(); + fx.off = false; + } +}, () => { + QUnit.test('list should have a single live region', function(assert) { + assert.strictEqual(this.getA11yStatus().length, 1); + }); + + QUnit.test('item count should be announced when the drop-down is opened', function(assert) { + assert.strictEqual(this.getA11yStatus().text(), this.getItemsAnnouncement(3)); + }); + + QUnit.test('noDataText should be announced if search returns no items', function(assert) { + this.search('4'); + + assert.strictEqual(this.getA11yStatus().text(), messageLocalization.format('dxCollectionWidget-noDataText')); + }); + + QUnit.test('item count should be announced if search returns items', function(assert) { + this.search('1'); + + assert.strictEqual(this.getA11yStatus().text(), this.getItemsAnnouncement(1)); + }); + + QUnit.test('live region should not be re-created on search', function(assert) { + const a11yStatusElement = this.getA11yStatus().get(0); + + this.search('4'); + + assert.strictEqual(this.getA11yStatus().get(0), a11yStatusElement, 'the live region is the same element'); + assert.strictEqual(a11yStatusElement.textContent, messageLocalization.format('dxCollectionWidget-noDataText'), + 'the announcement is written into that element, otherwise screen readers do not announce it'); + }); +}); + QUnit.module('default options', { beforeEach: function() { fx.off = true; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/numberBoxParts/common.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/numberBoxParts/common.tests.js index 9e82f2737153..63d935a73456 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/numberBoxParts/common.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/numberBoxParts/common.tests.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import SpinButton from '__internal/ui/number_box/m_number_box.spin'; +import SpinButton from '__internal/ui/number_box/number_box.spin'; import config from 'core/config'; import eventsEngine from 'common/core/events/core/events_engine'; import keyboardMock from '../../../helpers/keyboardMock.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/numberBoxParts/mask.caret.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/numberBoxParts/mask.caret.tests.js index 546b5dd3d923..b070db9ed5b1 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/numberBoxParts/mask.caret.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/numberBoxParts/mask.caret.tests.js @@ -4,7 +4,7 @@ import { getCaretInBoundaries, isCaretInBoundaries, getCaretAfterFormat, -} from '__internal/ui/number_box/m_number_box.caret'; +} from '__internal/ui/number_box/number_box.caret'; QUnit.module('format caret', () => { const customFormat = { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.form/form.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.form/form.tests.js index 0f7a304b3962..3533d04adc83 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.form/form.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.form/form.tests.js @@ -6,7 +6,8 @@ import resizeCallbacks from '__internal/core/utils/m_resize_callbacks'; import typeUtils from 'core/utils/type'; import { extend } from 'core/utils/extend'; import messageLocalization from 'localization/message'; -import visibilityEventsModule from 'common/core/events/visibility_change'; +import * as visibilityEventsModule from 'common/core/events/visibility_change'; +import { spyVisibilityEvent } from '../../helpers/visibilityChangeMock.js'; import { TABS_ITEM_CLASS } from '__internal/ui/tabs/tabs'; import 'fluent_blue_light.css!'; import $ from 'jquery'; @@ -1042,7 +1043,7 @@ QUnit.module('T986577', () => { } QUnit.test('Toolbar is rendered inside form. alignItemLabels = false', function(assert) { - const resizeEventSpy = sinon.spy(visibilityEventsModule, 'triggerResizeEvent'); + const resizeEventSpy = spyVisibilityEvent('triggerResizeEvent'); const $form = $('#form').dxForm(extend({ alignItemLabels: false }, getFormConfig())); const resizeEventArg = resizeEventSpy.getCall(0).args[0]; @@ -1055,7 +1056,7 @@ QUnit.module('T986577', () => { }); QUnit.test('Toolbar is rendered inside form. alignItemLabels = true', function(assert) { - const resizeEventSpy = sinon.spy(visibilityEventsModule, 'triggerResizeEvent'); + const resizeEventSpy = spyVisibilityEvent('triggerResizeEvent'); const $form = $('#form').dxForm(extend({ alignItemLabels: true }, getFormConfig())); const resizeEventArg = resizeEventSpy.getCall(0).args[0]; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditor.missingModules.tests.part1.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditor.missingModules.tests.part1.js index d61a6192935d..13cf0441b0c7 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditor.missingModules.tests.part1.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditor.missingModules.tests.part1.js @@ -1,2 +1,2 @@ -require('../../helpers/ignoreQuillTimers.js'); -require('./htmlEditorParts/importQuill.tests.js'); +import '../../helpers/ignoreQuillTimers.js'; +import './htmlEditorParts/importQuill.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditor.missingModules.tests.part2.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditor.missingModules.tests.part2.js index 339dd30efcf3..52f2e5017af0 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditor.missingModules.tests.part2.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditor.missingModules.tests.part2.js @@ -1 +1 @@ -require('../../helpers/ignoreQuillTimers.js'); +import '../../helpers/ignoreQuillTimers.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/importQuill.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/importQuill.tests.js index 8ba22c6b1e98..954d86128ce6 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/importQuill.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/importQuill.tests.js @@ -1,22 +1,13 @@ +import quillImporter from 'ui/html_editor/quill_importer'; -SystemJS.config({ - map: { - 'devextreme-quill': '/packages/devextreme/testing/helpers/quillDependencies/noQuill.js' - } -}); - -define(function(require) { - const getQuill = require('ui/html_editor/quill_importer').getQuill; - - QUnit.module('Import 3rd party', function() { - QUnit.test('it throw an error if the quill script isn\'t referenced', function(assert) { - assert.throws( - function() { getQuill(); }, - function(e) { - return /(E1041)[\s\S]*(Quill)/.test(e.message); - }, - 'The Quill script isn\'t referenced' - ); - }); +QUnit.module('Import 3rd party', function() { + QUnit.test('it throw an error if the quill script is not referenced', function(assert) { + assert.throws( + function() { quillImporter.getQuill(); }, + function(e) { + return /(E1041)[\s\S]*(Quill)/.test(e.message); + }, + 'The Quill script is not referenced' + ); }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/tableResizingModule.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/tableResizingModule.tests.js index 939672ded248..cf29a6de403a 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/tableResizingModule.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/tableResizingModule.tests.js @@ -72,6 +72,8 @@ const moduleConfig = { }, afterEach: function() { + this.clock.tick(1000); + resizeCallbacks.empty(); this.clock.restore(); } }; @@ -176,7 +178,7 @@ module('Table resizing module', moduleConfig, () => { resizeCallbacks.fire(); - assert.strictEqual(typeof resizingInstance._resizeHandlerWithContext, 'object', '_resizeHandler is an object'); + assert.ok(resizingInstance._resizeHandlerWithContext, '_resizeHandlerWithContext is registered'); }); test('Window resize callback should be cleaned after the widget dispose', function(assert) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/toolbarModule.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/toolbarModule.tests.js index 3afd5c78e87e..bcc686b7c3b1 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/toolbarModule.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.htmlEditor/htmlEditorParts/toolbarModule.tests.js @@ -1883,6 +1883,7 @@ testModule('Toolbar items state update', { test('state of the items in menu should be synchronized after toolbar repaint (t1117604)', function(assert) { this.options.items = this.mapToMenuItems(TABLE_OPERATIONS); + resizeCallbacks.empty(); const toolbar = new Toolbar(this.quillMock, this.options); this.quillMock.getFormat = () => ({ table: true }); toolbar.updateTableWidgets(); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/desktopTooltip.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/desktopTooltip.tests.js index 9205430c2753..c097e42497ff 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/desktopTooltip.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/desktopTooltip.tests.js @@ -1,3 +1,4 @@ +import 'jquery'; import { DesktopTooltipStrategy } from '__internal/scheduler/tooltip_strategies/desktop_tooltip_strategy'; import { FunctionTemplate } from 'core/templates/function_template'; import { extend } from 'core/utils/extend'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/editing.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/editing.tests.js index e2125aa08ba8..a42f81dc26e5 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/editing.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/editing.tests.js @@ -1,12 +1,12 @@ -const $ = require('jquery'); -const devices = require('core/devices'); -const fx = require('common/core/animation/fx'); -const keyboardMock = require('../../helpers/keyboardMock.js'); -const { createWrapper } = require('../../helpers/scheduler/helpers.js'); -const { waitAsync } = require('../../helpers/scheduler/waitForAsync.js'); - -require('__internal/scheduler/scheduler'); -require('ui/drop_down_button'); +import $ from 'jquery'; +import devices from 'core/devices'; +import fx from 'common/core/animation/fx'; +import keyboardMock from '../../helpers/keyboardMock.js'; +import { createWrapper } from '../../helpers/scheduler/helpers.js'; +import { waitAsync } from '../../helpers/scheduler/waitForAsync.js'; + +import '__internal/scheduler/scheduler'; +import 'ui/drop_down_button'; QUnit.testStart(function() { $('#qunit-fixture').html('
'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.base.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.base.tests.js index 344a1ac27a97..81f0ba81e33c 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.base.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.base.tests.js @@ -1,7 +1,16 @@ -const { getOuterHeight } = require('core/utils/size'); -const $ = require('jquery'); -const { createWrapper } = require('../../helpers/scheduler/helpers.js'); -const { waitAsync } = require('../../helpers/scheduler/waitForAsync.js'); +import { getOuterHeight } from 'core/utils/size'; +import $ from 'jquery'; +import { createWrapper } from '../../helpers/scheduler/helpers.js'; +import { waitAsync } from '../../helpers/scheduler/waitForAsync.js'; + +import 'fluent_blue_light.css!'; + +import { noop } from 'core/utils/common'; +import errors from 'ui/widget/ui.errors'; +import config from 'core/config'; + +import '__internal/scheduler/scheduler'; +import 'ui/drop_down_button'; QUnit.testStart(function() { $('#qunit-fixture').html( @@ -10,15 +19,6 @@ QUnit.testStart(function() {
'); }); -require('fluent_blue_light.css!'); - -const noop = require('core/utils/common').noop; -const errors = require('ui/widget/ui.errors'); -const config = require('core/config'); - -require('__internal/scheduler/scheduler'); -require('ui/drop_down_button'); - QUnit.module('Integration: Base', { beforeEach: function() { this.createInstance = async function(options) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.recurrenceRuleValidation.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.recurrenceRuleValidation.tests.js index a8d5b4170e91..9a93400f7933 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.recurrenceRuleValidation.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.recurrenceRuleValidation.tests.js @@ -1,4 +1,14 @@ -const $ = require('jquery'); +import $ from 'jquery'; + +import 'fluent_blue_light.css!'; + +import fx from 'common/core/animation/fx'; +import * as dragEvents from 'common/core/events/drag'; +import { DataSource } from 'common/data/data_source/data_source'; +import { createWrapper } from '../../helpers/scheduler/helpers.js'; + +import '__internal/scheduler/scheduler'; +import 'ui/drop_down_button'; QUnit.testStart(function() { $('#qunit-fixture').html( @@ -7,16 +17,6 @@ QUnit.testStart(function() {
'); }); -require('fluent_blue_light.css!'); - -const fx = require('common/core/animation/fx'); -const dragEvents = require('common/core/events/drag'); -const DataSource = require('common/data/data_source/data_source').DataSource; -const { createWrapper } = require('../../helpers/scheduler/helpers.js'); - -require('__internal/scheduler/scheduler'); -require('ui/drop_down_button'); - QUnit.module('Integration: recurrence rules validation', { beforeEach: function() { fx.off = true; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.viewSwitcher.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.viewSwitcher.tests.js index 17d055ea0ba7..c0a720c0c6d7 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.viewSwitcher.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/integration.viewSwitcher.tests.js @@ -1,4 +1,14 @@ -const $ = require('jquery'); +import $ from 'jquery'; + +import 'fluent_blue_light.css!'; +import 'ui/drop_down_button'; + +import { noop } from 'core/utils/common'; +import { DataSource } from 'common/data/data_source/data_source'; +import { createWrapper } from '../../helpers/scheduler/helpers.js'; +import { waitAsync } from '../../helpers/scheduler/waitForAsync.js'; + +import '__internal/scheduler/scheduler'; QUnit.testStart(function() { $('#qunit-fixture').html( @@ -7,16 +17,6 @@ QUnit.testStart(function() {
'); }); -require('fluent_blue_light.css!'); -require('ui/drop_down_button'); - -const noop = require('core/utils/common').noop; -const DataSource = require('common/data/data_source/data_source').DataSource; -const { createWrapper } = require('../../helpers/scheduler/helpers.js'); -const { waitAsync } = require('../../helpers/scheduler/waitForAsync.js'); - -require('__internal/scheduler/scheduler'); - QUnit.module('Integration: View switcher', () => { QUnit.test('dataSource should be filtered if \'currentView\' option is changed', async function(assert) { const dataSource = new DataSource({ diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/loading.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/loading.tests.js index f49ef240ae7c..4f72cf33ef2b 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/loading.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.scheduler/loading.tests.js @@ -1,8 +1,8 @@ -const $ = require('jquery'); -const loading = require('__internal/scheduler/loading'); -const viewPort = require('core/utils/view_port').value; -const fx = require('common/core/animation/fx'); -const LoadPanel = require('ui/load_panel'); +import $ from 'jquery'; +import * as loading from '__internal/scheduler/loading'; +import { value as viewPort } from 'core/utils/view_port'; +import fx from 'common/core/animation/fx'; +import LoadPanel from 'ui/load_panel'; QUnit.module('loading tests', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js index ee7a4c5f2ac6..a8cc8b81c41f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/dataController.tests.js @@ -72,7 +72,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod }); // assert - assert.notOk(this.dataController.dataSource().paginate(), 'paginate is disabled'); + assert.notOk(this.dataSourceController.getAdapter().paginate(), 'paginate is disabled'); }); QUnit.test('Paginate should be enabled when virtual scrolling is enabled', function(assert) { @@ -85,7 +85,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod }); // assert - assert.ok(this.dataController.dataSource().paginate(), 'paginate is enabled'); + assert.ok(this.dataSourceController.getAdapter().paginate(), 'paginate is enabled'); }); QUnit.test('Initialize from dataSource with plain structure', function(assert) { @@ -486,7 +486,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod }); // assert - assert.equal(this.dataController.totalItemsCount(), 4, 'totalItemsCount'); + assert.equal(this.dataSourceController.totalItemsCount(), 4, 'totalItemsCount'); const items = this.dataController.items(); assert.equal(items.length, 3, 'count items'); @@ -517,7 +517,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod }); // assert - assert.equal(this.dataController.totalItemsCount(), 5, 'totalItemsCount'); + assert.equal(this.dataSourceController.totalItemsCount(), 5, 'totalItemsCount'); assert.equal(this.getVisibleRows().length, 2, 'row count'); assert.strictEqual(this.getVisibleRows()[0].node, this.getNodeByKey(1), 'first node instance is correct'); }); @@ -545,7 +545,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.expandRow(2); // assert - assert.equal(this.dataController.totalItemsCount(), 5, 'totalItemsCount'); + assert.equal(this.dataSourceController.totalItemsCount(), 5, 'totalItemsCount'); const items = this.dataController.items(); assert.equal(items.length, 3, 'count items'); @@ -568,8 +568,8 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod this.dataController.load(); // assert - assert.equal(this.dataController.totalItemsCount(), 1, 'count visible items'); - assert.equal(this.dataController.totalCount(), 3, 'count all items'); + assert.equal(this.dataSourceController.totalItemsCount(), 1, 'count visible items'); + assert.equal(this.dataSourceController.totalCount(), 3, 'count all items'); }); QUnit.test('Getting key when there are keyExpr and store hasn\'t key', function(assert) { @@ -707,7 +707,7 @@ QUnit.module('Initialization', { beforeEach: setupModule, afterEach: teardownMod QUnit.test('There are no exceptions on getting node when hasn\'t datasource', function(assert) { // arrange - this.dataController.setDataSource(undefined); + this.dataController._disposeDataSource(); // act, assert assert.equal(this.getNodeByKey(1), undefined, 'no exceptions'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/stateStoring.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/stateStoring.tests.js index e8889da63876..b22518b54933 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/stateStoring.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.treeList/stateStoring.tests.js @@ -34,6 +34,9 @@ QUnit.module('State Storing', { keyExpr: 'id', parentIdExpr: 'parentId', loadingTimeout: null, + paging: { + enabled: true + }, scrolling: { mode: 'virtual' } diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/animator.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/animator.tests.js index 0d5648193c56..f75bb2a0a63f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/animator.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/animator.tests.js @@ -1,12 +1,13 @@ import Animator from '__internal/ui/scroll_view/animator'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const REQUEST_ANIMATION_FRAME_TIMEOUT = 10; QUnit.module('Animator', { beforeEach: function() { this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { return window.setTimeout(callback, REQUEST_ANIMATION_FRAME_TIMEOUT); }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagram.missingModules.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagram.missingModules.tests.js index 81ac9299d301..4d8198542df5 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagram.missingModules.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagram.missingModules.tests.js @@ -1 +1 @@ -require('./diagramParts/importDiagram.tests.js'); +import './diagramParts/importDiagram.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagramParts/importDiagram.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagramParts/importDiagram.tests.js index ecc19597f49f..11113aa6afb5 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagramParts/importDiagram.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/diagramParts/importDiagram.tests.js @@ -1,20 +1,12 @@ -SystemJS.config({ - map: { - 'devexpress-diagram': '/packages/devextreme/testing/helpers/noDiagram.js' - } -}); - -define(function(require) { - const getDiagram = require('__internal/ui/diagram/diagram.importer').getDiagram; +import { getDiagram } from '__internal/ui/diagram/diagram.importer'; - QUnit.module('Import devexpress-diagram', function() { - QUnit.test('throw an error if the devexpress-diagram script isn\'t referenced', function(assert) { - assert.throws( - function() { getDiagram(); }, - function(e) { - return /(E1041)[\s\S]*(devexpress-diagram)/.test(e.message); - } - ); - }); +QUnit.module('Import devexpress-diagram', function() { + QUnit.test('throw an error if the devexpress-diagram script isn\'t referenced', function(assert) { + assert.throws( + function() { getDiagram(); }, + function(e) { + return /(E1041)[\s\S]*(devexpress-diagram)/.test(e.message); + } + ); }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/draggable.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/draggable.tests.js index 4853c0288d5c..a213047904a8 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/draggable.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/draggable.tests.js @@ -4,6 +4,7 @@ import pointerMock from '../../helpers/pointerMock.js'; import viewPort from 'core/utils/view_port'; import GestureEmitter from 'common/core/events/gesture/emitter.gesture.js'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import translator from 'common/core/animation/translator'; import fx from 'common/core/animation/fx'; import keyboardMock from '../../helpers/keyboardMock.js'; @@ -1664,7 +1665,7 @@ QUnit.module('autoScroll', $.extend({}, moduleConfig, { this.clock = sinon.useFakeTimers(); setupDraggable(this, $('#scrollableItem')); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { return window.setTimeout(callback, 10); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/drawer.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/drawer.tests.js index 7ca9ed022c71..25c7f056db2b 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/drawer.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/drawer.tests.js @@ -7,7 +7,7 @@ import resizeCallbacks from 'core/utils/resize_callbacks'; import typeUtils from 'core/utils/type'; import { addShadowDomStyles } from 'core/utils/shadow_dom'; import eventsEngine from 'common/core/events/core/events_engine'; -import visibilityChange from 'common/core/events/visibility_change'; +import * as visibilityChange from 'common/core/events/visibility_change'; import $ from 'jquery'; import Button from 'ui/button'; import Drawer from 'ui/drawer'; @@ -281,16 +281,16 @@ QUnit.module('Drawer behavior', () => { const triggerResizeEventInitial = visibilityChange.triggerResizeEvent; - visibilityChange.triggerResizeEvent = ($element) => { + visibilityChange.DEBUG_set_triggerResizeEvent(($element) => { assert.ok(true, 'resize event call is expected'); assert.equal($element, drawer.viewContent(), 'ViewContent element is expected'); const rect = $(drawer.viewContent())[0].getBoundingClientRect(); assert.strictEqual(rect.width, 90, 'ViewContent element width'); assert.strictEqual(rect.height, 50, 'ViewContent element height'); - visibilityChange.triggerResizeEvent = triggerResizeEventInitial; + visibilityChange.DEBUG_set_triggerResizeEvent(triggerResizeEventInitial); done(); - }; + }); drawer.toggle(); }); @@ -311,18 +311,18 @@ QUnit.module('Drawer behavior', () => { const triggerFunction = visibilityChange.triggerResizeEvent; try { - visibilityChange.triggerResizeEvent = ($element) => { + visibilityChange.DEBUG_set_triggerResizeEvent(($element) => { assert.ok(true, 'resize event call is expected'); assert.equal($element, drawer.viewContent(), 'ViewContent element is expected'); const rect = $(drawer.viewContent())[0].getBoundingClientRect(); assert.strictEqual(rect.width, 90, 'ViewContent element width'); assert.strictEqual(rect.height, 50, 'ViewContent element height'); - }; + }); drawer.toggle(); } finally { - visibilityChange.triggerResizeEvent = triggerFunction; + visibilityChange.DEBUG_set_triggerResizeEvent(triggerFunction); } }); @@ -337,15 +337,15 @@ QUnit.module('Drawer behavior', () => { assert.expect(2); try { - visibilityChange.triggerResizeEvent = function($element) { + visibilityChange.DEBUG_set_triggerResizeEvent(function($element) { assert.ok(true, 'event was triggered'); assert.equal($element, instance.viewContent(), 'Event was triggered for right element'); - }; + }); instance.option('position', 'left'); } finally { - visibilityChange.triggerResizeEvent = triggerFunction; + visibilityChange.DEBUG_set_triggerResizeEvent(triggerFunction); } }); @@ -979,7 +979,7 @@ QUnit.module('Drawer behavior', () => { minSize: minSize }).dxDrawer('instance'); - visibilityChange.triggerResizeEvent = ($element) => { + visibilityChange.DEBUG_set_triggerResizeEvent(($element) => { resizeCallCount++; assert.strictEqual(resizeCallCount, 1, 'resize event should be triggered once'); assert.equal($element, drawer.viewContent(), 'ViewContent element is expected'); @@ -993,9 +993,9 @@ QUnit.module('Drawer behavior', () => { assert.strictEqual(viewRect.width, expectedViewRect.width, 'ViewContent width'); assert.strictEqual(viewRect.height, expectedViewRect.height, 'ViewContent height'); - visibilityChange.triggerResizeEvent = triggerResizeEventInitial; + visibilityChange.DEBUG_set_triggerResizeEvent(triggerResizeEventInitial); done(); - }; + }); drawer.toggle(); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/dropDownButton.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/dropDownButton.tests.js index 0c4faa275ab1..ffff4a3f23a7 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/dropDownButton.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/dropDownButton.tests.js @@ -1,7 +1,7 @@ import { getHeight, getOuterHeight, getOuterWidth, getWidth } from 'core/utils/size'; import $ from 'jquery'; import DropDownButton from 'ui/drop_down_button'; -import typeUtils, { isRenderer } from 'core/utils/type'; +import { isRenderer, isPromise } from 'core/utils/type'; import config from 'core/config'; import eventsEngine from 'common/core/events/core/events_engine'; import keyboardMock from '../../helpers/keyboardMock.js'; @@ -1726,7 +1726,7 @@ QUnit.module('public methods', { const togglePromise = this.dropDownButton.toggle(); assert.strictEqual(popup.option('visible'), false, 'popup visibility is inverted'); - assert.ok(typeUtils.isPromise(togglePromise), 'toggle should return promise'); + assert.ok(isPromise(togglePromise), 'toggle should return promise'); }); QUnit.test('open method', function(assert) { @@ -1735,7 +1735,7 @@ QUnit.module('public methods', { const openPromise = this.dropDownButton.open(); assert.strictEqual(popup.option('visible'), true, 'popup is opened'); - assert.ok(typeUtils.isPromise(openPromise), 'open should return promise'); + assert.ok(isPromise(openPromise), 'open should return promise'); }); QUnit.test('close method', function(assert) { @@ -1745,7 +1745,7 @@ QUnit.module('public methods', { const closePromise = this.dropDownButton.close(); assert.strictEqual(popup.option('visible'), false, 'popup is closed'); - assert.ok(typeUtils.isPromise(closePromise), 'close should return promise'); + assert.ok(isPromise(closePromise), 'close should return promise'); }); QUnit.test('opened option', function(assert) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/gallery.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/gallery.tests.js index d4627e9de67f..ff68e064e97e 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/gallery.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/gallery.tests.js @@ -1,10 +1,11 @@ import { getHeight, getOuterHeight, getOuterWidth, getWidth } from 'core/utils/size'; import $ from 'jquery'; import { DataSource } from 'common/data/data_source/data_source'; -import visibilityChange from 'common/core/events/visibility_change'; +import { spyVisibilityEvent } from '../../helpers/visibilityChangeMock.js'; import ArrayStore from 'common/data/array_store'; import fx from 'common/core/animation/fx'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import resizeCallbacks from 'core/utils/resize_callbacks'; import { isRenderer } from 'core/utils/type'; import config from 'core/config'; @@ -731,7 +732,7 @@ QUnit.module('behavior', { }); QUnit.test('resizeCallback is called after item is rendered (T1132935)', function(assert) { - const resizeEventSpy = sinon.spy(visibilityChange, 'triggerResizeEvent'); + const resizeEventSpy = spyVisibilityEvent('triggerResizeEvent'); this.$element.dxGallery({ items: [0, 1, 2, 3], @@ -2176,7 +2177,7 @@ QUnit.module('api', { QUnit.test('animationDuration', function(assert) { fx.off = false; - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { return window.setTimeout(callback, 10); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/gantt.missingModules.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/gantt.missingModules.tests.js index 9510f40ebac3..c79cad2d7a43 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/gantt.missingModules.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/gantt.missingModules.tests.js @@ -1 +1 @@ -require('./ganttParts/importGantt.tests.js'); +import './ganttParts/importGantt.tests.js'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/ganttParts/importGantt.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/ganttParts/importGantt.tests.js index 66e095bfe854..58162b185b4c 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/ganttParts/importGantt.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/ganttParts/importGantt.tests.js @@ -1,21 +1,12 @@ +import { getGanttViewCore as getGantt } from '__internal/ui/gantt/gantt_importer'; -SystemJS.config({ - map: { - 'devexpress-gantt': '/packages/devextreme/testing/helpers/noGantt.js' - } -}); - -define(function(require) { - const getGantt = require('__internal/ui/gantt/gantt_importer').getGanttViewCore; - - QUnit.module('Import devexpress-gantt', function() { - QUnit.test('throw an error if the devexpress-gantt script isn\'t referenced', function(assert) { - assert.throws( - function() { getGantt(); }, - function(e) { - return /(E1041)[\s\S]*(devexpress-gantt)/.test(e.message); - } - ); - }); +QUnit.module('Import devexpress-gantt', function() { + QUnit.test('throw an error if the devexpress-gantt script isn\'t referenced', function(assert) { + assert.throws( + function() { getGantt(); }, + function(e) { + return /(E1041)[\s\S]*(devexpress-gantt)/.test(e.message); + } + ); }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js index 806250bc64e4..bf34a80301c4 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js @@ -19,9 +19,12 @@ import { CustomStore } from 'common/data/custom_store'; import DOMComponent from 'core/dom_component'; import List from 'ui/list'; import { setScrollView } from '__internal/ui/list/list.base'; +import { SCREEN_READER_ONLY_CLASS } from '__internal/core/widget/widget'; + import ScrollView from 'ui/scroll_view'; import eventsEngine from 'common/core/events/core/events_engine'; import ariaAccessibilityTestHelper from '../../../helpers/ariaAccessibilityTestHelper.js'; +import messageLocalization from 'common/core/localization/message'; const LIST_ITEM_CLASS = 'dx-list-item'; const LIST_ITEM_CONTENT_CLASS = 'dx-list-item-content'; @@ -1004,7 +1007,7 @@ QUnit.module('options', moduleSetup, () => { pageLoadMode: 'scrollBottom' }); - assert.equal(element.text(), '12345'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12345'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2, 3, 4, 5]); }); @@ -1014,7 +1017,7 @@ QUnit.module('options', moduleSetup, () => { pageLoadMode: 'scrollBottom' }); - assert.equal(element.text(), '12345'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12345'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2, 3, 4, 5]); }); @@ -1024,7 +1027,7 @@ QUnit.module('options', moduleSetup, () => { pageLoadMode: 'scrollBottom' }); - assert.equal(element.text(), '12345'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12345'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2, 3, 4, 5]); }); @@ -1068,7 +1071,7 @@ QUnit.module('options changed', moduleSetup, () => { pageLoadMode: 'scrollBottom' }); - assert.equal(element.text(), '01354'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '01354'); assert.deepEqual(element.dxList('instance').option('items'), [0, 1, 3, 5, 4]); element.dxList({ @@ -1077,7 +1080,7 @@ QUnit.module('options changed', moduleSetup, () => { } }); - assert.equal(element.text(), '26897'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '26897'); assert.deepEqual(element.dxList('instance').option('items'), [2, 6, 8, 9, 7]); }); @@ -1086,13 +1089,13 @@ QUnit.module('options changed', moduleSetup, () => { items: [0, 1, 3, 5, 4] }); - assert.equal(element.text(), '01354'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '01354'); element.dxList({ items: [2, 6, 8, 9, 7] }); - assert.equal(element.text(), '26897'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '26897'); }); QUnit.test('scrollingEnabled', function(assert) { @@ -2759,14 +2762,14 @@ QUnit.module('infinite list scenario', moduleSetup, () => { } }); - assert.equal(element.text(), '12', 'correct items generated'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12', 'correct items generated'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2], 'correct items presented in options'); element.find(`.${LIST_ITEM_CLASS}`).data('rendered', true); element.dxScrollView('instance').scrollBottom(); - assert.equal(element.text(), '1234', 'correct items generated'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '1234', 'correct items generated'); assert.deepEqual(element.dxList('instance').option('items'), [1, 2, 3, 4], 'correct items presented in options'); assert.strictEqual(element.find(`.${LIST_ITEM_CLASS}`).eq(0).data('rendered'), true, 'first item is not rerendered'); @@ -2875,7 +2878,7 @@ QUnit.module('infinite list scenario', moduleSetup, () => { element.dxScrollView('instance').scrollBottom(); - assert.equal(element.text(), '12', 'error occurred'); + assert.equal(element.find(`.${LIST_ITEMS_CLASS}`).text(), '12', 'error occurred'); assert.equal(element.dxList('instance')._startIndexForAppendedItems, null, 'flag set correctly'); }); @@ -4952,6 +4955,9 @@ if(devices.real().deviceType === 'desktop') { } QUnit.module('Accessibility', () => { + const getA11yStatus = (instance) => instance.$element().children('[role="status"]'); + const getItemsAnnouncement = (itemsCount) => `${messageLocalization.format('dxList-listAriaLabel')}: ${itemsCount}`; + QUnit.test('SelectAll checkbox should have aria-label="Select All" attribute', function(assert) { $('#list').dxList({ selectionMode: 'all', @@ -5378,4 +5384,104 @@ QUnit.module('Accessibility', () => { assert.strictEqual(instance.$element().find(`.${SCROLLVIEW_CONTENT_CLASS}`).eq(0).attr('role'), 'application'); }); }); + + QUnit.test('list should render a single visually hidden live region (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: ['Item 1'] }).dxList('instance'); + + const $a11yStatus = getA11yStatus(instance); + + assert.strictEqual($a11yStatus.length, 1, 'the list has a single live region'); + assert.ok($a11yStatus.hasClass(SCREEN_READER_ONLY_CLASS), 'the live region is visually hidden'); + }); + + QUnit.test('item count should be announced (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: ['Item 1', 'Item 2'] }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(2)); + }); + + QUnit.test('item count of a grouped list should not count groups (T1334729)', function(assert) { + const instance = $('#list').dxList({ + grouped: true, + items: [ + { key: 'group 1', items: ['Item 1', 'Item 2'] }, + { key: 'group 2', items: ['Item 3'] }, + ], + }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(3)); + }); + + QUnit.test('noDataText should be announced when the list is empty (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: [] }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), messageLocalization.format('dxCollectionWidget-noDataText')); + }); + + QUnit.test('custom noDataText should be announced and follow its runtime change (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: [], noDataText: 'custom-no-data' }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), 'custom-no-data', 'custom noDataText is announced'); + + instance.option('noDataText', 'another-no-data'); + + assert.strictEqual(getA11yStatus(instance).text(), 'another-no-data', 'announcement is updated after runtime change'); + }); + + QUnit.test('nothing should be announced when the list is empty and noDataText is empty (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: [], noDataText: '' }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), ''); + }); + + QUnit.test('nothing should be announced while the initial data is loading (T1334729)', function(assert) { + const loadResult = $.Deferred(); + const instance = $('#list').dxList({ + dataSource: new CustomStore({ load: () => loadResult.promise(), loadMode: 'processed' }) + }).dxList('instance'); + + assert.strictEqual(getA11yStatus(instance).text(), '', 'nothing is announced while data is loading'); + + loadResult.resolve(['Item 1', 'Item 2']); + + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(2), 'item count is announced once data is loaded'); + }); + + QUnit.test('announcement should be cleared while new data is loading (T1334729)', function(assert) { + const instance = $('#list').dxList({ items: ['Item 1', 'Item 2', 'Item 3'] }).dxList('instance'); + const loadResult = $.Deferred(); + + instance.option('dataSource', new CustomStore({ load: () => loadResult.promise(), loadMode: 'processed' })); + + assert.strictEqual(getA11yStatus(instance).text(), '', + 'neither the outdated item count nor noDataText is announced while data is loading'); + + loadResult.resolve(['Item 1']); + + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(1), 'item count is announced once data is loaded'); + }); + + [true, false].forEach(repaintChangesOnly => { + QUnit.test(`noDataText should be announced when dataSource is cleared at runtime and repaintChangesOnly=${repaintChangesOnly} (T1334729)`, function(assert) { + const instance = $('#list').dxList({ dataSource: ['Item 1'], repaintChangesOnly }).dxList('instance'); + const a11yStatusElement = getA11yStatus(instance).get(0); + + instance.option('dataSource', []); + + assert.strictEqual(getA11yStatus(instance).get(0), a11yStatusElement, + 'the live region is not re-created, otherwise screen readers do not announce its text'); + assert.strictEqual(getA11yStatus(instance).text(), messageLocalization.format('dxCollectionWidget-noDataText')); + }); + + QUnit.test(`item count should be announced when dataSource is set at runtime and repaintChangesOnly=${repaintChangesOnly} (T1334729)`, function(assert) { + const instance = $('#list').dxList({ dataSource: [], repaintChangesOnly }).dxList('instance'); + const a11yStatusElement = getA11yStatus(instance).get(0); + + instance.option('dataSource', ['Item 1', 'Item 2']); + + assert.strictEqual(getA11yStatus(instance).get(0), a11yStatusElement, + 'the live region is not re-created, otherwise screen readers do not announce its text'); + assert.strictEqual(getA11yStatus(instance).text(), getItemsAnnouncement(2)); + }); + }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/bingTests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/bingTests.js index 3a6a0a1acc7e..7df2f8f97e39 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/bingTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/bingTests.js @@ -2,17 +2,13 @@ import $ from 'jquery'; import errorsLogger from 'core/errors'; -import testing from './utils.js'; +import { LOCATIONS, MARKERS, ROUTES } from './utils.js'; import BingProvider from '__internal/ui/map/provider.dynamic.bing'; import ajaxMock from '../../../helpers/ajaxMock.js'; import errors from 'ui/widget/ui.errors'; import 'ui/map'; -const LOCATIONS = testing.LOCATIONS; -const MARKERS = testing.MARKERS; -const ROUTES = testing.ROUTES; - const prepareTestingBingProvider = function(abortDirectionsUpdate) { window.geocodedLocation = new Microsoft.Maps.Location(-1.12345, -1.12345); window.geocodedWithErrorLocation = new Microsoft.Maps.Location(); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/commonTests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/commonTests.js index 9d3f32f59dcb..754ac7c8141a 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/commonTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/commonTests.js @@ -1,12 +1,9 @@ import $ from 'jquery'; -import testing from './utils.js'; +import { MARKERS, ROUTES } from './utils.js'; import Map from 'ui/map'; import GoogleStaticProvider from '__internal/ui/map/provider.google_static'; import ajaxMock from '../../../helpers/ajaxMock.js'; -const MARKERS = testing.MARKERS; -const ROUTES = testing.ROUTES; - const MAP_CLASS = 'dx-map'; const MAP_CONTAINER_CLASS = 'dx-map-container'; const MAP_SHIELD_CLASS = 'dx-map-shield'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/googleStaticTests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/googleStaticTests.js index 1e8697ba5a38..8917af82b6fc 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/googleStaticTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/googleStaticTests.js @@ -1,14 +1,10 @@ import $ from 'jquery'; -import testing from './utils.js'; +import { LOCATIONS, MARKERS, ROUTES } from './utils.js'; import Map from 'ui/map'; import GoogleStaticProvider from '__internal/ui/map/provider.google_static'; import Color from 'color'; import ajaxMock from '../../../helpers/ajaxMock.js'; -const LOCATIONS = testing.LOCATIONS; -const MARKERS = testing.MARKERS; -const ROUTES = testing.ROUTES; - const MAP_CONTAINER_CLASS = 'dx-map-container'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/utils.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/utils.js index 428c4949e952..6ab1eaebda00 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/utils.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/mapParts/utils.js @@ -1,10 +1,10 @@ -exports.LOCATIONS = [ +export const LOCATIONS = [ 'Brooklyn Bridge,New York,NY', { lat: 40.537102, lng: -73.990318 }, [40.539102, -73.970318], '40.557102, -72.990318' ]; -exports.MARKERS = [ +export const MARKERS = [ { tooltip: { text: 'A', @@ -41,7 +41,7 @@ exports.MARKERS = [ } } ]; -exports.ROUTES = [ +export const ROUTES = [ { weight: 5, color: 'blue', diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/overlay.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/overlay.tests.js index 9b60cc53f2b4..2df7d757220a 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/overlay.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/overlay.tests.js @@ -10,7 +10,9 @@ import resizeCallbacks from 'core/utils/resize_callbacks'; import { isRenderer } from 'core/utils/type'; import { value as viewPort } from 'core/utils/view_port'; import eventsEngine from 'common/core/events/core/events_engine'; -import visibilityChange, { triggerHidingEvent, triggerShownEvent } from 'common/core/events/visibility_change'; +import * as visibilityChange from 'common/core/events/visibility_change'; +import { triggerHidingEvent, triggerShownEvent } from 'common/core/events/visibility_change'; +import { stubVisibilityEvent } from '../../helpers/visibilityChangeMock.js'; import $ from 'jquery'; import { hideCallback as hideTopOverlayCallback } from 'common/core/environment/hide_callback'; import errors from 'core/errors'; @@ -20,6 +22,7 @@ import * as zIndex from '__internal/ui/overlay/z_index'; import 'ui/scroll_view/ui.scrollable'; import selectors from '__internal/core/utils/m_selectors'; import swatch from '__internal/core/utils/swatch_container'; +import documentSizeCallbacks from '__internal/core/utils/document_size_callbacks'; import keyboardMock from '../../helpers/keyboardMock.js'; import pointerMock from '../../helpers/pointerMock.js'; import nativePointerMock from '../../helpers/nativePointerMock.js'; @@ -778,9 +781,9 @@ testModule('visibility', moduleConfig, () => { const triggerFunction = visibilityChange.triggerResizeEvent; try { - visibilityChange.triggerResizeEvent = () => { + visibilityChange.DEBUG_set_triggerResizeEvent(() => { assert.ok(true, 'event triggered'); - }; + }); const $overlay = $('#overlay').dxOverlay({ visible: true }); const overlay = $overlay.dxOverlay('instance'); @@ -789,7 +792,7 @@ testModule('visibility', moduleConfig, () => { overlay.show(); } finally { - visibilityChange.triggerResizeEvent = triggerFunction; + visibilityChange.DEBUG_set_triggerResizeEvent(triggerFunction); } }); @@ -3127,7 +3130,7 @@ testModule('API', moduleConfig, () => { const instance = $element.dxOverlay({ visible: true }).dxOverlay('instance'); - const resizeStub = sinon.stub(visibilityChange, 'triggerResizeEvent'); + const resizeStub = stubVisibilityEvent('triggerResizeEvent'); instance.repaint(); @@ -4469,3 +4472,81 @@ QUnit.module('Memory Leaks', { assert.strictEqual(positionController._$visualContainer, undefined, 'PositionController._$visualContainer is undefined after dispose'); }); }); + +QUnit.module('document size subscription', { + beforeEach: function() { + fx.off = true; + this.$element = $('#overlay'); + this.addSpy = sinon.spy(documentSizeCallbacks, 'add'); + this.removeSpy = sinon.spy(documentSizeCallbacks, 'remove'); + this.subscribedHandler = () => this.addSpy.lastCall.args[0]; + this.createOverlay = (options) => { + this.overlay = new Overlay(this.$element, options); + + return this.overlay; + }; + }, + afterEach: function() { + if(this.overlay) { + this.overlay.dispose(); + } + this.addSpy.restore(); + this.removeSpy.restore(); + fx.off = false; + } +}, () => { + QUnit.test('overlay should subscribe when shown and unsubscribe when hidden', function(assert) { + const overlay = this.createOverlay({ visible: true }); + + assert.ok(this.addSpy.calledOnce, 'subscribed while visible'); + + const handler = this.subscribedHandler(); + + overlay.hide(); + + assert.ok(this.removeSpy.calledWith(handler), 'unsubscribed when hidden'); + }); + + QUnit.test('overlay should unsubscribe on dispose', function(assert) { + const overlay = this.createOverlay({ visible: true }); + const handler = this.subscribedHandler(); + + overlay.dispose(); + this.overlay = null; + + assert.ok(this.removeSpy.calledWith(handler), 'unsubscribed on dispose'); + }); + + QUnit.test('geometry should be re-rendered when the visible area changes', function(assert) { + const overlay = this.createOverlay({ visible: true }); + const handler = this.subscribedHandler(); + const renderGeometrySpy = sinon.spy(overlay, '_renderGeometry'); + + handler(); + + assert.strictEqual(renderGeometrySpy.callCount, 1, 'geometry is re-rendered'); + }); + + QUnit.test('geometry should be re-rendered for an overlay placed against an element as well', function(assert) { + const overlay = this.createOverlay({ + visible: true, + visualContainer: $('#container') + }); + const handler = this.subscribedHandler(); + const renderGeometrySpy = sinon.spy(overlay, '_renderGeometry'); + + handler(); + + assert.strictEqual(renderGeometrySpy.callCount, 1, 'geometry is re-rendered'); + }); + + QUnit.test('a visible area change should be handled the same way as a window resize', function(assert) { + const overlay = this.createOverlay({ visible: true }); + const handler = this.subscribedHandler(); + const dimensionChangedSpy = sinon.spy(overlay, '_dimensionChanged'); + + handler(); + + assert.strictEqual(dimensionChangedSpy.callCount, 1, '_dimensionChanged is called'); + }); +}); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/popup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/popup.tests.js index 1fcb96b26eef..142a559496e8 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/popup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/popup.tests.js @@ -23,7 +23,7 @@ import windowUtils from '__internal/core/utils/m_window'; import uiErrors from 'ui/widget/ui.errors'; import themes from 'ui/themes'; import executeAsyncMock from '../../helpers/executeAsyncMock.js'; -import visibilityChangeUtils from 'common/core/events/visibility_change'; +import { spyVisibilityEvent } from '../../helpers/visibilityChangeMock.js'; import domAdapter from '__internal/core/m_dom_adapter'; import { TEMPLATE_WRAPPER_CLASS, @@ -33,6 +33,8 @@ import { BUTTON_CLASS } from '__internal/ui/button/button'; import 'fluent_blue_light.css!'; import 'ui/popup'; +import 'ui/tooltip'; +import 'ui/load_panel'; import 'ui/tab_panel'; import 'ui/scroll_view'; import 'ui/date_box'; @@ -1532,7 +1534,7 @@ QUnit.module('options changed callbacks', { QUnit.module('T934380, T1245421', { beforeEach() { - this.resizeEventSpy = sinon.spy(visibilityChangeUtils, 'triggerResizeEvent'); + this.resizeEventSpy = spyVisibilityEvent('triggerResizeEvent'); }, afterEach() { this.resizeEventSpy.restore(); @@ -1676,7 +1678,7 @@ QUnit.module('options changed callbacks', { QUnit.test('titleTemplate option change should trigger resize event for content correct geometry rendering', function(assert) { this.instance.option('visible', true); - const resizeEventSpy = sinon.spy(visibilityChangeUtils, 'triggerResizeEvent'); + const resizeEventSpy = spyVisibilityEvent('triggerResizeEvent'); try { this.instance.option({ @@ -1691,7 +1693,7 @@ QUnit.module('options changed callbacks', { QUnit.test('bottomTemplate option change should trigger resize event for content correct geometry rendering', function(assert) { this.instance.option('visible', true); - const resizeEventSpy = sinon.spy(visibilityChangeUtils, 'triggerResizeEvent'); + const resizeEventSpy = spyVisibilityEvent('triggerResizeEvent'); try { this.instance.option({ @@ -2774,6 +2776,41 @@ QUnit.module('keyboard navigation', { assert.strictEqual(this.popup.option('visible'), true, 'popup remains visible when _ignoreCloseOnChildEscape is true'); }); + + QUnit.test('should remain visible when child element presses escape and a tooltip is shown above it (T1334708)', function(assert) { + this.init({ dragEnabled: false }); + + const $input = $('').appendTo(this.popup.$content()); + const tooltip = $('
') + .appendTo('#qunit-fixture') + .dxTooltip({ target: $input, visible: true, animation: null }) + .dxTooltip('instance'); + + assert.strictEqual(tooltip.option('visible'), true, 'tooltip is shown'); + + keyboardMock($input).keyDown('esc'); + + assert.strictEqual(tooltip.option('visible'), false, 'tooltip is hidden'); + assert.strictEqual(this.popup.option('visible'), true, 'popup remains visible'); + + tooltip.dispose(); + }); + + QUnit.test('should be closed on child element escape key press when an overlay that ignores escape is shown above it', function(assert) { + this.init({ dragEnabled: false }); + + const $input = $('').appendTo(this.popup.$content()); + const loadPanel = $('
') + .appendTo('#qunit-fixture') + .dxLoadPanel({ visible: true, animation: null }) + .dxLoadPanel('instance'); + + keyboardMock($input).keyDown('esc'); + + assert.strictEqual(this.popup.option('visible'), false, 'popup is closed'); + + loadPanel.dispose(); + }); }); QUnit.module('rendering', { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/resizable.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/resizable.tests.js index 688151935f87..3125bdcfe254 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/resizable.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/resizable.tests.js @@ -1,5 +1,5 @@ import translator from 'common/core/animation/translator'; -import visibilityChange from 'common/core/events/visibility_change'; +import * as visibilityChange from 'common/core/events/visibility_change'; import $ from 'jquery'; import 'ui/resizable'; import pointerMock from '../../helpers/pointerMock.js'; @@ -1808,14 +1808,14 @@ QUnit.module('actions', () => { assert.expect(1); try { - visibilityChange.triggerResizeEvent = function() { + visibilityChange.DEBUG_set_triggerResizeEvent(function() { assert.ok(true, 'event triggered'); - }; + }); pointer.dragStart().drag(10, 0); } finally { - visibilityChange.triggerResizeEvent = triggerFunction; + visibilityChange.DEBUG_set_triggerResizeEvent(triggerFunction); } }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollView.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollView.tests.js index a68329e45dd3..b6d6c37f0586 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollView.tests.js @@ -3,6 +3,7 @@ import renderer from 'core/renderer'; import { noop } from 'core/utils/common'; import { getTranslateValues } from '__internal/ui/scroll_view/utils/get_translate_values'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import devices from '__internal/core/m_devices'; import eventsEngine from 'common/core/events/core/events_engine'; import themes from 'ui/themes'; @@ -55,7 +56,7 @@ devices.current('iPhone'); const moduleConfig = { beforeEach: function() { this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { callback(); }); return new Promise((resolve) => themes.initialized(resolve)); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.actions.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.actions.tests.js index c7f2f6363390..9d46c86faa86 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.actions.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.actions.tests.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import { noop } from 'core/utils/common'; import { getTranslateValues } from '__internal/ui/scroll_view/utils/get_translate_values'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import pointerMock from '../../../helpers/pointerMock.js'; import { @@ -33,7 +34,7 @@ const moduleConfig = { $('#qunit-fixture').html(markup); this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { callback(); }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.dynamic.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.dynamic.tests.js index 9979ac9fa302..4881f45ded04 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.dynamic.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.dynamic.tests.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import { getTranslateValues } from '__internal/ui/scroll_view/utils/get_translate_values'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import resizeCallbacks from 'core/utils/resize_callbacks'; import pointerMock from '../../../helpers/pointerMock.js'; @@ -33,7 +34,7 @@ const moduleConfig = { $('#qunit-fixture').html(markup); this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { callback(); }); }, @@ -176,9 +177,9 @@ QUnit.test('gesture prevent when scrollable is full and bounce enabled false', f QUnit.test('stop inertia on click', function(assert) { assert.expect(1); - animationFrame.requestAnimationFrame = function(callback) { + animationFrame.DEBUG_set_requestAnimationFrame(function(callback) { setTimeout(callback, 0); - }; + }); const moveDistance = -10; const moveDuration = 10; @@ -209,9 +210,9 @@ QUnit.test('stop inertia on click', function(assert) { QUnit.test('scrollbar is hidden on stop', function(assert) { assert.expect(1); - animationFrame.requestAnimationFrame = function(callback) { + animationFrame.DEBUG_set_requestAnimationFrame(function(callback) { setTimeout(callback, 0); - }; + }); const $scrollable = $('#scrollable').dxScrollable({ showScrollbar: 'onScroll', @@ -285,9 +286,9 @@ QUnit.test('bounce up', function(assert) { let scroll = 0; - animationFrame.requestAnimationFrame = function(callback) { + animationFrame.DEBUG_set_requestAnimationFrame(function(callback) { setTimeout(callback, 0); - }; + }); const $scrollable = $('#scrollable').dxScrollable({ useNative: false, @@ -313,9 +314,9 @@ QUnit.test('bounce up', function(assert) { QUnit.test('stop bounce on click', function(assert) { assert.expect(1); - animationFrame.requestAnimationFrame = function(callback) { + animationFrame.DEBUG_set_requestAnimationFrame(function(callback) { setTimeout(callback, 0); - }; + }); const moveDistance = -10; const moveDuration = 10; @@ -346,9 +347,9 @@ QUnit.test('stop bounce on click', function(assert) { QUnit.test('stop inertia bounce on after mouse up', function(assert) { assert.expect(1); - animationFrame.requestAnimationFrame = function(callback) { + animationFrame.DEBUG_set_requestAnimationFrame(function(callback) { setTimeout(callback, 0); - }; + }); const moveDistance = -10; const moveDuration = 10; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.main.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.main.tests.js index 4c3b07c73706..983cde84a48c 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.main.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.main.tests.js @@ -1,4 +1,5 @@ import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import { getTranslateValues } from '__internal/ui/scroll_view/utils/get_translate_values'; import devices from '__internal/core/m_devices'; import domUtils from '__internal/core/utils/m_dom'; @@ -42,7 +43,7 @@ const moduleConfig = { $('#qunit-fixture').html(markup); this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { callback(); }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.mouseWheel.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.mouseWheel.tests.js index 91e41942b078..d5e054bba654 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.mouseWheel.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.mouseWheel.tests.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import devices from '__internal/core/m_devices'; import pointerMock from '../../../helpers/pointerMock.js'; @@ -33,7 +34,7 @@ const moduleConfig = { $('#qunit-fixture').html(markup); this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { callback(); }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.rtl.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.rtl.tests.js index c1e98042c9d5..68bc0ca1cc08 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.rtl.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.rtl.tests.js @@ -1,4 +1,5 @@ import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import { triggerShownEvent } from 'common/core/events/visibility_change'; import $ from 'jquery'; import Scrollable from 'ui/scroll_view/ui.scrollable'; @@ -32,7 +33,7 @@ const moduleConfig = { $('#qunit-fixture').html(markup); this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { callback(); }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.scrollbar.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.scrollbar.tests.js index 2fd827da508f..7a90a2f17912 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.scrollbar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.scrollbar.tests.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import devices from '__internal/core/m_devices'; import Scrollbar from '__internal/ui/scroll_view/scrollbar'; import pointerMock from '../../../helpers/pointerMock.js'; @@ -80,7 +81,7 @@ const moduleConfig = { $('#qunit-fixture').html(markup); this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { callback(); }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.scrollingByThumb.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.scrollingByThumb.tests.js index 5d3a73a8fe38..e2b0547dab63 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.scrollingByThumb.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.scrollingByThumb.tests.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import { getTranslateValues } from '__internal/ui/scroll_view/utils/get_translate_values'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import Scrollbar from '__internal/ui/scroll_view/scrollbar'; import pointerMock from '../../../helpers/pointerMock.js'; @@ -34,7 +35,7 @@ const moduleConfig = { $('#qunit-fixture').html(markup); this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { callback(); }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.useNative.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.useNative.tests.js index 8744cc522027..43e425b55496 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.useNative.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/scrollableParts/scrollable.useNative.tests.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import { getTranslateValues } from '__internal/ui/scroll_view/utils/get_translate_values'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import pointerMock from '../../../helpers/pointerMock.js'; import { @@ -33,7 +34,7 @@ const moduleConfig = { $('#qunit-fixture').html(markup); this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { callback(); }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/sortable.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/sortable.tests.js index 8013b9b0b350..780a1d32e7c2 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/sortable.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/sortable.tests.js @@ -4,6 +4,7 @@ import 'ui/sortable'; import 'ui/scroll_view'; import fx from 'common/core/animation/fx'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import browser from 'core/utils/browser'; import translator from 'common/core/animation/translator'; import viewPort from 'core/utils/view_port'; @@ -2642,7 +2643,7 @@ function getModuleConfigForTestsWithScroll(elementSelector, scrollSelector) { beforeEach: function() { this.clock = sinon.useFakeTimers(); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake((callback) => { + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake((callback) => { return window.setTimeout(callback, 10); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/tabPanel.markup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/tabPanel.markup.tests.js index 738a7c0e6c4b..0c3a12da8732 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/tabPanel.markup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/tabPanel.markup.tests.js @@ -202,4 +202,96 @@ QUnit.module('aria accessibility', () => { instance.option('focusedElement', $element.find('.dx-item:eq(1)')); assert.equal($element.attr('aria-activedescendant'), undefined, 'aria-activedescendant does not exist after selection update'); }); + + QUnit.module('aria-label of tabs', { + beforeEach: function() { + this.createTabPanel = (options) => { + this.$element = $('
').appendTo('#qunit-fixture').dxTabPanel(options); + this.instance = this.$element.dxTabPanel('instance'); + }; + this.getTab = (index) => this.$element.find(`.${TABS_ITEM_CLASS}`).eq(index); + } + }, () => { + QUnit.test('tab with title and icon should not get aria-label', function(assert) { + this.createTabPanel({ items: [{ title: 'User', icon: 'user' }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined); + }); + + QUnit.test('string item should not get aria-label', function(assert) { + this.createTabPanel({ items: ['User'] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined); + }); + + QUnit.test('tab of the item without title and icon should not get aria-label', function(assert) { + this.createTabPanel({ items: [{ text: 'User content' }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined); + }); + + [ + { icon: 'user', expected: 'user', description: 'a dxIcon without a localized message' }, + { icon: 'close', expected: 'Close', description: 'a dxIcon with a localized message' }, + { icon: '/path/file.png', expected: 'file', description: 'a path to an image' }, + ].forEach(({ icon, expected, description }) => { + QUnit.test(`icon-only tab should get aria-label "${expected}" for ${description}`, function(assert) { + this.createTabPanel({ items: [{ icon }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), expected); + }); + }); + + QUnit.test('icon-only tab should get aria-label even if the item has content text or html', function(assert) { + this.createTabPanel({ + items: [ + { icon: 'user', text: 'User content' }, + { icon: 'find', html: 'Find content' }, + ], + }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), 'user', 'item with text'); + assert.strictEqual(this.getTab(1).attr('aria-label'), 'find', 'item with html'); + }); + + QUnit.test('icon-only tab with badge should get aria-label derived from icon', function(assert) { + this.createTabPanel({ items: [{ icon: 'user', badge: '5' }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), 'user'); + }); + + QUnit.test('aria-label should be derived from item data when a custom itemTitleTemplate is used', function(assert) { + this.createTabPanel({ + items: [{ icon: 'user' }, { icon: 'user', title: 'User' }], + itemTitleTemplate: (data) => ``, + }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), 'user', 'icon-only item'); + assert.strictEqual(this.getTab(1).attr('aria-label'), undefined, 'item with title'); + }); + + [true, false].forEach((repaintChangesOnly) => { + QUnit.test(`aria-label should follow item title and icon changes, repaintChangesOnly: ${repaintChangesOnly}`, function(assert) { + this.createTabPanel({ items: [{ title: 'User', icon: 'user' }], repaintChangesOnly }); + + this.instance.option('items[0].title', ''); + assert.strictEqual(this.getTab(0).attr('aria-label'), 'user', 'label appears when title is cleared'); + + this.instance.option('items[0].icon', 'close'); + assert.strictEqual(this.getTab(0).attr('aria-label'), 'Close', 'label follows the icon'); + + this.instance.option('items[0].title', 'Close'); + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined, 'label is removed when title is set'); + }); + + QUnit.test(`aria-label should be recalculated when items are replaced, repaintChangesOnly: ${repaintChangesOnly}`, function(assert) { + this.createTabPanel({ items: [{ icon: 'user' }], repaintChangesOnly }); + + this.instance.option('items', [{ title: 'User', icon: 'user' }, { icon: 'find' }]); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined, 'title and icon item'); + assert.strictEqual(this.getTab(1).attr('aria-label'), 'find', 'icon-only item'); + }); + }); + }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/tabs.markup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/tabs.markup.tests.js index cb451d655c54..05fa8dab32f4 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/tabs.markup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/tabs.markup.tests.js @@ -1,10 +1,12 @@ import $ from 'jquery'; import Tabs from 'ui/tabs'; +import { DataSource } from 'common/data/data_source/data_source'; import windowUtils from 'core/utils/window'; import ariaAccessibilityTestHelper from '../../helpers/ariaAccessibilityTestHelper.js'; import { TABS_CLASS, TABS_WRAPPER_CLASS, + TABS_ITEM_CLASS, TABS_ITEM_TEXT_SPAN_CLASS, TABS_ITEM_TEXT_SPAN_PSEUDO_CLASS } from '__internal/ui/tabs/tabs'; @@ -195,6 +197,144 @@ QUnit.module('Aria accessibility', { }); }); +QUnit.module('Aria label', { + beforeEach: function() { + this.createTabs = (options) => { + this.$element = $('#tabs').dxTabs(options); + this.instance = this.$element.dxTabs('instance'); + }; + this.getTab = (index) => this.$element.find(`.${TABS_ITEM_CLASS}`).eq(index); + } +}, () => { + QUnit.test('item with text and icon should not get aria-label', function(assert) { + this.createTabs({ items: [{ text: 'User', icon: 'user' }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined); + }); + + QUnit.test('item with html and icon should not get aria-label', function(assert) { + this.createTabs({ items: [{ html: 'User', icon: 'user' }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined); + }); + + QUnit.test('string item should not get aria-label', function(assert) { + this.createTabs({ items: ['User'] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined); + }); + + QUnit.test('item without text and icon should not get aria-label', function(assert) { + this.createTabs({ items: [{ badge: '5' }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined); + }); + + [ + { icon: 'user', expected: 'user', description: 'a dxIcon without a localized message' }, + { icon: 'close', expected: 'Close', description: 'a dxIcon with a localized message' }, + { icon: 'fa fa-home', expected: 'fa fa-home', description: 'a font icon' }, + { icon: '/path/file.png', expected: 'file', description: 'a path to an image' }, + { icon: 'Svg title', expected: 'Svg title', description: 'an svg with a title' }, + ].forEach(({ icon, expected, description }) => { + QUnit.test(`icon-only item should get aria-label "${expected}" for ${description}`, function(assert) { + this.createTabs({ items: [{ icon }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), expected); + }); + }); + + [ + { icon: 'data:image/png;base64,qwerty', description: 'a base64 image' }, + { icon: 'https://example.com/path/file.png', description: 'an image URL' }, + { icon: '', description: 'an svg without a title' }, + ].forEach(({ icon, description }) => { + QUnit.test(`icon-only item should not get aria-label for ${description}`, function(assert) { + this.createTabs({ items: [{ icon }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined); + }); + }); + + QUnit.test('icon-only item with badge should get aria-label derived from icon', function(assert) { + this.createTabs({ items: [{ icon: 'user', badge: '5' }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), 'user'); + }); + + QUnit.test('only icon-only items should get aria-label', function(assert) { + this.createTabs({ items: [{ text: 'Users' }, { icon: 'user' }, { text: 'Find', icon: 'find' }] }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined, 'text item'); + assert.strictEqual(this.getTab(1).attr('aria-label'), 'user', 'icon-only item'); + assert.strictEqual(this.getTab(2).attr('aria-label'), undefined, 'text and icon item'); + }); + + QUnit.test('aria-label should be derived from item data when a custom itemTemplate is used', function(assert) { + this.createTabs({ + items: [{ icon: 'user' }, { icon: 'user', text: 'User' }], + itemTemplate: (data) => ``, + }); + + assert.strictEqual(this.getTab(0).attr('aria-label'), 'user', 'icon-only item'); + assert.strictEqual(this.getTab(1).attr('aria-label'), undefined, 'item with text'); + }); + + [true, false].forEach((repaintChangesOnly) => { + QUnit.test(`aria-label should follow item text and icon changes, repaintChangesOnly: ${repaintChangesOnly}`, function(assert) { + this.createTabs({ items: [{ text: 'User', icon: 'user' }], repaintChangesOnly }); + + this.instance.option('items[0].text', ''); + assert.strictEqual(this.getTab(0).attr('aria-label'), 'user', 'label appears when text is cleared'); + + this.instance.option('items[0].icon', 'close'); + assert.strictEqual(this.getTab(0).attr('aria-label'), 'Close', 'label follows the icon'); + + this.instance.option('items[0].text', 'Close'); + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined, 'label is removed when text is set'); + }); + + QUnit.test(`aria-label should be recalculated when items are replaced, repaintChangesOnly: ${repaintChangesOnly}`, function(assert) { + this.createTabs({ items: [{ icon: 'user' }], repaintChangesOnly }); + + this.instance.option('items', [{ text: 'User', icon: 'user' }, { icon: 'find' }]); + + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined, 'text and icon item'); + assert.strictEqual(this.getTab(1).attr('aria-label'), 'find', 'icon-only item'); + }); + }); + + QUnit.test('aria-label should be recalculated when the _itemAriaLabelExpr option changes', function(assert) { + this.createTabs({ items: [{ icon: 'user', badge: 'New' }] }); + + this.instance.option('_itemAriaLabelExpr', (data) => data.badge); + assert.strictEqual(this.getTab(0).attr('aria-label'), 'New', 'label follows the new rule'); + + this.instance.option('_itemAriaLabelExpr', () => undefined); + assert.strictEqual(this.getTab(0).attr('aria-label'), undefined, 'label is removed when the rule returns nothing'); + }); + + QUnit.test('items inserted and updated through the data source push should get aria-label', function(assert) { + const data = [{ id: 0, text: 'User', icon: 'user' }]; + const dataSource = new DataSource({ + paginate: false, + pushAggregationTimeout: 0, + load: () => data, + key: 'id', + }); + + this.createTabs({ dataSource, repaintChangesOnly: true }); + + const store = dataSource.store(); + + store.push([{ type: 'insert', data: { id: 1, icon: 'find' } }]); + assert.strictEqual(this.getTab(1).attr('aria-label'), 'find', 'inserted icon-only item'); + + store.push([{ type: 'update', key: 0, data: { id: 0, text: '', icon: 'user' } }]); + assert.strictEqual(this.getTab(0).attr('aria-label'), 'user', 'updated item without text'); + }); +}); + QUnit.module('Default template', { beforeEach: function() { this.prepareItemTest = (data) => { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/toolbar.kbn.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/toolbar.kbn.tests.js index 564c6efcfa6b..d5fec4952418 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/toolbar.kbn.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/toolbar.kbn.tests.js @@ -29,7 +29,7 @@ import { DROP_DOWN_BUTTON_CLASS } from '__internal/ui/drop_down_button'; import { TEXTEDITOR_CLASS, TEXTEDITOR_INPUT_CLASS } from '__internal/ui/text_box/text_editor.base'; import { TEXTBOX_CLASS } from '__internal/ui/text_box/text_box'; -import { WIDGET_CLASS as NUMBERBOX_CLASS } from '__internal/ui/number_box/m_number_box.base'; +import { WIDGET_CLASS as NUMBERBOX_CLASS } from '__internal/ui/number_box/number_box.base'; import { SELECTBOX_CLASS } from '__internal/ui/select_box'; import { SWITCH_CLASS } from '__internal/ui/switch'; diff --git a/packages/devextreme/testing/tests/DevExpress.ui/themes.tests.js b/packages/devextreme/testing/tests/DevExpress.ui/themes.tests.js index 3e98fd22e59e..dff805a7fd8e 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui/themes.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui/themes.tests.js @@ -770,7 +770,7 @@ QUnit.module('initialized method', (hooks) => { test('initialized fires for ordinary link (init before link addition - should wait theme loading)', function(assert) { const done = assert.async(); - const url = ROOT_URL + 'packages/devextreme/testing' + '/helpers/themeMarker.css'; // WA for systemjs builder + const url = ROOT_URL + 'packages/devextreme/testing' + '/helpers/themeMarker.css'; const $frame = createFrame(); themes.setDefaultTimeout(30000); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.integration.tests.js index 56837f471a08..f3b976cd4dae 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.integration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.integration.tests.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import { Renderer } from '../../helpers/vizMocks.js'; import executeAsyncMock from '../../helpers/executeAsyncMock.js'; import rendererModule from 'viz/core/renderers/renderer_default'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import legendModule from 'viz/components/legend'; import titleModule from 'viz/core/title'; import dxChart from 'viz/chart'; @@ -12,7 +13,7 @@ import seriesFamilyModule from 'viz/core/series_family'; import { setupSeriesFamily } from '../../helpers/chartMocks.js'; import pointerMock from '../../helpers/pointerMock.js'; -const seriesFamilyNativeConstructor = { ...seriesFamilyModule }.SeriesFamily; +const seriesFamilyNativeConstructor = seriesFamilyModule.SeriesFamily; setupSeriesFamily(); QUnit.testStart(function() { const markup = @@ -2325,7 +2326,7 @@ QUnit.test('check horizontal alignment === center', function(assert) { QUnit.module('Auto hide point markers', $.extend({}, moduleSetup, { beforeEach: function() { moduleSetup.beforeEach.call(this); - seriesFamilyModule.SeriesFamily = seriesFamilyNativeConstructor; + seriesFamilyModule.DEBUG_set_SeriesFamily(seriesFamilyNativeConstructor); const dataSource = []; for(let i = 0; i < 500000; i += 250) { const y1 = Math.sin(i); @@ -3300,7 +3301,7 @@ QUnit.module('Option changing in onDrawn after zooming', { beforeEach: function() { this.legendShiftSpy = sinon.spy(legendModule.Legend.prototype, 'move'); this.titleShiftSpy = sinon.spy(titleModule.Title.prototype, 'move'); - sinon.stub(rendererModule, 'Renderer').callsFake(function() { + stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer').callsFake(function() { return new Renderer(); }); }, @@ -4901,7 +4902,7 @@ QUnit.test('Reset axes animation before adjusting position of vertical axes (fix QUnit.module('SeriesFamily', $.extend({}, moduleSetup, { beforeEach: function() { moduleSetup.beforeEach.call(this); - seriesFamilyModule.SeriesFamily = seriesFamilyNativeConstructor; + seriesFamilyModule.DEBUG_set_SeriesFamily(seriesFamilyNativeConstructor); } })); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part1.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part1.tests.js index 98903e0642df..dd81141d23df 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part1.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part1.tests.js @@ -19,7 +19,7 @@ import layoutManagerModule from 'viz/chart_components/layout_manager'; import trackerModule from 'viz/chart_components/tracker'; import dxChart from 'viz/chart'; import resizeCallbacks from 'core/utils/resize_callbacks'; -import vizUtils from 'viz/core/utils'; +import vizUtils from 'viz/core/utils_default'; import { MockSeries, seriesMockData, @@ -951,10 +951,10 @@ QUnit.module('isReady', $.extend({}, environment, { beforeEach: function() { const that = this; environment.beforeEach.apply(this, arguments); - rendererModule.Renderer = sinon.spy(function(parameters) { + rendererModule.DEBUG_set_Renderer(sinon.spy(function(parameters) { that.renderer = new Renderer(parameters); return that.renderer; - }); + })); }, afterEach: function() { environment.afterEach.apply(this, arguments); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part2.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part2.tests.js index 9cf6b8b5b947..8b834b77359a 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part2.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part2.tests.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import { environment } from './chartParts/commons.js'; -import vizUtils from 'viz/core/utils'; +import vizUtils from 'viz/core/utils_default'; import { MockSeries, commonMethodsForTests, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part4.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part4.tests.js index c8b435a2130d..f6a80c436a9e 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part4.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part4.tests.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import { environment, createChartInstance } from './chartParts/commons.js'; import vizUtils from 'viz/core/utils_default'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import { MockSeries, commonMethodsForTests, categories, seriesMockData } from '../../helpers/chartMocks.js'; $('
').appendTo('#qunit-fixture'); @@ -276,7 +277,7 @@ QUnit.test('Create clipRects. With financial series', function(assert) { }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; }); const stubSeries = new MockSeries(); @@ -338,7 +339,7 @@ QUnit.test('Create clipRects. With series with errorBars', function(assert) { }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; }); const stubSeries = new MockSeries({ @@ -409,7 +410,7 @@ QUnit.test('Create clipRects. With financial series. Rotated', function(assert) originalBottom: 70 }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; }); const stubSeries = new MockSeries(); @@ -471,7 +472,7 @@ QUnit.test('Create clipRects. With financial series. Two panes', function(assert originalBottom: 70 }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; panes[1].canvas = rect; }); @@ -554,7 +555,7 @@ QUnit.test('Create clipRects. With financial series. For second panes', function originalBottom: 70 }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; panes[1].canvas = rect; }); @@ -646,7 +647,7 @@ QUnit.test('Create clipRects. With financial series. Two panes. Rotated', functi originalBottom: 70 }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; panes[1].canvas = rect; }); @@ -730,7 +731,7 @@ QUnit.test('Create clipRects. With financial series. For second panes. Rotated', originalBottom: 70 }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; panes[1].canvas = rect; }); @@ -842,7 +843,7 @@ QUnit.test('Update clipRects. With financial series', function(assert) { originalBottom: 70 }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; }); @@ -958,7 +959,7 @@ QUnit.test('Update clipRects. With financial series. When start series does not originalBottom: 70 }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; }); @@ -1049,7 +1050,7 @@ QUnit.test('Create clipRects with visible pane borders. With financial series', originalBottom: 70 }; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; }); const stubSeries = new MockSeries(); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part7.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part7.tests.js index 4f947b7a2d25..473f593472fe 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part7.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.part7.tests.js @@ -10,11 +10,14 @@ import { createChartInstance, LabelCtor, } from './chartParts/commons.js'; -import { ERROR_MESSAGES as dxErrors } from 'viz/core/errors_warnings'; +import errorsWarnings from 'viz/core/errors_warnings'; import seriesModule from 'viz/series/base_series'; import dataValidatorModule from 'viz/components/data_validator'; import { MockSeries, categories, seriesMockData, MockTranslator } from '../../helpers/chartMocks.js'; import graphicObjects from '__internal/common/charts'; +import { stubSeam } from '../../helpers/moduleSeam.js'; + +const dxErrors = errorsWarnings.ERROR_MESSAGES; $('
').appendTo('#qunit-fixture'); @@ -69,7 +72,7 @@ $('
').appendTo('#qunit-fixture'); const stubSeries = new MockSeries({}); seriesMockData.series.push(stubSeries); - seriesModule.Series = function() { return { isUpdated: false }; }; + seriesModule.DEBUG_set_Series(function() { return { isUpdated: false }; }); const chart = this.createChart({ series: { @@ -84,7 +87,7 @@ $('
').appendTo('#qunit-fixture'); QUnit.test('dxChart with single series, series type is unknown in option series', function(assert) { const stubSeries = new MockSeries({}); seriesMockData.series.push(stubSeries); - seriesModule.Series = function() { return { isUpdated: false }; }; + seriesModule.DEBUG_set_Series(function() { return { isUpdated: false }; }); const chart = this.createChart({ series: { @@ -708,7 +711,7 @@ $('
').appendTo('#qunit-fixture'); environment.afterEach.call(this); }, mockValidateData: function() { - this.validateData = sinon.stub(dataValidatorModule, 'validateData').callsFake(function(data) { + this.validateData = stubSeam(dataValidatorModule, 'validateData', 'DEBUG_set_validateData').callsFake(function(data) { return { x: data || [] }; }); }, @@ -981,7 +984,7 @@ $('
').appendTo('#qunit-fixture'); const stubSeries = new MockSeries({}); seriesMockData.series.push(stubSeries); - seriesModule.Series = function() { return { isUpdated: false }; }; + seriesModule.DEBUG_set_Series(function() { return { isUpdated: false }; }); const chart = this.createChart({ series: { @@ -1003,7 +1006,7 @@ $('
').appendTo('#qunit-fixture'); const stubSeries = new MockSeries({}); seriesMockData.series.push(stubSeries); - seriesModule.Series = function() { return { isUpdated: false }; }; + seriesModule.DEBUG_set_Series(function() { return { isUpdated: false }; }); const chart = this.createChart({ series: { @@ -1025,7 +1028,7 @@ $('
').appendTo('#qunit-fixture'); const stubSeries = new MockSeries({}); seriesMockData.series.push(stubSeries); - seriesModule.Series = function() { return { isUpdated: false }; }; + seriesModule.DEBUG_set_Series(function() { return { isUpdated: false }; }); const chart = createChartInstance({ series: { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.tests.js index 4cb701dc1d9d..b4cb60bd99d5 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chart.tests.js @@ -5,6 +5,7 @@ import seriesModule from 'viz/series/base_series'; import pointModule from 'viz/series/points/base_point'; import axisModule from 'viz/axes/base_axis'; import titleModule from 'viz/core/title'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import dataValidatorModule from 'viz/components/data_validator'; import legendModule from 'viz/components/legend'; import rangeModule from 'viz/translators/range'; @@ -45,7 +46,6 @@ const environment = { layoutManagerModule.LayoutManager.restore(); seriesModule.Series.restore(); pointModule.Point.restore(); - this.Title.restore(); this.Legend.restore(); }, @@ -64,12 +64,12 @@ const environment = { return chart; }, _stubLayoutManager: function() { - this.LayoutManager = sinon.stub(layoutManagerModule, 'LayoutManager').callsFake(function() { + this.LayoutManager = stubSeam(layoutManagerModule, 'LayoutManager', 'DEBUG_set_LayoutManager').callsFake(function() { return new LayoutManager(arguments); }); }, _stubLegend: function() { - this.Legend = sinon.stub(legendModule, 'Legend').callsFake(function() { + this.Legend = stubSeam(legendModule, 'Legend', '_setLegend').callsFake(function() { const legend = new Legend(); legend.getTemplatesGroups = sinon.spy(function() { return []; @@ -81,12 +81,12 @@ const environment = { }); }, _stubTitle: function() { - this.Title = sinon.stub(titleModule, 'Title').callsFake(function() { + this.Title = stubSeam(titleModule, 'Title', 'DEBUG_set_title').callsFake(function() { return new ChartTitle(); }); }, _stubAxis: function() { - this.Axis = sinon.stub(axisModule, 'Axis').callsFake(function() { + this.Axis = stubSeam(axisModule, 'Axis', 'DEBUG_set_Axis').callsFake(function() { const axis = new Axis(); axis.updateOptions = sinon.spy(function(options) { axis.name = options.name; @@ -106,24 +106,24 @@ const environment = { }); }, _stubRange: function() { - sinon.stub(rangeModule, 'Range').callsFake(function(opt) { + stubSeam(rangeModule, 'Range', 'DEBUG_set_Range').callsFake(function(opt) { const range = new Range(); $.extend(range, opt); return range; }); }, _stubSeriesAndPoint: function() { - sinon.stub(seriesModule, 'Series').callsFake(function() { + stubSeam(seriesModule, 'Series', 'DEBUG_set_Series').callsFake(function() { const series = new Series(); return series; }); - sinon.stub(pointModule, 'Point').callsFake(function() { + stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { return new Point(); }); }, _stubValidateData: function() { - this.validateData = sinon.stub(dataValidatorModule, 'validateData'); + this.validateData = stubSeam(dataValidatorModule, 'validateData', 'DEBUG_set_validateData'); }, _restoreValidateData: function() { this.validateData.restore(); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chartAxisDrawing.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chartAxisDrawing.tests.js index bd3a9991fa33..cf1d64d104d5 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chartAxisDrawing.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chartAxisDrawing.tests.js @@ -14,10 +14,11 @@ import titleModule from 'viz/core/title'; import rendererModule from 'viz/core/renderers/renderer_default'; import multiAxesSynchronizer from '__internal/viz/chart_components/multi_axes_synchronizer'; import { Deferred } from 'core/utils/deferred'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const TitleOrig = titleModule.Title; -rendererModule.Renderer = sinon.stub(); +rendererModule.DEBUG_set_Renderer(sinon.stub()); const environment = { beforeEach: function() { @@ -33,7 +34,7 @@ const environment = { getMargins: sinon.stub() }; - this.scrollBarStub = sinon.stub(scrollBarModule, 'ScrollBar').callsFake(function(renderer, group) { + this.scrollBarStub = stubSeam(scrollBarModule, 'ScrollBar', 'DEBUG_set_ScrollBar').callsFake(function(renderer, group) { const scrollBar = new originalScrollBar(renderer, group); const originalUpdateSize = scrollBar.updateSize; @@ -49,7 +50,7 @@ const environment = { let axisIndex = 0; const originalAxis = axisModule.Axis; - this.axisStub = sinon.stub(axisModule, 'Axis').callsFake(function(renderingSettings) { + this.axisStub = stubSeam(axisModule, 'Axis', 'DEBUG_set_Axis').callsFake(function(renderingSettings) { const axis = new originalAxis(renderingSettings); for(const stubName in axesStubs[axisIndex]) { @@ -62,7 +63,7 @@ const environment = { this.title = new Title(); this.legend = new Legend(); - this.legendStub = sinon.stub(legendModule, 'Legend').callsFake(() =>{ + this.legendStub = stubSeam(legendModule, 'Legend', '_setLegend').callsFake(() =>{ this.legend.getTemplatesGroups = sinon.spy(function() { return []; }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chartParts/commons.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chartParts/commons.js index da30309955ac..efc788fec0e6 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chartParts/commons.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chartParts/commons.js @@ -32,6 +32,7 @@ import { } from '../../../helpers/chartMocks.js'; import exportModule from '__internal/viz/core/exportModule'; import { _test_prepareSegmentRectPoints } from 'viz/utils'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; const ThemeManager = stubClass(chartThemeManagerModule.ThemeManager); const LayoutManager = stubClass(layoutManagerModule.LayoutManager); @@ -78,7 +79,7 @@ const defaultCrosshairOptions = { }; // stubs -sinon.stub(rendererModule, 'Renderer').callsFake((parameters) => { +stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer').callsFake((parameters) => { return new Renderer(parameters); }); @@ -89,7 +90,7 @@ titleModule.DEBUG_set_title(sinon.spy(function(parameters) { return title; })); -sinon.stub(legendModule, 'Legend').callsFake((parameters) => { +stubSeam(legendModule, 'Legend', '_setLegend').callsFake((parameters) => { const legend = new Legend(parameters); legend.getActionCallback = sinon.spy(function(arg) { return arg; @@ -196,11 +197,11 @@ const environment = { that.layoutManager.layoutElements = sinon.spy(function() { arguments[2](); }); - this.StubLayoutManager = sinon.stub(layoutManagerModule, 'LayoutManager').callsFake(function() { + this.StubLayoutManager = stubSeam(layoutManagerModule, 'LayoutManager', 'DEBUG_set_LayoutManager').callsFake(function() { return that.layoutManager; }); - sinon.stub(scrollBarClassModule, 'ScrollBar').callsFake(function() { + stubSeam(scrollBarClassModule, 'ScrollBar', 'DEBUG_set_ScrollBar').callsFake(function() { const ScrollBar = stubClass(ScrollBarClass); const scrollBar = new ScrollBar(); scrollBar.stub('init').returns(scrollBar); @@ -227,11 +228,11 @@ const environment = { }, options.argumentAxis)); return createChartInstance(options, this.$container); }; - this.createThemeManager = sinon.stub(chartThemeManagerModule, 'ThemeManager').callsFake(function() { + this.createThemeManager = stubSeam(chartThemeManagerModule, 'ThemeManager', 'DEBUG_set_ThemeManager').callsFake(function() { return that.themeManager; }); const family = sinon.createStubInstance(seriesFamilyModule.SeriesFamily); - this.createSeriesFamily = sinon.stub(seriesFamilyModule, 'SeriesFamily').callsFake(function() { + this.createSeriesFamily = stubSeam(seriesFamilyModule, 'SeriesFamily', 'DEBUG_set_SeriesFamily').callsFake(function() { family.pane = 'default'; family.adjustSeriesDimensions = sinon.stub(); family.adjustSeriesValues = sinon.stub(); @@ -239,14 +240,14 @@ const environment = { return family; }); this.prepareSegmentRectPoints = _test_prepareSegmentRectPoints(function(x, y, w, h, borderOptions) { return { points: [x, y, w, h], pathType: borderOptions }; }); - this.createCrosshair = sinon.stub(crosshairModule, 'Crosshair').callsFake(function() { + this.createCrosshair = stubSeam(crosshairModule, 'Crosshair', 'DEBUG_set_Crosshair').callsFake(function() { return sinon.createStubInstance(Crosshair); }); tooltipModule.DEBUG_set_tooltip(sinon.spy(function(parameters) { return that.tooltip; })); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes, canvas) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes, canvas) { $.each(panes, function(_, item) { item.canvas = $.extend({}, canvas); }); @@ -287,7 +288,7 @@ const environment = { tooltipModule.DEBUG_set_tooltip(null); }, mockValidateData: function() { - this.validateData = sinon.stub(dataValidatorModule, 'validateData').callsFake(function(data, groupsData) { + this.validateData = stubSeam(dataValidatorModule, 'validateData', 'DEBUG_set_validateData').callsFake(function(data, groupsData) { const categories = []; if(data) { data.forEach(function(item) { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/chartSync.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/chartSync.tests.js index bb388fa2dc37..378810dedb48 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/chartSync.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/chartSync.tests.js @@ -19,6 +19,7 @@ import { CustomStore } from 'common/data/custom_store'; import chartThemeManagerModule from 'viz/components/chart_theme_manager'; import scrollBarModule from 'viz/chart_components/scroll_bar'; import dxChart from 'viz/chart'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import { MockSeries, MockPoint, @@ -37,9 +38,9 @@ const ScrollBar = scrollBarModule.ScrollBar; $('
').appendTo('#qunit-fixture'); setupSeriesFamily(); -rendererModule.Renderer = function(parameters) { +rendererModule.DEBUG_set_Renderer(function(parameters) { return new Renderer(parameters); -}; +}); const defaultCrosshairOptions = { horizontalLine: {}, @@ -63,7 +64,7 @@ exportModule.DEBUG_set_ExportMenu(sinon.spy(function() { return new ExportMenu(); })); -legendModule.Legend = sinon.spy(function(parameters) { +legendModule._setLegend(sinon.spy(function(parameters) { const legend = new Legend(parameters); legend.update = sinon.spy(function(params, settings) { legend.getPosition = sinon.stub().returns(settings.position); @@ -82,7 +83,7 @@ legendModule.Legend = sinon.spy(function(parameters) { return []; }); return legend; -}); +})); function getLegendStub() { return legendModule.Legend.lastCall.returnValue; @@ -118,13 +119,13 @@ const environment = { that.themeManager.getOptions.withArgs('resolveLabelOverlapping').returns(false); that.themeManager.getOptions.returns({}); - titleModule.Title = sinon.spy(function(parameters) { + titleModule.DEBUG_set_title(sinon.spy(function(parameters) { const title = new Title(parameters); title.getLayoutOptions = sinon.stub().returns({ verticalAlignment: that.titleVerticalAlignment || 'bottom' }); return title; - }); + })); that.createChart = function(options) { options = $.extend(true, { @@ -145,7 +146,7 @@ const environment = { return createChartInstance(options, this.$container); }; - this.createThemeManager = sinon.stub(chartThemeManagerModule, 'ThemeManager').callsFake(function() { + this.createThemeManager = stubSeam(chartThemeManagerModule, 'ThemeManager', 'DEBUG_set_ThemeManager').callsFake(function() { return that.themeManager; }); this.layoutManager = new LayoutManager(); @@ -153,7 +154,7 @@ const environment = { arguments[2] && arguments[2](); }); - sinon.stub(layoutManagerModule, 'LayoutManager').callsFake(function() { + stubSeam(layoutManagerModule, 'LayoutManager', 'DEBUG_set_LayoutManager').callsFake(function() { const layoutManager = new LayoutManager(); layoutManager .stub('needMoreSpaceForPanesCanvas') @@ -166,17 +167,17 @@ const environment = { return layoutManager; }); - sinon.stub(tooltipModule, 'Tooltip').callsFake(function(parameters) { + stubSeam(tooltipModule, 'Tooltip', 'DEBUG_set_tooltip').callsFake(function(parameters) { return new StubTooltip(parameters); }); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes, canvas) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes, canvas) { $.each(panes, function(_, item) { item.canvas = $.extend({}, canvas); }); }); - validateData = sinon.stub(dataValidatorModule, 'validateData').callsFake(function(data) { + validateData = stubSeam(dataValidatorModule, 'validateData', 'DEBUG_set_validateData').callsFake(function(data) { return { arg: data || [] }; }); }, @@ -219,7 +220,7 @@ const environment = { const spyLayoutManager = layoutManagerModule.LayoutManager; vizUtils.updatePanesCanvases.restore(); - sinon.stub(vizUtils, 'updatePanesCanvases').callsFake(function(panes) { + stubSeam(vizUtils, 'updatePanesCanvases', 'DEBUG_set_updatePanesCanvases').callsFake(function(panes) { panes[0].canvas = rect; }); @@ -674,7 +675,7 @@ const environment = { }); QUnit.test('draw chart when scrollBar is visible', function(assert) { - sinon.stub(scrollBarModule, 'ScrollBar').callsFake(function() { + stubSeam(scrollBarModule, 'ScrollBar', 'DEBUG_set_ScrollBar').callsFake(function() { const stub = sinon.createStubInstance(ScrollBar); stub.init.returns(stub); stub.update.returns(stub); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/charts.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/charts.tests.js index a2a66750d1db..55ee38c98908 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/charts.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/charts.tests.js @@ -1,11 +1,11 @@ import testGlobalExports from '../../helpers/publicModulesHelper.js'; import * as AdvancedChartModule from '__internal/viz/chart_components/advanced_chart'; import * as baseChartModule from '__internal/viz/chart_components/base_chart'; -import * as CrosshairModule from 'viz/chart_components/crosshair'; -import * as LayoutManagerModule from 'viz/chart_components/layout_manager'; +import CrosshairModule from 'viz/chart_components/crosshair'; +import LayoutManagerModule from 'viz/chart_components/layout_manager'; import multiAxesSynchronizer from '__internal/viz/chart_components/multi_axes_synchronizer'; -import * as ScrollBarModule from 'viz/chart_components/scroll_bar'; -import * as trackerModule from 'viz/chart_components/tracker'; +import ScrollBarModule from 'viz/chart_components/scroll_bar'; +import trackerModule from 'viz/chart_components/tracker'; import 'viz/chart'; import 'viz/pie_chart'; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/equalPieSize.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/equalPieSize.tests.js index 68bbc7ed15e0..0241bd8d3782 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/equalPieSize.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/equalPieSize.tests.js @@ -6,6 +6,7 @@ import layoutManagerModule from 'viz/chart_components/layout_manager'; import dxPieChart from 'viz/pie_chart'; import { MockSeries, MockPoint, insertMockFactory, restoreMockFactory, resetMockFactory, seriesMockData } from '../../helpers/chartMocks.js'; import { rendererModule, resetModules } from './chartParts/commons.js'; +import { stubSeam } from '../../helpers/moduleSeam.js'; function getContainer(hidden) { const div = $('
').appendTo('#qunit-fixture'); @@ -60,18 +61,18 @@ const dataSourceTemplate = [ { cat: 'Third', val: 300 } ]; -rendererModule.Renderer = sinon.spy(function(parameters) { +rendererModule.DEBUG_set_Renderer(sinon.spy(function(parameters) { return new Renderer(parameters); -}); +})); const environment = { beforeEach: function() { setupMocks.call(this); this.originalLayoutManagerCtor = layoutManagerModule.LayoutManager; - this.LayoutManager = sinon.stub(layoutManagerModule, 'LayoutManager'); + this.LayoutManager = stubSeam(layoutManagerModule, 'LayoutManager', 'DEBUG_set_LayoutManager'); - this.validateData = sinon.stub(dataValidatorModule, 'validateData').callsFake(function(data) { + this.validateData = stubSeam(dataValidatorModule, 'validateData', 'DEBUG_set_validateData').callsFake(function(data) { return { arg: data || [] }; }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/pieChart.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/pieChart.tests.js index 5678d675a517..a32866de372b 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/pieChart.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/pieChart.tests.js @@ -19,7 +19,7 @@ import { import exportModule from '__internal/viz/core/exportModule'; import seriesModule from 'viz/series/base_series'; import { BaseChart } from '__internal/viz/chart_components/base_chart'; -import * as labelModule from 'viz/series/points/label'; +import labelModule from 'viz/series/points/label'; import dataValidatorModule from 'viz/components/data_validator'; import translator1DModule from 'viz/translators/translator1d'; import { CustomStore } from 'common/data/custom_store'; @@ -35,6 +35,7 @@ import TemplateManagerModule from '__internal/core/m_template_manager'; import graphicObjects from '__internal/common/charts'; import eventsEngine from 'common/core/events/core/events_engine'; import devices from '__internal/core/m_devices'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const LabelCtor = new ObjectPool(labelModule.Label); @@ -46,9 +47,9 @@ const dataSourceTemplate = [ { cat: 'Third', val: 300 } ]; -rendererModule.Renderer = sinon.spy(function(parameters) { +rendererModule.DEBUG_set_Renderer(sinon.spy(function(parameters) { return new Renderer(parameters); -}); +})); function createPieChart(options) { this.container = $('#chartContainer'); @@ -168,14 +169,14 @@ const environment = { that.layoutManager.needMoreSpaceForPanesCanvas.returns(true); that.layoutManager.applyPieChartSeriesLayout.returns({ radiusInner: 0, radiusOuter: 300, centerX: 100, centerY: 200 }); - that.LayoutManager = sinon.stub(layoutManagerModule, 'LayoutManager').callsFake(function() { + that.LayoutManager = stubSeam(layoutManagerModule, 'LayoutManager', 'DEBUG_set_LayoutManager').callsFake(function() { return that.layoutManager; }); - this.createThemeManager = sinon.stub(chartThemeManagerModule, 'ThemeManager').callsFake(function() { + this.createThemeManager = stubSeam(chartThemeManagerModule, 'ThemeManager', 'DEBUG_set_ThemeManager').callsFake(function() { return that.themeManager; }); - this.validateData = sinon.stub(dataValidatorModule, 'validateData').callsFake(function(data) { + this.validateData = stubSeam(dataValidatorModule, 'validateData', 'DEBUG_set_validateData').callsFake(function(data) { return { arg: data || [] }; }); }, @@ -717,7 +718,7 @@ const overlappingEnvironment = $.extend({}, environment, { QUnit.test('dxChart with single series, series type is unknown', function(assert) { const stubSeries = new MockSeries({}); seriesMockData.series.push(stubSeries); - seriesModule.Series = function() { return { isUpdated: false }; }; + seriesModule.DEBUG_set_Series(function() { return { isUpdated: false }; }); const chart = this.createPieChart({ dataSource: dataSourceTemplate, @@ -832,7 +833,7 @@ const overlappingEnvironment = $.extend({}, environment, { environment.beforeEach.apply(this, arguments); const translatorClass = new stubClass(translator1DModule.Translator1D); - sinon.stub(translator1DModule, 'Translator1D').callsFake(function() { + stubSeam(translator1DModule, 'Translator1D', 'DEBUG_set_Translator1D').callsFake(function() { const translator = new translatorClass(); translator.stub('setDomain').returnsThis(); translator.stub('setCodomain').returnsThis(); @@ -891,7 +892,7 @@ const overlappingEnvironment = $.extend({}, environment, { const translatorClass = new stubClass(translator1DModule.Translator1D); - sinon.stub(translator1DModule, 'Translator1D').callsFake(function() { + stubSeam(translator1DModule, 'Translator1D', 'DEBUG_set_Translator1D').callsFake(function() { const translator = new translatorClass(); translator.stub('setDomain').returnsThis(); translator.stub('setCodomain').returnsThis(); @@ -1073,7 +1074,7 @@ const overlappingEnvironment = $.extend({}, environment, { this.mockSeries2 = new MockSeries({ argumentField: 'arg' }); const translatorClass = new stubClass(translator1DModule.Translator1D); - sinon.stub(translator1DModule, 'Translator1D').callsFake(function() { + stubSeam(translator1DModule, 'Translator1D', 'DEBUG_set_Translator1D').callsFake(function() { const translator = new translatorClass(); translator.stub('setDomain').returnsThis(); translator.stub('setCodomain').returnsThis(); @@ -2046,7 +2047,7 @@ const overlappingEnvironment = $.extend({}, environment, { const translatorClass = new stubClass(translator1DModule.Translator1D); - sinon.stub(translator1DModule, 'Translator1D').callsFake(function() { + stubSeam(translator1DModule, 'Translator1D', 'DEBUG_set_Translator1D').callsFake(function() { const translator = new translatorClass(); translator.stub('setDomain').returnsThis(); translator.stub('setCodomain').returnsThis(); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/polarChart.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/polarChart.tests.js index 781beda4acb4..f66a6fc6528f 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/polarChart.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/polarChart.tests.js @@ -5,7 +5,7 @@ import { Renderer, Legend, ExportMenu } from '../../helpers/vizMocks.js'; import trackerModule from 'viz/chart_components/tracker'; import chartThemeManagerModule from 'viz/components/chart_theme_manager'; import legendModule from 'viz/components/legend'; -import seriesModule, { Series } from 'viz/series/base_series'; +import seriesModule from 'viz/series/base_series'; import seriesFamilyModule from 'viz/core/series_family'; import axisModule from 'viz/axes/base_axis'; import dxPolarChart from 'viz/polar_chart'; @@ -17,6 +17,9 @@ import layoutManagerModule from 'viz/chart_components/layout_manager'; import exportModule from '__internal/viz/core/exportModule'; import 'viz/chart'; +import { stubSeam } from '../../helpers/moduleSeam.js'; + +const { Series } = seriesModule; const stubTooltip = sinon.createStubInstance(tooltipModule.Tooltip); const stubRange = sinon.createStubInstance(rangeModule.Range); @@ -30,7 +33,7 @@ QUnit.testStart(function() { chartContainer.appendTo('#qunit-fixture'); }); -legendModule.Legend = sinon.spy(function(parameters) { +legendModule._setLegend(sinon.spy(function(parameters) { const legend = new Legend(parameters); legend.getActionCallback = sinon.spy(function(arg) { return arg; @@ -42,7 +45,7 @@ legendModule.Legend = sinon.spy(function(parameters) { return []; }); return legend; -}); +})); function stubExport() { const exportMenuInstance = new ExportMenu(); @@ -150,7 +153,7 @@ const environment = { that.$container = $('#chartContainer'); - this.createThemeManager = sinon.stub(chartThemeManagerModule, 'ThemeManager').callsFake(function() { + this.createThemeManager = stubSeam(chartThemeManagerModule, 'ThemeManager', 'DEBUG_set_ThemeManager').callsFake(function() { resetStub(stubThemeManager); that.themeManager = stubThemeManager; return stubThemeManager; @@ -167,25 +170,25 @@ const environment = { }; }; - that.createRenderer = sinon.stub(rendererModule, 'Renderer').callsFake(function() { + that.createRenderer = stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer').callsFake(function() { const stubRenderer = new Renderer(); stubRenderer.clipCircle = that.clipFunc; stubRenderer.clipRect = that.clipFunc; return stubRenderer; }); - that.createTooltip = sinon.stub(tooltipModule, 'Tooltip').callsFake(function() { + that.createTooltip = stubSeam(tooltipModule, 'Tooltip', 'DEBUG_set_tooltip').callsFake(function() { resetStub(stubTooltip); return stubTooltip; }); - that.range = sinon.stub(rangeModule, 'Range').callsFake(function() { + that.range = stubSeam(rangeModule, 'Range', 'DEBUG_set_Range').callsFake(function() { resetStub(stubRange); stubRange.addRange = function() { this.min = 2; }; return stubRange; }); - that.createSeries = sinon.stub(seriesModule, 'Series').callsFake(function(settings, seriesTheme) { + that.createSeries = stubSeam(seriesModule, 'Series', 'DEBUG_set_Series').callsFake(function(settings, seriesTheme) { resetStub(stubSeries[seriesIndex]); stubSeries[seriesIndex].getValueAxis.returns(settings.valueAxis); if(seriesTheme.valueErrorBar) { @@ -194,7 +197,7 @@ const environment = { return $.extend(true, stubSeries[seriesIndex++], seriesTheme); }); - that.createAxis = sinon.stub(axisModule, 'Axis').callsFake(function() { + that.createAxis = stubSeam(axisModule, 'Axis', 'DEBUG_set_Axis').callsFake(function() { resetStub(stubAxes[axesIndex]); stubAxes[axesIndex].getMargins.returns({ @@ -207,12 +210,12 @@ const environment = { return stubAxes[axesIndex++]; }); - that.createSeriesFamily = sinon.stub(seriesFamilyModule, 'SeriesFamily').callsFake(function() { + that.createSeriesFamily = stubSeam(seriesFamilyModule, 'SeriesFamily', 'DEBUG_set_SeriesFamily').callsFake(function() { resetStub(stubSeriesFamily); return stubSeriesFamily; }); - that.createLayoutManager = sinon.stub(layoutManagerModule, 'LayoutManager').callsFake(function() { + that.createLayoutManager = stubSeam(layoutManagerModule, 'LayoutManager', 'DEBUG_set_LayoutManager').callsFake(function() { resetStub(stubLayoutManager); return stubLayoutManager; }); @@ -343,7 +346,7 @@ QUnit.test('create series with panes', function(assert) { }); QUnit.test('give series in groups to data validator', function(assert) { - const validateData = sinon.stub(dataValidatorModule, 'validateData').callsFake(function(data) { + const validateData = stubSeam(dataValidatorModule, 'validateData', 'DEBUG_set_validateData').callsFake(function(data) { return data || []; }); try { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.charts/scrollBar.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.charts/scrollBar.tests.js index 5233d0a2ba6e..70b8554fbb65 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.charts/scrollBar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.charts/scrollBar.tests.js @@ -8,6 +8,7 @@ import { ScrollBar } from 'viz/chart_components/scroll_bar'; import translator2DModule from 'viz/translators/translator2d'; import pointerMock from '../../helpers/pointerMock.js'; import dragEvents from 'common/core/events/drag'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const Translator = stubClass(translator2DModule.Translator2D); @@ -34,7 +35,7 @@ const environment = { this.group = this.renderer.g(); - sinon.stub(translator2DModule, 'Translator2D').callsFake(function() { + stubSeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D').callsFake(function() { const stub = new Translator(); stub.getScale = sinon.stub().returns(1); stub.stub('getCanvasVisibleArea'); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/areaSeries.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/areaSeries.tests.js index 7c44e7490fee..d0347e0f12f2 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/areaSeries.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/areaSeries.tests.js @@ -4,6 +4,7 @@ import { } from '../../helpers/vizMocks.js'; import { noop } from 'core/utils/common'; import vizUtils from 'viz/core/utils_default'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import pointModule from 'viz/series/points/base_point'; import SeriesModule from 'viz/series/base_series'; import { insertMockFactory, MockAxis, restoreMockFactory } from '../../helpers/chartMocks.js'; @@ -99,7 +100,7 @@ const environmentWithSinonStubPoint = { beforeEach: function() { environment.beforeEach.call(this); let mockPointIndex = 0; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function(series, data) { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function(series, data) { const stub = mockPoints[mockPointIndex++]; stub.argument = 1; stub.angle = -data.argument; @@ -3059,7 +3060,7 @@ function setDiscreteType(series) { QUnit.module('Polar Series', { beforeEach: function() { environmentWithSinonStubPoint.beforeEach.call(this); - sinon.stub(vizUtils, 'getCosAndSin'); + stubSeam(vizUtils, 'getCosAndSin', 'DEBUG_set_getCosAndSin'); vizUtils.getCosAndSin.returns({ cos: 1, sin: -1 }); }, afterEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/barPoint.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/barPoint.tests.js index 5298fb48ac87..0c9fc618952f 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/barPoint.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/barPoint.tests.js @@ -76,14 +76,15 @@ const environment = { }; this.label = sinon.createStubInstance(labelModule.Label); - this.labelFactory = labelModule.Label = sinon.spy(function() { + this.labelFactory = sinon.spy(function() { return that.label; }); + labelModule.DEBUG_set_Label(this.labelFactory); this.label.getLayoutOptions.returns(this.options.label); this.label.getBoundingRect.returns({ height: 10, width: 20 }); }, afterEach: function() { - labelModule.Label = originalLabel; + labelModule.DEBUG_set_Label(originalLabel); } }; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/barSeries.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/barSeries.tests.js index e2cf41d7b953..a1c67c6d1460 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/barSeries.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/barSeries.tests.js @@ -6,6 +6,7 @@ import Color from 'color'; import pointModule from 'viz/series/points/base_point'; import SeriesModule from 'viz/series/base_series'; import { MockAxis, MockTranslator } from '../../helpers/chartMocks.js'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const Series = SeriesModule.Series; @@ -67,7 +68,7 @@ const environment = { this.renderer = new Renderer(); this.seriesGroup = this.renderer.g(); this.data = [{ arg: 1, val: 10 }, { arg: 2, val: 20 }, { arg: 3, val: 30 }, { arg: 4, val: 40 }]; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function() { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { const stub = mockPoints[mockPointIndex++]; stub.argument = 1; stub.getMarkerCoords.returns({ x: 1, y: 2, width: 20, height: 10 }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/basePoint.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/basePoint.tests.js index 986b07c4157f..20e5543b98da 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/basePoint.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/basePoint.tests.js @@ -6,6 +6,7 @@ import pointModule from 'viz/series/points/base_point'; import labelModule from 'viz/series/points/label'; import SeriesModule from 'viz/series/base_series'; import { MockTranslator } from '../../helpers/chartMocks.js'; +import { stubSeam, spySeam } from '../../helpers/moduleSeam.js'; const Series = SeriesModule.Series; @@ -540,7 +541,7 @@ QUnit.module('Draw', { attributes: { r: 6 }, symbol: 'circle' }; - this.sinonFactory = sinon.stub(labelModule, 'Label').callsFake(function() { + this.sinonFactory = stubSeam(labelModule, 'Label', 'DEBUG_set_Label').callsFake(function() { return sinon.createStubInstance(originalLabel); }); this.series = { @@ -606,7 +607,7 @@ QUnit.module('Label', { this.renderer = new Renderer(); this.group = this.renderer.g(); - this.sinonFactory = sinon.stub(labelModule, 'Label').callsFake(function() { + this.sinonFactory = stubSeam(labelModule, 'Label', 'DEBUG_set_Label').callsFake(function() { return sinon.createStubInstance(originalLabel); }); this.labelsGroup = {}; @@ -1244,7 +1245,7 @@ QUnit.module('Dispose', { attributes: { r: 6 }, symbol: 'circle' }; - this.sinonFactory = sinon.stub(labelModule, 'Label').callsFake(function() { + this.sinonFactory = stubSeam(labelModule, 'Label', 'DEBUG_set_Label').callsFake(function() { return sinon.createStubInstance(originalLabel); }); this.series = { @@ -1355,7 +1356,7 @@ QUnit.module('API', { _argumentChecker: function() { return true; }, _valueChecker: function() { return true; } }; - sinon.spy(labelModule, 'Label'); + spySeam(labelModule, 'Label', 'DEBUG_set_Label'); }, afterEach: function() { labelModule.Label.restore(); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/baseSeries.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/baseSeries.tests.js index 65621c4d4211..90892b53c9ca 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/baseSeries.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/baseSeries.tests.js @@ -8,6 +8,7 @@ import typeUtils from 'core/utils/type'; import pointModule from 'viz/series/points/base_point'; import SeriesModule from 'viz/series/base_series'; import { insertMockFactory, MockTranslator, MockAxis, restoreMockFactory } from '../../helpers/chartMocks.js'; +import { stubSeam, spySeam } from '../../helpers/moduleSeam.js'; const Series = SeriesModule.Series; const mixins = SeriesModule.mixins; @@ -67,12 +68,12 @@ const environment = { this.renderer = new Renderer(); _this.realCreatePoint = pointModule.Point; - pointModule.Point = function() { + pointModule.DEBUG_set_Point(function() { _this.pointsCreatingCount++; const point = _this.realCreatePoint.apply(null, arguments); point.setInvisibility = sinon.stub(); return point; - }; + }); chartSeriesNS['mocktype'] = { stylesHistory: [], @@ -160,7 +161,7 @@ const environment = { mixins.pie['mocktype'] = mixins.chart['mocktype']; }, afterEach: function() { - pointModule.Point = this.realCreatePoint; + pointModule.DEBUG_set_Point(this.realCreatePoint); restoreMockFactory(); } }; @@ -178,7 +179,7 @@ const environmentWithSinonStubPoint = { beforeEach: function() { environment.beforeEach.call(this); let mockPointIndex = 0; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function(series, data) { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function(series, data) { const stub = mockPoints[mockPointIndex++]; stub.series = series; stub.argument = data.argument || 1; @@ -794,7 +795,7 @@ QUnit.test('Pass errorBars options to point (on update). ErrorBars are visible', QUnit.module('tag to points', { beforeEach: function() { - this.spy = sinon.spy(pointModule, 'Point'); + this.spy = spySeam(pointModule, 'Point', 'DEBUG_set_Point'); this.data = [{ arg: 1, val: 1 }, { arg: 2, val: 2 }, { arg: 3, val: 3 }]; }, afterEach: function() { @@ -2183,7 +2184,7 @@ QUnit.test('Points count > maxLabelCount', function(assert) { QUnit.module('Series states - excludePointsMode', { beforeEach: function() { environment.beforeEach.call(this); - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function() { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { const stub = sinon.createStubInstance(originalPoint); stub.argument = 1; stub.hasValue.returns(true); @@ -2551,7 +2552,7 @@ QUnit.test('setHoverState after Selected State in includePointsMode', function(a QUnit.module('Series states - nearestPoint Mode', { beforeEach: function() { environment.beforeEach.call(this); - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function(_, data) { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function(_, data) { const stub = sinon.createStubInstance(originalPoint); stub.argument = 1; @@ -2910,7 +2911,7 @@ QUnit.test('reset nearest point on select', function(assert) { QUnit.module('Series states - includePointsMode', { beforeEach: function() { environment.beforeEach.call(this); - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function() { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { const stub = sinon.createStubInstance(originalPoint); stub.argument = 1; stub.hasValue.returns(true); @@ -3269,7 +3270,7 @@ QUnit.test('clear selection hovered', function(assert) { QUnit.module('Series states - none mode', { beforeEach: function() { environment.beforeEach.call(this); - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function() { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { const stub = sinon.createStubInstance(originalPoint); stub.argument = 1; stub.hasValue.returns(true); @@ -4877,7 +4878,7 @@ QUnit.module('Legend states', { beforeEach: function() { this.legendCallback = sinon.stub(); environment.beforeEach.call(this); - sinon.stub(pointModule, 'Point').callsFake(function(series) { + stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function(series) { const point = new Point(); point.argument = 1; point.series = series; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/bubblePoint.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/bubblePoint.tests.js index 85ff6594fec9..43f0abdb54b5 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/bubblePoint.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/bubblePoint.tests.js @@ -344,14 +344,15 @@ QUnit.module('Draw Label', { _valueChecker: function() { return true; } }; this.label = sinon.createStubInstance(labelModule.Label); - this.labelFactory = labelModule.Label = sinon.spy(function() { + this.labelFactory = sinon.spy(function() { return that.label; }); + labelModule.DEBUG_set_Label(this.labelFactory); this.label.getLayoutOptions.returns(this.options.label); this.label.getBoundingRect.returns({ height: 10, width: 20 }); }, afterEach: function() { - labelModule.Label = originalLabel; + labelModule.DEBUG_set_Label(originalLabel); } }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/bubbleSeries.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/bubbleSeries.tests.js index 7fee08d47f4d..4b608fd0ae54 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/bubbleSeries.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/bubbleSeries.tests.js @@ -6,6 +6,7 @@ import pointModule from 'viz/series/points/base_point'; import SeriesModule from 'viz/series/base_series'; const Series = SeriesModule.Series; import { MockAxis, insertMockFactory, restoreMockFactory } from '../../helpers/chartMocks.js'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const createSeries = function(options, renderSettings) { renderSettings = renderSettings || {}; @@ -66,7 +67,7 @@ const environment = { this.seriesGroup = this.renderer.g(); this.data = [{ arg: 1, val: 10, size: 1 }, { arg: 2, val: 20, size: 1 }, { arg: 3, val: 30, size: 1 }, { arg: 4, val: 40, size: 1 }]; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function() { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { const stub = mockPoints[mockPointIndex++]; stub.argument = 1; stub.hasValue.returns(true); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/financialPoint.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/financialPoint.tests.js index b4d7fac27d4a..1ec80f003e77 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/financialPoint.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/financialPoint.tests.js @@ -6,6 +6,7 @@ import pointModule from 'viz/series/points/base_point'; import labelModule from 'viz/series/points/label'; import { MockTranslator, MockSeries } from '../../helpers/chartMocks.js'; import tooltipModule from 'viz/core/tooltip'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const originalLabel = labelModule.Label; @@ -1761,7 +1762,7 @@ QUnit.module('Draw label', { failOnWrongData: true }) }; - this.sinonFactory = sinon.stub(labelModule, 'Label').callsFake(function() { + this.sinonFactory = stubSeam(labelModule, 'Label', 'DEBUG_set_Label').callsFake(function() { const label = sinon.createStubInstance(originalLabel); label.getLayoutOptions.returns(that.options.label); label.getBoundingRect.returns({ height: 10, width: 20 }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/financialSeries.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/financialSeries.tests.js index 8000b8eeaadc..f4276f83161d 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/financialSeries.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/financialSeries.tests.js @@ -5,6 +5,7 @@ import { import pointModule from 'viz/series/points/base_point'; import SeriesModule from 'viz/series/base_series'; import { MockAxis, insertMockFactory, restoreMockFactory } from '../../helpers/chartMocks.js'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const Series = SeriesModule.Series; @@ -29,7 +30,7 @@ const environment = { this.data = [ { date: 'arg1', high: 'high1', low: 'low1', open: 'open1', close: 'close1' } ]; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function() { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { const stub = mockPoints[mockPointIndex++]; stub.argument = 1; stub.hasValue.returns(true); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/lineSeries.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/lineSeries.tests.js index e1b096619b24..82656c4556e3 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/lineSeries.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/lineSeries.tests.js @@ -4,6 +4,7 @@ import { } from '../../helpers/vizMocks.js'; import { noop } from 'core/utils/common'; import vizUtils from 'viz/core/utils_default'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import pointModule from 'viz/series/points/base_point'; import SeriesModule from 'viz/series/base_series'; import { @@ -78,7 +79,7 @@ const environmentWithSinonStubPoint = { environment.beforeEach.call(this); let mockPointIndex = 0; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function(series, data) { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function(series, data) { const stub = mockPoints[mockPointIndex++]; stub.argument = 1; stub.angle = -data.argument; @@ -1350,7 +1351,7 @@ function setDiscreteType(series) { this.options = { type: 'line' }; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function() { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { const stub = sinon.createStubInstance(originalPoint); stub.argument = 1; stub.hasValue.returns(true); @@ -2277,7 +2278,7 @@ function setDiscreteType(series) { QUnit.module('polar Series', { beforeEach: function() { environmentWithSinonStubPoint.beforeEach.call(this); - sinon.stub(vizUtils, 'getCosAndSin'); + stubSeam(vizUtils, 'getCosAndSin', 'DEBUG_set_getCosAndSin'); vizUtils.getCosAndSin.returns({ cos: 1, sin: -1 }); }, afterEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/piePoint.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/piePoint.tests.js index 31b8f88887f6..960879cfd04d 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/piePoint.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/piePoint.tests.js @@ -1348,12 +1348,13 @@ QUnit.module('Connector', { this.label = sinon.createStubInstance(labelModule.Label); this.label.getLayoutOptions.returns(this.options.label); this.label.getBoundingRect.returns({ height: 10, width: 20 }); - this.labelFactory = labelModule.Label = sinon.spy(function() { + this.labelFactory = sinon.spy(function() { return that.label; }); + labelModule.DEBUG_set_Label(this.labelFactory); }, afterEach: function() { - labelModule.Label = originalLabel; + labelModule.DEBUG_set_Label(originalLabel); } }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/pieSeries.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/pieSeries.tests.js index 8fc75f54cf79..585a858f3a20 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/pieSeries.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/pieSeries.tests.js @@ -6,6 +6,7 @@ import { noop } from 'core/utils/common'; import pointModule from 'viz/series/points/base_point'; import labelModule from 'viz/series/points/label'; import SeriesModule from 'viz/series/base_series'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const Series = SeriesModule.Series; @@ -78,7 +79,7 @@ const environment = { beforeEach: function() { this.data = [{ arg: 1, val: 10 }, { arg: 2, val: 20 }, { arg: 3, val: 30 }, { arg: 4, val: 40 }]; let mockPointIndex = 0; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function(series, data) { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function(series, data) { const stub = mockPoints[mockPointIndex++]; stub.argument = data.argument || 1; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/polarPoint.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/polarPoint.tests.js index e6cbb4c97fd5..00459e50d36b 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/polarPoint.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/polarPoint.tests.js @@ -5,6 +5,7 @@ import { import pointModule from 'viz/series/points/base_point'; import labelModule from 'viz/series/points/label'; import SeriesModule from 'viz/series/base_series'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const Series = SeriesModule.Series; @@ -61,7 +62,7 @@ const environment = { series._argumentChecker.returns(true); series._valueChecker.returns(true); - this.createLabel = sinon.stub(labelModule, 'Label').callsFake(function() { + this.createLabel = stubSeam(labelModule, 'Label', 'DEBUG_set_Label').callsFake(function() { label.getBoundingRect.returns({ x: 1, y: 2, width: 20, height: 10 }); label.getLayoutOptions.returns({ alignment: 'center', radialOffset: 0 }); resetStub(label); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/rangePoint.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/rangePoint.tests.js index 434d377fb08a..54ad00fca54f 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/rangePoint.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/rangePoint.tests.js @@ -7,6 +7,7 @@ import pointModule from 'viz/series/points/base_point'; import labelModule from 'viz/series/points/label'; import { MockTranslator, MockAxis } from '../../helpers/chartMocks.js'; import tooltipModule from 'viz/core/tooltip'; +import { spySeam } from '../../helpers/moduleSeam.js'; const originalLabel = labelModule.Label; @@ -71,12 +72,13 @@ const environment = { attributes: {} } }; - this.labelFactory = labelModule.Label = sinon.spy(function() { + this.labelFactory = sinon.spy(function() { const label = sinon.createStubInstance(originalLabel); label.getLayoutOptions.returns(that.options.label); label.getBoundingRect.returns({ height: 10, width: 20 }); return label; }); + labelModule.DEBUG_set_Label(this.labelFactory); this.series = { name: 'series', _labelsGroup: {}, @@ -92,7 +94,7 @@ const environment = { }; }, afterEach: function() { - labelModule.Label = originalLabel; + labelModule.DEBUG_set_Label(originalLabel); } }; @@ -2129,7 +2131,7 @@ QUnit.module('API', { _argumentChecker: function() { return true; }, _valueChecker: function() { return true; } }; - sinon.spy(labelModule, 'Label'); + spySeam(labelModule, 'Label', 'DEBUG_set_Label'); this.translators = { arg: new MockTranslator({ diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/rangeSeries.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/rangeSeries.tests.js index 6423c169a150..23ea6787513b 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/rangeSeries.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/rangeSeries.tests.js @@ -6,6 +6,7 @@ import pointModule from 'viz/series/points/base_point'; import SeriesModule from 'viz/series/base_series'; import { MockAxis, insertMockFactory, restoreMockFactory } from '../../helpers/chartMocks.js'; import { noop } from 'core/utils/common'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const Series = SeriesModule.Series; @@ -79,7 +80,7 @@ const environmentWithSinonStubPoint = { beforeEach: function() { environment.beforeEach.call(this); let mockPointIndex = 0; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function(params, data) { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function(params, data) { const stub = mockPoints[mockPointIndex++]; stub.argument = 1; stub.hasValue.returns(true); @@ -181,7 +182,7 @@ const environmentWithSinonStubPoint = { QUnit.module('RangeSeries. API', { beforeEach: function() { environment.beforeEach.call(this); - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function() { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { const stub = sinon.createStubInstance(originalPoint); stub.argument = 1; stub.hasValue.returns(true); @@ -339,7 +340,7 @@ const environmentWithSinonStubPoint = { this.options = { type: 'rangearea' }; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function() { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function() { const stub = sinon.createStubInstance(originalPoint); stub.argument = 1; stub.hasValue.returns(true); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/scatterSeries.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/scatterSeries.tests.js index b0ebdb1172b8..99d40b13daea 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/scatterSeries.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/scatterSeries.tests.js @@ -7,6 +7,7 @@ import pointModule from 'viz/series/points/base_point'; import labelModule from 'viz/series/points/label'; import SeriesModule from 'viz/series/base_series'; import { insertMockFactory, MockAxis, restoreMockFactory } from '../../helpers/chartMocks.js'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const Series = SeriesModule.Series; @@ -85,7 +86,7 @@ const environment = { this.renderer = new Renderer(); this.seriesGroup = this.renderer.g(); this.data = [{ arg: 1, val: 10 }, { arg: 2, val: 20 }, { arg: 3, val: 30 }, { arg: 4, val: 40 }]; - this.createPoint = sinon.stub(pointModule, 'Point').callsFake(function(series, data) { + this.createPoint = stubSeam(pointModule, 'Point', 'DEBUG_set_Point').callsFake(function(series, data) { const stub = mockPoints[mockPointIndex++]; data = data || {}; stub.argument = data.argument || 1; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core.series/symbolPoint.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core.series/symbolPoint.tests.js index fcda018b09c2..36cbe03cc342 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core.series/symbolPoint.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core.series/symbolPoint.tests.js @@ -33,9 +33,10 @@ const environment = { return !this.draw.calledWith(false); }); - this.labelFactory = labelModule.Label = sinon.spy(function() { + this.labelFactory = sinon.spy(function() { return that.label; }); + labelModule.DEBUG_set_Label(this.labelFactory); this.options = { widgetType: 'chart', visible: true, @@ -71,7 +72,7 @@ const environment = { }; }, afterEach: function() { - labelModule.Label = originalLabel; + labelModule.DEBUG_set_Label(originalLabel); } }; const translateXData = { 'canvas_position_default': 'x0', 1: 'x1', 2: 'x2', 3: 'x3', 4: 'x4', 5: 'x5' }; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/annotations.plugins.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/annotations.plugins.tests.js index 4651ac45599d..edc6f85f9664 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/annotations.plugins.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/annotations.plugins.tests.js @@ -896,7 +896,7 @@ QUnit.test('Get coordinates using argument and location `edge`', function(assert QUnit.module('Lifecycle', { beforeEach() { this.renderer = new Renderer(); - rendererModule.Renderer = sinon.spy(() => this.renderer); + rendererModule.DEBUG_set_Renderer(sinon.spy(() => this.renderer)); this.createAnnotationStub = sinon.stub().returns([{ draw: sinon.spy(), plaque: { clear: sinon.spy() } }]); __test_utils.stub_createAnnotations(this.createAnnotationStub); @@ -1289,16 +1289,16 @@ QUnit.module('Lifecycle', { const environment = { beforeEach() { this.renderer = new Renderer(); - rendererModule.Renderer = sinon.spy(() => this.renderer); + rendererModule.DEBUG_set_Renderer(sinon.spy(() => this.renderer)); - TooltipModule.Tooltip = sinon.spy((options) => { + TooltipModule.DEBUG_set_tooltip(sinon.spy((options) => { this.tooltip = new Tooltip(options); this.tooltip.show = sinon.stub().returns(true); this.tooltip.hide = sinon.spy(); this.tooltip.move = sinon.spy(); this.tooltip.isCursorOnTooltip = sinon.stub().returns(false); return this.tooltip; - }); + })); }, createChart(options) { const chart = $('
').appendTo('#qunit-fixture').dxChart($.extend(true, { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/axesTicksGeneration.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/axesTicksGeneration.tests.js index 4ebb6c9dc17f..84f02146c2b7 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/axesTicksGeneration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/axesTicksGeneration.tests.js @@ -9,6 +9,7 @@ import { import { Axis } from 'viz/axes/base_axis'; import translator2DModule from 'viz/translators/translator2d'; import { Range } from 'viz/translators/range'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const StubTranslator = stubClass(translator2DModule.Translator2D, { updateBusinessRange: function(range) { @@ -29,7 +30,7 @@ function getArray(len, content) { const environment = { beforeEach: function() { const that = this; - sinon.stub(translator2DModule, 'Translator2D').callsFake(function() { + stubSeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D').callsFake(function() { return that.translator; }); this.renderer = new Renderer(); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/axisDrawing.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/axisDrawing.tests.js index 5a818a865b72..cec3cac77391 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/axisDrawing.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/axisDrawing.tests.js @@ -1,14 +1,17 @@ import $ from 'jquery'; -import { ERROR_MESSAGES as dxErrors } from 'viz/core/errors_warnings'; +import errorsWarnings from 'viz/core/errors_warnings'; import translator2DModule from 'viz/translators/translator2d'; import { Range } from 'viz/translators/range'; import tickGeneratorModule from 'viz/axes/tick_generator'; import { Axis } from 'viz/axes/base_axis'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import { Renderer, stubClass, } from '../../helpers/vizMocks.js'; +const dxErrors = errorsWarnings.ERROR_MESSAGES; + const StubTranslator = stubClass(translator2DModule.Translator2D, { updateBusinessRange: function(range) { this.getBusinessRange.returns(range); @@ -44,12 +47,12 @@ const environment = { }; const that = this; - sinon.stub(translator2DModule, 'Translator2D').callsFake(function() { + stubSeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D').callsFake(function() { return that.translator; }); this.renderer = new Renderer(); - this.tickGenerator = sinon.stub(tickGeneratorModule, 'tickGenerator').callsFake(function() { + this.tickGenerator = stubSeam(tickGeneratorModule, 'tickGenerator', 'DEBUG_set_tickGenerator').callsFake(function() { return function() { return { ticks: that.generatedTicks || [], diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/axisFormatting.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/axisFormatting.tests.js index 7138b4ecfdec..51da67218c09 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/axisFormatting.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/axisFormatting.tests.js @@ -4,6 +4,7 @@ import translator2DModule from 'viz/translators/translator2d'; import { Range } from 'viz/translators/range'; import tickGeneratorModule from 'viz/axes/tick_generator'; import { Axis } from 'viz/axes/base_axis'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import { Renderer, stubClass, @@ -41,12 +42,12 @@ const environment = { }; const that = this; - sinon.stub(translator2DModule, 'Translator2D').callsFake(function() { + stubSeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D').callsFake(function() { return that.translator; }); this.renderer = new Renderer(); - this.tickGenerator = sinon.stub(tickGeneratorModule, 'tickGenerator').callsFake(function() { + this.tickGenerator = stubSeam(tickGeneratorModule, 'tickGenerator', 'DEBUG_set_tickGenerator').callsFake(function() { return function() { return { ticks: that.generatedTicks || [], diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/baseAxis.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/baseAxis.tests.js index 344d26c1edce..898cd12ed372 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/baseAxis.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/baseAxis.tests.js @@ -10,6 +10,7 @@ import translator2DModule from 'viz/translators/translator2d'; import { Range } from 'viz/translators/range'; import xyMethods from '__internal/viz/axes/xy_axes'; import { isFunction, isDeferred } from 'core/utils/type'; +import { stubSeam, spySeam } from '../../helpers/moduleSeam.js'; const StubTranslator = stubClass(translator2DModule.Translator2D, { updateBusinessRange: function(range) { @@ -29,7 +30,7 @@ const environment = { tickInterval: that.generatedTickInterval }; }); - this.tickGenerator = sinon.stub(tickGeneratorModule, 'tickGenerator').callsFake(function() { + this.tickGenerator = stubSeam(tickGeneratorModule, 'tickGenerator', 'DEBUG_set_tickGenerator').callsFake(function() { return that.tickGeneratorSpy; }); @@ -175,7 +176,7 @@ QUnit.module('API', { beforeEach: function() { const that = this; - sinon.stub(translator2DModule, 'Translator2D').callsFake(function() { + stubSeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D').callsFake(function() { return that.translator; }); environment.beforeEach.call(this); @@ -1195,7 +1196,7 @@ QUnit.test('Validate visualRange, option is set', function(assert) { QUnit.module('Zoom', { beforeEach: function() { const that = this; - sinon.stub(translator2DModule, 'Translator2D').callsFake(function() { + stubSeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D').callsFake(function() { return that.translator; }); @@ -1698,7 +1699,7 @@ QUnit.test('Get visualRange. visualRange is defined', function(assert) { const dataMarginsEnvironment = { beforeEach: function() { - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); environment.beforeEach.call(this); @@ -3597,7 +3598,7 @@ QUnit.test('Do not correct zero level if max > 0', function(assert) { QUnit.module('Set business range', { beforeEach: function() { environment.beforeEach.call(this); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); this.axis = new Axis({ renderer: this.renderer, @@ -4442,7 +4443,7 @@ QUnit.test('Logarithmic axis. Do not allowNegatives if option is not set and min QUnit.module('Set business range. Value axis', { beforeEach: function() { environment.beforeEach.call(this); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); this.axis = new Axis({ renderer: this.renderer, @@ -4608,7 +4609,7 @@ QUnit.test('Value axis ignores visual range on update option', function(assert) QUnit.module('Visual range on update. Argument axis', { beforeEach: function() { environment.beforeEach.call(this); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); this.axis = new Axis({ renderer: this.renderer, @@ -4992,7 +4993,7 @@ QUnit.test('Auto. Discrete axis - reset if visualRange consist of all old catego QUnit.module('Get scroll bounds', { beforeEach: function() { environment.beforeEach.call(this); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); this.axis = new Axis({ renderer: this.renderer, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/baseThemeManager.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/baseThemeManager.tests.js index 3ed3e29c14cd..850c89478d2d 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/baseThemeManager.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/baseThemeManager.tests.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import themeModule from 'viz/themes'; import { BaseThemeManager } from 'viz/core/base_theme_manager'; import paletteModule from '__internal/viz/paletteModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const currentTheme = themeModule.currentTheme(); @@ -34,9 +35,9 @@ const environment = { this.themeManager = new BaseThemeManager({ fontFields: [] }); this.callback = sinon.spy(); this.themeManager.setCallback(this.callback); - this.createPalette = sinon.stub(paletteModule, 'createPalette'); - this.getDiscretePalette = sinon.stub(paletteModule, 'getDiscretePalette'); - this.getAccentColor = sinon.stub(paletteModule, 'getAccentColor'); + this.createPalette = stubSeam(paletteModule, 'createPalette', 'DEBUG_set_createPalette'); + this.getDiscretePalette = stubSeam(paletteModule, 'getDiscretePalette', 'DEBUG_set_getDiscretePalette'); + this.getAccentColor = stubSeam(paletteModule, 'getAccentColor', 'DEBUG_set_getAccentColor'); }, afterEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/baseWidget.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/baseWidget.tests.js index 9acf3a6812df..438481f779ec 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/baseWidget.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/baseWidget.tests.js @@ -12,6 +12,7 @@ import BaseWidget from '__internal/viz/core/base_widget'; import { DEBUG_createEventTrigger, DEBUG_createResizeHandler } from '__internal/viz/core/base_widget.utils'; import { BaseThemeManager } from 'viz/core/base_theme_manager'; import rendererModule from 'viz/core/renderers/renderer_default'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import { stubClass, environmentMethodInvoker, LoadingIndicator, Renderer, Title } from '../../helpers/vizMocks.js'; import { implementationsMap } from 'core/utils/size'; @@ -54,7 +55,7 @@ QUnit.begin(function() { registerComponent('dxBaseWidgetTester', dxBaseWidgetTester); - sinon.stub(rendererModule, 'Renderer').callsFake(function() { + stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer').callsFake(function() { return currentTest().renderer; }); }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/export.integration.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/export.integration.tests.js index 7f974ef2aef4..90e8cd06303d 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/export.integration.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/export.integration.tests.js @@ -2,7 +2,8 @@ import '__internal/viz/tree_map/tree_map'; import $ from 'jquery'; import { Renderer, ExportMenu } from '../../helpers/vizMocks.js'; import rendererModule from 'viz/core/renderers/renderer_default'; -import clientExporter from 'exporter'; +import * as clientExporter from 'exporter'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import exportModule from '__internal/viz/core/exportModule'; import { Deferred } from 'core/utils/deferred'; import { logger } from 'core/utils/console'; @@ -22,14 +23,14 @@ QUnit.module('Export', { beforeEach: function() { this.$container = $('#test-container'); const renderer = this.renderer = new Renderer(); - rendererModule.Renderer = function() { + rendererModule.DEBUG_set_Renderer(function() { return renderer; - }; + }); const exportMenu = this.exportMenu = new ExportMenu(); exportModule.DEBUG_set_ExportMenu(sinon.spy(function() { return exportMenu; })); - sinon.stub(clientExporter, 'export').returns(new Deferred()); + stubSeam(clientExporter, 'export').returns(new Deferred()); this.toDataURLStub = sinon.stub(window.HTMLCanvasElement.prototype, 'toDataURL'); this.toDataURLStub.returnsArg(0); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/export.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/export.tests.js index 15ce7e3c2c38..a745f3048761 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/export.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/export.tests.js @@ -2,7 +2,8 @@ import $ from 'jquery'; import { Renderer } from '../../helpers/vizMocks.js'; import exportModule from '__internal/viz/core/exportModule'; import themeModule from 'viz/themes'; -import clientExporter from 'exporter'; +import * as clientExporter from 'exporter'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import localization from 'localization'; const combineMarkupsOrig = exportModule.combineMarkups; @@ -559,7 +560,7 @@ QUnit.test('Combine widgets markups (combineMarkups) in grid layout with bottom- QUnit.module('API. Export methods', { beforeEach: function() { - sinon.stub(clientExporter, 'export'); + stubSeam(clientExporter, 'export'); this.toDataURLStub = sinon.stub(window.HTMLCanvasElement.prototype, 'toDataURL'); this.toDataURLStub.returnsArg(0); }, @@ -828,7 +829,7 @@ QUnit.module('API', { this.renderer = new Renderer(); this.incidentOccurred = sinon.spy(); - sinon.stub(clientExporter, 'export'); + stubSeam(clientExporter, 'export'); this.options = { printingEnabled: true, formats: ['JPEG'], @@ -1055,7 +1056,7 @@ QUnit.module('Events', { this.renderer = new Renderer(); this.incidentOccurred = sinon.spy(); - sinon.stub(clientExporter, 'export'); + stubSeam(clientExporter, 'export'); this.options = { enabled: true, @@ -1365,7 +1366,7 @@ QUnit.module('Layout', { this.renderer = new Renderer(); this.incidentOccurred = sinon.spy(); - sinon.stub(clientExporter, 'export'); + stubSeam(clientExporter, 'export'); this.options = { enabled: true, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/legend.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/legend.tests.js index 055dfdbe8026..1927e8eb96da 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/legend.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/legend.tests.js @@ -2215,7 +2215,7 @@ const titleEnvironment = $.extend({}, environment, { const titleConstructor = module.Title; that.titleLayout = { height: 17, width: 20, x: 4, y: 5 }; - module.Title = function(params) { + module.DEBUG_set_title(function(params) { that.title = new titleConstructor(params); that.title.getLayoutOptions = sinon.stub(); @@ -2231,7 +2231,7 @@ const titleEnvironment = $.extend({}, environment, { that.title.shift = sinon.spy(); return that.title; - }; + }); that.themeManagerTitleOptions = { backgroundColor: '#ffffff', diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/polarAxes.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/polarAxes.tests.js index 7d035cb017b9..0db79c4ec9e0 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/polarAxes.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/polarAxes.tests.js @@ -8,6 +8,7 @@ import tickGeneratorModule from 'viz/axes/tick_generator'; import rangeModule from 'viz/translators/range'; import { Axis } from 'viz/axes/base_axis'; import { extend } from 'core/utils/extend'; +import { stubSeam, spySeam } from '../../helpers/moduleSeam.js'; const TranslatorStubCtor = new ObjectPool(translator2DModule.Translator2D); const RangeStubCtor = new ObjectPool(rangeModule.Range); @@ -31,7 +32,7 @@ const environment = { this.renderer = new Renderer(); - this.tickGenerator = sinon.stub(tickGeneratorModule, 'tickGenerator').callsFake(function() { + this.tickGenerator = stubSeam(tickGeneratorModule, 'tickGenerator', 'DEBUG_set_tickGenerator').callsFake(function() { return sinon.spy(function() { return { ticks: that.generatedTicks || [], @@ -95,7 +96,7 @@ const environment = { br.isEmpty.returns(true); this.translator.getBusinessRange.returns(br); - sinon.stub(translator2DModule, 'Translator2D').callsFake(function() { + stubSeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D').callsFake(function() { return that.translator; }); }, @@ -139,7 +140,7 @@ QUnit.module('Translators in axis', { beforeEach: function() { environment.beforeEach.call(this); translator2DModule.Translator2D.restore(); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); }, afterEach: function() { environment.afterEach.call(this); @@ -2230,7 +2231,7 @@ QUnit.module('Circular axis. Margins', $.extend({}, environment, { environment.beforeEach.call(this); translator2DModule.Translator2D.restore(); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); $.extend(this.renderSettings, { axisType: 'polarAxes', @@ -2328,7 +2329,7 @@ QUnit.module('Linear axis. Margins', $.extend({}, environment, { environment.beforeEach.call(this); translator2DModule.Translator2D.restore(); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); $.extend(this.renderSettings, { axisType: 'polarAxes', diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/tooltip.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/tooltip.tests.js index de756462d436..aeef5b01833c 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/tooltip.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/tooltip.tests.js @@ -56,11 +56,11 @@ function getInitialOptions() { }; } -rendererModule.Renderer = function(parameters) { +rendererModule.DEBUG_set_Renderer(function(parameters) { const renderer = new Renderer(parameters); currentTest().renderer = renderer; return renderer; -}; +}); QUnit.module('Main functionality', { beforeEach: function() { @@ -68,10 +68,10 @@ QUnit.module('Main functionality', { this._oldPatchFontOptions = vizUtils.patchFontOptions; this.patchFontOptions = sinon.spy(function() { return this._oldPatchFontOptions.apply(null, arguments); }.bind(this)); - vizUtils.patchFontOptions = this.patchFontOptions; + vizUtils.DEBUG_set_patchFontOptions(this.patchFontOptions); }, afterEach: function() { - vizUtils.patchFontOptions = this._oldPatchFontOptions; + vizUtils.DEBUG_set_patchFontOptions(this._oldPatchFontOptions); } }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.core/xyAxes.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.core/xyAxes.tests.js index ddee7066bde7..561c72f18776 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.core/xyAxes.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.core/xyAxes.tests.js @@ -9,6 +9,7 @@ import rangeModule from 'viz/translators/range'; import { Axis } from 'viz/axes/base_axis'; import { MockSeries } from '../../helpers/chartMocks.js'; import { patchFontOptions } from 'viz/core/utils'; +import { stubSeam, spySeam } from '../../helpers/moduleSeam.js'; const Translator2D = translator2DModule.Translator2D; @@ -45,11 +46,11 @@ const environment = { breaks: breaks }; }); - this.tickGenerator = sinon.stub(tickGeneratorModule, 'tickGenerator').callsFake(function() { + this.tickGenerator = stubSeam(tickGeneratorModule, 'tickGenerator', 'DEBUG_set_tickGenerator').callsFake(function() { return that.tickGeneratorSpy; }); - sinon.stub(translator2DModule, 'Translator2D').callsFake(function() { + stubSeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D').callsFake(function() { return that.translator; }); @@ -208,7 +209,7 @@ QUnit.module('Translators in axis', { beforeEach: function() { environment.beforeEach.call(this); translator2DModule.Translator2D.restore(); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); }, afterEach: function() { environment.afterEach.call(this); @@ -4377,7 +4378,7 @@ QUnit.module('XY axes margin calculation', { environment.beforeEach.call(this); translator2DModule.Translator2D.restore(); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); this.canvas = { top: 200, @@ -5084,7 +5085,7 @@ QUnit.module('Custom positioning', { }; translator2DModule.Translator2D.restore(); - sinon.spy(translator2DModule, 'Translator2D'); + spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); }, afterEach: function() { environment.afterEach.call(this); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.funnel/commonParts/common.js b/packages/devextreme/testing/tests/DevExpress.viz.funnel/commonParts/common.js index b8176f837f89..b323705b352c 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.funnel/commonParts/common.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.funnel/commonParts/common.js @@ -3,6 +3,7 @@ import { Renderer } from '../../../helpers/vizMocks.js'; import rendererModule from 'viz/core/renderers/renderer_default'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import * as tiling from '__internal/viz/funnel/tiling'; import '__internal/viz/funnel/funnel'; @@ -44,7 +45,7 @@ export const environment = { this.itemGroupNumber = 0; - sinon.stub(rendererModule, 'Renderer').callsFake(function() { + stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer').callsFake(function() { return that.renderer; }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.funnel/commonParts/label.js b/packages/devextreme/testing/tests/DevExpress.viz.funnel/commonParts/label.js index f2090e457b63..8eb284de2244 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.funnel/commonParts/label.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.funnel/commonParts/label.js @@ -2,10 +2,11 @@ import $ from 'jquery'; import { environment, stubAlgorithm } from './common.js'; import labelModule from 'viz/series/points/label'; import { - stubClass + stubClass, } from '../../../helpers/vizMocks.js'; import * as labels from '__internal/viz/funnel/label'; import dxFunnel from '__internal/viz/funnel/funnel'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; const Label = labelModule.Label; const stubLabel = stubClass(Label); @@ -34,7 +35,7 @@ export const labelEnvironment = $.extend({}, environment, { const that = this; - sinon.stub(labelModule, 'Label').callsFake(function() { + stubSeam(labelModule, 'Label', 'DEBUG_set_Label').callsFake(function() { const stub = new stubLabel(); stub.stub('isVisible').returns(true); stub.stub('getBoundingRect').returns(that.labelBoxes[(labelBoxesIndex++) % that.labelBoxes.length]); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.funnel/funnel.base.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.funnel/funnel.base.tests.js index 1082fd01539c..5b44ef929e8f 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.funnel/funnel.base.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.funnel/funnel.base.tests.js @@ -1,9 +1,10 @@ import $ from 'jquery'; import { createFunnel, environment, stubAlgorithm } from './commonParts/common.js'; -import rendererModule from 'viz/core/renderers/renderer'; +import rendererModule from 'viz/core/renderers/renderer_default'; import paletteModule from '__internal/viz/paletteModule'; import themeModule from 'viz/themes'; +import { spySeam } from '../../helpers/moduleSeam.js'; themeModule.registerTheme({ name: 'test-theme', @@ -271,7 +272,7 @@ QUnit.test('Resize', function(assert) { }); QUnit.test('palette', function(assert) { - sinon.spy(paletteModule, 'createPalette'); + spySeam(paletteModule, 'createPalette', 'DEBUG_set_createPalette'); stubAlgorithm.normalizeValues.returns([1, 1]); stubAlgorithm.getFigures.returns([ @@ -452,7 +453,7 @@ QUnit.test('Update inverted option', function(assert) { }); QUnit.test('Update palette', function(assert) { - sinon.spy(paletteModule, 'createPalette'); + spySeam(paletteModule, 'createPalette', 'DEBUG_set_createPalette'); stubAlgorithm.normalizeValues.returns([1, 1]); stubAlgorithm.getFigures.returns([ diff --git a/packages/devextreme/testing/tests/DevExpress.viz.funnel/funnel.tracker.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.funnel/funnel.tracker.tests.js index e7a9d31d308d..966ee647702e 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.funnel/funnel.tracker.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.funnel/funnel.tracker.tests.js @@ -8,6 +8,7 @@ import legendModule from 'viz/components/legend'; import { createFunnel, environment } from './commonParts/common.js'; import labelModule from 'viz/series/points/label'; import { stubClass } from '../../helpers/vizMocks.js'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const stubLabel = stubClass(labelModule.Label); const stubLegend = stubClass(legendModule.Legend); @@ -28,7 +29,7 @@ const trackerEnvironment = $.extend({}, environment, { const that = this; environment.beforeEach.apply(this); this.legend = new stubLegend(); - sinon.stub(labelModule, 'Label').callsFake(function() { + stubSeam(labelModule, 'Label', 'DEBUG_set_Label').callsFake(function() { const stub = new stubLabel(); stub.stub('getBoundingRect').returns({ width: 0, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge.tests.js index 6f4bb5c49201..0858624beb52 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge.tests.js @@ -24,7 +24,7 @@ const { BarWrapper, stubBarWrapper, restoreBarWrapper } = barGaugeModule; $('
').appendTo('#qunit-fixture'); let renderer; QUnit.begin(function() { - rendererModule.Renderer = sinon.spy(function() { + rendererModule.DEBUG_set_Renderer(sinon.spy(function() { const test = currentTest(); test.renderer = renderer || new Renderer(); test.renderer.g = sinon.spy(function() { @@ -61,12 +61,21 @@ QUnit.begin(function() { step(1); that.animationStep && that.animationStep(1); complete(); - that.animationComplete && that.animationComplete(); + // Drop the hook before calling it — overlapping animate() + // chains (or a second completion after done()) must not + // re-enter assert.async() under native ESM scheduling. + const onGroupComplete = that.animationComplete; + that.animationComplete = null; + onGroupComplete && onGroupComplete(); test.renderer.animationCompleted && test.renderer.animationCompleted(); } } if(arguments[1] && typeof arguments[1].step === 'function') { + // Real renderer replaces the in-flight animation; without this, + // a second animate() leaves an orphan setTimeout chain and + // animationComplete / assert.async() fire twice. + this.stopAnimation(); that = this; step = arguments[1].step; complete = arguments[1].complete || noop; @@ -77,13 +86,14 @@ QUnit.begin(function() { }; group.stopAnimation = function() { clearTimeout(this.__animation); + this.__animation = null; return this; }; return group; }); return test.renderer; - }); + })); titleModule.DEBUG_set_title(sinon.spy(function() { const title = new Title(); title.getLayoutOptions = () => ({ diff --git a/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge_new.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge_new.tests.js index 167d8682b962..3565c73117cf 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge_new.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.gauges/barGauge_new.tests.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import legendModule, { Legend } from 'viz/components/legend'; +import legendModule from 'viz/components/legend'; import { Renderer, Title, @@ -8,6 +8,7 @@ import { stubClass } from '../../helpers/vizMocks.js'; import rendererModule from 'viz/core/renderers/renderer_default'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import titleModule from 'viz/core/title'; import tooltipModule from 'viz/core/tooltip'; import loadingIndicatorModule from 'viz/core/loading_indicator'; @@ -15,11 +16,13 @@ import dxBarGauge from 'viz/bar_gauge'; import '__internal/viz/gauges/bar_gauge'; +const { Legend } = legendModule; + const environment = { beforeEach() { this.renderer = new Renderer(); - sinon.stub(rendererModule, 'Renderer').callsFake(() => { + stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer').callsFake(() => { return this.renderer; }); }, @@ -47,13 +50,13 @@ $('
') const _LoadingIndicator = loadingIndicatorModule.LoadingIndicator; titleModule.DEBUG_set_title(Title); -tooltipModule.Tooltip = Tooltip; +tooltipModule.DEBUG_set_tooltip(Tooltip); loadingIndicatorModule.DEBUG_set_LoadingIndicator(LoadingIndicator); QUnit.module('Misc', { beforeEach: function() { const renderer = this.renderer = new Renderer(); - rendererModule.Renderer = function() { return renderer; }; + rendererModule.DEBUG_set_Renderer(function() { return renderer; }); }, create: function(options) { @@ -169,7 +172,7 @@ QUnit.module('Legend', { beforeEach() { this.renderer = new Renderer(); - sinon.stub(rendererModule, 'Renderer').callsFake(() => { + stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer').callsFake(() => { return this.renderer; }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.gauges/baseGauge.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.gauges/baseGauge.tests.js index 4ee430fb18e3..1e8f6a2df266 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.gauges/baseGauge.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.gauges/baseGauge.tests.js @@ -52,9 +52,9 @@ $.each(ABSTRACT_METHODS, function(_, name) { BaseGauge.prototype[name] = sinon.stub(); }); -rendererModule.Renderer = sinon.spy(function() { +rendererModule.DEBUG_set_Renderer(sinon.spy(function() { return currentTest().renderer; -}); +})); themeManagerModule.ThemeManager = sinon.spy(function() { return currentTest().themeManager; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.gauges/circularGauge.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.gauges/circularGauge.tests.js index 7aa45225f2d6..d173e86d006a 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.gauges/circularGauge.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.gauges/circularGauge.tests.js @@ -8,6 +8,7 @@ import { import dxCircularGauge from 'viz/circular_gauge'; import axisModule from 'viz/axes/base_axis'; import rendererModule from 'viz/core/renderers/renderer_default'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const factory = dxCircularGauge.prototype._factory; @@ -105,9 +106,9 @@ class TestPointerElement extends TestElement { } (function circularGauge() { - rendererModule.Renderer = sinon.stub(); + rendererModule.DEBUG_set_Renderer(sinon.stub()); - sinon.stub(axisModule, 'Axis').callsFake(function(parameters) { + stubSeam(axisModule, 'Axis', 'DEBUG_set_Axis').callsFake(function(parameters) { const axis = new Axis(parameters); axis.measureLabels = sinon.stub().returns({ width: 30, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.gauges/common.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.gauges/common.tests.js index f17fb9d6541c..1dfb91b51612 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.gauges/common.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.gauges/common.tests.js @@ -24,12 +24,13 @@ import rangeModule from 'viz/translators/range'; import translator1DModule from 'viz/translators/translator1d'; import rendererModule from 'viz/core/renderers/renderer_default'; import themeManagerModule from '__internal/viz/gauges/theme_manager'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const stubRange = stubClass(rangeModule.Range); $('
').appendTo('#qunit-fixture'); -sinon.stub(rangeModule, 'Range').callsFake(function(parameters) { +stubSeam(rangeModule, 'Range', 'DEBUG_set_Range').callsFake(function(parameters) { return new stubRange(parameters); }); @@ -80,11 +81,11 @@ const factory = dxTestGauge.prototype._factory = objectUtils.clone(dxGauge.proto registerComponent('dxTestGauge', dxTestGauge); const StubTooltip = Tooltip; -tooltipModule.Tooltip = function(parameters) { +tooltipModule.DEBUG_set_tooltip(function(parameters) { return new StubTooltip(parameters); -}; +}); -sinon.stub(axisModule, 'Axis').callsFake(function(parameters) { +stubSeam(axisModule, 'Axis', 'DEBUG_set_Axis').callsFake(function(parameters) { return new Axis(parameters); }); @@ -181,7 +182,7 @@ loadingIndicatorModule.DEBUG_set_LoadingIndicator(function(parameters) { return new LoadingIndicator(parameters); }); -sinon.stub(rendererModule, 'Renderer').callsFake(function() { +stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer').callsFake(function() { return currentTest().renderer; }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.gauges/linearGauge.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.gauges/linearGauge.tests.js index d04bbcc54d8f..92025d3131fa 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.gauges/linearGauge.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.gauges/linearGauge.tests.js @@ -5,6 +5,7 @@ import { Axis as VizMocksAxis, Renderer as VizMocksRenderer } from '../../helper import dxLinearGauge from 'viz/linear_gauge'; import axisModule from 'viz/axes/base_axis'; import rendererModule from 'viz/core/renderers/renderer_default'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const factory = dxLinearGauge.prototype._factory; @@ -100,9 +101,9 @@ class TestPointerElement extends TestElement { } (function linearGauge() { - rendererModule.Renderer = sinon.stub(); + rendererModule.DEBUG_set_Renderer(sinon.stub()); - sinon.stub(axisModule, 'Axis').callsFake(function(parameters) { + stubSeam(axisModule, 'Axis', 'DEBUG_set_Axis').callsFake(function(parameters) { const axis = new VizMocksAxis(parameters); axis.measureLabels = sinon.stub().returns({ width: 30, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part1.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part1.tests.js index 3ecc2744688f..3891b97818c0 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part1.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part1.tests.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import trackerModule from 'viz/range_selector/tracker'; +import { spySeam } from '../../helpers/moduleSeam.js'; import { DataSource } from 'common/data/data_source/data_source'; import seriesDataSourceModule from 'viz/range_selector/series_data_source'; import { @@ -196,7 +197,7 @@ QUnit.test('correct sliders place holder size by values', function(assert) { }); QUnit.test('Tracker creation', function(assert) { - const spy = sinon.spy(trackerModule, 'Tracker'); + const spy = spySeam(trackerModule, 'Tracker', 'DEBUG_set_Tracker'); this.createWidget(); assert.deepEqual(spy.lastCall.args, [{ renderer: this.renderer, controller: this.slidersController }]); @@ -261,7 +262,7 @@ QUnit.test('dataSource is loaded', function(assert) { }); QUnit.test('Update axis canvas before create series dataSorce', function(assert) { - const spy = sinon.spy(seriesDataSourceModule, 'SeriesDataSource'); + const spy = spySeam(seriesDataSourceModule, 'SeriesDataSource', 'DEBUG_set_SeriesDataSource'); this.seriesDataSource.stub('getBoundRange').returns({ arg: new StubRange(), val: new StubRange() @@ -379,7 +380,7 @@ QUnit.test('scale. not valid logarithmBase, string', function(assert) { }); QUnit.test('valueAxis. logarithmic type', function(assert) { - const spy = sinon.spy(seriesDataSourceModule, 'SeriesDataSource'); + const spy = spySeam(seriesDataSourceModule, 'SeriesDataSource', 'DEBUG_set_SeriesDataSource'); this.seriesDataSource.stub('getBoundRange').returns({ arg: new StubRange(), val: new StubRange() @@ -400,7 +401,7 @@ QUnit.test('valueAxis. logarithmic type', function(assert) { }); QUnit.test('valueAxis. not valid logarithmBase', function(assert) { - const spy = sinon.spy(seriesDataSourceModule, 'SeriesDataSource'); + const spy = spySeam(seriesDataSourceModule, 'SeriesDataSource', 'DEBUG_set_SeriesDataSource'); this.seriesDataSource.stub('isShowChart').returns(true); this.seriesDataSource.stub('getBoundRange').returns({ arg: new StubRange(), diff --git a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part2.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part2.tests.js index fd2ddaa1be90..1aeaabbf35b1 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part2.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part2.tests.js @@ -56,10 +56,10 @@ const environmentWithDataSource = $.extend({}, environment, { beforeEach: function() { environment.beforeEach.apply(this, arguments); const test = this; - seriesDataSourceModule.SeriesDataSource = function(params) { + seriesDataSourceModule.DEBUG_set_SeriesDataSource(function(params) { test.seriesDataSource = new _SeriesDataSource(params); return test.seriesDataSource; - }; + }); } }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part3.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part3.tests.js index 34f401fc6702..6b0d5fe5bad7 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part3.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common.part3.tests.js @@ -8,6 +8,7 @@ import { StubRange, } from './rangeSelectorParts/commons.js'; import slidersControllerModule from 'viz/range_selector/sliders_controller'; +import { spySeam } from '../../helpers/moduleSeam.js'; import seriesDataSourceModule from 'viz/range_selector/series_data_source'; import { DataSource } from 'common/data/data_source/data_source'; import dateLocalization from 'common/core/localization/date'; @@ -23,7 +24,7 @@ const formatsAreEqual = function(format1, format2) { QUnit.module('Parsing data', $.extend({}, environment, { beforeEach: function() { environment.beforeEach.apply(this, arguments); - seriesDataSourceModule.SeriesDataSource = _SeriesDataSource; + seriesDataSourceModule.DEBUG_set_SeriesDataSource(_SeriesDataSource); this.dataSource = [ { x: '10', y1: 0, y2: 10 }, { x: '15', y1: 6, y2: 12 }, @@ -400,7 +401,7 @@ QUnit.test('rangeSelector with scale.valueType and dataSourceField and without c QUnit.module('Semidiscrete scale', $.extend({}, environment, { beforeEach: function() { environment.beforeEach.apply(this, arguments); - seriesDataSourceModule.SeriesDataSource = _SeriesDataSource; + seriesDataSourceModule.DEBUG_set_SeriesDataSource(_SeriesDataSource); this.$container.width(1000); } @@ -1299,7 +1300,7 @@ QUnit.test('T214998. scale multi-line text label', function(assert) { }); QUnit.test('range selectedRangeChanged initialization', function(assert) { - const spy = sinon.spy(slidersControllerModule, 'SlidersController'); + const spy = spySeam(slidersControllerModule, 'SlidersController', 'DEBUG_set_SlidersController'); this.createWidget(); assert.strictEqual(typeof spy.lastCall.args[0].updateSelectedRange, 'function'); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common_new.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common_new.tests.js index 6eb75b4446ea..e53b49d267dd 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common_new.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/common_new.tests.js @@ -7,6 +7,7 @@ import rendererModule from 'viz/core/renderers/renderer_default'; import axisModule from 'viz/axes/base_axis'; import translator2DModule from 'viz/translators/translator2d'; import '__internal/viz/range_selector/range_selector'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const StubAxis = stubClass(axisModule.Axis); @@ -27,10 +28,10 @@ QUnit.module('RangeSelector', { const that = this; this.$container = $('#test-container'); const renderer = this.renderer = new Renderer(); - rendererModule.Renderer = function() { return renderer; }; + rendererModule.DEBUG_set_Renderer(function() { return renderer; }); this.axis = new StubAxis(); this.axis.stub('getVisibleArea').returns([]); - sinon.stub(axisModule, 'Axis').callsFake(function() { + stubSeam(axisModule, 'Axis', 'DEBUG_set_Axis').callsFake(function() { return that.axis; }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/rangeSelectorParts/commons.js b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/rangeSelectorParts/commons.js index e2d9825cb24c..8cc8190597fe 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/rangeSelectorParts/commons.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/rangeSelectorParts/commons.js @@ -16,6 +16,7 @@ import { Renderer, } from '../../../helpers/vizMocks.js'; import '__internal/viz/range_selector/range_selector'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; const StubThemeManager = stubClass(themeManagerModule.BaseThemeManager); const StubRangeView = stubClass(rangeViewModule.RangeView); @@ -75,15 +76,15 @@ export const environment = { this.axis.calculateInterval = function(a, b) { return a - b; }; this.seriesDataSource = new StubSeriesDataSource(); - rendererModule.Renderer = returnValue(this.renderer); - themeManagerModule.BaseThemeManager = returnValue(this.themeManager); - rangeViewModule.RangeView = returnValue(this.rangeView); - slidersControllerModule.SlidersController = returnValue(this.slidersController); - trackerModule.Tracker = returnValue(this.tracker); - seriesDataSourceModule.SeriesDataSource = returnValue(this.seriesDataSource); - translator2DModule.Translator2D = returnValue(this.translator); + rendererModule.DEBUG_set_Renderer(returnValue(this.renderer)); + themeManagerModule.DEBUG_set_BaseThemeManager(returnValue(this.themeManager)); + rangeViewModule.DEBUG_set_RangeView(returnValue(this.rangeView)); + slidersControllerModule.DEBUG_set_SlidersController(returnValue(this.slidersController)); + trackerModule.DEBUG_set_Tracker(returnValue(this.tracker)); + seriesDataSourceModule.DEBUG_set_SeriesDataSource(returnValue(this.seriesDataSource)); + translator2DModule.DEBUG_set_Translator2D(returnValue(this.translator)); - sinon.stub(axisModule, 'Axis'); + stubSeam(axisModule, 'Axis', 'DEBUG_set_Axis'); axisModule.Axis.returns(this.axis); }, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/rangeView.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/rangeView.tests.js index 7c09e4d92efb..a78728925ff8 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/rangeView.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/rangeView.tests.js @@ -3,6 +3,7 @@ import { Renderer, Element } from '../../helpers/vizMocks.js'; import translator2DModule from 'viz/translators/translator2d'; import rangeViewModule from 'viz/range_selector/range_view'; import { MockAxis } from '../../helpers/chartMocks.js'; +import { spySeam } from '../../helpers/moduleSeam.js'; QUnit.module('RangeView', { beforeEach: function() { @@ -99,7 +100,7 @@ QUnit.test('Chart view', function(assert) { QUnit.test('Chart view is not created because of seriesDataSource', function(assert) { const seriesDataSource = { isShowChart: function() { return false; } }; - const Translator2D = sinon.spy(translator2DModule, 'Translator2D'); + const Translator2D = spySeam(translator2DModule, 'Translator2D', 'DEBUG_set_Translator2D'); try { this.rangeView.update({ color: 'red', image: { url: 'url' } }, { visible: true, image: { location: 'loc' } }, this.canvas, false, 'animation-enabled', seriesDataSource, 'translator'); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/slidersController.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/slidersController.tests.js index 894f6c168aae..45c3daf026a6 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/slidersController.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.rangeSelector/slidersController.tests.js @@ -12,9 +12,9 @@ const environment = { beforeEach: function() { const renderer = this.renderer = new Renderer(); - rendererModule.Renderer = function() { + rendererModule.DEBUG_set_Renderer(function() { return renderer; - }; + }); this.translator = new translator2DModule.Translator2D({}, {}); this.translator.update({ min: 10, max: 30 }, { left: 1000, width: 3000 }, { isHorizontal: true }); this.root = new Element(); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.renderers/Animation.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.renderers/Animation.tests.js index 31eb6bc37b2a..4a704f9ae1e5 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.renderers/Animation.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.renderers/Animation.tests.js @@ -2,10 +2,11 @@ import $ from 'jquery'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; import commonUtils from 'core/utils/common'; import typeUtils from 'core/utils/type'; import animationModule from 'viz/core/renderers/animation'; -import rendererModule from 'viz/core/renderers/renderer'; +import rendererModule from 'viz/core/renderers/renderer_default'; import { stubClass } from '../../helpers/vizMocks.js'; @@ -49,10 +50,10 @@ import { this.animationController.dispose(); }, mockRequestAnimationFrame: function(callback) { - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake(callback); + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake(callback); }, mockCancelAnimationFrame: function(callback) { - this.cancelAnimationFrameStub = sinon.stub(animationFrame, 'cancelAnimationFrame').callsFake(callback); + this.cancelAnimationFrameStub = stubSeam(animationFrame, 'cancelAnimationFrame', 'DEBUG_set_cancelAnimationFrame').callsFake(callback); } }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.renderers/Renderer.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.renderers/Renderer.tests.js index 78cf7bb4d83b..0c6015395584 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.renderers/Renderer.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.renderers/Renderer.tests.js @@ -19,7 +19,7 @@ function getMockElement() { }; } -utils.getNextDefsSvgId = sinon.stub().returns('DevExpressId'); +utils.DEBUG_set_getNextDefsSvgId(sinon.stub().returns('DevExpressId')); QUnit.testDone(function() { renderers.SvgElement.resetHistory && renderers.SvgElement.resetHistory(); @@ -55,7 +55,7 @@ function resetMockElements() { }); } -animationModule.AnimationController = stubClass(animationModule.AnimationController); +animationModule.DEBUG_set_AnimationController(stubClass(animationModule.AnimationController)); const Renderer = renderers.Renderer; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.renderers/SvgElement.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.renderers/SvgElement.tests.js index b86cd6bc5562..18217fde7c8c 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.renderers/SvgElement.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.renderers/SvgElement.tests.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import typeUtils from 'core/utils/type'; -import rendererModule from 'viz/core/renderers/renderer'; +import rendererModule from 'viz/core/renderers/renderer_default'; import coreRenderer from 'core/renderer'; import eventsEngine from 'common/core/events/core/events_engine'; import domAdapter from '__internal/core/m_dom_adapter'; @@ -562,12 +562,11 @@ function checkDashStyle(assert, elem, result, style, value) { this.Element = renderer.SvgElement; - this.jQuery = $; - $ = coreRenderer; + this.$ = coreRenderer; this.eventsEngine = eventsEngine; this.rendererStub = { fake: 'fake', root: { element: document.createElement('div') } }; - this.$emptyStub = sinon.stub($.fn, 'empty'); - this.$removeStub = sinon.stub($.fn, 'remove').callsFake(function() { return this; }); + this.$emptyStub = sinon.stub(this.$.fn, 'empty'); + this.$removeStub = sinon.stub(this.$.fn, 'remove').callsFake(function() { return this; }); this.$onStub = sinon.stub(this.eventsEngine, 'on'); this.$offStub = sinon.stub(this.eventsEngine, 'off'); this.$triggerStub = sinon.stub(this.eventsEngine, 'trigger'); @@ -578,7 +577,6 @@ function checkDashStyle(assert, elem, result, style, value) { this.$onStub.restore(); this.$offStub.restore(); this.$triggerStub.restore(); - $ = this.jQuery; } }); @@ -588,8 +586,8 @@ function checkDashStyle(assert, elem, result, style, value) { const result = elem.clear(); assert.equal(result, elem); - assert.ok($.fn.empty.calledOnce); - assert.equal($.fn.empty.firstCall.thisValue.get(0), $(elem.element).get(0)); + assert.ok(this.$.fn.empty.calledOnce); + assert.equal(this.$.fn.empty.firstCall.thisValue.get(0), this.$(elem.element).get(0)); }); QUnit.test('Disposing', function(assert) { @@ -598,8 +596,8 @@ function checkDashStyle(assert, elem, result, style, value) { const result = elem.dispose(); assert.equal(result, elem); - assert.ok($.fn.remove.calledOnce); - assert.equal($.fn.remove.firstCall.thisValue.get(0), $(elem.element).get(0)); + assert.ok(this.$.fn.remove.calledOnce); + assert.equal(this.$.fn.remove.firstCall.thisValue.get(0), this.$(elem.element).get(0)); }); QUnit.test('On', function(assert) { @@ -610,7 +608,7 @@ function checkDashStyle(assert, elem, result, style, value) { assert.equal(result, elem); assert.ok(this.eventsEngine.on.calledOnce); assert.deepEqual(this.eventsEngine.on.firstCall.args.slice(1), [1, 2, 3, 4]); - assert.equal(this.eventsEngine.on.firstCall.args[0].get(0), $(elem.element).get(0)); + assert.equal(this.eventsEngine.on.firstCall.args[0].get(0), this.$(elem.element).get(0)); }); QUnit.test('Off', function(assert) { @@ -622,7 +620,7 @@ function checkDashStyle(assert, elem, result, style, value) { assert.ok(this.eventsEngine.off.calledOnce); assert.deepEqual(this.eventsEngine.off.firstCall.args.slice(1), [1, 2, 3, 4]); - assert.equal(this.eventsEngine.off.firstCall.args[0].get(0), $(elem.element).get(0)); + assert.equal(this.eventsEngine.off.firstCall.args[0].get(0), this.$(elem.element).get(0)); }); QUnit.test('Trigger', function(assert) { @@ -635,7 +633,7 @@ function checkDashStyle(assert, elem, result, style, value) { assert.ok(this.eventsEngine.trigger.calledOnce); assert.deepEqual(this.eventsEngine.trigger.firstCall.args.slice(1), [1, 2, 3, 4]); assert.equal(this.eventsEngine.trigger.firstCall.args[0].length, 1); - assert.equal(this.eventsEngine.trigger.firstCall.args[0].get(0), $(elem.element).get(0)); + assert.equal(this.eventsEngine.trigger.firstCall.args[0].get(0), this.$(elem.element).get(0)); }); QUnit.module('SvgElement. attr API, set attrs', { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.sankey/commonParts/common.js b/packages/devextreme/testing/tests/DevExpress.viz.sankey/commonParts/common.js index 5e38e191f7a3..0c2bd84b63fe 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.sankey/commonParts/common.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.sankey/commonParts/common.js @@ -3,6 +3,7 @@ import { Renderer, } from '../../../helpers/vizMocks.js'; import rendererModule from 'viz/core/renderers/renderer_default'; +import { stubSeam } from '../../../helpers/moduleSeam.js'; import '__internal/viz/sankey/sankey'; import 'viz/themes'; import { layout as layoutBuilder } from '__internal/viz/sankey/layout'; @@ -40,7 +41,7 @@ const environment = { this.nodesGroupIndex = 1; this.labelsGroupIndex = 2; - sinon.stub(rendererModule, 'Renderer').callsFake(function() { + stubSeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer').callsFake(function() { return that.renderer; }); }, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.sankey/sankey.base.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.sankey/sankey.base.tests.js index 0dca120df794..48584317190c 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.sankey/sankey.base.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.sankey/sankey.base.tests.js @@ -1,8 +1,9 @@ import $ from 'jquery'; import { testData, createSankey, layoutBuilder, spiesLayoutBuilder, environment, find } from './commonParts/common.js'; -import rendererModule from 'viz/core/renderers/renderer'; +import rendererModule from 'viz/core/renderers/renderer_default'; import paletteModule from '__internal/viz/paletteModule'; import themeModule from 'viz/themes'; +import { spySeam } from '../../helpers/moduleSeam.js'; themeModule.registerTheme({ name: 'test-theme', @@ -558,7 +559,7 @@ QUnit.test('Resize', function(assert) { }); QUnit.test('Palette', function(assert) { - sinon.spy(paletteModule, 'createPalette'); + spySeam(paletteModule, 'createPalette', 'DEBUG_set_createPalette'); createSankey({ dataSource: [{ source: 'A', target: 'Z', weight: 1 }, { source: 'B', target: 'Z', weight: 1 }], @@ -737,7 +738,7 @@ QUnit.test('Update color of links', function(assert) { }); QUnit.test('Update palette', function(assert) { - sinon.spy(paletteModule, 'createPalette'); + spySeam(paletteModule, 'createPalette', 'DEBUG_set_createPalette'); const sankey = createSankey({ dataSource: [{ source: 'A', target: 'Z', weight: 1 }], diff --git a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/baseSparklineTooltipEvents.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/baseSparklineTooltipEvents.tests.js index 6eaa14b4626b..ee165c407d14 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/baseSparklineTooltipEvents.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/baseSparklineTooltipEvents.tests.js @@ -18,9 +18,9 @@ $('
') .appendTo(fixture); QUnit.begin(function() { - rendererModule.Renderer = function() { + rendererModule.DEBUG_set_Renderer(function() { return new Renderer(); - }; + }); }); const environment = (widget) => ({ diff --git a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/bullet.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/bullet.tests.js index 7918b96cbd85..eafab5d50af4 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/bullet.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/bullet.tests.js @@ -20,9 +20,9 @@ QUnit.begin(function() { getCanvasVisibleArea: function() { return {}; } }); - translator2DModule.Translator2D = sinon.spy(function() { + translator2DModule.DEBUG_set_Translator2D(sinon.spy(function() { return currentTest().translators.pop(); - }); + })); }); QUnit.testStart(function() { @@ -34,10 +34,10 @@ QUnit.testStart(function() { translator2DModule.Translator2D.resetHistory(); }); -rendererModule.Renderer = sinon.spy(function(parameters) { +rendererModule.DEBUG_set_Renderer(sinon.spy(function(parameters) { currentTest().renderer = new Renderer(parameters); return currentTest().renderer; -}); +})); tooltipModule.DEBUG_set_tooltip(sinon.spy(function() { return currentTest().tooltip; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/bulletTooltip.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/bulletTooltip.tests.js index f7b42f70408c..4f0b8ca8d434 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/bulletTooltip.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/bulletTooltip.tests.js @@ -26,13 +26,13 @@ const StubTooltip = stubClass(tooltipModule.Tooltip, { isEnabled: function() { r tooltipModule.DEBUG_set_tooltip(function(parameters) { return new StubTooltip(parameters); }); -rendererModule.Renderer = function() { +rendererModule.DEBUG_set_Renderer(function() { return currentTest().renderer; -}; +}); -baseThemeManagerModule.BaseThemeManager = function() { +baseThemeManagerModule.DEBUG_set_BaseThemeManager(function() { return currentTest().themeManager; -}; +}); StubThemeManager.prototype.setTheme = function() { forceThemeOptions(this); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/sparkline.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/sparkline.tests.js index c6bb2c091825..3b9dc963c9ee 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/sparkline.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/sparkline.tests.js @@ -16,6 +16,7 @@ import translator2DModule from 'viz/translators/translator2d'; import seriesModule from 'viz/series/base_series'; import { DataSource } from 'common/data/data_source/data_source'; import 'viz/sparkline'; +import { stubSeam } from '../../helpers/moduleSeam.js'; $('
') .attr('id', 'container') @@ -30,17 +31,17 @@ QUnit.begin(function() { const StubSeries = Series; const StubTooltip = Tooltip; - rendererModule.Renderer = sinon.spy(function() { + rendererModule.DEBUG_set_Renderer(sinon.spy(function() { return currentTest().renderer; - }); + })); - translator2DModule.Translator2D = sinon.spy(function() { + translator2DModule.DEBUG_set_Translator2D(sinon.spy(function() { return new FakeTranslator(); - }); + })); - seriesModule.Series = sinon.spy(function() { + seriesModule.DEBUG_set_Series(sinon.spy(function() { return currentTest().series; - }); + })); tooltipModule.DEBUG_set_tooltip(sinon.spy(function() { return currentTest().tooltip; @@ -95,7 +96,7 @@ QUnit.begin(function() { return $.extend({}, environment, { beforeEach: function() { environment.beforeEach.apply(this, arguments); - this.validateData = sinon.stub(dataValidatorModule, 'validateData').callsFake(function() { + this.validateData = stubSeam(dataValidatorModule, 'validateData', 'DEBUG_set_validateData').callsFake(function() { return { arg: [{ argument: 1, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/sparklineTooltip.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/sparklineTooltip.tests.js index 2f3ae286da64..5302b5f47339 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.sparklines/sparklineTooltip.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.sparklines/sparklineTooltip.tests.js @@ -34,13 +34,13 @@ StubThemeManager.prototype.setTheme = function() { tooltipModule.DEBUG_set_tooltip(function(parameters) { return new StubTooltip(parameters); }); -rendererModule.Renderer = function() { +rendererModule.DEBUG_set_Renderer(function() { return new Renderer(); -}; +}); -baseThemeManagerModule.BaseThemeManager = function() { +baseThemeManagerModule.DEBUG_set_BaseThemeManager(function() { return currentTest().themeManager; -}; +}); function getSparklineTooltip(sparkline) { return sparkline._tooltip; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.treeMap/commonParts/common.js b/packages/devextreme/testing/tests/DevExpress.viz.treeMap/commonParts/common.js index 8d8d94d95296..d961d03ded39 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.treeMap/commonParts/common.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.treeMap/commonParts/common.js @@ -22,7 +22,7 @@ $('#test-container').css({ width: '600px', height: '400px' }); /** Create a mocked renderer for TreeMap tests */ export function createRenderer() { const renderer = new Renderer(); - rendererModule.Renderer = function() { return renderer; }; + rendererModule.DEBUG_set_Renderer(function() { return renderer; }); return renderer; }; diff --git a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/controlBar.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/controlBar.tests.js index 04f7ed9a1ff0..0ef0bf53391e 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/controlBar.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/controlBar.tests.js @@ -3,7 +3,7 @@ import { noop } from 'core/utils/common'; import { Renderer, } from '../../helpers/vizMocks.js'; -import * as controlBarModule from 'viz/vector_map/control_bar/control_bar'; +import controlBarModule from 'viz/vector_map/control_bar/control_bar'; function returnValue(value) { return function() { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/dataExchanger.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/dataExchanger.tests.js index b0d890ba6339..f3832caec158 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/dataExchanger.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/dataExchanger.tests.js @@ -1,4 +1,4 @@ -import * as dataExchangerModule from 'viz/vector_map/data_exchanger'; +import dataExchangerModule from 'viz/vector_map/data_exchanger'; QUnit.module('DataExchanger', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/dxVectorMap.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/dxVectorMap.tests.js index ffbbc3da422a..fa21a60efff8 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/dxVectorMap.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/dxVectorMap.tests.js @@ -18,6 +18,7 @@ import { } from '../../helpers/vizMocks.js'; import typeUtils from 'core/utils/type'; import plaqueModule from 'viz/core/plaque'; +import { stubSeam, spySeam } from '../../helpers/moduleSeam.js'; import '__internal/viz/vector_map/vector_map'; @@ -26,7 +27,7 @@ const stubLayersEnvironment = $.extend({}, environment, { environment.beforeEach.apply(this, arguments); this.layerCollection.stub('items').returns([]); this.tracker.on = sinon.stub().returns(noop); - sinon.stub(plaqueModule, 'Plaque').returns({ draw: sinon.stub(), hitTest: sinon.stub(), clear: sinon.stub() }); + stubSeam(plaqueModule, 'Plaque', 'DEBUG_set_Plaque').returns({ draw: sinon.stub(), hitTest: sinon.stub(), clear: sinon.stub() }); }, afterEach: function() { plaqueModule.Plaque.restore(); @@ -36,7 +37,7 @@ const stubLayersEnvironment = $.extend({}, environment, { QUnit.module('Map - elements', stubLayersEnvironment); QUnit.test('Renderer', function(assert) { - const spy = sinon.spy(rendererModule, 'Renderer'); + const spy = spySeam(rendererModule, 'Renderer', 'DEBUG_set_Renderer'); this.createMap({ pathModified: 'path-modified' }); @@ -63,7 +64,7 @@ QUnit.test('Background', function(assert) { }); QUnit.test('Layer collection', function(assert) { - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); this.createMap({ layers: [{ tag: 'layer-1', dataSource: 'data-1' }, { tag: 'layer-2', dataSource: 'data-2' }] @@ -90,7 +91,7 @@ QUnit.test('Layer collection', function(assert) { }); QUnit.test('Set bounds when data ready called. Without bounds in options', function(assert) { - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); const layers = [{ proxy: { tag: 'p1', getBounds: function() { return [0, 0, 10, 10]; } }, getData: function() { return { count: function() { return 0; } }; } @@ -113,7 +114,7 @@ QUnit.test('Set bounds when data ready called. Without bounds in options', funct }); QUnit.test('Projection by data. Default bounds are include common bounds', function(assert) { - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); const layers = [{ proxy: { tag: 'p1', getBounds: function() { return [0, 0, 10, 10]; } }, getData: function() { return { count: function() { return 0; } }; } @@ -134,7 +135,7 @@ QUnit.test('Projection by data. Default bounds are include common bounds', funct }); QUnit.test('Projection by data. Without projection in options', function(assert) { - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); const layers = [{ proxy: { tag: 'p1', getBounds: function() { return [0, 0, 10, 10]; } }, getData: function() { return { count: function() { return 0; } }; } @@ -157,7 +158,7 @@ QUnit.test('Projection by data. Without projection in options', function(assert) }); QUnit.test('Projection by data. Projection in options', function(assert) { - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); const layers = [{ proxy: { tag: 'p1', getBounds: function() { return [0, 0, 10, 10]; } }, getData: function() { return { count: function() { return 0; } }; } @@ -178,7 +179,7 @@ QUnit.test('Projection by data. Projection in options', function(assert) { }); QUnit.test('Bounds by data. Empty bbox', function(assert) { - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); const layers = []; this.layerCollection.stub('items').returns(layers); @@ -195,7 +196,7 @@ QUnit.test('Bounds by data. Empty bbox', function(assert) { }); QUnit.test('Set bounds when data ready called. With bounds in options', function(assert) { - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); const layers = [{ proxy: { tag: 'p1', getBounds: function() { return [0, 0, 10, 10]; } }, getData: function() { @@ -232,7 +233,7 @@ QUnit.test('Layer collection - object option', function(assert) { }); QUnit.test('Projection', function(assert) { - const spy = sinon.spy(projectionModule, 'Projection'); + const spy = spySeam(projectionModule, 'Projection', 'DEBUG_set_Projection'); this.createMap({ projection: 'projection', @@ -256,7 +257,7 @@ QUnit.test('Projection', function(assert) { }); QUnit.test('DataExchanger', function(assert) { - const spy = sinon.spy(dataExchangerModule, 'DataExchanger'); + const spy = spySeam(dataExchangerModule, 'DataExchanger', 'DEBUG_set_DataExchanger'); this.createMap(); @@ -264,7 +265,7 @@ QUnit.test('DataExchanger', function(assert) { }); QUnit.test('GestureHandler', function(assert) { - const spy = sinon.spy(gestureHandlerModule, 'GestureHandler'); + const spy = spySeam(gestureHandlerModule, 'GestureHandler', 'DEBUG_set_GestureHandler'); this.createMap({ panningEnabled: 1, @@ -282,7 +283,7 @@ QUnit.test('GestureHandler', function(assert) { }); QUnit.test('LayoutControl', function(assert) { - const spy = sinon.spy(layoutModule, 'LayoutControl'); + const spy = spySeam(layoutModule, 'LayoutControl', 'DEBUG_set_LayoutControl'); const map = this.createMap({ layers: {} }); @@ -297,7 +298,7 @@ QUnit.test('LayoutControl', function(assert) { }); QUnit.test('Tracker', function(assert) { - const spy = sinon.spy(trackerModule, 'Tracker'); + const spy = spySeam(trackerModule, 'Tracker', 'DEBUG_set_Tracker'); this.createMap({ touchEnabled: 0, @@ -315,7 +316,7 @@ QUnit.test('Tracker', function(assert) { }); QUnit.test('Control bar', function(assert) { - const spy = sinon.spy(controlBarModule, 'ControlBar'); + const spy = spySeam(controlBarModule, 'ControlBar', 'DEBUG_set_ControlBar'); this.themeManager.theme.withArgs('controlBar').returns({ theme: 'control-bar' }); this.createMap({ @@ -339,7 +340,7 @@ QUnit.test('Control bar', function(assert) { }); QUnit.test('Legends', function(assert) { - const spy = sinon.spy(legendModule, 'LegendsControl'); + const spy = spySeam(legendModule, 'LegendsControl', 'DEBUG_set_LegendsControl'); const map = this.createMap({ legends: { @@ -362,7 +363,7 @@ QUnit.test('Legends', function(assert) { }); QUnit.test('TooltipViewer', function(assert) { - const spy = sinon.spy(tooltipViewerModule, 'TooltipViewer'); + const spy = spySeam(tooltipViewerModule, 'TooltipViewer', 'DEBUG_set_TooltipViewer'); this.createMap(); @@ -437,8 +438,8 @@ QUnit.test('Should created group for annotations', function(assert) { }); QUnit.test('Should created group for annotations before controll bar and legend', function(assert) { - const spyLegend = sinon.spy(legendModule, 'LegendsControl'); - const spyControlBar = sinon.spy(controlBarModule, 'ControlBar'); + const spyLegend = spySeam(legendModule, 'LegendsControl', 'DEBUG_set_LegendsControl'); + const spyControlBar = spySeam(controlBarModule, 'ControlBar', 'DEBUG_set_ControlBar'); this.createMap(); assert.ok(this.renderer.g.returnValues[1].attr.calledBefore(spyControlBar)); @@ -548,7 +549,7 @@ QUnit.test('Applying bounds by data', function(assert) { }; } }]; - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); this.createMap({ getBoundsFromData: true }); @@ -1019,7 +1020,7 @@ QUnit.module('drawn', stubLayersEnvironment); QUnit.test('call drawn after layer collection ready', function(assert) { const onDrawn = sinon.spy(); - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); this.createMap({ onDrawn: onDrawn }); diff --git a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/map.elementsInteraction.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/map.elementsInteraction.tests.js index 817d22978f41..93bba032c0f1 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/map.elementsInteraction.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/map.elementsInteraction.tests.js @@ -6,6 +6,7 @@ import { } from './vectorMapParts/commons.js'; import mapLayerModule from 'viz/vector_map/map_layer'; import projectionModule from 'viz/vector_map/projection.main'; +import { spySeam } from '../../helpers/moduleSeam.js'; import resizeCallbacks from 'core/utils/resize_callbacks'; import { implementationsMap } from 'core/utils/size'; import { @@ -23,7 +24,7 @@ QUnit.module('Map - projection events', $.extend({}, environment, { QUnit.test('On center', function(assert) { const onCenterChanged = sinon.spy(); - const spy = sinon.spy(projectionModule, 'Projection'); + const spy = spySeam(projectionModule, 'Projection', 'DEBUG_set_Projection'); this.createMap({ onCenterChanged: onCenterChanged }); spy.lastCall.args[0].centerChanged('test-center'); @@ -33,7 +34,7 @@ QUnit.test('On center', function(assert) { QUnit.test('On zoom', function(assert) { const onZoomFactorChanged = sinon.spy(); - const spy = sinon.spy(projectionModule, 'Projection'); + const spy = spySeam(projectionModule, 'Projection', 'DEBUG_set_Projection'); this.createMap({ onZoomFactorChanged: onZoomFactorChanged }); spy.lastCall.args[0].zoomChanged('test-zoom'); @@ -43,7 +44,7 @@ QUnit.test('On zoom', function(assert) { QUnit.module('Map - event trigger interaction', $.extend({}, environment, { createMap: function() { - const spy = sinon.spy(mapLayerModule, 'MapLayerCollection'); + const spy = spySeam(mapLayerModule, 'MapLayerCollection', 'DEBUG_set_MapLayerCollection'); environment.createMap.apply(this, arguments); this.eventTrigger = spy.lastCall.args[0].eventTrigger; }, diff --git a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/mapLayer_new.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/mapLayer_new.tests.js index ee960f4526c4..afd83b5c966d 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/mapLayer_new.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/mapLayer_new.tests.js @@ -27,9 +27,9 @@ titleModule.DEBUG_set_title(stubClass(titleModule.Title, { })); tooltipModule.DEBUG_set_tooltip(stubClass(tooltipModule.Tooltip)); exportMenuModule.DEBUG_set_ExportMenu(stubClass(exportMenuModule.ExportMenu)); // TODO maybe if you test layer - you should create exact layer? loadingIndicatorModule.DEBUG_set_LoadingIndicator(stubClass(loadingIndicatorModule.LoadingIndicator)); -controlBarModule.ControlBar = stubClass(controlBarModule.ControlBar); -legendModule.LegendsControl = stubClass(legendModule.LegendsControl); -tooltipViewerModule.TooltipViewer = stubClass(tooltipViewerModule.TooltipViewer); +controlBarModule.DEBUG_set_ControlBar(stubClass(controlBarModule.ControlBar)); +legendModule.DEBUG_set_LegendsControl(stubClass(legendModule.LegendsControl)); +tooltipViewerModule.DEBUG_set_TooltipViewer(stubClass(tooltipViewerModule.TooltipViewer)); const simpleProjection = projection({ aspectRatio: 4 / 3, @@ -68,7 +68,7 @@ const createData = function(featureType, items) { const environment = { beforeEach: function() { const renderer = this.renderer = new Renderer(); - rendererModule.Renderer = function() { return renderer; }; + rendererModule.DEBUG_set_Renderer(function() { return renderer; }); }, createLayer: function(options) { @@ -473,7 +473,7 @@ QUnit.test('Line labels', function(assert) { QUnit.module('Layers management', { beforeEach: function() { const renderer = this.renderer = new Renderer(); - rendererModule.Renderer = function() { return renderer; }; + rendererModule.DEBUG_set_Renderer(function() { return renderer; }); }, createLayers: function(options) { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/tracker.tests.js b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/tracker.tests.js index d28f0c618e0e..142bbc7985ba 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/tracker.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/tracker.tests.js @@ -13,6 +13,7 @@ import { import trackerModule from 'viz/vector_map/tracker'; import { _TESTS_eventEmitterMethods } from '__internal/viz/vector_map/event_emitter'; import animationFrame from '__internal/common/core/animation/frameModule'; +import { stubSeam } from '../../helpers/moduleSeam.js'; const FOCUS_OFF_DELAY = 100; @@ -55,8 +56,8 @@ const environment = { $.each(this.stubbedCallbacks || [], $.proxy(function(_, name) { this[name] = sinon.stub(); }, this)); - this.requestAnimationFrameStub = sinon.stub(animationFrame, 'requestAnimationFrame').callsFake(noop); - this.cancelAnimationFrameStub = sinon.stub(animationFrame, 'cancelAnimationFrame').callsFake(noop); + this.requestAnimationFrameStub = stubSeam(animationFrame, 'requestAnimationFrame', 'DEBUG_set_requestAnimationFrame').callsFake(noop); + this.cancelAnimationFrameStub = stubSeam(animationFrame, 'cancelAnimationFrame', 'DEBUG_set_cancelAnimationFrame').callsFake(noop); this.clock = sinon.useFakeTimers(); }, afterEach: function() { diff --git a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/vectorMapParts/commons.js b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/vectorMapParts/commons.js index 15850d90551d..a26c73e7a5b5 100644 --- a/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/vectorMapParts/commons.js +++ b/packages/devextreme/testing/tests/DevExpress.viz.vectorMap/vectorMapParts/commons.js @@ -48,21 +48,21 @@ StubThemeManager.prototype.setTheme = function() { }; function stubComponentConstructors(test) { - rendererModule.Renderer = returnValue(test.renderer); + rendererModule.DEBUG_set_Renderer(returnValue(test.renderer)); titleModule.DEBUG_set_title(returnValue(test.title)); tooltipModule.DEBUG_set_tooltip(returnValue(test.tooltip)); exportModule.DEBUG_set_ExportMenu(returnValue(test.exportMenu)); - projectionModule.Projection = returnValue(test.projection); - controlBarModule.ControlBar = returnValue(test.controlBar); - gestureHandlerModule.GestureHandler = returnValue(test.gestureHandler); - trackerModule.Tracker = returnValue(test.tracker); - themeManagerModule.BaseThemeManager = returnValue(test.themeManager); - dataExchangerModule.DataExchanger = returnValue(test.dataExchanger); - legendModule.LegendsControl = returnValue(test.legendsControl); - layoutModule.LayoutControl = returnValue(test.layoutControl); - mapLayerModule.MapLayerCollection = returnValue(test.layerCollection); - tooltipViewerModule.TooltipViewer = returnValue(test.tooltipViewer); + projectionModule.DEBUG_set_Projection(returnValue(test.projection)); + controlBarModule.DEBUG_set_ControlBar(returnValue(test.controlBar)); + gestureHandlerModule.DEBUG_set_GestureHandler(returnValue(test.gestureHandler)); + trackerModule.DEBUG_set_Tracker(returnValue(test.tracker)); + themeManagerModule.DEBUG_set_BaseThemeManager(returnValue(test.themeManager)); + dataExchangerModule.DEBUG_set_DataExchanger(returnValue(test.dataExchanger)); + legendModule.DEBUG_set_LegendsControl(returnValue(test.legendsControl)); + layoutModule.DEBUG_set_LayoutControl(returnValue(test.layoutControl)); + mapLayerModule.DEBUG_set_MapLayerCollection(returnValue(test.layerCollection)); + tooltipViewerModule.DEBUG_set_TooltipViewer(returnValue(test.tooltipViewer)); } export { stubComponentConstructors }; diff --git a/packages/devextreme/testing/tests/DevExpress/color.tests.js b/packages/devextreme/testing/tests/DevExpress/color.tests.js index 52f75ff1ba14..0eeb0af899bd 100644 --- a/packages/devextreme/testing/tests/DevExpress/color.tests.js +++ b/packages/devextreme/testing/tests/DevExpress/color.tests.js @@ -1,4 +1,4 @@ -const Color = require('color'); +import Color from 'color'; QUnit.module('Colors parsing', { beforeEach: function() { diff --git a/packages/devextreme/testing/tests/Memory Leaks/vizWidgets.tests.js b/packages/devextreme/testing/tests/Memory Leaks/vizWidgets.tests.js index 4799c4cef71f..56edcb6d686e 100644 --- a/packages/devextreme/testing/tests/Memory Leaks/vizWidgets.tests.js +++ b/packages/devextreme/testing/tests/Memory Leaks/vizWidgets.tests.js @@ -1,4 +1,4 @@ -window.DevExpress = { viz: { map: { sources: {} } } }; +window.DevExpress = window.DevExpress || { viz: { map: { sources: {} } } }; import $ from 'core/renderer'; import { getWidth, getHeight, setWidth, setHeight } from 'core/utils/size'; @@ -18,6 +18,8 @@ import 'viz/tree_map'; import '/packages/devextreme/artifacts/js/vectormap-data/world.js'; import '/packages/devextreme/artifacts/js/vectormap-data/usa.js'; +const DevExpress = window.DevExpress; + const chartTestsSignature = { getInitOptions() { return { diff --git a/packages/nx-infra-plugin/AGENTS.md b/packages/nx-infra-plugin/AGENTS.md index a54a48137d65..d5b7ab411526 100644 --- a/packages/nx-infra-plugin/AGENTS.md +++ b/packages/nx-infra-plugin/AGENTS.md @@ -65,3 +65,24 @@ Each behavior is owned by exactly ONE executor's canonical tests; consumers must 3. Preserve exact functional parity. Verify with the executor's e2e spec before and after. 4. Update consumer imports in one batch. 5. Run the full validation pipeline. + +## Former gulp tasks (gulp fully removed) + +`gulpfile.js`, `build/gulp/`, and all gulp dependencies have been deleted outright — devextreme no longer depends on gulp or ships a gulp CLI. The Nx-consumed build assets that used to live under `build/gulp/` (`transpile-config.js`, `modules_metadata.json`, the `*.jst` templates) were relocated to purpose-named folders directly under `build/` (`build/transpile-config.js`, `build/modules_metadata.json`, `build/localization-templates/`, `build/vectormap-templates/`). The table below is a historical reference mapping each removed gulp task to its Nx replacement: + +| Former gulp task | Nx target | Notes | +| --------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `clean` | `clean:artifacts` | Uses `devextreme-nx-infra-plugin:clean` with `excludePatterns` to preserve `artifacts/css`, `artifacts/npm/devextreme/package.json`, and `artifacts/npm/devextreme-dist`. Run directly via `pnpm nx clean:artifacts devextreme`. | +| `bundler-config-watch` | `build:devextreme-bundler-config:watch` | Uses `devextreme-nx-infra-plugin:concatenate-files` in `watch` mode (chokidar over the `build/bundle-templates/modules/parts` sources) with `additionalPasses` so a change rebuilds both `dx.custom.js` and the derived `dx.custom.config.js`, matching the old gulp `bundler-config` chain. `cache: false`. | +| `bundler-config` (non-watch) | `build:devextreme-bundler-config` | Run via `pnpm nx build:devextreme-bundler-config devextreme` (add `-c prod` for parity with the old uglified variant). | +| `generate-community-locales` | `build:community-localization` | Uses `devextreme-nx-infra-plugin:generate-community-locales` to normalize `js/localization/messages/*.json` against `en.json` in place (fills translations, English fallback for missing/`TODO` values, escapes quotes, inherits en's key order/formatting). Target is `cache: false` with no `outputs` (input dir == output dir — a source-normalization task, not a cached build artifact). Run via `pnpm nx build:community-localization devextreme`. | +| `test-env` | `test-env` | Uses `nx:run-commands` to wrap the existing `node ./testing/launch` script (compiles the QUnit runner, starts the test server on port 20060, opens the browser). `cache: false`, no outputs (long-running server). `cwd` is `{projectRoot}`. | +| `transpile-watch` | `build:transpile:watch` | `nx:run-commands` (parallel, `cache: false`) fanning out to the incremental watch targets `build:ts:internal:watch` (TypeScript watch program emitting `dist_ts`), `build:npm:esm:watch -c qunit`, and `build:npm:esm:internal:watch -c qunit`. Together these keep `artifacts/transpiled-esm-npm/esm` fresh for the native-ESM QUnit runner; `-c qunit` sets `removeDebug: false` so the `/// #DEBUG` seams tests stub through survive. The former CJS watchers (`build:cjs:watch` / `build:cjs:internal:watch`, feeding `artifacts/transpiled` and `artifacts/transpiled-renovation-npm`) were removed together with SystemJS — CJS is now built only by the non-watch `build:transpile` default/`internal` configurations. Watch capability lives in the `babel-transform` (per-file, chokidar + debounce, `watch` option) and `build-typescript` (`ts.createWatchProgram`, `watch` option) executors via the shared `src/utils/watch.ts` helper. `babel-transform` watch is intentionally file-level incremental and does **not** do an initial full transform — it relies on a preceding build having already populated its source directory (mirroring gulp-watch, which fed babel from a single in-memory TS-compiler stream with no such gap). Because `build:transpile`'s last step (`clean:dist-ts`) deletes `artifacts/dist_ts` — the exact directory `build:npm:esm:internal:watch` reads from and that runs right before `dev-watch` starts — `build:transpile:watch` itself (not the leaf watch target, to avoid two parallel invocations each re-running the compile) declares `dependsOn: ["build:ts:internal"]`, so a real (or Nx-cache-restored) TS compile always repopulates `dist_ts/__internal` once, up front, before any of the fanned-out watch processes start; without it, whichever of the parallel TS-watch/babel-watch processes started first would decide — nondeterministically — whether the initial compile burst is picked up. | +| `transpile-tests` | `transpile:tests` | Uses `devextreme-nx-infra-plugin:babel-transform` with the flat (keyless) `./testing/tests.babelrc.json` config to transpile `testing/**/*.js` in place. `dependsOn: ["build:devextreme-bundler-config"]` reproduces the old gulp `series('bundler-config', …)` prerequisite. `cache: false` (in-place source transform, not a cached artifact). Run via `pnpm nx transpile:tests devextreme`. | +| `transpile-systemjs` | _(removed)_ | QUnit uses native ESM + import maps only. `build:systemjs` and `testing/systemjs-builder.js` are gone. ESM for QUnit is `artifacts/transpiled-esm-npm` from `build:transpile -c ci` (via `build:dev` / CI) or the dedicated `build:qunit-esm` target. | +| `js-bundles-watch` | _(removed)_ | Was `bundle:watch` (webpack watch via `devextreme-nx-infra-plugin:bundle` with `watch: true`). Dropped from `dev-watch` with the SystemJS removal — QUnit loads ESM artifacts directly and no longer needs watched debug bundles. Use the one-shot `bundle:debug` when a bundle is actually needed. | +| `js-bundles-prod` | `bundle:prod` | Use `pnpm nx bundle:prod devextreme` (add `-c production` for uglify/dist parity). | +| `js-bundles-debug` | `bundle:debug` | Use `pnpm nx bundle:debug devextreme` (add `-c production` for uglify/dist parity). | +| `dev-watch` | `dev-watch` | `nx:run-commands` (parallel, `cache: false`, `cwd: {projectRoot}`) fanning out to three watch targets — `build:transpile:watch`, `build:devextreme-bundler-config:watch`, `test-env`. The old gulp equivalent was `gulp.parallel('transpile-watch', 'bundler-config-watch', 'js-bundles-watch', 'test-env')`; the `js-bundles-watch` leg (`bundle:watch`) was dropped with the SystemJS removal. | +| `dev` | `dev` | `nx:run-commands` with `dependsOn: ["build:dev"]`; its own command then runs `pnpm nx dev-watch devextreme`, reproducing the old `gulp.series('default-dev', 'dev-watch')` build-then-watch sequencing. Reuses `build:dev` as the initial-build step rather than replicating gulp's lighter `default-dev` variant (which skipped `clean` and the one-shot `js-bundles-debug` build) — an accepted small startup-cost tradeoff. | +| `default` / `main-batch` / `misc-batch` | `build`, `build-dist`, `build:dev` | Gulp orchestration (`gulp.series` / `gulp-multi-process`) is gone. Native Nx `build` covers the non-uglify default batch; `build -c production` (+ `build:npm -c production`) matches the old `gulp default --uglify`; `build -c production-internal` matches uglify + `BUILD_INTERNAL_PACKAGE`; `build -c testing` matches `BUILD_TEST_INTERNAL_PACKAGE`. `build-dist` is a thin wrapper (`pnpm nx run devextreme:build -c production`, `-c internal` → `production-internal`). `build:dev` is the former `DEVEXTREME_TEST_CI` path: clean → localization → `build:transpile -c ci` → parallel `build:vectormap,copy:vendor` (skips all CJS transpile/bundle steps, prod bundles, aspnet, declarations, npm, license checks). npm scripts `build:dev` / `build-dist` / `clean` / `transpile-tests` call Nx directly. | diff --git a/packages/sbom/pnpm-lock.yaml b/packages/sbom/pnpm-lock.yaml index 9c823e841f73..fd286ee42947 100644 --- a/packages/sbom/pnpm-lock.yaml +++ b/packages/sbom/pnpm-lock.yaml @@ -8,7 +8,8 @@ overrides: tar@<7.5.19: ^7.5.19 brace-expansion@<5.0.8: ^5.0.8 body-parser@>=2.0.0 <2.3.0: ^2.3.0 - fast-uri@>=3.0.0 <3.1.5: ^3.1.5 + fast-uri@>=3.0.0 <3.1.6: ^3.1.6 + qs@>=2.2.5 <6.16.0: ^6.16.0 undici@>=7.23.0 <7.29.0: ^7.29.0 brace-expansion@>=3.0.0 <5.0.9: ^5.0.9 @@ -422,8 +423,8 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + fast-uri@3.1.7: + resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} finalhandler@1.1.2: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} @@ -691,8 +692,8 @@ packages: resolution: {integrity: sha512-WPn+h9RGEExOKdu4bsF4HksG/uzd3cFq3MFtq8PsFeExPse5Ha/VOjQNyHhjboBFwGXGev6muJYTSPAOkROq2g==} engines: {node: '>=18'} - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} quick-lru@5.1.1: @@ -1157,7 +1158,7 @@ snapshots: ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 + fast-uri: 3.1.7 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -1183,7 +1184,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.16.0 raw-body: 3.0.2 type-is: 2.1.0 transitivePeerDependencies: @@ -1381,7 +1382,7 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-uri@3.1.5: {} + fast-uri@3.1.7: {} finalhandler@1.1.2: dependencies: @@ -1661,7 +1662,7 @@ snapshots: transitivePeerDependencies: - supports-color - qs@6.15.3: + qs@6.16.0: dependencies: es-define-property: 1.0.1 side-channel: 1.1.1 diff --git a/packages/sbom/pnpm-workspace.yaml b/packages/sbom/pnpm-workspace.yaml index 9f665ac75ee6..f2a0edddd8fc 100644 --- a/packages/sbom/pnpm-workspace.yaml +++ b/packages/sbom/pnpm-workspace.yaml @@ -8,6 +8,7 @@ overrides: tar@<7.5.19: ^7.5.19 brace-expansion@<5.0.8: ^5.0.8 body-parser@>=2.0.0 <2.3.0: ^2.3.0 - fast-uri@>=3.0.0 <3.1.5: ^3.1.5 + fast-uri@>=3.0.0 <3.1.6: ^3.1.6 + qs@>=2.2.5 <6.16.0: ^6.16.0 undici@>=7.23.0 <7.29.0: ^7.29.0 brace-expansion@>=3.0.0 <5.0.9: ^5.0.9 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aed252f8908c..41e647c51ba8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,8 +83,8 @@ catalogs: specifier: 8.52.0 version: 8.52.0 axe-core: - specifier: 4.11.3 - version: 4.11.3 + specifier: 4.13.0 + version: 4.13.0 devexpress-diagram: specifier: 2.2.31 version: 2.2.31 @@ -179,15 +179,15 @@ catalogs: overrides: basic-ftp@<5.3.1: '>=5.3.1' - js-yaml@<4.3.1: ^4.3.1 + js-yaml@<4.3.2: ^4.3.2 rollup@<4.59.0: ^4.59.0 json5@<1.0.2: ^2.2.3 axios@<1.18.0: ^1.18.0 braces@<3.0.3: ^3.0.3 semver@<5.7.2: ^5.7.2 - qs: '>=6.15.2' + qs: '>=6.16.0' vite@>=7.0.0 <=7.3.4: ^7.3.5 - hono@<4.12.34: ^4.12.34 + hono@<4.13.5: ^4.13.5 minimatch@>=9.0.0 < 9.0.7: 9.0.9 picomatch@>=4.0.0 <4.0.4: 4.0.4 micromatch@<4.0.8: ^4.0.8 @@ -223,13 +223,16 @@ overrides: tar@<7.5.19: ^7.5.19 shell-quote@<=1.8.4: ^1.9.0 babel-traverse: npm:@babel/traverse@^7.23.2 - fast-uri@>=3.0.0 <3.1.5: ^3.1.5 + fast-uri@>=3.0.0 <3.1.6: ^3.1.6 '@hono/node-server@<2.0.12': ^2.0.12 dompurify@<=3.4.12: ^3.4.13 nanoid@<3.3.17: ^3.3.17 browserslist@<4.28.7: ^4.28.7 postcss-selector-parser@>=6.1.0 <6.1.3: ^6.1.3 postcss-selector-parser@>=7.1.0 <7.1.3: ^7.1.3 + fflate@>=0.8.0 <0.8.3: ^0.8.3 + colord@<2.9.4: ^2.9.4 + smol-toml@<1.7.0: ^1.7.1 patchedDependencies: testcafe-hammerhead@31.7.8: 8655c07786177d3b611a05abf6b0ea87d32796eb2601f92a800ef041095f264c @@ -261,7 +264,7 @@ importers: version: 17.0.35 axe-core: specifier: 'catalog:' - version: 4.11.3 + version: 4.13.0 chokidar: specifier: 5.0.0 version: 5.0.0 @@ -367,13 +370,13 @@ importers: dependencies: '@angular-devkit/build-angular': specifier: ^22.0.9 - version: 22.1.2(9ab32ed578a68caf92174a202d695664) + version: 22.1.2(5ff3e14daa3065d6e7c118ba510a3c83) '@angular/animations': specifier: ^22.0.8 version: 22.1.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.2)) '@angular/cli': specifier: ^22.0.9 - version: 22.1.2(@types/node@26.1.1)(chokidar@5.0.0) + version: 22.1.2(@types/node@20.19.37)(chokidar@5.0.0) '@angular/common': specifier: ^22.0.8 version: 22.1.0(@angular/core@22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) @@ -623,7 +626,7 @@ importers: version: 5.10.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)) '@testcafe-community/axe': specifier: 3.5.0 - version: 3.5.0(axe-core@4.11.3)(testcafe@3.7.5) + version: 3.5.0(axe-core@4.13.0)(testcafe@3.7.5) '@types/eslint': specifier: 8.56.12 version: 8.56.12 @@ -656,7 +659,7 @@ importers: version: 0.9.1(typescript@6.0.3)(vue@3.5.32(typescript@6.0.3)) axe-core: specifier: 'catalog:' - version: 4.11.3 + version: 4.13.0 babel-jest: specifier: 30.4.1 version: 30.4.1(@babel/core@7.29.7) @@ -680,7 +683,7 @@ importers: version: 18.0.0(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)) eslint-config-devextreme: specifier: 'catalog:' - version: 1.1.12(554dde2dfd68364add0a2758e579b8b7) + version: 1.1.12(6e359676285ac6d918269845dc7dce9a) eslint-plugin-deprecation: specifier: 3.0.0 version: 3.0.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) @@ -689,7 +692,7 @@ importers: version: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)) eslint-plugin-jest: specifier: 29.15.2 - version: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3) + version: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3) eslint-plugin-no-only-tests: specifier: 'catalog:' version: 3.3.0 @@ -722,7 +725,7 @@ importers: version: 14.1.1(supports-color@7.2.0) jest: specifier: 30.4.2 - version: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) + version: 30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) jest-environment-node: specifier: 30.4.1 version: 30.4.1 @@ -980,7 +983,7 @@ importers: version: 9.39.4(jiti@2.6.1) eslint-config-devextreme: specifier: 'catalog:' - version: 1.1.12(f43ede1c63aef08b124dbf33bc5f83fb) + version: 1.1.12(3d401928aa66bf2e591f933b2739e58c) eslint-plugin-i18n: specifier: 'catalog:' version: 2.4.0 @@ -1052,7 +1055,7 @@ importers: version: 9.39.4(jiti@2.6.1) eslint-config-devextreme: specifier: 'catalog:' - version: 1.1.12(5552857efccfb3f1ba025df865cdfd0e) + version: 1.1.12(a25cda3163463b9772ba2a773a10316c) eslint-migration-utils: specifier: workspace:* version: link:../../packages/eslint-migration-utils @@ -1085,7 +1088,7 @@ importers: devDependencies: '@babel/eslint-parser': specifier: 'catalog:' - version: 7.29.7(@babel/core@7.29.7)(eslint@9.39.4(jiti@2.6.1)) + version: 7.29.7(@babel/core@8.0.1)(eslint@9.39.4(jiti@2.6.1)) '@babel/plugin-transform-runtime': specifier: 7.29.7 version: 7.29.7(@babel/core@7.29.7) @@ -1100,7 +1103,7 @@ importers: version: 5.10.0(eslint@9.39.4(jiti@2.6.1)) '@testcafe-community/axe': specifier: 3.5.0 - version: 3.5.0(axe-core@4.11.3)(testcafe@3.7.5) + version: 3.5.0(axe-core@4.13.0)(testcafe@3.7.5) '@types/jquery': specifier: 'catalog:' version: 4.0.1 @@ -1112,7 +1115,7 @@ importers: version: 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) axe-core: specifier: 'catalog:' - version: 4.11.3 + version: 4.13.0 devextreme: specifier: workspace:* version: link:../../packages/devextreme/artifacts/npm/devextreme @@ -1127,7 +1130,7 @@ importers: version: 9.39.4(jiti@2.6.1) eslint-config-devextreme: specifier: 'catalog:' - version: 1.1.12(70f56e711195e33f9b23259278af4356) + version: 1.1.12(7dd464a0604f1d8e6bd5fce5260d6037) eslint-migration-utils: specifier: workspace:* version: link:../../packages/eslint-migration-utils @@ -1435,7 +1438,7 @@ importers: version: 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5) axe-core: specifier: 'catalog:' - version: 4.11.3 + version: 4.13.0 babel-plugin-inferno: specifier: 6.8.5 version: 6.8.5(@babel/core@7.29.7) @@ -1468,7 +1471,7 @@ importers: version: 18.0.0(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) eslint-config-devextreme: specifier: 'catalog:' - version: 1.1.12(e659abf94f261cb548f87457fcc52d78) + version: 1.1.12(25bff8f024558126068f85a113b20273) eslint-migration-utils: specifier: workspace:* version: link:../eslint-migration-utils @@ -1562,21 +1565,6 @@ importers: sinon: specifier: 18.0.1 version: 18.0.1 - systemjs: - specifier: 0.19.41 - version: 0.19.41 - systemjs-plugin-babel: - specifier: 0.0.25 - version: 0.0.25 - systemjs-plugin-css: - specifier: 0.1.37 - version: 0.1.37 - systemjs-plugin-json: - specifier: 0.3.0 - version: 0.3.0 - systemjs-plugin-text: - specifier: 0.0.11 - version: 0.0.11 terser-webpack-plugin: specifier: 5.3.17 version: 5.3.17(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(webpack@5.105.4(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)) @@ -1650,7 +1638,7 @@ importers: version: 20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@babel/eslint-parser': specifier: 'catalog:' - version: 7.29.7(@babel/core@7.29.7)(eslint@9.39.4(jiti@2.6.1)) + version: 7.29.7(@babel/core@8.0.1)(eslint@9.39.4(jiti@2.6.1)) '@eslint-stylistic/metadata': specifier: 'catalog:' version: 2.13.0 @@ -1689,7 +1677,7 @@ importers: version: 9.39.4(jiti@2.6.1) eslint-config-devextreme: specifier: 'catalog:' - version: 1.1.12(5552857efccfb3f1ba025df865cdfd0e) + version: 1.1.12(a25cda3163463b9772ba2a773a10316c) eslint-migration-utils: specifier: workspace:* version: link:../eslint-migration-utils @@ -1809,7 +1797,7 @@ importers: devDependencies: '@babel/eslint-parser': specifier: 'catalog:' - version: 7.29.7(@babel/core@7.29.7)(eslint@9.39.4(jiti@2.6.1)) + version: 7.29.7(@babel/core@8.0.1)(eslint@9.39.4(jiti@2.6.1)) '@eslint/eslintrc': specifier: 'catalog:' version: 3.3.5 @@ -1851,7 +1839,7 @@ importers: version: 18.0.0(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) eslint-config-devextreme: specifier: 'catalog:' - version: 1.1.12(e43b4562c396170e08a0bcb0bebad46e) + version: 1.1.12(a8f9319687a53bec0d2a70b73ac5ccad) eslint-migration-utils: specifier: workspace:* version: link:../eslint-migration-utils @@ -1882,7 +1870,7 @@ importers: devDependencies: '@stylistic/stylelint-plugin': specifier: 3.1.3 - version: 3.1.3(stylelint@16.22.0(typescript@6.0.3)) + version: 3.1.3(stylelint@16.22.0(typescript@5.9.3)) autoprefixer: specifier: 10.5.0 version: 10.5.0(postcss@8.5.23) @@ -1900,16 +1888,16 @@ importers: version: 1.93.3 stylelint: specifier: 'catalog:' - version: 16.22.0(typescript@6.0.3) + version: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) stylelint-config-standard-scss: specifier: 14.0.0 - version: 14.0.0(postcss@8.5.23)(stylelint@16.22.0(typescript@6.0.3)) + version: 14.0.0(postcss@8.5.23)(stylelint@16.22.0(typescript@5.9.3)) stylelint-scss: specifier: 6.10.0 - version: 6.10.0(stylelint@16.22.0(typescript@6.0.3)) + version: 6.10.0(stylelint@16.22.0(typescript@5.9.3)) ts-jest: specifier: 29.4.12 - version: 29.4.12(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@6.0.3) + version: 29.4.12(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3) packages/devextreme-themebuilder: dependencies: @@ -1961,7 +1949,7 @@ importers: version: 18.0.0(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) eslint-config-devextreme: specifier: 'catalog:' - version: 1.1.12(4c739bafb4997092ce83f049f167b131) + version: 1.1.12(ba83ebe55aa7666ff88d6674f941478c) eslint-plugin-import: specifier: 'catalog:' version: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1)) @@ -2004,7 +1992,7 @@ importers: devDependencies: '@babel/eslint-parser': specifier: 'catalog:' - version: 7.29.7(@babel/core@7.29.7)(eslint@9.39.4(jiti@2.6.1)) + version: 7.29.7(@babel/core@8.0.1)(eslint@9.39.4(jiti@2.6.1)) '@eslint-stylistic/metadata': specifier: 'catalog:' version: 2.13.0 @@ -2043,7 +2031,7 @@ importers: version: 18.0.0(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) eslint-config-devextreme: specifier: 'catalog:' - version: 1.1.12(e43b4562c396170e08a0bcb0bebad46e) + version: 1.1.12(a8f9319687a53bec0d2a70b73ac5ccad) eslint-plugin-i18n: specifier: 'catalog:' version: 2.4.0 @@ -2191,6 +2179,34 @@ importers: specifier: 'catalog:' version: 3.7.5(supports-color@7.2.0) + tools/flaky-test-reporter: + dependencies: + yargs: + specifier: 17.7.2 + version: 17.7.2 + devDependencies: + '@types/jest': + specifier: 30.0.0 + version: 30.0.0 + '@types/node': + specifier: catalog:tools + version: 20.19.37 + '@types/yargs': + specifier: 17.0.35 + version: 17.0.35 + prettier: + specifier: catalog:tools + version: 3.5.3 + ts-jest: + specifier: 29.4.12 + version: 29.4.12(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3) + ts-node: + specifier: catalog:tools + version: 10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3) + typescript: + specifier: catalog:tools + version: 5.9.3 + packages: '@adobe/css-tools@4.4.4': @@ -4405,33 +4421,16 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-parser-algorithms@2.7.1': - resolution: {integrity: sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - '@csstools/css-tokenizer': ^2.4.1 - '@csstools/css-parser-algorithms@3.0.5': resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} engines: {node: '>=18'} peerDependencies: '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-tokenizer@2.4.1': - resolution: {integrity: sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==} - engines: {node: ^14 || ^16 || >=18} - '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@csstools/media-query-list-parser@2.1.13': - resolution: {integrity: sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - '@csstools/css-parser-algorithms': ^2.7.1 - '@csstools/css-tokenizer': ^2.4.1 - '@csstools/media-query-list-parser@3.0.1': resolution: {integrity: sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==} engines: {node: '>=18'} @@ -4446,12 +4445,6 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 - '@csstools/selector-specificity@3.1.1': - resolution: {integrity: sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - postcss-selector-parser: ^6.1.3 - '@csstools/selector-specificity@5.0.0': resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} engines: {node: '>=18'} @@ -4764,7 +4757,7 @@ packages: resolution: {integrity: sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==} engines: {node: '>=20'} peerDependencies: - hono: ^4.12.34 + hono: ^4.13.5 '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} @@ -5754,6 +5747,7 @@ packages: '@ngtools/webpack@22.1.2': resolution: {integrity: sha512-5o3zbOdEHv5+wjRCMN+t6RBou28dbPkio5/rxEJBsQ9iEq11K+atsSKEaZXBMUhYr9nLYR8Pckm5Cqx2FP9R2Q==} engines: {node: ^22.22.3 || ^24.15.0 || >=26.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + deprecated: Angular's Webpack support is deprecated. Use the esbuild and Vite-based "@angular/build" package instead. peerDependencies: '@angular/compiler-cli': ^22.0.0 typescript: '>=6.0 <6.1' @@ -7576,9 +7570,6 @@ packages: '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -8668,10 +8659,6 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - asn1.js@4.10.1: resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==} @@ -8750,12 +8737,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.11.3: - resolution: {integrity: sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg==} - engines: {node: '>=4'} - - axe-core@4.12.1: - resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} + axe-core@4.13.0: + resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==} engines: {node: '>=4'} axios@1.18.1: @@ -9176,10 +9159,6 @@ packages: camel-case@4.1.2: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - camelcase-keys@7.0.2: - resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} - engines: {node: '>=12'} - camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -9482,8 +9461,8 @@ packages: resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} engines: {node: '>=18'} - colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + colord@2.10.0: + resolution: {integrity: sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==} colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -9778,10 +9757,6 @@ packages: css-select@6.0.0: resolution: {integrity: sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==} - css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-tree@3.2.1: resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -9890,18 +9865,6 @@ packages: supports-color: optional: true - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - decamelize@5.0.1: - resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} - engines: {node: '>=10'} - decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} @@ -10319,10 +10282,6 @@ packages: resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} - enhanced-resolve@5.24.3: - resolution: {integrity: sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==} - engines: {node: '>=10.13.0'} - enhanced-resolve@5.24.5: resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} engines: {node: '>=10.13.0'} @@ -10717,6 +10676,7 @@ packages: eslint@9.39.4: resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -10896,8 +10856,8 @@ packages: fast-string-width@3.0.2: resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + fast-uri@3.1.7: + resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} fast-wrap-ansi@0.2.2: resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} @@ -10931,8 +10891,8 @@ packages: fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} @@ -10941,10 +10901,6 @@ packages: file-entry-cache@10.1.4: resolution: {integrity: sha512-5XRUFc0WTtUbjfGzEwXc42tiGxQHBmtbUG1h9L2apu4SulCGN3Hqm//9D6FAolf8MYNL7f/YlJl9vy08pj5JuA==} - file-entry-cache@7.0.2: - resolution: {integrity: sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==} - engines: {node: '>=12.0.0'} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -11326,10 +11282,6 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - harmony-reflect@1.6.2: resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} @@ -11444,8 +11396,8 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - hono@4.13.0: - resolution: {integrity: sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ==} + hono@4.13.7: + resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==} engines: {node: '>=16.9.0'} hookified@1.15.1: @@ -11461,10 +11413,6 @@ packages: hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - hosted-git-info@9.0.2: resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -11695,10 +11643,6 @@ packages: resolution: {integrity: sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==} engines: {node: '>=6'} - import-lazy@4.0.0: - resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} - engines: {node: '>=8'} - import-local@3.2.0: resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} @@ -12002,10 +11946,6 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -12489,8 +12429,8 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@4.3.1: - resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true jsdom@26.1.0: @@ -12640,9 +12580,6 @@ packages: knockout@3.5.3: resolution: {integrity: sha512-6iPv8M/xDPYfsiKyEyIPcSYBt8L+FZoJS/frLT8Nq3n9kMvP+WZ6ZPehYZV5c22Qc64Ie7unmTqYqgSdCB1Taw==} - known-css-properties@0.29.0: - resolution: {integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==} - known-css-properties@0.35.0: resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==} @@ -13048,14 +12985,6 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - match-url-wildcard@0.0.4: resolution: {integrity: sha512-R1XhQaamUZPWLOPtp4ig5j+3jctN+skhgRmEQTUamMzmNtRG69QEirQs0NZKLtHMR7tzWpmtnS4Eqv65DcgXUA==} @@ -13087,9 +13016,6 @@ packages: mdast-util-to-string@3.2.0: resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} - mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} @@ -13121,10 +13047,6 @@ packages: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} - meow@10.1.5: - resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} @@ -13308,10 +13230,6 @@ packages: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -13653,10 +13571,6 @@ packages: normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -14473,6 +14387,7 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qified@0.9.1: @@ -14487,12 +14402,8 @@ packages: resolution: {integrity: sha512-ZvWjbAj4MWAj6bnCc9CnculsXnJr7eoKsvH/8rVpZbqYxP2z05HNQa43ZVwe/dVRcFxgfFHE2CkUqn0sCyLfHw==} hasBin: true - qs@6.15.2: - resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} - engines: {node: '>=0.6'} - - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + qs@6.16.0: + resolution: {integrity: sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==} engines: {node: '>=0.6'} querystring-es3@0.2.1: @@ -14509,10 +14420,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - quill-delta@5.1.0: resolution: {integrity: sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==} engines: {node: '>= 12.0.0'} @@ -14632,18 +14539,10 @@ packages: read-only-stream@2.0.0: resolution: {integrity: sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==} - read-pkg-up@8.0.0: - resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} - engines: {node: '>=12'} - read-pkg@5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} - read-pkg@6.0.0: - resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} - engines: {node: '>=12'} - readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -14691,10 +14590,6 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - redent@4.0.0: - resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} - engines: {node: '>=12'} - reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} @@ -15391,8 +15286,8 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - smol-toml@1.6.1: - resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} + smol-toml@1.8.0: + resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==} engines: {node: '>= 18'} socket.io-adapter@2.5.8: @@ -15750,11 +15645,6 @@ packages: peerDependencies: stylelint: ^16.0.2 - stylelint@15.11.0: - resolution: {integrity: sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==} - engines: {node: ^14.13.1 || >=16.0.0} - hasBin: true - stylelint@16.22.0: resolution: {integrity: sha512-SVEMTdjKNV4ollUrIY9ordZ36zHv2/PHzPjfPMau370MlL2VYXeLgSNMMiEbLGRO8RmD2R8/BVUeF2DfnfkC0w==} engines: {node: '>=18.12.0'} @@ -15819,21 +15709,6 @@ packages: syntax-error@1.4.0: resolution: {integrity: sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==} - systemjs-plugin-babel@0.0.25: - resolution: {integrity: sha512-RMKSizWWlw4+IpDB385ugxn7Owd9W+HEtjYDQ6yO1FpsnER/vk6FbXRweUF+mvRi6EHgk8vDdUdtui7ReDwX3w==} - - systemjs-plugin-css@0.1.37: - resolution: {integrity: sha512-wCGG62zYXuOlNji5FlBjeMFAnLeAO/HQmFg+8UBX/mlHoAKLHlGFYRstlhGKibRU2oxk/BH9DaihOuhhNLi7Kg==} - - systemjs-plugin-json@0.3.0: - resolution: {integrity: sha512-GPHZgc6bGIDIQsoNAkhthddApy4ErFhy30rMBrEepkoDidhs0JeSk821htUOSrtqJjnUPBf2gge325B5GfsW0w==} - - systemjs-plugin-text@0.0.11: - resolution: {integrity: sha512-buWE27P6iM3WZYXcsiy6+fiulQ/x+Puux4ni5ejTlcUgqUg3/sUvoAUZ4GGPACC0acjxmnaCt3kHb0+uNs1ekw==} - - systemjs@0.19.41: - resolution: {integrity: sha512-8E9CmZ01dIr52po2LNhc3QuKyeSTyvQfshHMi3lekSbEOdR9OAUOX2X+wPKunZX3CpudM6w3r8eTCjGQrK79Wg==} - table@6.9.0: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} @@ -16060,10 +15935,6 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - trim-newlines@4.1.1: - resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} - engines: {node: '>=12'} - trim-trailing-lines@2.1.0: resolution: {integrity: sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==} @@ -16932,9 +16803,6 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - when@3.7.8: - resolution: {integrity: sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==} - which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -17190,9 +17058,6 @@ packages: zod@4.1.13: resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} - zod@4.4.2: - resolution: {integrity: sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==} - zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} @@ -17498,13 +17363,13 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-angular@22.1.2(9ab32ed578a68caf92174a202d695664)': + '@angular-devkit/build-angular@22.1.2(5ff3e14daa3065d6e7c118ba510a3c83)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2201.2(chokidar@5.0.0) '@angular-devkit/build-webpack': 0.2201.2(chokidar@5.0.0)(webpack-dev-server@5.2.6(tslib@2.8.1)(webpack@5.109.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(postcss@8.5.23)))(webpack@5.109.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(postcss@8.5.23)) '@angular-devkit/core': 22.1.2(chokidar@5.0.0) - '@angular/build': 22.1.2(7d59fb6e9d8777bab2b063c5a6c353b4) + '@angular/build': 22.1.2(3e08e0bd92b41fa22a30f22cc0508f3a) '@angular/compiler-cli': 22.1.0(@angular/compiler@22.1.0)(typescript@6.0.3) '@babel/core': 8.0.1 '@babel/generator': 8.0.0 @@ -17841,7 +17706,7 @@ snapshots: - tsx - yaml - '@angular/build@22.1.2(7d59fb6e9d8777bab2b063c5a6c353b4)': + '@angular/build@22.1.2(3e08e0bd92b41fa22a30f22cc0508f3a)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2201.2(chokidar@5.0.0) @@ -17850,8 +17715,8 @@ snapshots: '@babel/core': 8.0.1 '@babel/helper-annotate-as-pure': 8.0.0 '@babel/helper-split-export-declaration': 7.24.7 - '@inquirer/confirm': 6.1.1(@types/node@26.1.1) - '@vitejs/plugin-basic-ssl': 2.3.0(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0)) + '@inquirer/confirm': 6.1.1(@types/node@20.19.37) + '@vitejs/plugin-basic-ssl': 2.3.0(vite@8.1.5(@types/node@20.19.37)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0)) beasties: 0.4.3 browserslist: 4.28.7 esbuild: 0.28.1 @@ -17871,7 +17736,7 @@ snapshots: tinyglobby: 0.2.17 tslib: 2.8.1 typescript: 6.0.3 - vite: 8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0) + vite: 8.1.5(@types/node@20.19.37)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0) watchpack: 2.5.2 optionalDependencies: '@angular/core': 22.1.0(@angular/compiler@22.1.0)(rxjs@7.8.2)(zone.js@0.16.2) @@ -17975,13 +17840,13 @@ snapshots: - chokidar - supports-color - '@angular/cli@22.1.2(@types/node@26.1.1)(chokidar@5.0.0)': + '@angular/cli@22.1.2(@types/node@20.19.37)(chokidar@5.0.0)': dependencies: '@angular-devkit/architect': 0.2201.2(chokidar@5.0.0) '@angular-devkit/core': 22.1.2(chokidar@5.0.0) '@angular-devkit/schematics': 22.1.2(chokidar@5.0.0) - '@inquirer/prompts': 8.5.2(@types/node@26.1.1) - '@listr2/prompt-adapter-inquirer': 4.2.4(@inquirer/prompts@8.5.2(@types/node@26.1.1))(@types/node@26.1.1)(listr2@10.2.2) + '@inquirer/prompts': 8.5.2(@types/node@20.19.37) + '@listr2/prompt-adapter-inquirer': 4.2.4(@inquirer/prompts@8.5.2(@types/node@20.19.37))(@types/node@20.19.37)(listr2@10.2.2) '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@schematics/angular': 22.1.2(chokidar@5.0.0) jsonc-parser: 3.3.1 @@ -18093,7 +17958,7 @@ snapshots: '@standard-schema/spec': 1.1.0 rxjs: 7.8.2 tslib: 2.8.1 - zod: 4.4.2 + zod: 4.4.3 '@angular/platform-browser-dynamic@20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.27)(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.27(@angular/common@20.3.27(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.27(@angular/compiler@20.3.27)(rxjs@7.8.2)(zone.js@0.15.1)))': dependencies: @@ -18235,6 +18100,14 @@ snapshots: eslint-visitor-keys: 2.1.0 semver: 6.3.1 + '@babel/eslint-parser@7.29.7(@babel/core@8.0.1)(eslint@9.39.4(jiti@2.6.1))': + dependencies: + '@babel/core': 8.0.1 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 9.39.4(jiti@2.6.1) + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + '@babel/generator@7.28.3': dependencies: '@babel/parser': 7.29.7 @@ -20456,23 +20329,12 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1)': - dependencies: - '@csstools/css-tokenizer': 2.4.1 - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-tokenizer@2.4.1': {} - '@csstools/css-tokenizer@3.0.4': {} - '@csstools/media-query-list-parser@2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1)': - dependencies: - '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) - '@csstools/css-tokenizer': 2.4.1 - '@csstools/media-query-list-parser@3.0.1(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) @@ -20483,10 +20345,6 @@ snapshots: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/selector-specificity@3.1.1(postcss-selector-parser@6.1.4)': - dependencies: - postcss-selector-parser: 6.1.4 - '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.5)': dependencies: postcss-selector-parser: 7.1.5 @@ -20716,7 +20574,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.3.1 + js-yaml: 4.3.2 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -20757,9 +20615,9 @@ snapshots: '@harperfast/extended-iterable@1.0.3': optional: true - '@hono/node-server@2.0.12(hono@4.13.0)': + '@hono/node-server@2.0.12(hono@4.13.7)': dependencies: - hono: 4.13.0 + hono: 4.13.7 '@humanfs/core@0.19.2': dependencies: @@ -20811,14 +20669,14 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/checkbox@5.2.1(@types/node@26.1.1)': + '@inquirer/checkbox@5.2.1(@types/node@20.19.37)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/confirm@5.1.14(@types/node@20.11.17)': dependencies: @@ -20855,12 +20713,12 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/confirm@6.1.1(@types/node@26.1.1)': + '@inquirer/confirm@6.1.1(@types/node@20.19.37)': dependencies: - '@inquirer/core': 11.2.1(@types/node@26.1.1) - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/core@10.3.2(@types/node@20.11.17)': dependencies: @@ -20901,17 +20759,17 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/core@11.2.1(@types/node@26.1.1)': + '@inquirer/core@11.2.1(@types/node@20.19.37)': dependencies: '@inquirer/ansi': 2.0.7 '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@20.19.37) cli-width: 4.1.0 fast-wrap-ansi: 0.2.2 mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/editor@4.2.23(@types/node@20.11.17)': dependencies: @@ -20937,13 +20795,13 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/editor@5.2.2(@types/node@26.1.1)': + '@inquirer/editor@5.2.2(@types/node@20.19.37)': dependencies: - '@inquirer/core': 11.2.1(@types/node@26.1.1) - '@inquirer/external-editor': 3.0.3(@types/node@26.1.1) - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) + '@inquirer/external-editor': 3.0.3(@types/node@20.19.37) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/expand@4.0.23(@types/node@20.11.17)': dependencies: @@ -20969,12 +20827,12 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/expand@5.1.1(@types/node@26.1.1)': + '@inquirer/expand@5.1.1(@types/node@20.19.37)': dependencies: - '@inquirer/core': 11.2.1(@types/node@26.1.1) - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/external-editor@1.0.3(@types/node@20.11.17)': dependencies: @@ -20997,12 +20855,12 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/external-editor@3.0.3(@types/node@26.1.1)': + '@inquirer/external-editor@3.0.3(@types/node@20.19.37)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/figures@1.0.15': {} @@ -21029,12 +20887,12 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/input@5.1.2(@types/node@26.1.1)': + '@inquirer/input@5.1.2(@types/node@20.19.37)': dependencies: - '@inquirer/core': 11.2.1(@types/node@26.1.1) - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/number@3.0.23(@types/node@20.11.17)': dependencies: @@ -21057,12 +20915,12 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/number@4.1.1(@types/node@26.1.1)': + '@inquirer/number@4.1.1(@types/node@20.19.37)': dependencies: - '@inquirer/core': 11.2.1(@types/node@26.1.1) - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/password@4.0.23(@types/node@20.11.17)': dependencies: @@ -21088,13 +20946,13 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/password@5.1.1(@types/node@26.1.1)': + '@inquirer/password@5.1.1(@types/node@20.19.37)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@26.1.1) - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/prompts@7.8.2(@types/node@20.11.17)': dependencies: @@ -21141,20 +20999,20 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/prompts@8.5.2(@types/node@26.1.1)': + '@inquirer/prompts@8.5.2(@types/node@20.19.37)': dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@26.1.1) - '@inquirer/confirm': 6.1.1(@types/node@26.1.1) - '@inquirer/editor': 5.2.2(@types/node@26.1.1) - '@inquirer/expand': 5.1.1(@types/node@26.1.1) - '@inquirer/input': 5.1.2(@types/node@26.1.1) - '@inquirer/number': 4.1.1(@types/node@26.1.1) - '@inquirer/password': 5.1.1(@types/node@26.1.1) - '@inquirer/rawlist': 5.3.1(@types/node@26.1.1) - '@inquirer/search': 4.2.1(@types/node@26.1.1) - '@inquirer/select': 5.2.1(@types/node@26.1.1) + '@inquirer/checkbox': 5.2.1(@types/node@20.19.37) + '@inquirer/confirm': 6.1.1(@types/node@20.19.37) + '@inquirer/editor': 5.2.2(@types/node@20.19.37) + '@inquirer/expand': 5.1.1(@types/node@20.19.37) + '@inquirer/input': 5.1.2(@types/node@20.19.37) + '@inquirer/number': 4.1.1(@types/node@20.19.37) + '@inquirer/password': 5.1.1(@types/node@20.19.37) + '@inquirer/rawlist': 5.3.1(@types/node@20.19.37) + '@inquirer/search': 4.2.1(@types/node@20.19.37) + '@inquirer/select': 5.2.1(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/rawlist@4.1.11(@types/node@20.11.17)': dependencies: @@ -21180,12 +21038,12 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/rawlist@5.3.1(@types/node@26.1.1)': + '@inquirer/rawlist@5.3.1(@types/node@20.19.37)': dependencies: - '@inquirer/core': 11.2.1(@types/node@26.1.1) - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/search@3.2.2(@types/node@20.11.17)': dependencies: @@ -21214,13 +21072,13 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/search@4.2.1(@types/node@26.1.1)': + '@inquirer/search@4.2.1(@types/node@20.19.37)': dependencies: - '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/select@4.4.2(@types/node@20.11.17)': dependencies: @@ -21252,14 +21110,14 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/select@5.2.1(@types/node@26.1.1)': + '@inquirer/select@5.2.1(@types/node@20.19.37)': dependencies: '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@26.1.1) + '@inquirer/core': 11.2.1(@types/node@20.19.37) '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/type': 4.0.7(@types/node@20.19.37) optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@inquirer/type@3.0.10(@types/node@20.11.17)': optionalDependencies: @@ -21273,9 +21131,9 @@ snapshots: optionalDependencies: '@types/node': 26.1.1 - '@inquirer/type@4.0.7(@types/node@26.1.1)': + '@inquirer/type@4.0.7(@types/node@20.19.37)': optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 '@isaacs/cliui@8.0.2': dependencies: @@ -21297,7 +21155,7 @@ snapshots: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 - js-yaml: 4.3.1 + js-yaml: 4.3.2 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.6': {} @@ -21999,10 +21857,10 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@listr2/prompt-adapter-inquirer@4.2.4(@inquirer/prompts@8.5.2(@types/node@26.1.1))(@types/node@26.1.1)(listr2@10.2.2)': + '@listr2/prompt-adapter-inquirer@4.2.4(@inquirer/prompts@8.5.2(@types/node@20.19.37))(@types/node@20.19.37)(listr2@10.2.2)': dependencies: - '@inquirer/prompts': 8.5.2(@types/node@26.1.1) - '@inquirer/type': 4.0.7(@types/node@26.1.1) + '@inquirer/prompts': 8.5.2(@types/node@20.19.37) + '@inquirer/type': 4.0.7(@types/node@20.19.37) listr2: 10.2.2 transitivePeerDependencies: - '@types/node' @@ -22081,7 +21939,7 @@ snapshots: '@modelcontextprotocol/sdk@1.26.0(supports-color@7.2.0)(zod@4.1.13)': dependencies: - '@hono/node-server': 2.0.12(hono@4.13.0) + '@hono/node-server': 2.0.12(hono@4.13.7) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -22091,7 +21949,7 @@ snapshots: eventsource-parser: 3.0.8 express: 5.2.1(supports-color@7.2.0) express-rate-limit: 8.3.2(express@5.2.1) - hono: 4.13.0 + hono: 4.13.7 jose: 6.2.2 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -22103,7 +21961,7 @@ snapshots: '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': dependencies: - '@hono/node-server': 2.0.12(hono@4.13.0) + '@hono/node-server': 2.0.12(hono@4.13.7) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -22113,7 +21971,7 @@ snapshots: eventsource-parser: 3.0.8 express: 5.2.1(supports-color@7.2.0) express-rate-limit: 8.3.2(express@5.2.1) - hono: 4.13.0 + hono: 4.13.7 jose: 6.2.2 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -23935,7 +23793,7 @@ snapshots: estraverse: 5.3.0 picomatch: 4.0.4 - '@stylistic/stylelint-plugin@3.1.3(stylelint@16.22.0(typescript@6.0.3))': + '@stylistic/stylelint-plugin@3.1.3(stylelint@16.22.0(typescript@5.9.3))': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 @@ -23945,7 +23803,7 @@ snapshots: postcss-selector-parser: 6.1.4 postcss-value-parser: 4.2.0 style-search: 0.1.0 - stylelint: 16.22.0(typescript@6.0.3) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) '@swc/core-darwin-arm64@1.15.30': optional: true @@ -24012,9 +23870,9 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@testcafe-community/axe@3.5.0(axe-core@4.11.3)(testcafe@3.7.5)': + '@testcafe-community/axe@3.5.0(axe-core@4.13.0)(testcafe@3.7.5)': dependencies: - axe-core: 4.11.3 + axe-core: 4.13.0 chalk: 2.4.2 testcafe: 3.7.5(supports-color@7.2.0) @@ -24286,8 +24144,6 @@ snapshots: '@types/minimatch@5.1.2': {} - '@types/minimist@1.2.5': {} - '@types/ms@2.1.0': {} '@types/node-fetch@2.6.13': @@ -24517,22 +24373,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.52.0 - '@typescript-eslint/type-utils': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/utils': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.52.0 - eslint: 9.39.4(jiti@2.6.1) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/experimental-utils@5.62.0(eslint@9.39.4(jiti@2.6.1))(supports-color@7.2.0)(typescript@4.9.5)': dependencies: '@typescript-eslint/utils': 5.62.0(eslint@9.39.4(jiti@2.6.1))(supports-color@7.2.0)(typescript@4.9.5) @@ -24545,7 +24385,7 @@ snapshots: dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 6.21.0(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.3(supports-color@7.2.0) eslint: 9.39.4(jiti@2.6.1)(supports-color@7.2.0) @@ -24665,6 +24505,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.58.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@5.9.3) + '@typescript-eslint/types': 8.62.0 + debug: 4.4.3(supports-color@7.2.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.62.0(supports-color@7.2.0)(typescript@4.9.5)': dependencies: '@typescript-eslint/tsconfig-utils': 8.62.0(typescript@4.9.5) @@ -24728,6 +24577,10 @@ snapshots: dependencies: typescript: 5.8.3 + '@typescript-eslint/tsconfig-utils@8.58.2(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.58.2(typescript@6.0.3)': dependencies: typescript: 6.0.3 @@ -24750,7 +24603,7 @@ snapshots: '@typescript-eslint/type-utils@6.21.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 6.21.0(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/utils': 6.21.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(typescript@6.0.3) debug: 4.4.3(supports-color@7.2.0) eslint: 9.39.4(jiti@2.6.1)(supports-color@7.2.0) @@ -24808,18 +24661,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3)': - dependencies: - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/typescript-estree': 8.52.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3) - debug: 4.4.3(supports-color@7.2.0) - eslint: 9.39.4(jiti@2.6.1) - ts-api-utils: 2.5.0(typescript@6.0.3) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/types@5.62.0': {} '@typescript-eslint/types@6.21.0': {} @@ -24860,7 +24701,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@6.21.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@6.21.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 @@ -24995,6 +24836,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.58.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.58.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@5.9.3) + '@typescript-eslint/types': 8.58.2 + '@typescript-eslint/visitor-keys': 8.58.2 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.4 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/typescript-estree@8.62.0(supports-color@7.2.0)(typescript@4.9.5)': dependencies: '@typescript-eslint/project-service': 8.62.0(supports-color@7.2.0)(typescript@4.9.5) @@ -25032,7 +24888,7 @@ snapshots: '@types/semver': 7.7.1 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 6.21.0(supports-color@7.2.0)(typescript@6.0.3) eslint: 9.39.4(jiti@2.6.1)(supports-color@7.2.0) semver: 7.8.5 transitivePeerDependencies: @@ -25094,17 +24950,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3)': - dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.52.0 - '@typescript-eslint/types': 8.52.0 - '@typescript-eslint/typescript-estree': 8.52.0(typescript@6.0.3) - eslint: 9.39.4(jiti@2.6.1) - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/utils@8.58.2(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)) @@ -25138,14 +24983,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3)': + '@typescript-eslint/utils@8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.4(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.58.2 '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.58.2(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) - typescript: 6.0.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -25275,9 +25120,9 @@ snapshots: vite: 7.3.6(@types/node@26.1.1)(jiti@2.6.1)(less@4.8.0)(lightningcss@1.32.0)(sass-embedded@1.93.3)(sass@1.90.0)(terser@5.49.0)(yaml@2.9.0) optional: true - '@vitejs/plugin-basic-ssl@2.3.0(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0))': + '@vitejs/plugin-basic-ssl@2.3.0(vite@8.1.5(@types/node@20.19.37)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0))': dependencies: - vite: 8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0) + vite: 8.1.5(@types/node@20.19.37)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0) '@vitejs/plugin-react@4.7.0(vite@8.0.16(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.8.3))': dependencies: @@ -25807,14 +25652,14 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 + fast-uri: 3.1.7 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 + fast-uri: 3.1.7 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -25999,8 +25844,6 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 - arrify@1.0.1: {} - asn1.js@4.10.1: dependencies: bn.js: 4.12.3 @@ -26076,9 +25919,7 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axe-core@4.11.3: {} - - axe-core@4.12.1: {} + axe-core@4.13.0: {} axios@1.18.1: dependencies: @@ -26437,7 +26278,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.16.0 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -26452,7 +26293,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.3 + qs: 6.16.0 raw-body: 3.0.2 type-is: 2.1.0 transitivePeerDependencies: @@ -26699,13 +26540,6 @@ snapshots: pascal-case: 3.1.2 tslib: 2.8.1 - camelcase-keys@7.0.2: - dependencies: - camelcase: 6.3.0 - map-obj: 4.3.0 - quick-lru: 5.1.1 - type-fest: 1.4.0 - camelcase@5.3.1: {} camelcase@6.3.0: {} @@ -27024,7 +26858,7 @@ snapshots: color-convert: 3.1.3 color-string: 2.1.4 - colord@2.9.3: {} + colord@2.10.0: {} colorette@2.0.20: {} @@ -27205,47 +27039,29 @@ snapshots: path-type: 4.0.0 yaml: 1.10.3 - cosmiconfig@8.3.6(typescript@4.9.5): - dependencies: - import-fresh: 3.3.1 - js-yaml: 4.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 4.9.5 - - cosmiconfig@8.3.6(typescript@5.8.3): - dependencies: - import-fresh: 3.3.1 - js-yaml: 4.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 5.8.3 - cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.3.1 + js-yaml: 4.3.2 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: typescript: 5.9.3 - cosmiconfig@8.3.6(typescript@6.0.3): + cosmiconfig@9.0.1(typescript@4.9.5): dependencies: + env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.3.1 + js-yaml: 4.3.2 parse-json: 5.2.0 - path-type: 4.0.0 optionalDependencies: - typescript: 6.0.3 + typescript: 4.9.5 cosmiconfig@9.0.1(typescript@5.8.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.3.1 + js-yaml: 4.3.2 parse-json: 5.2.0 optionalDependencies: typescript: 5.8.3 @@ -27254,7 +27070,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.3.1 + js-yaml: 4.3.2 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -27263,7 +27079,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.3.1 + js-yaml: 4.3.2 parse-json: 5.2.0 optionalDependencies: typescript: 6.0.3 @@ -27423,11 +27239,6 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 - css-tree@2.3.1: - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.2.1 - css-tree@3.2.1: dependencies: mdn-data: 2.27.1 @@ -27507,15 +27318,6 @@ snapshots: optionalDependencies: supports-color: 7.2.0 - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - - decamelize@5.0.1: {} - decimal.js@10.6.0: {} decode-named-character-reference@1.3.0: @@ -27554,7 +27356,7 @@ snapshots: object-keys: 1.1.1 object.assign: 4.1.7 regexp.prototype.flags: 1.5.4 - side-channel: 1.1.0 + side-channel: 1.1.1 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 which-typed-array: 1.1.20 @@ -27982,11 +27784,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.2 - enhanced-resolve@5.24.3: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - enhanced-resolve@5.24.5: dependencies: graceful-fs: 4.2.11 @@ -28260,14 +28057,14 @@ snapshots: transitivePeerDependencies: - eslint-plugin-import - eslint-config-devextreme@1.1.12(4c739bafb4997092ce83f049f167b131): + eslint-config-devextreme@1.1.12(25bff8f024558126068f85a113b20273): dependencies: '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.4(jiti@2.6.1)) '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5) '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5) eslint: 9.39.4(jiti@2.6.1) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@20.11.17)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.11.17)(typescript@4.9.5)))(typescript@4.9.5) + eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@4.9.5) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-no-only-tests: 3.3.0 eslint-plugin-qunit: 8.2.6(eslint@9.39.4(jiti@2.6.1)) @@ -28276,17 +28073,36 @@ snapshots: eslint-plugin-rulesdir: 0.2.2 eslint-plugin-spellcheck: 0.0.20(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.0.0(eslint@9.39.4(jiti@2.6.1))) - stylelint: 15.11.0(typescript@4.9.5) - stylelint-config-standard: 38.0.0(stylelint@15.11.0(typescript@4.9.5)) + stylelint: 16.22.0(typescript@4.9.5) + stylelint-config-standard: 38.0.0(stylelint@16.22.0(typescript@4.9.5)) - eslint-config-devextreme@1.1.12(554dde2dfd68364add0a2758e579b8b7): + eslint-config-devextreme@1.1.12(3d401928aa66bf2e591f933b2739e58c): + dependencies: + '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.4(jiti@2.6.1)) + '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-no-only-tests: 3.3.0 + eslint-plugin-qunit: 8.2.6(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-react-perf: 3.3.3(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-rulesdir: 0.2.2 + eslint-plugin-spellcheck: 0.0.20(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.0.0(eslint@9.39.4(jiti@2.6.1))) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) + stylelint-config-standard: 38.0.0(stylelint@16.22.0(typescript@5.9.3)) + + eslint-config-devextreme@1.1.12(6e359676285ac6d918269845dc7dce9a): dependencies: '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)) '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint: 9.39.4(jiti@2.6.1)(supports-color@7.2.0) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)) - eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3) + eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)) eslint-plugin-no-only-tests: 3.3.0 eslint-plugin-qunit: 8.2.6(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0)) @@ -28298,14 +28114,14 @@ snapshots: stylelint: 16.22.0(typescript@6.0.3) stylelint-config-standard: 38.0.0(stylelint@16.22.0(typescript@6.0.3)) - eslint-config-devextreme@1.1.12(5552857efccfb3f1ba025df865cdfd0e): + eslint-config-devextreme@1.1.12(7dd464a0604f1d8e6bd5fce5260d6037): dependencies: '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) - '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.8.3) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-no-only-tests: 3.3.0 eslint-plugin-qunit: 8.2.6(eslint@9.39.4(jiti@2.6.1)) @@ -28313,18 +28129,18 @@ snapshots: eslint-plugin-react-perf: 3.3.3(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-rulesdir: 0.2.2 eslint-plugin-spellcheck: 0.0.20(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.0.0(eslint@9.39.4(jiti@2.6.1))) - stylelint: 15.11.0(typescript@5.8.3) - stylelint-config-standard: 38.0.0(stylelint@15.11.0(typescript@5.8.3)) + eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.0.0(eslint@9.39.4(jiti@2.6.1))) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) + stylelint-config-standard: 38.0.0(stylelint@16.22.0(typescript@5.9.3)) - eslint-config-devextreme@1.1.12(70f56e711195e33f9b23259278af4356): + eslint-config-devextreme@1.1.12(a25cda3163463b9772ba2a773a10316c): dependencies: '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) eslint: 9.39.4(jiti@2.6.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@6.0.3) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.8.3) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-no-only-tests: 3.3.0 eslint-plugin-qunit: 8.2.6(eslint@9.39.4(jiti@2.6.1)) @@ -28332,11 +28148,11 @@ snapshots: eslint-plugin-react-perf: 3.3.3(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-rulesdir: 0.2.2 eslint-plugin-spellcheck: 0.0.20(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.0.0(eslint@9.39.4(jiti@2.6.1))) - stylelint: 15.11.0(typescript@6.0.3) - stylelint-config-standard: 38.0.0(stylelint@15.11.0(typescript@6.0.3)) + eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.0.0(eslint@9.39.4(jiti@2.6.1))) + stylelint: 16.22.0(typescript@5.8.3) + stylelint-config-standard: 38.0.0(stylelint@16.22.0(typescript@5.8.3)) - eslint-config-devextreme@1.1.12(e43b4562c396170e08a0bcb0bebad46e): + eslint-config-devextreme@1.1.12(a8f9319687a53bec0d2a70b73ac5ccad): dependencies: '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.4(jiti@2.6.1)) '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5) @@ -28352,17 +28168,17 @@ snapshots: eslint-plugin-rulesdir: 0.2.2 eslint-plugin-spellcheck: 0.0.20(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.0.0(eslint@9.39.4(jiti@2.6.1))) - stylelint: 15.11.0(typescript@4.9.5) - stylelint-config-standard: 38.0.0(stylelint@15.11.0(typescript@4.9.5)) + stylelint: 16.22.0(typescript@4.9.5) + stylelint-config-standard: 38.0.0(stylelint@16.22.0(typescript@4.9.5)) - eslint-config-devextreme@1.1.12(e659abf94f261cb548f87457fcc52d78): + eslint-config-devextreme@1.1.12(ba83ebe55aa7666ff88d6674f941478c): dependencies: '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.4(jiti@2.6.1)) '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5) '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5) eslint: 9.39.4(jiti@2.6.1) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@4.9.5) + eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@20.11.17)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.11.17)(typescript@4.9.5)))(typescript@4.9.5) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-no-only-tests: 3.3.0 eslint-plugin-qunit: 8.2.6(eslint@9.39.4(jiti@2.6.1)) @@ -28371,27 +28187,8 @@ snapshots: eslint-plugin-rulesdir: 0.2.2 eslint-plugin-spellcheck: 0.0.20(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@4.9.5))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.0.0(eslint@9.39.4(jiti@2.6.1))) - stylelint: 15.11.0(typescript@4.9.5) - stylelint-config-standard: 38.0.0(stylelint@15.11.0(typescript@4.9.5)) - - eslint-config-devextreme@1.1.12(f43ede1c63aef08b124dbf33bc5f83fb): - dependencies: - '@stylistic/eslint-plugin': 5.10.0(eslint@9.39.4(jiti@2.6.1)) - '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3) - '@typescript-eslint/parser': 8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@6.0.3) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-qunit: 8.2.6(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-react-perf: 3.3.3(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-rulesdir: 0.2.2 - eslint-plugin-spellcheck: 0.0.20(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.0.0(eslint@9.39.4(jiti@2.6.1))) - stylelint: 15.11.0(typescript@6.0.3) - stylelint-config-standard: 38.0.0(stylelint@15.11.0(typescript@6.0.3)) + stylelint: 16.22.0(typescript@4.9.5) + stylelint-config-standard: 38.0.0(stylelint@16.22.0(typescript@4.9.5)) eslint-import-resolver-node@0.3.10: dependencies: @@ -28580,13 +28377,13 @@ snapshots: dependencies: eslint: 9.39.4(jiti@2.6.1) - eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3): + eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)))(supports-color@7.2.0)(typescript@6.0.3): dependencies: '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint: 9.39.4(jiti@2.6.1)(supports-color@7.2.0) optionalDependencies: '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) - jest: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) + jest: 30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -28635,25 +28432,25 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@6.0.3): + eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) optionalDependencies: '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - jest: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) - typescript: 6.0.3 + jest: 30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@6.0.3): + eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.58.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) jest: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) - typescript: 6.0.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -28663,7 +28460,7 @@ snapshots: array-includes: 3.1.9 array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 - axe-core: 4.12.1 + axe-core: 4.13.0 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 @@ -28682,7 +28479,7 @@ snapshots: array-includes: 3.1.9 array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 - axe-core: 4.12.1 + axe-core: 4.13.0 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 @@ -28743,8 +28540,8 @@ snapshots: '@babel/parser': 7.29.2 eslint: 9.39.4(jiti@2.6.1) hermes-parser: 0.25.1 - zod: 4.4.2 - zod-validation-error: 4.0.2(zod@4.4.2) + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) transitivePeerDependencies: - supports-color @@ -29180,7 +28977,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.13 proxy-addr: 2.0.7 - qs: 6.15.2 + qs: 6.16.0 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -29215,7 +29012,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.3 + qs: 6.16.0 range-parser: 1.3.0 router: 2.2.0(supports-color@7.2.0) send: 1.2.1(supports-color@7.2.0) @@ -29279,7 +29076,7 @@ snapshots: dependencies: fast-string-truncated-width: 3.0.3 - fast-uri@3.1.5: {} + fast-uri@3.1.7: {} fast-wrap-ansi@0.2.2: dependencies: @@ -29313,7 +29110,7 @@ snapshots: fecha@4.2.3: {} - fflate@0.8.2: {} + fflate@0.8.3: {} figures@3.2.0: dependencies: @@ -29323,10 +29120,6 @@ snapshots: dependencies: flat-cache: 6.1.22 - file-entry-cache@7.0.2: - dependencies: - flat-cache: 3.2.0 - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -29779,8 +29572,6 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - hard-rejection@2.1.0: {} - harmony-reflect@1.6.2: {} has-bigints@1.1.0: {} @@ -29965,7 +29756,7 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - hono@4.13.0: {} + hono@4.13.7: {} hookified@1.15.1: {} @@ -29977,10 +29768,6 @@ snapshots: hosted-git-info@2.8.9: {} - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 - hosted-git-info@9.0.2: dependencies: lru-cache: 11.3.5 @@ -30294,8 +30081,6 @@ snapshots: import-lazy@3.1.0: {} - import-lazy@4.0.0: {} - import-local@3.2.0: dependencies: pkg-dir: 4.2.0 @@ -30553,8 +30338,6 @@ snapshots: is-path-inside@3.0.3: {} - is-plain-obj@1.1.0: {} - is-plain-obj@4.1.0: {} is-plain-object@2.0.4: @@ -30878,15 +30661,15 @@ snapshots: - supports-color - ts-node - jest-cli@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)): + jest-cli@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)): dependencies: - '@jest/core': 30.4.2(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) + '@jest/core': 30.4.2(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) '@jest/test-result': 30.4.1 '@jest/types': 30.4.1 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) + jest-config: 30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) jest-util: 30.4.1 jest-validate: 30.4.1 yargs: 17.7.2 @@ -30899,15 +30682,15 @@ snapshots: - supports-color - ts-node - jest-cli@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)): + jest-cli@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)): dependencies: - '@jest/core': 30.4.2(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) + '@jest/core': 30.4.2(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) '@jest/test-result': 30.4.1 '@jest/types': 30.4.1 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) + jest-config: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) jest-util: 30.4.1 jest-validate: 30.4.1 yargs: 17.7.2 @@ -31242,38 +31025,6 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)): - dependencies: - '@babel/core': 7.29.7 - '@jest/get-type': 30.1.0 - '@jest/pattern': 30.4.0 - '@jest/test-sequencer': 30.4.1 - '@jest/types': 30.4.1 - babel-jest: 30.4.1(@babel/core@7.29.7) - chalk: 4.1.2 - ci-info: 4.4.0 - deepmerge: 4.3.1 - glob: 10.5.0 - graceful-fs: 4.2.11 - jest-circus: 30.4.2(babel-plugin-macros@3.1.0) - jest-docblock: 30.4.0 - jest-environment-node: 30.4.1 - jest-regex-util: 30.4.0 - jest-resolve: 30.4.1 - jest-runner: 30.4.2 - jest-util: 30.4.1 - jest-validate: 30.4.1 - parse-json: 5.2.0 - pretty-format: 30.4.1 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 26.1.1 - ts-node: 10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -31812,12 +31563,12 @@ snapshots: - supports-color - ts-node - jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)): + jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)): dependencies: - '@jest/core': 30.4.2(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) + '@jest/core': 30.4.2(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) '@jest/types': 30.4.1 import-local: 3.2.0 - jest-cli: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) + jest-cli: 30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) optionalDependencies: node-notifier: 9.0.1 transitivePeerDependencies: @@ -31827,12 +31578,12 @@ snapshots: - supports-color - ts-node - jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)): + jest@30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)): dependencies: - '@jest/core': 30.4.2(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) + '@jest/core': 30.4.2(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) '@jest/types': 30.4.1 import-local: 3.2.0 - jest-cli: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@6.0.3)) + jest-cli: 30.4.2(@types/node@26.1.1)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) optionalDependencies: node-notifier: 9.0.1 transitivePeerDependencies: @@ -31872,7 +31623,7 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@4.3.1: + js-yaml@4.3.2: dependencies: argparse: 2.0.1 @@ -31953,7 +31704,7 @@ snapshots: dependencies: '@babel/runtime': 7.29.2 fast-png: 6.4.0 - fflate: 0.8.2 + fflate: 0.8.3 optionalDependencies: canvg: 3.0.11 core-js: 3.49.0 @@ -32067,8 +31818,6 @@ snapshots: knockout@3.5.3: {} - known-css-properties@0.29.0: {} - known-css-properties@0.35.0: {} known-css-properties@0.37.0: {} @@ -32521,10 +32270,6 @@ snapshots: dependencies: tmpl: 1.0.5 - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - match-url-wildcard@0.0.4: dependencies: escape-string-regexp: 1.0.5 @@ -32593,8 +32338,6 @@ snapshots: dependencies: '@types/mdast': 3.0.15 - mdn-data@2.0.30: {} - mdn-data@2.27.1: {} mdn-data@2.28.0: {} @@ -32631,21 +32374,6 @@ snapshots: memorystream@0.3.1: {} - meow@10.1.5: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 7.0.2 - decamelize: 5.0.1 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 8.0.0 - redent: 4.0.0 - trim-newlines: 4.1.1 - type-fest: 1.4.0 - yargs-parser: 20.2.9 - meow@13.2.0: {} merge-descriptors@1.0.3: {} @@ -32869,12 +32597,6 @@ snapshots: dependencies: brace-expansion: 2.1.4 - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - minimist@1.2.8: {} minimizer-webpack-plugin@5.6.1(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.23)(webpack@5.109.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(esbuild@0.28.1)(lightningcss@1.32.0)(postcss@8.5.23)): @@ -33241,13 +32963,6 @@ snapshots: semver: 5.7.2 validate-npm-package-license: 3.0.4 - normalize-package-data@3.0.3: - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.16.2 - semver: 7.8.5 - validate-npm-package-license: 3.0.4 - normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -33432,7 +33147,7 @@ snapshots: safe-buffer: 5.2.1 semver: 7.7.4 signal-exit: 3.0.7 - smol-toml: 1.6.1 + smol-toml: 1.8.0 string-width: 4.2.3 string_decoder: 1.3.0 strip-ansi: 6.0.1 @@ -34277,11 +33992,7 @@ snapshots: qrcode-terminal@0.10.0: {} - qs@6.15.2: - dependencies: - side-channel: 1.1.0 - - qs@6.15.3: + qs@6.16.0: dependencies: es-define-property: 1.0.1 side-channel: 1.1.1 @@ -34294,8 +34005,6 @@ snapshots: queue-microtask@1.2.3: {} - quick-lru@5.1.1: {} - quill-delta@5.1.0: dependencies: fast-diff: 1.3.0 @@ -34435,12 +34144,6 @@ snapshots: dependencies: readable-stream: 2.3.8 - read-pkg-up@8.0.0: - dependencies: - find-up: 5.0.0 - read-pkg: 6.0.0 - type-fest: 1.4.0 - read-pkg@5.2.0: dependencies: '@types/normalize-package-data': 2.4.4 @@ -34448,13 +34151,6 @@ snapshots: parse-json: 5.2.0 type-fest: 0.6.0 - read-pkg@6.0.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 3.0.3 - parse-json: 5.2.0 - type-fest: 1.4.0 - readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -34516,11 +34212,6 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 - redent@4.0.0: - dependencies: - indent-string: 5.0.0 - strip-indent: 4.1.1 - reflect-metadata@0.1.13: {} reflect-metadata@0.2.2: {} @@ -35365,7 +35056,7 @@ snapshots: smart-buffer@4.2.0: {} - smol-toml@1.6.1: {} + smol-toml@1.8.0: {} socket.io-adapter@2.5.8: dependencies: @@ -35763,12 +35454,12 @@ snapshots: postcss-html: 1.8.1 stylelint: 16.22.0(typescript@6.0.3) - stylelint-config-recommended-scss@14.1.0(postcss@8.5.23)(stylelint@16.22.0(typescript@6.0.3)): + stylelint-config-recommended-scss@14.1.0(postcss@8.5.23)(stylelint@16.22.0(typescript@5.9.3)): dependencies: postcss-scss: 4.0.9(postcss@8.5.23) - stylelint: 16.22.0(typescript@6.0.3) - stylelint-config-recommended: 14.0.1(stylelint@16.22.0(typescript@6.0.3)) - stylelint-scss: 6.10.0(stylelint@16.22.0(typescript@6.0.3)) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) + stylelint-config-recommended: 14.0.1(stylelint@16.22.0(typescript@5.9.3)) + stylelint-scss: 6.10.0(stylelint@16.22.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.23 @@ -35780,60 +35471,60 @@ snapshots: stylelint-config-html: 1.1.0(postcss-html@1.8.1)(stylelint@16.22.0(typescript@6.0.3)) stylelint-config-recommended: 16.0.0(stylelint@16.22.0(typescript@6.0.3)) - stylelint-config-recommended@14.0.1(stylelint@16.22.0(typescript@6.0.3)): + stylelint-config-recommended@14.0.1(stylelint@16.22.0(typescript@5.9.3)): dependencies: - stylelint: 16.22.0(typescript@6.0.3) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) - stylelint-config-recommended@16.0.0(stylelint@15.11.0(typescript@4.9.5)): + stylelint-config-recommended@16.0.0(stylelint@16.22.0(typescript@4.9.5)): dependencies: - stylelint: 15.11.0(typescript@4.9.5) + stylelint: 16.22.0(typescript@4.9.5) - stylelint-config-recommended@16.0.0(stylelint@15.11.0(typescript@5.8.3)): + stylelint-config-recommended@16.0.0(stylelint@16.22.0(typescript@5.8.3)): dependencies: - stylelint: 15.11.0(typescript@5.8.3) + stylelint: 16.22.0(typescript@5.8.3) - stylelint-config-recommended@16.0.0(stylelint@15.11.0(typescript@6.0.3)): + stylelint-config-recommended@16.0.0(stylelint@16.22.0(typescript@5.9.3)): dependencies: - stylelint: 15.11.0(typescript@6.0.3) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) stylelint-config-recommended@16.0.0(stylelint@16.22.0(typescript@6.0.3)): dependencies: stylelint: 16.22.0(typescript@6.0.3) - stylelint-config-standard-scss@14.0.0(postcss@8.5.23)(stylelint@16.22.0(typescript@6.0.3)): + stylelint-config-standard-scss@14.0.0(postcss@8.5.23)(stylelint@16.22.0(typescript@5.9.3)): dependencies: - stylelint: 16.22.0(typescript@6.0.3) - stylelint-config-recommended-scss: 14.1.0(postcss@8.5.23)(stylelint@16.22.0(typescript@6.0.3)) - stylelint-config-standard: 36.0.1(stylelint@16.22.0(typescript@6.0.3)) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) + stylelint-config-recommended-scss: 14.1.0(postcss@8.5.23)(stylelint@16.22.0(typescript@5.9.3)) + stylelint-config-standard: 36.0.1(stylelint@16.22.0(typescript@5.9.3)) optionalDependencies: postcss: 8.5.23 - stylelint-config-standard@36.0.1(stylelint@16.22.0(typescript@6.0.3)): + stylelint-config-standard@36.0.1(stylelint@16.22.0(typescript@5.9.3)): dependencies: - stylelint: 16.22.0(typescript@6.0.3) - stylelint-config-recommended: 14.0.1(stylelint@16.22.0(typescript@6.0.3)) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) + stylelint-config-recommended: 14.0.1(stylelint@16.22.0(typescript@5.9.3)) - stylelint-config-standard@38.0.0(stylelint@15.11.0(typescript@4.9.5)): + stylelint-config-standard@38.0.0(stylelint@16.22.0(typescript@4.9.5)): dependencies: - stylelint: 15.11.0(typescript@4.9.5) - stylelint-config-recommended: 16.0.0(stylelint@15.11.0(typescript@4.9.5)) + stylelint: 16.22.0(typescript@4.9.5) + stylelint-config-recommended: 16.0.0(stylelint@16.22.0(typescript@4.9.5)) - stylelint-config-standard@38.0.0(stylelint@15.11.0(typescript@5.8.3)): + stylelint-config-standard@38.0.0(stylelint@16.22.0(typescript@5.8.3)): dependencies: - stylelint: 15.11.0(typescript@5.8.3) - stylelint-config-recommended: 16.0.0(stylelint@15.11.0(typescript@5.8.3)) + stylelint: 16.22.0(typescript@5.8.3) + stylelint-config-recommended: 16.0.0(stylelint@16.22.0(typescript@5.8.3)) - stylelint-config-standard@38.0.0(stylelint@15.11.0(typescript@6.0.3)): + stylelint-config-standard@38.0.0(stylelint@16.22.0(typescript@5.9.3)): dependencies: - stylelint: 15.11.0(typescript@6.0.3) - stylelint-config-recommended: 16.0.0(stylelint@15.11.0(typescript@6.0.3)) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) + stylelint-config-recommended: 16.0.0(stylelint@16.22.0(typescript@5.9.3)) stylelint-config-standard@38.0.0(stylelint@16.22.0(typescript@6.0.3)): dependencies: stylelint: 16.22.0(typescript@6.0.3) stylelint-config-recommended: 16.0.0(stylelint@16.22.0(typescript@6.0.3)) - stylelint-scss@6.10.0(stylelint@16.22.0(typescript@6.0.3)): + stylelint-scss@6.10.0(stylelint@16.22.0(typescript@5.9.3)): dependencies: css-tree: 3.2.1 is-plain-object: 5.0.0 @@ -35843,92 +35534,44 @@ snapshots: postcss-resolve-nested-selector: 0.1.6 postcss-selector-parser: 7.1.5 postcss-value-parser: 4.2.0 - stylelint: 16.22.0(typescript@6.0.3) + stylelint: 16.22.0(supports-color@7.2.0)(typescript@5.9.3) - stylelint@15.11.0(typescript@4.9.5): - dependencies: - '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) - '@csstools/css-tokenizer': 2.4.1 - '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.4) - balanced-match: 2.0.0 - colord: 2.9.3 - cosmiconfig: 8.3.6(typescript@4.9.5) - css-functions-list: 3.3.3 - css-tree: 2.3.1 - debug: 4.4.3(supports-color@7.2.0) - fast-glob: 3.3.3 - fastest-levenshtein: 1.0.16 - file-entry-cache: 7.0.2 - global-modules: 2.0.0 - globby: 11.1.0 - globjoin: 0.1.4 - html-tags: 3.3.1 - ignore: 5.3.2 - import-lazy: 4.0.0 - imurmurhash: 0.1.4 - is-plain-object: 5.0.0 - known-css-properties: 0.29.0 - mathml-tag-names: 2.1.3 - meow: 10.1.5 - micromatch: 4.0.8 - normalize-path: 3.0.0 - picocolors: 1.1.1 - postcss: 8.5.23 - postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 6.0.0(postcss@8.5.23) - postcss-selector-parser: 6.1.4 - postcss-value-parser: 4.2.0 - resolve-from: 5.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - style-search: 0.1.0 - supports-hyperlinks: 3.2.0 - svg-tags: 1.0.0 - table: 6.9.0 - write-file-atomic: 5.0.1 - transitivePeerDependencies: - - supports-color - - typescript - - stylelint@15.11.0(typescript@5.8.3): + stylelint@16.22.0(supports-color@7.2.0)(typescript@5.9.3): dependencies: - '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) - '@csstools/css-tokenizer': 2.4.1 - '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.5) + '@dual-bundle/import-meta-resolve': 4.2.1 balanced-match: 2.0.0 - colord: 2.9.3 - cosmiconfig: 8.3.6(typescript@5.8.3) + colord: 2.10.0 + cosmiconfig: 9.0.1(typescript@5.9.3) css-functions-list: 3.3.3 - css-tree: 2.3.1 + css-tree: 3.2.1 debug: 4.4.3(supports-color@7.2.0) fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 - file-entry-cache: 7.0.2 + file-entry-cache: 10.1.4 global-modules: 2.0.0 globby: 11.1.0 globjoin: 0.1.4 html-tags: 3.3.1 - ignore: 5.3.2 - import-lazy: 4.0.0 + ignore: 7.0.5 imurmurhash: 0.1.4 is-plain-object: 5.0.0 - known-css-properties: 0.29.0 + known-css-properties: 0.37.0 mathml-tag-names: 2.1.3 - meow: 10.1.5 + meow: 13.2.0 micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.1 postcss: 8.5.23 postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 6.0.0(postcss@8.5.23) - postcss-selector-parser: 6.1.4 + postcss-safe-parser: 7.0.1(postcss@8.5.23) + postcss-selector-parser: 7.1.5 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 string-width: 4.2.3 - strip-ansi: 6.0.1 - style-search: 0.1.0 supports-hyperlinks: 3.2.0 svg-tags: 1.0.0 table: 6.9.0 @@ -35937,44 +35580,42 @@ snapshots: - supports-color - typescript - stylelint@15.11.0(typescript@6.0.3): + stylelint@16.22.0(typescript@4.9.5): dependencies: - '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1) - '@csstools/css-tokenizer': 2.4.1 - '@csstools/media-query-list-parser': 2.1.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1) - '@csstools/selector-specificity': 3.1.1(postcss-selector-parser@6.1.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.5) + '@dual-bundle/import-meta-resolve': 4.2.1 balanced-match: 2.0.0 - colord: 2.9.3 - cosmiconfig: 8.3.6(typescript@6.0.3) + colord: 2.10.0 + cosmiconfig: 9.0.1(typescript@4.9.5) css-functions-list: 3.3.3 - css-tree: 2.3.1 + css-tree: 3.2.1 debug: 4.4.3(supports-color@7.2.0) fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 - file-entry-cache: 7.0.2 + file-entry-cache: 10.1.4 global-modules: 2.0.0 globby: 11.1.0 globjoin: 0.1.4 html-tags: 3.3.1 - ignore: 5.3.2 - import-lazy: 4.0.0 + ignore: 7.0.5 imurmurhash: 0.1.4 is-plain-object: 5.0.0 - known-css-properties: 0.29.0 + known-css-properties: 0.37.0 mathml-tag-names: 2.1.3 - meow: 10.1.5 + meow: 13.2.0 micromatch: 4.0.8 normalize-path: 3.0.0 picocolors: 1.1.1 postcss: 8.5.23 postcss-resolve-nested-selector: 0.1.6 - postcss-safe-parser: 6.0.0(postcss@8.5.23) - postcss-selector-parser: 6.1.4 + postcss-safe-parser: 7.0.1(postcss@8.5.23) + postcss-selector-parser: 7.1.5 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 string-width: 4.2.3 - strip-ansi: 6.0.1 - style-search: 0.1.0 supports-hyperlinks: 3.2.0 svg-tags: 1.0.0 table: 6.9.0 @@ -35983,7 +35624,7 @@ snapshots: - supports-color - typescript - stylelint@16.22.0(supports-color@7.2.0)(typescript@5.9.3): + stylelint@16.22.0(typescript@5.8.3): dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 @@ -35991,8 +35632,8 @@ snapshots: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.5) '@dual-bundle/import-meta-resolve': 4.2.1 balanced-match: 2.0.0 - colord: 2.9.3 - cosmiconfig: 9.0.1(typescript@5.9.3) + colord: 2.10.0 + cosmiconfig: 9.0.1(typescript@5.8.3) css-functions-list: 3.3.3 css-tree: 3.2.1 debug: 4.4.3(supports-color@7.2.0) @@ -36035,7 +35676,7 @@ snapshots: '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.5) '@dual-bundle/import-meta-resolve': 4.2.1 balanced-match: 2.0.0 - colord: 2.9.3 + colord: 2.10.0 cosmiconfig: 9.0.1(typescript@6.0.3) css-functions-list: 3.3.3 css-tree: 3.2.1 @@ -36124,18 +35765,6 @@ snapshots: dependencies: acorn-node: 1.8.2 - systemjs-plugin-babel@0.0.25: {} - - systemjs-plugin-css@0.1.37: {} - - systemjs-plugin-json@0.3.0: {} - - systemjs-plugin-text@0.0.11: {} - - systemjs@0.19.41: - dependencies: - when: 3.7.8 - table@6.9.0: dependencies: ajv: 8.20.0 @@ -36578,8 +36207,6 @@ snapshots: trim-lines@3.0.1: {} - trim-newlines@4.1.1: {} - trim-trailing-lines@2.1.0: {} triple-beam@1.4.1: {} @@ -36698,26 +36325,6 @@ snapshots: babel-jest: 30.4.1(@babel/core@7.29.7) jest-util: 30.4.1 - ts-jest@29.4.12(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@7.29.7))(jest-util@30.4.1)(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@6.0.3): - dependencies: - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - handlebars: 4.7.9 - jest: 30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)) - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.8.5 - type-fest: 4.41.0 - typescript: 6.0.3 - yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.29.7 - '@jest/transform': 30.4.1 - '@jest/types': 30.4.1 - babel-jest: 30.4.1(@babel/core@7.29.7) - jest-util: 30.4.1 - ts-jest@29.4.12(@babel/core@8.0.1)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.4.1(@babel/core@8.0.1))(jest-util@30.4.1)(jest@30.4.2(@types/node@20.19.37)(babel-plugin-macros@3.1.0)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.15.30(@swc/helpers@0.5.21))(@types/node@20.19.37)(typescript@5.9.3)))(typescript@4.9.5): dependencies: bs-logger: 0.2.6 @@ -37057,7 +36664,7 @@ snapshots: union@0.5.0: dependencies: - qs: 6.15.2 + qs: 6.16.0 unist-util-find-after@4.0.1: dependencies: @@ -37174,7 +36781,7 @@ snapshots: url@0.11.4: dependencies: punycode: 1.4.1 - qs: 6.15.2 + qs: 6.16.0 use-sync-external-store@1.6.0(react@18.3.1): dependencies: @@ -37389,7 +36996,7 @@ snapshots: terser: 5.49.0 yaml: 2.8.3 - vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0): + vite@8.1.5(@types/node@20.19.37)(esbuild@0.28.1)(jiti@2.6.1)(less@4.8.0)(sass-embedded@1.93.3)(sass@1.101.0)(terser@5.49.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.5 @@ -37397,7 +37004,7 @@ snapshots: rolldown: 1.1.5 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.1.1 + '@types/node': 20.19.37 esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.6.1 @@ -37849,7 +37456,7 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.17.0) browserslist: 4.28.7 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.24.3 + enhanced-resolve: 5.24.5 es-module-lexer: 2.3.1 eslint-scope: 5.1.1 events: 3.3.0 @@ -38103,8 +37710,6 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - when@3.7.8: {} - which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -38352,16 +37957,14 @@ snapshots: dependencies: zod: 4.4.3 - zod-validation-error@4.0.2(zod@4.4.2): + zod-validation-error@4.0.2(zod@4.4.3): dependencies: - zod: 4.4.2 + zod: 4.4.3 zod@3.24.4: {} zod@4.1.13: {} - zod@4.4.2: {} - zod@4.4.3: {} zone.js@0.15.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 723fdeb948d3..7c2a3b53ac5c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,25 +3,29 @@ packages: - 'e2e/*' - 'packages/*' - '!packages/sbom' + - 'tools/*' linkWorkspacePackages: true preferWorkspacePackages: true saveWorkspaceProtocol: false packageManagerStrict: false engineStrict: true -minimumReleaseAge: 0 + +# Supply-chain guard: ignore npm versions published less than 24 hours ago. +# Need a fresher version? Add a targeted minimumReleaseAgeExclude entry with the exact version. +minimumReleaseAge: 1440 # ⚠️ Do NOT modify this. overrides: "basic-ftp@<5.3.1": ">=5.3.1" - "js-yaml@<4.3.1": "^4.3.1" + "js-yaml@<4.3.2": "^4.3.2" "rollup@<4.59.0": "^4.59.0" "json5@<1.0.2": ^2.2.3 "axios@<1.18.0": ^1.18.0 "braces@<3.0.3": ^3.0.3 "semver@<5.7.2": ^5.7.2 - qs: ">=6.15.2" + qs: ">=6.16.0" "vite@>=7.0.0 <=7.3.4": ^7.3.5 - "hono@<4.12.34": "^4.12.34" + "hono@<4.13.5": "^4.13.5" "minimatch@>=9.0.0 < 9.0.7": 9.0.9 "picomatch@>=4.0.0 <4.0.4": 4.0.4 "micromatch@<4.0.8": ^4.0.8 @@ -57,15 +61,18 @@ overrides: "tar@<7.5.19": "^7.5.19" "shell-quote@<=1.8.4": ^1.9.0 "babel-traverse": "npm:@babel/traverse@^7.23.2" - "fast-uri@>=3.0.0 <3.1.5": "^3.1.5" + "fast-uri@>=3.0.0 <3.1.6": "^3.1.6" "@hono/node-server@<2.0.12": "^2.0.12" "dompurify@<=3.4.12": "^3.4.13" "nanoid@<3.3.17": "^3.3.17" "browserslist@<4.28.7": "^4.28.7" "postcss-selector-parser@>=6.1.0 <6.1.3": "^6.1.3" "postcss-selector-parser@>=7.1.0 <7.1.3": "^7.1.3" + "fflate@>=0.8.0 <0.8.3": "^0.8.3" + "colord@<2.9.4": "^2.9.4" + "smol-toml@<1.7.0": "^1.7.1" catalog: - axe-core: 4.11.3 + axe-core: 4.13.0 inferno: ^8.2.3 inferno-hydrate: ^8.2.3 inferno-create-element: ^8.2.3 diff --git a/tools/.prettierignore b/tools/.prettierignore new file mode 100644 index 000000000000..090fc6536af2 --- /dev/null +++ b/tools/.prettierignore @@ -0,0 +1,3 @@ +# Shared by every tools/* package. Prettier does not search parent directories for this file, +# so each package passes it explicitly via --ignore-path. +*.json diff --git a/tools/.prettierrc.yaml b/tools/.prettierrc.yaml new file mode 100644 index 000000000000..08e20ffc0b60 --- /dev/null +++ b/tools/.prettierrc.yaml @@ -0,0 +1,12 @@ +arrowParens: always +bracketSameLine: true +bracketSpacing: true +endOfLine: lf +experimentalOperatorPosition: start +objectWrap: preserve +printWidth: 100 +quoteProps: preserve +semi: true +singleQuote: true +tabWidth: 2 +trailingComma: all diff --git a/tools/flaky-test-reporter/jest.config.js b/tools/flaky-test-reporter/jest.config.js new file mode 100644 index 000000000000..ae286b8ce8ad --- /dev/null +++ b/tools/flaky-test-reporter/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + displayName: 'flaky-test-reporter', + preset: '../jest.config.base.js', +}; diff --git a/tools/flaky-test-reporter/package.json b/tools/flaky-test-reporter/package.json new file mode 100644 index 000000000000..6a5ef539078c --- /dev/null +++ b/tools/flaky-test-reporter/package.json @@ -0,0 +1,27 @@ +{ + "name": "flaky-test-reporter", + "version": "26.2.0", + "type": "commonjs", + "private": true, + "author": "Developer Express Inc.", + "license": "MIT", + "scripts": { + "collect": "ts-node src/main.ts", + "format": "prettier --write . --ignore-path ../.prettierignore", + "format:check": "prettier --check . --ignore-path ../.prettierignore", + "lint": "pnpm run format:check", + "test": "jest" + }, + "dependencies": { + "yargs": "17.7.2" + }, + "devDependencies": { + "@types/jest": "30.0.0", + "@types/node": "catalog:tools", + "@types/yargs": "17.0.35", + "prettier": "catalog:tools", + "ts-jest": "29.4.12", + "ts-node": "catalog:tools", + "typescript": "catalog:tools" + } +} diff --git a/tools/flaky-test-reporter/src/main.test.ts b/tools/flaky-test-reporter/src/main.test.ts new file mode 100644 index 000000000000..7d09cbcaffeb --- /dev/null +++ b/tools/flaky-test-reporter/src/main.test.ts @@ -0,0 +1,44 @@ +import { parseArgs } from './main'; + +const flags = [ + '--repo', + 'o/r', + '--workflow', + 'testcafe_tests.yml', + '--artifact', + 'flaky-candidates', + '--branch', + 'main', + '--window-hours', + '48', + '--out-json', + 'report.json', + '--out-markdown', + 'report.md', +]; + +describe('parseArgs', () => { + it('parses the flags', () => { + expect(parseArgs(flags)).toMatchObject({ + repo: 'o/r', + workflow: 'testcafe_tests.yml', + artifact: 'flaky-candidates', + branch: 'main', + windowHours: 48, + outJson: 'report.json', + outMarkdown: 'report.md', + }); + }); + + it('parses identically when invoked through `pnpm run