diff --git a/.gitattributes b/.gitattributes index 98dea90aab..5489057bab 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,12 +1,3 @@ -*.png filter=lfs diff=lfs merge=lfs -text -*.jpg filter=lfs diff=lfs merge=lfs -text -*.mp4 filter=lfs diff=lfs merge=lfs -text -*.mp3 filter=lfs diff=lfs merge=lfs -text -*.wav filter=lfs diff=lfs merge=lfs -text -*.gif filter=lfs diff=lfs merge=lfs -text -*.pdn filter=lfs diff=lfs merge=lfs -text -*.svg filter=lfs diff=lfs merge=lfs -text -*.xlsx filter=lfs diff=lfs merge=lfs -text *.json eol=crlf *.ts eol=crlf diff --git a/.github/workflows/deprecated/build-and-upload.yml b/.github/workflows/deprecated/build-and-upload.yml deleted file mode 100644 index 761bb6faab..0000000000 --- a/.github/workflows/deprecated/build-and-upload.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Build and Upload -on: - push: - branches: - - none - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20] - - steps: - - name: Install crcmod - run: | - sudo apt-get install gcc python-dev python-setuptools - sudo pip install -I --no-cache-dir -U crcmod - - uses: actions/checkout@v3 - with: - lfs: true - - name: Checkout LFS objects - run: git lfs checkout - - name: Node ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: 'yarn' - - name: Setup GCloud - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master - with: - version: "290.0.1" - project_id: ${{ secrets.GCP_PROJECT_ID }} - service_account_key: ${{ secrets.GCP_CREDENTIALS }} - export_default_credentials: true - - run: gcloud info - - name: Populate firebaseConfig.ts - env: - FIREBASE_CONFIG_TS: ${{ secrets.FIREBASE_CONFIG_TS }} - run: echo $FIREBASE_CONFIG_TS > src/environments/firebaseConfig.ts - - name: yarn install - run: yarn install - - name: Set Deployment - run: yarn workflow deployment set "$APP_DEPLOYMENT_NAME" - env: - APP_DEPLOYMENT_NAME: ${{ secrets.APP_DEPLOYMENT_NAME }} - - name: yarn build - run: yarn build - - id: upload-files - name: Upload files to Google Cloud Storage (rsync) - timeout-minutes: 15 - run: gsutil rsync -d -R -c ./www gs://parenting-app-ui-master1/ && gsutil web set -m index.html gs://parenting-app-ui-master1/ diff --git a/.github/workflows/deprecated/deployment-hosting.yml b/.github/workflows/deprecated/deployment-hosting.yml deleted file mode 100644 index 89317c308d..0000000000 --- a/.github/workflows/deprecated/deployment-hosting.yml +++ /dev/null @@ -1,92 +0,0 @@ -# Deploy to firebase hosting on PR merge -# Specifies deployment target based on branch -# Master -> debug -# Deployment/{deployment_name} -> {deployment_name} -name: Deployment Hosting -on: - push: - branches: - - deployment/* - - master - -concurrency: - group: deployment-hosting-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - uses: ./.github/workflows/web-build.yml - secrets: inherit - with: - build-flags: --configuration "production,glitchtip" - # use branch name from PR target as default - # https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables - deployment: ${{ github.ref }} - - sourcemaps_upload: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: set env - run: | - echo "APP_VERSION=$(echo "$(cat package.json | jq -r '.version')")" >> $GITHUB_ENV - echo "DEPLOYMENT_NAME=${{needs.build.outputs.DEPLOYMENT_NAME}}" >> $GITHUB_ENV - echo "GIT_SHA=${{needs.build.outputs.GIT_SHA}}" >> $GITHUB_ENV - - name: Download Build Artifact - uses: actions/download-artifact@v4 - with: - name: www - - name: Extract Build folder - run: | - mkdir www - tar -xf artifact.tar --directory www - - name: Upload sourcemaps - run: | - npx @sentry/cli releases delete ${DEPLOYMENT_NAME}-${APP_VERSION}-${GIT_SHA} - npx @sentry/cli releases files ${DEPLOYMENT_NAME}-${APP_VERSION}-${GIT_SHA} upload-sourcemaps www/ - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN}} - SENTRY_URL: https://app.glitchtip.com/ - SENTRY_ORG: idems - SENTRY_PROJECT: ${{env.DEPLOYMENT_NAME}} - continue-on-error: true - - name: Store sourcemaps artifact - uses: actions/upload-artifact@v4 - with: - name: sourcemaps-$GIT_SHA - path: www/*.map - # Only used for short-term debugging purposes or manual upload - retention-days: 30 - - deploy: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Download Build Artifact - uses: actions/download-artifact@v4 - with: - name: www - - name: Extract Build folder - run: | - mkdir www - tar -xf artifact.tar --directory www - - name: Remove sourcemaps from build - run: | - echo $(ls www) - find . -name "*.map" -type f -delete - echo $(ls www) - - ### Deploy ### - - uses: FirebaseExtended/action-hosting-deploy@v0 - with: - repoToken: "${{ secrets.GITHUB_TOKEN }}" - firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_PLH_TEENS_APP1 }}" - projectId: plh-teens-app1 - # NOTE - deployment target should be set in firebase.json and .firebaserc - target: "${{needs.build.outputs.DEPLOYMENT_NAME}}" - channelId: live - # Known issue - success message does not specify correct deployment url - # Not used anywhere else so assume fine for now (only gh action output incorrect) - # https://github.com/FirebaseExtended/action-hosting-deploy/issues/126 diff --git a/.github/workflows/deprecated/pr-build.yml b/.github/workflows/deprecated/pr-build.yml deleted file mode 100644 index d83084062c..0000000000 --- a/.github/workflows/deprecated/pr-build.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: PR Build -on: - push: - branches: - - none -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20] - - steps: - - name: Get PR Number - run: | - PR_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") - echo "PR Number is $PR_NUMBER" - echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV - - uses: actions/checkout@v3 - with: - lfs: true - - name: Checkout LFS objects - run: git lfs checkout - - name: Node ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: 'yarn' - - name: Populate firebaseConfig.ts - env: - FIREBASE_CONFIG_TS: ${{ secrets.FIREBASE_CONFIG_TS }} - run: echo $FIREBASE_CONFIG_TS > src/environments/firebaseConfig.ts - - name: yarn install - run: yarn install - - name: Set Deployment - run: yarn workflow deployment set "$APP_DEPLOYMENT_NAME" - env: - APP_DEPLOYMENT_NAME: ${{ secrets.APP_DEPLOYMENT_NAME }} - - name: Build Angular for PR - run: npx ng build --prod --base-href /$PR_NUMBER/ --deploy-url /$PR_NUMBER/ - - name: index.html as 404 page - run: cp ./www/index.html ./www/404.html - - name: Deploy to Github pages 🚀 - uses: JamesIves/github-pages-deploy-action@4.1.0 - with: - branch: gh-pages - folder: www - target-folder: ${{ env.PR_NUMBER }} - repository-name: IDEMSInternational/parenting-app-ui-pr-preview - ssh-key: ${{ secrets.PR_GH_DEPLOY_KEY }} - - uses: ouzi-dev/commit-status-updater@v1.1.0 - env: - PR_URL: https://plh-pr.idems.international/${{ env.PR_NUMBER }}/index.html - with: - status: "success" - url: ${{ env.PR_URL }} - description: "Uploaded PR Preview to Github Pages" - name: "PR Preview" diff --git a/.github/workflows/deprecated/sourcemaps-upload.yml b/.github/workflows/deprecated/sourcemaps-upload.yml deleted file mode 100644 index 09db67796c..0000000000 --- a/.github/workflows/deprecated/sourcemaps-upload.yml +++ /dev/null @@ -1,77 +0,0 @@ -# DEPRECATED CC-2023-08-15 -# Not currently in use, could add back if troubleshooting sourcemaps (will need refactor to common build format) - - -# Secrets -# SENTRY_AUTH_TOKEN - -# Upload build sourcemaps to 3rd party error monitoring service -# NOTE - likely will merge with deploy code unless can find a tidy way to link - -# NOTE - currently only used for debugging as methods are integrated into build scripts -# Not recommended for full testing as likely file changes during ci scripts across actions will invalidate sourcemaps - -name: Sourcemaps Upload -on: - workflow_dispatch: -jobs: - sourcemaps_upload: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - lfs: true - - name: Set Environment Deployment - # NOTE - setting SHA_SHORT will have different value if target pull_request / workflow_dispatch - # https://github.com/orgs/community/discussions/25191 - run: | - echo "SHA_SHORT=$(git rev-parse --short=6 HEAD)" >> $GITHUB_ENV - echo "DEPLOYMENT_NAME=plh_global" >> $GITHUB_ENV - shell: bash - - name: Node ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: 20.17.0 - cache: 'yarn' - - name: Populate environment config - env: - FIREBASE_CONFIG_TS: ${{ secrets.FIREBASE_CONFIG_TS }} - run: | - echo "export const GIT_SHA = \"$SHA_SHORT\";" > src/environments/sha.ts - echo $FIREBASE_CONFIG_TS > src/environments/firebaseConfig.ts - - run: yarn install - - name: Set Scripts Deployment - run: yarn workflow deployment set $DEPLOYMENT_NAME - ### Generate and upload sourcemaps ### - - run: yarn build --configuration "production,glitchtip" - env: - # Fix possible out-of-memory issues - NODE_OPTIONS: --max_old_space_size=6144 - - name: set app version - run: echo "APP_VERSION=$(echo "$(cat package.json | jq -r '.version')")" >> $GITHUB_ENV - - name: Upload sourcemaps - run: | - npx @sentry/cli releases delete ${DEPLOYMENT_NAME}-${APP_VERSION}-${SHA_SHORT} - npx @sentry/cli releases files ${DEPLOYMENT_NAME}-${APP_VERSION}-${SHA_SHORT} upload-sourcemaps www/ - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN}} - SENTRY_URL: https://app.glitchtip.com/ - SENTRY_ORG: idems - SENTRY_PROJECT: ${{env.DEPLOYMENT_NAME}} - continue-on-error: true - - name: Store sourcemaps artifact - uses: actions/upload-artifact@v4 - with: - name: sourcemaps-$SHA_SHORT - path: www/*.map - # Only used for short-term debugging purposes or manual upload - retention-days: 30 - - name: Remove sourcemaps from build - run: | - echo $(ls www) - find . -name "*.map" -type f -delete - echo $(ls www) - -# NOTE - if testing locally equivalent commands can also be passed as args -# npx @sentry/cli --url https://app.glitchtip.com login -# npx @sentry/cli --url https://app.glitchtip.com releases list --org idems diff --git a/.github/workflows/deprecated/test-e2e.yml b/.github/workflows/deprecated/test-e2e.yml deleted file mode 100644 index b66dece188..0000000000 --- a/.github/workflows/deprecated/test-e2e.yml +++ /dev/null @@ -1,53 +0,0 @@ -# DEPRECATED CC-2023-08-15 -# Not currently in use, should look to add back in the future alongside better e2e strategy - -# Compile and run E2E tests using Cypress. -name: Test E2E -on: - # Run manually for now. - workflow_dispatch: - # NB: Dicuss whether this workflow should be configured to run once - # other types of test runs are complete. - # workflow_run: - # workflows: [Test-Visual Generate Screenshots] - # types: - # - completed -jobs: - test_e2e: - # NB: Potentially use a fixed version? - runs-on: ubuntu-latest - # Use the Cypress provided Docker image with - # Chrome 87 and Firefox 82 pre-installed. - container: cypress/browsers:node12.18.3-chrome87-ff82 - timeout-minutes: 60 - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - lfs: true - - name: Node ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: 20.17.0 - cache: 'yarn' - - name: Populate firebaseConfig.ts - env: - FIREBASE_CONFIG_TS: ${{ secrets.FIREBASE_CONFIG_TS }} - run: echo $FIREBASE_CONFIG_TS > src/environments/firebaseConfig.ts - - run: yarn install - - run: npm run --silent scripts e2e-data parse "./../../PLH E2E Example.xlsx" > "./packages/test-e2e/projects/plh/integration/common/home.spec.clone.js" - - name: Cypress on Chrome - uses: cypress-io/github-action@v2 - timeout-minutes: 10 - with: - browser: chrome - build: yarn precompile - command: yarn workspace test-e2e run:plh - install: false - # These may be useful when not running custom command. - project: "packages/test-e2e" - spec: "packages/test-e2e/projects/plh/integration/**/*" - start: yarn exec ng serve - wait-on: "http://localhost:4200" - # Wait four minutes. - wait-on-timeout: 480 diff --git a/.github/workflows/documentation-deploy.yml b/.github/workflows/documentation-deploy.yml index d8ace0e149..de3826c67e 100644 --- a/.github/workflows/documentation-deploy.yml +++ b/.github/workflows/documentation-deploy.yml @@ -11,13 +11,6 @@ jobs: steps: - name: Checkout main uses: actions/checkout@v4 - # Only pull lfs files for documentation folder - # https://github.com/git-lfs/git-lfs/issues/1351 - - name: Checkout git lfs partial files - run: git lfs pull --include "documentation/*" - - name: Remove Git LFS requirements - # Github pages fails when expecting LFS, so remove hook - run: rm .git/hooks/pre-push - name: Deploy docs uses: mhausenblas/mkdocs-deploy-gh-pages@e55ecab6718b449a90ebd4313f1320f9327f1386 env: diff --git a/.github/workflows/reusable-android-build.yml b/.github/workflows/reusable-android-build.yml index 53705817e6..495b2d4240 100644 --- a/.github/workflows/reusable-android-build.yml +++ b/.github/workflows/reusable-android-build.yml @@ -63,7 +63,6 @@ jobs: with: repository: "IDEMSInternational/open-app-builder.git" ref: ${{env.APP_CODE_BRANCH}} - lfs: true - name: Checkout parent repo if needed if: env.PARENT_DEPLOYMENT_REPO != '' diff --git a/.github/workflows/reusable-app-build.yml b/.github/workflows/reusable-app-build.yml index 2f0f7342a3..77e5c2fb38 100644 --- a/.github/workflows/reusable-app-build.yml +++ b/.github/workflows/reusable-app-build.yml @@ -41,7 +41,7 @@ on: type: string default: "" lfs: - description: Enable git lfs to include download of all binary assets (user-facing deployments) + description: Enable git lfs asset download for content repos (if used by repo) type: boolean default: true @@ -72,6 +72,8 @@ jobs: path: ".idems_app/deployments/${{env.PARENT_DEPLOYMENT_NAME}}" repository: ${{env.PARENT_DEPLOYMENT_REPO}} ref: ${{env.PARENT_DEPLOYMENT_BRANCH}} + # main repo does not use lfs so explicitly omit + lfs: false - name: Checkout deployment uses: actions/checkout@v4 @@ -79,6 +81,7 @@ jobs: ref: ${{inputs.branch}} path: ".idems_app/deployments/${{env.DEPLOYMENT_NAME}}" fetch-depth: 0 + # content repo optionally includes lfs lfs: ${{inputs.lfs}} - name: Populate Encryption key diff --git a/.github/workflows/test-visual-generate.yml b/.github/workflows/test-visual-generate.yml index 44b37563c6..c4e806e581 100644 --- a/.github/workflows/test-visual-generate.yml +++ b/.github/workflows/test-visual-generate.yml @@ -2,9 +2,6 @@ name: Test-Visual Generate Screenshots on: workflow_dispatch: - push: - branches: - - master concurrency: group: test-visual-generate-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true diff --git a/.github/workflows/test-visual.yml b/.github/workflows/test-visual.yml index 2cd3a9aa5c..98f3f97ec9 100644 --- a/.github/workflows/test-visual.yml +++ b/.github/workflows/test-visual.yml @@ -64,12 +64,6 @@ jobs: mkdir www tar -xf artifact.tar --directory www - # HACK - as lifecycle_actions block template view simply remove them all - # TODO - review if still required (CC 2023-08-15) - - name: HACK - Remove lifecycle actions - run: | - rm -f -R www/assets/app_data/sheets/data_list/lifecycle_actions - ########################################################################################### # Generate ########################################################################################### @@ -138,10 +132,10 @@ jobs: ${{ env.BIGGEST_DIFFS }} **Download Link** - https://nightly.link/IDEMSInternational/parenting-app-ui/actions/runs/${{github.run_id}} + https://nightly.link/IDEMSInternational/open-app-builder/actions/runs/${{github.run_id}} **Run Details** - https://github.com/IDEMSInternational/parenting-app-ui/actions/runs/${{github.run_id}} + https://github.com/IDEMSInternational/open-app-builder/actions/runs/${{github.run_id}} # Alt implementation to DL artifact using action instead of download script diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml index e12a3a9943..c958f10156 100644 --- a/.github/workflows/web-build.yml +++ b/.github/workflows/web-build.yml @@ -42,7 +42,7 @@ on: type: boolean default: false lfs: - description: Enable git lfs to include download of all binary assets (user-facing deployments) + description: Enable git lfs asset download for content repos (if used by repo) type: boolean default: true skip-upload: @@ -91,7 +91,8 @@ jobs: steps: - uses: actions/checkout@v4 with: - lfs: ${{inputs.lfs}} + # main repo does not use lfs so explicitly omit + lfs: false ref: ${{inputs.branch}} - name: Setup Node @@ -148,23 +149,30 @@ jobs: run: yarn workflow deployment set ${{env.DEPLOYMENT_NAME}} ############################################################################# - # Build - # Run optional tests before building and uploading final build as artifact - # for use in other actions + # Test + # Run optional tests before building ############################################################################# - name: Lint if: ${{inputs.include-tests}} run: yarn lint && yarn workspace api lint - - # Ensure test xlsx files checked out - - name: Checkout test files - if: ${{ inputs.include-tests && !inputs.lfs }} - run: git lfs pull --include "packages/scripts/test/data/input/*" - - name: Test + - name: Test - Shared + if: ${{inputs.include-tests}} + run: yarn workspace shared test + + - name: Test - Scripts if: ${{inputs.include-tests}} - run: yarn test:workspaces + run: yarn workspace scripts test + + - name: Test - Src + if: ${{inputs.include-tests}} + run: yarn ng test --source-map=false --browsers=ChromeHeadless --watch=false + + ############################################################################# + # Build + # Build and upload as artifact for use in other actions + ############################################################################# - name: Build run: yarn build ${{inputs.build-flags}} diff --git a/.gitignore b/.gitignore index d053d13e66..2827aa49c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,99 +1,101 @@ -*~ -*.sw[mnpcod] -.tmp -*.tmp -*.tmp.* -*.sublime-project -*.sublime-workspace -.DS_Store -Thumbs.db -UserInterfaceState.xcuserstate -$RECYCLE.BIN/ -~$*.xlsx - -*.log -log.txt -npm-debug.log* - -/.idea -/.ionic -/.sass-cache -/.sourcemaps -/.versions -.vscode/* -!.vscode/launch.json -!.vscode/extensions.json -/coverage -/dist -node_modules -/platforms -/plugins -/www -yarn.lock.json -google-services.json -GoogleService-Info.plist - -resources/android -resources/ios -package-lock.json -src/assets/app_data -**/*.jks - - - -/scripts/input -/scripts/output -/scripts/plh-spreadsheet/input -/scripts/plh-spreadsheet/output - -.env -scripts/config/token.json - -.eslintcache - -# Ignore yarn-v2 dependencies -# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored -.yarn/* -!.yarn/patches -!.yarn/releases -!.yarn/plugins -!.yarn/sdks -!.yarn/versions -.pnp.* -node_modules -.vs/slnx.sqlite - -.angular -.gradle - -# Ignore auto-lock-install -yarn.auto-install - -# Ignore supabase CLI config folder -# Generated by https://supabase.com/docs/reference/cli/supabase-init -supabase - -# Firebase -firebase.json -.firebase -.firebaserc - -# Avoid comitting private keys -private.key - -.nx - -# Native config files populated by scripts -capacitor.config.ts -capacitor.config.json -android/app/build.gradle -android/app/src/main/AndroidManifest.xml -android/app/src/main/java/international/idems/debug_app/MainActivity.java -android/app/src/main/res/values/strings.xml -ios/App/App.xcodeproj/project.pbxproj -ios/App/App/capacitor.config.json -ios/App/App/config.xml - - -# Deployment-specific files +*~ +*.sw[mnpcod] +.tmp +*.tmp +*.tmp.* +*.sublime-project +*.sublime-workspace +.DS_Store +Thumbs.db +UserInterfaceState.xcuserstate +$RECYCLE.BIN/ +~$*.xlsx + +*.log +log.txt +npm-debug.log* + +/.idea +/.ionic +/.sass-cache +/.sourcemaps +/.versions +.vscode/* +!.vscode/launch.json +!.vscode/extensions.json +/coverage +/dist +node_modules +/platforms +/plugins +/www +yarn.lock.json +google-services.json +GoogleService-Info.plist + +resources/android +resources/ios +package-lock.json +src/assets/app_data +**/*.jks + + + +/scripts/input +/scripts/output +/scripts/plh-spreadsheet/input +/scripts/plh-spreadsheet/output + +.env +scripts/config/token.json + +.eslintcache + +# Ignore yarn-v2 dependencies +# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored +.yarn/* +!.yarn/patches +!.yarn/releases +!.yarn/plugins +!.yarn/sdks +!.yarn/versions +.pnp.* +node_modules +.vs/slnx.sqlite + +.angular +.gradle + +# Ignore auto-lock-install +yarn.auto-install + +# Ignore supabase CLI config folder +# Generated by https://supabase.com/docs/reference/cli/supabase-init +supabase + +# Firebase +firebase.json +.firebase +.firebaserc + +# Avoid comitting private keys +private.key + +.nx + +karma-result.json + +# Native config files populated by scripts +capacitor.config.ts +capacitor.config.json +android/app/build.gradle +android/app/src/main/AndroidManifest.xml +android/app/src/main/java/international/idems/debug_app/MainActivity.java +android/app/src/main/res/values/strings.xml +ios/App/App.xcodeproj/project.pbxproj +ios/App/App/capacitor.config.json +ios/App/App/config.xml + + +# Deployment-specific files src/**/*.deployment.ts \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index dd33ce440e..0000000000 --- a/.gitpod.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Commands to start on workspace startup -tasks: - - name: Start App Server - # env vars stored as project environment variable in gitpod - before: | - sudo apt-get install git-lfs - git lfs pull - echo $FIREBASE_CONFIG_TS_B64 | base64 -d > src/environments/firebaseConfig.ts - init: yarn install - # https://www.gitpod.io/blog/gitpodify/#unreachable-localhost-urls - command: yarn ng serve --host 0.0.0.0 --disable-host-check - ## Could provide additional task to run in parallel terminal - # - name: Sync content - -# Ports to expose on workspace startup -ports: - - port: 4200 - onOpen: open-preview -github: - prebuilds: - master: true - branches: false - pullRequests: false - pullRequestsFromForks: false - addCheck: false - addComment: false - addBadge: false -vscode: - extensions: - - Angular.ng-template - - dbaeumer.vscode-eslint - - esbenp.prettier-vscode diff --git a/.husky/pre-push b/.husky/pre-push deleted file mode 100755 index 0f0089bc25..0000000000 --- a/.husky/pre-push +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; } -git lfs pre-push "$@" diff --git a/.idems_app/deployments/local/sheets/demo.xlsx b/.idems_app/deployments/local/sheets/demo.xlsx index 6f86a46fa6..41b566c754 100644 Binary files a/.idems_app/deployments/local/sheets/demo.xlsx and b/.idems_app/deployments/local/sheets/demo.xlsx differ diff --git a/README.md b/README.md index 1a7d5f3589..1154baa5ab 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/IDEMSInternational/open-app-builder.git) - # Open App Builder [Online Documentation](https://idemsinternational.github.io/open-app-builder/) @@ -11,13 +9,10 @@ 1. Download and install [Git](https://git-scm.com/downloads) This will be used to download the repository -2. Download and install [Git LFS](https://git-lfs.github.com/) - This will be used to download any required binary assets, such as images or pdfs - -3. Download and install [Node](https://nodejs.org/en/download/) +2. Download and install [Node](https://nodejs.org/en/download/) This is the programming language required to run the project. We currently support any of the versions prefixed `v20.x.x` or `v18.x.x` -4. Download and Install [Yarn](https://classic.yarnpkg.com/en/docs/install) +3. Download and Install [Yarn](https://classic.yarnpkg.com/en/docs/install) This manages all 3rd-party code dependencies ## Installation @@ -26,9 +21,8 @@ To download the repo into the current working directory, run: ``` -git lfs clone https://github.com/IDEMSInternational/open-app-builder.git +git clone https://github.com/IDEMSInternational/open-app-builder.git ``` -Note - if you do a regular git clone, you can always run `git lfs fetch --all` later to sync assets ### Install required dependencies Navigate to the newly cloned directory if you have not done so already: diff --git a/android/app/src/main/res/drawable-land-hdpi/splash.png b/android/app/src/main/res/drawable-land-hdpi/splash.png index c395fb7c14..26e566f048 100644 Binary files a/android/app/src/main/res/drawable-land-hdpi/splash.png and b/android/app/src/main/res/drawable-land-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-land-mdpi/splash.png b/android/app/src/main/res/drawable-land-mdpi/splash.png index 6d77bbfbe9..5c8413689b 100644 Binary files a/android/app/src/main/res/drawable-land-mdpi/splash.png and b/android/app/src/main/res/drawable-land-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-land-xhdpi/splash.png b/android/app/src/main/res/drawable-land-xhdpi/splash.png index 1592bd6033..0b14a60499 100644 Binary files a/android/app/src/main/res/drawable-land-xhdpi/splash.png and b/android/app/src/main/res/drawable-land-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/android/app/src/main/res/drawable-land-xxhdpi/splash.png index b615292b59..eafbedf36a 100644 Binary files a/android/app/src/main/res/drawable-land-xxhdpi/splash.png and b/android/app/src/main/res/drawable-land-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/android/app/src/main/res/drawable-land-xxxhdpi/splash.png index 19a8c15802..db986ac30e 100644 Binary files a/android/app/src/main/res/drawable-land-xxxhdpi/splash.png and b/android/app/src/main/res/drawable-land-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-hdpi/splash.png b/android/app/src/main/res/drawable-port-hdpi/splash.png index d460d8be76..db70d116da 100644 Binary files a/android/app/src/main/res/drawable-port-hdpi/splash.png and b/android/app/src/main/res/drawable-port-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-mdpi/splash.png b/android/app/src/main/res/drawable-port-mdpi/splash.png index d2c9bc51e0..99aef87afb 100644 Binary files a/android/app/src/main/res/drawable-port-mdpi/splash.png and b/android/app/src/main/res/drawable-port-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-xhdpi/splash.png b/android/app/src/main/res/drawable-port-xhdpi/splash.png index 5b42711eb4..3a24d5d141 100644 Binary files a/android/app/src/main/res/drawable-port-xhdpi/splash.png and b/android/app/src/main/res/drawable-port-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/android/app/src/main/res/drawable-port-xxhdpi/splash.png index 9c63e2a9fe..7353fcdbcd 100644 Binary files a/android/app/src/main/res/drawable-port-xxhdpi/splash.png and b/android/app/src/main/res/drawable-port-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/android/app/src/main/res/drawable-port-xxxhdpi/splash.png index fe6dcd503b..abaa1b93d6 100644 Binary files a/android/app/src/main/res/drawable-port-xxxhdpi/splash.png and b/android/app/src/main/res/drawable-port-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable/splash.png b/android/app/src/main/res/drawable/splash.png index 6d77bbfbe9..5c8413689b 100644 Binary files a/android/app/src/main/res/drawable/splash.png and b/android/app/src/main/res/drawable/splash.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 44ba2bf54c..f5317dff5e 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png index 1b8d09cf93..38435a7a4d 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index 412d54c22c..04b9875eec 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index 44ba2bf54c..f5317dff5e 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 227b43f912..b66078e48b 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png index 79ac17911f..464e8ffda7 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 2d194888d6..c42e93bd02 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 227b43f912..b66078e48b 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 8d0e7e92a1..60f99a4344 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png index d8799e2cc3..8b692de755 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index d17b6666b7..5761fc4bcf 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 8d0e7e92a1..60f99a4344 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 5a05c5cf8a..847a318dbb 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png index 5ca788df4e..f2491cdc98 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index 89c26bc21a..838023e2d9 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index 5a05c5cf8a..847a318dbb 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 33e07382ed..dacd7f3857 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png index 7fee2e5d4b..ae0780b67b 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index e3f4893645..217ee9a4cb 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 33e07382ed..dacd7f3857 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/documentation/docs/assets/images/button.png b/documentation/docs/assets/images/button.png index 59664f741c..ec61336b46 100644 Binary files a/documentation/docs/assets/images/button.png and b/documentation/docs/assets/images/button.png differ diff --git a/documentation/docs/authors/_images/looping-data.png b/documentation/docs/authors/_images/looping-data.png index 73b40848ff..963fd4e683 100644 Binary files a/documentation/docs/authors/_images/looping-data.png and b/documentation/docs/authors/_images/looping-data.png differ diff --git a/documentation/docs/authors/_images/notifications-campaign-dropdown.png b/documentation/docs/authors/_images/notifications-campaign-dropdown.png index c37bc46803..f17fe0f1e0 100644 Binary files a/documentation/docs/authors/_images/notifications-campaign-dropdown.png and b/documentation/docs/authors/_images/notifications-campaign-dropdown.png differ diff --git a/documentation/docs/authors/_images/notifications-campaigns-screen.png b/documentation/docs/authors/_images/notifications-campaigns-screen.png index 5d33d4aaec..6e159a377d 100644 Binary files a/documentation/docs/authors/_images/notifications-campaigns-screen.png and b/documentation/docs/authors/_images/notifications-campaigns-screen.png differ diff --git a/documentation/docs/authors/_images/notifications-debug-screen.png b/documentation/docs/authors/_images/notifications-debug-screen.png index 8a0cf52834..d9f5973b40 100644 Binary files a/documentation/docs/authors/_images/notifications-debug-screen.png and b/documentation/docs/authors/_images/notifications-debug-screen.png differ diff --git a/documentation/docs/authors/images/feedback-menu.png b/documentation/docs/authors/images/feedback-menu.png index 3bc015b199..fbc9c81816 100644 Binary files a/documentation/docs/authors/images/feedback-menu.png and b/documentation/docs/authors/images/feedback-menu.png differ diff --git a/documentation/docs/authors/images/feedback-sidebar.png b/documentation/docs/authors/images/feedback-sidebar.png index c5a6e5a27a..acdca7d892 100644 Binary files a/documentation/docs/authors/images/feedback-sidebar.png and b/documentation/docs/authors/images/feedback-sidebar.png differ diff --git a/documentation/docs/authors/images/notifications-campaign-dropdown.png b/documentation/docs/authors/images/notifications-campaign-dropdown.png index c37bc46803..f17fe0f1e0 100644 Binary files a/documentation/docs/authors/images/notifications-campaign-dropdown.png and b/documentation/docs/authors/images/notifications-campaign-dropdown.png differ diff --git a/documentation/docs/authors/images/notifications-campaigns-screen.png b/documentation/docs/authors/images/notifications-campaigns-screen.png index 5d33d4aaec..6e159a377d 100644 Binary files a/documentation/docs/authors/images/notifications-campaigns-screen.png and b/documentation/docs/authors/images/notifications-campaigns-screen.png differ diff --git a/documentation/docs/authors/images/notifications-debug-screen.png b/documentation/docs/authors/images/notifications-debug-screen.png index 8a0cf52834..d9f5973b40 100644 Binary files a/documentation/docs/authors/images/notifications-debug-screen.png and b/documentation/docs/authors/images/notifications-debug-screen.png differ diff --git a/documentation/docs/components/images/audio.png b/documentation/docs/components/images/audio.png index a0d08e05d3..278fa2ce30 100644 Binary files a/documentation/docs/components/images/audio.png and b/documentation/docs/components/images/audio.png differ diff --git a/documentation/docs/components/images/button.png b/documentation/docs/components/images/button.png index 59664f741c..ec61336b46 100644 Binary files a/documentation/docs/components/images/button.png and b/documentation/docs/components/images/button.png differ diff --git a/documentation/docs/components/images/combo_box.png b/documentation/docs/components/images/combo_box.png index 4782ef0177..52499b3ca2 100644 Binary files a/documentation/docs/components/images/combo_box.png and b/documentation/docs/components/images/combo_box.png differ diff --git a/documentation/docs/components/images/html.png b/documentation/docs/components/images/html.png index 830d2c45b2..02855e2ef3 100644 Binary files a/documentation/docs/components/images/html.png and b/documentation/docs/components/images/html.png differ diff --git a/documentation/docs/components/images/latex.png b/documentation/docs/components/images/latex.png index fc37556cbc..2cca719628 100644 Binary files a/documentation/docs/components/images/latex.png and b/documentation/docs/components/images/latex.png differ diff --git a/documentation/docs/components/images/pdf.png b/documentation/docs/components/images/pdf.png index 371631d28a..800366e029 100644 Binary files a/documentation/docs/components/images/pdf.png and b/documentation/docs/components/images/pdf.png differ diff --git a/documentation/docs/components/images/radio_group.png b/documentation/docs/components/images/radio_group.png index e5965ef15c..79363fe9e8 100644 Binary files a/documentation/docs/components/images/radio_group.png and b/documentation/docs/components/images/radio_group.png differ diff --git a/documentation/docs/components/images/round_button.png b/documentation/docs/components/images/round_button.png index 72f9ea6bd3..b9dc634460 100644 Binary files a/documentation/docs/components/images/round_button.png and b/documentation/docs/components/images/round_button.png differ diff --git a/documentation/docs/components/images/simple_checkbox.png b/documentation/docs/components/images/simple_checkbox.png index 58c3b95f44..956dfc9205 100644 Binary files a/documentation/docs/components/images/simple_checkbox.png and b/documentation/docs/components/images/simple_checkbox.png differ diff --git a/documentation/docs/components/images/slider.png b/documentation/docs/components/images/slider.png index dcdefb9efc..654f809f17 100644 Binary files a/documentation/docs/components/images/slider.png and b/documentation/docs/components/images/slider.png differ diff --git a/documentation/docs/components/images/square_button.png b/documentation/docs/components/images/square_button.png index 813062f347..5e3e0318e7 100644 Binary files a/documentation/docs/components/images/square_button.png and b/documentation/docs/components/images/square_button.png differ diff --git a/documentation/docs/components/images/text.png b/documentation/docs/components/images/text.png index 2b2abb0be9..0789da08ac 100644 Binary files a/documentation/docs/components/images/text.png and b/documentation/docs/components/images/text.png differ diff --git a/documentation/docs/components/images/timer.png b/documentation/docs/components/images/timer.png index a9da02d78c..818ef215cf 100644 Binary files a/documentation/docs/components/images/timer.png and b/documentation/docs/components/images/timer.png differ diff --git a/documentation/docs/components/images/video.png b/documentation/docs/components/images/video.png index dc261e1076..afc989baf7 100644 Binary files a/documentation/docs/components/images/video.png and b/documentation/docs/components/images/video.png differ diff --git a/documentation/docs/components/videos/combo_box.mp4 b/documentation/docs/components/videos/combo_box.mp4 index 69d0829151..9c672accdc 100644 Binary files a/documentation/docs/components/videos/combo_box.mp4 and b/documentation/docs/components/videos/combo_box.mp4 differ diff --git a/documentation/docs/contributors/images/paste-image.png b/documentation/docs/contributors/images/paste-image.png index df507590a0..f7b95c203d 100644 Binary files a/documentation/docs/contributors/images/paste-image.png and b/documentation/docs/contributors/images/paste-image.png differ diff --git a/documentation/docs/developers/images/api-docs.png b/documentation/docs/developers/images/api-docs.png index a3a5db80ba..991b537d4d 100644 Binary files a/documentation/docs/developers/images/api-docs.png and b/documentation/docs/developers/images/api-docs.png differ diff --git a/documentation/docs/developers/images/deployment-gdrive-ids.png b/documentation/docs/developers/images/deployment-gdrive-ids.png index 797528ee32..216b9f22aa 100644 Binary files a/documentation/docs/developers/images/deployment-gdrive-ids.png and b/documentation/docs/developers/images/deployment-gdrive-ids.png differ diff --git a/documentation/docs/developers/images/device-testing-1.png b/documentation/docs/developers/images/device-testing-1.png index 809ea943e4..dfde5641f3 100644 Binary files a/documentation/docs/developers/images/device-testing-1.png and b/documentation/docs/developers/images/device-testing-1.png differ diff --git a/documentation/docs/developers/images/device-testing-2.png b/documentation/docs/developers/images/device-testing-2.png index 6baa7be787..caba700ad0 100644 Binary files a/documentation/docs/developers/images/device-testing-2.png and b/documentation/docs/developers/images/device-testing-2.png differ diff --git a/documentation/docs/developers/images/firebase-hosting-add-site.png b/documentation/docs/developers/images/firebase-hosting-add-site.png index aa57d6273c..3f477a31c6 100644 Binary files a/documentation/docs/developers/images/firebase-hosting-add-site.png and b/documentation/docs/developers/images/firebase-hosting-add-site.png differ diff --git a/documentation/docs/developers/images/generated-icon-files.png b/documentation/docs/developers/images/generated-icon-files.png index e483b649a5..a33a40ffb0 100644 Binary files a/documentation/docs/developers/images/generated-icon-files.png and b/documentation/docs/developers/images/generated-icon-files.png differ diff --git a/documentation/docs/developers/images/generated-splash-files.png b/documentation/docs/developers/images/generated-splash-files.png index 8704506c35..e686883a08 100644 Binary files a/documentation/docs/developers/images/generated-splash-files.png and b/documentation/docs/developers/images/generated-splash-files.png differ diff --git a/documentation/docs/developers/images/glitchtip-error-2.png b/documentation/docs/developers/images/glitchtip-error-2.png index 826816a93e..ca39593afc 100644 Binary files a/documentation/docs/developers/images/glitchtip-error-2.png and b/documentation/docs/developers/images/glitchtip-error-2.png differ diff --git a/documentation/docs/developers/images/glitchtip-error.png b/documentation/docs/developers/images/glitchtip-error.png index 3572d050b4..c788507ce9 100644 Binary files a/documentation/docs/developers/images/glitchtip-error.png and b/documentation/docs/developers/images/glitchtip-error.png differ diff --git a/documentation/docs/developers/images/icon-background.png b/documentation/docs/developers/images/icon-background.png index 2854d2db53..deef2f30ad 100644 Binary files a/documentation/docs/developers/images/icon-background.png and b/documentation/docs/developers/images/icon-background.png differ diff --git a/documentation/docs/developers/images/icon-foreground.png b/documentation/docs/developers/images/icon-foreground.png index 8204c4018d..bcf5532fd1 100644 Binary files a/documentation/docs/developers/images/icon-foreground.png and b/documentation/docs/developers/images/icon-foreground.png differ diff --git a/documentation/docs/developers/images/icon.png b/documentation/docs/developers/images/icon.png index 0ab7cb4e51..333534e560 100644 Binary files a/documentation/docs/developers/images/icon.png and b/documentation/docs/developers/images/icon.png differ diff --git a/documentation/docs/developers/images/splash.png b/documentation/docs/developers/images/splash.png index ce29d0f84c..bdf25ba6ba 100644 Binary files a/documentation/docs/developers/images/splash.png and b/documentation/docs/developers/images/splash.png differ diff --git a/documentation/docs/developers/images/xcode-target.png b/documentation/docs/developers/images/xcode-target.png index a084094c20..3ba932ce8b 100644 Binary files a/documentation/docs/developers/images/xcode-target.png and b/documentation/docs/developers/images/xcode-target.png differ diff --git a/documentation/docs/generated/assets/images/button.png b/documentation/docs/generated/assets/images/button.png index 59664f741c..ec61336b46 100644 Binary files a/documentation/docs/generated/assets/images/button.png and b/documentation/docs/generated/assets/images/button.png differ diff --git a/documentation/docs/generated/fonts/ionicons.svg b/documentation/docs/generated/fonts/ionicons.svg index b9f8359ca8..ba35c41f6f 100644 --- a/documentation/docs/generated/fonts/ionicons.svg +++ b/documentation/docs/generated/fonts/ionicons.svg @@ -1,3 +1,2090 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b7ea66c720fae091c26c534f2e82c1c2c9a647d3236e0f6aec080b8bd326e8d2 -size 312749 + + + + + +Created by FontForge 20160407 at Thu Jun 14 08:50:34 2018 + By Adam Bradley +Copyright (c) 2018, Adam Bradley + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/docs/generated/fonts/roboto-v15-latin-300.svg b/documentation/docs/generated/fonts/roboto-v15-latin-300.svg index 2ad7e67c13..52b2832799 100644 --- a/documentation/docs/generated/fonts/roboto-v15-latin-300.svg +++ b/documentation/docs/generated/fonts/roboto-v15-latin-300.svg @@ -1,3 +1,314 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bdc613d60655fab5e7f78d7a4b573e8a72cd3110176d4cb85ef0ebd7e658d211 -size 49930 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/docs/generated/fonts/roboto-v15-latin-700.svg b/documentation/docs/generated/fonts/roboto-v15-latin-700.svg index a3fcf756fd..fc8d42f92f 100644 --- a/documentation/docs/generated/fonts/roboto-v15-latin-700.svg +++ b/documentation/docs/generated/fonts/roboto-v15-latin-700.svg @@ -1,3 +1,310 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8af2654240dc0e632782a4f919c6fe5885c7b9e6b856efa0a7ea989a14fb56fe -size 49092 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/docs/generated/fonts/roboto-v15-latin-italic.svg b/documentation/docs/generated/fonts/roboto-v15-latin-italic.svg index 80a3bbacd1..738b8295f0 100644 --- a/documentation/docs/generated/fonts/roboto-v15-latin-italic.svg +++ b/documentation/docs/generated/fonts/roboto-v15-latin-italic.svg @@ -1,3 +1,323 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea2903eaf37222d05d2c812d4305ae4107ace6a0f62c80ab331952f047e17740 -size 54099 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/docs/generated/fonts/roboto-v15-latin-regular.svg b/documentation/docs/generated/fonts/roboto-v15-latin-regular.svg index 657b03e32b..ed55c105d7 100644 --- a/documentation/docs/generated/fonts/roboto-v15-latin-regular.svg +++ b/documentation/docs/generated/fonts/roboto-v15-latin-regular.svg @@ -1,3 +1,308 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc9d8f0f4fd1364ef57762589dce1c56c97c744043afdde55ca68ce977906a3d -size 48978 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/docs/generated/graph/dependencies.svg b/documentation/docs/generated/graph/dependencies.svg index 49c2f47155..a9c22c00d1 100644 --- a/documentation/docs/generated/graph/dependencies.svg +++ b/documentation/docs/generated/graph/dependencies.svg @@ -1,3 +1,24 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5030de2a7bb010fd08f33320dbed6f3db60705e4a0d84c5947f74dd60aaba5c0 -size 1759 + + + + + + +dependencies + +Legend + +  Declarations + +  Module + +  Bootstrap + +  Providers + +  Exports + + diff --git a/documentation/docs/generated/images/compodoc-vectorise-inverted.png b/documentation/docs/generated/images/compodoc-vectorise-inverted.png index 27fe42a9ef..e95ccfb06c 100644 Binary files a/documentation/docs/generated/images/compodoc-vectorise-inverted.png and b/documentation/docs/generated/images/compodoc-vectorise-inverted.png differ diff --git a/documentation/docs/generated/images/compodoc-vectorise-inverted.svg b/documentation/docs/generated/images/compodoc-vectorise-inverted.svg index 704f3354d2..d1479a564b 100644 --- a/documentation/docs/generated/images/compodoc-vectorise-inverted.svg +++ b/documentation/docs/generated/images/compodoc-vectorise-inverted.svg @@ -1,3 +1,201 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3df721f741ecebbb11cf767299ecb3ec8828606e8d8f4a56b0e563bce5f9905f -size 18459 + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/docs/generated/images/compodoc-vectorise.png b/documentation/docs/generated/images/compodoc-vectorise.png index 985597fd38..8137403549 100644 Binary files a/documentation/docs/generated/images/compodoc-vectorise.png and b/documentation/docs/generated/images/compodoc-vectorise.png differ diff --git a/documentation/docs/generated/images/compodoc-vectorise.svg b/documentation/docs/generated/images/compodoc-vectorise.svg index 98557254c1..5e21f1e3fd 100644 --- a/documentation/docs/generated/images/compodoc-vectorise.svg +++ b/documentation/docs/generated/images/compodoc-vectorise.svg @@ -1,3 +1,201 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2f58b7cda4f6c611c69d0d7d6c1b0b66a7b2c33017f2ce35b3d0475bfed8e6e8 -size 18459 + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/documentation/docs/generated/images/coverage-badge-documentation.svg b/documentation/docs/generated/images/coverage-badge-documentation.svg index 9e48bb90a6..b2ee758113 100644 --- a/documentation/docs/generated/images/coverage-badge-documentation.svg +++ b/documentation/docs/generated/images/coverage-badge-documentation.svg @@ -1,3 +1,9 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3cc5b140509deca0d6718996f0ee22f6eb3143ea7249d15376bcea3f7bb327cd -size 894 + + + + + + documentation + 19% + + diff --git a/documentation/docs/index.md b/documentation/docs/index.md index 318a938168..bcc72619a9 100644 --- a/documentation/docs/index.md +++ b/documentation/docs/index.md @@ -5,13 +5,10 @@ 1. Download and install [Git](https://git-scm.com/downloads) This will be used to download the repository -2. Download and install [Git LFS](https://git-lfs.github.com/) - This will be used to download any required binary assets, such as images or pdfs - -3. Download and install [Node](https://nodejs.org/en/download/) +2. Download and install [Node](https://nodejs.org/en/download/) This is the programming language required to run the project. We currently support any of the versions prefixed `v20.x.x` or `v18.x.x` -4. Download and Install [Yarn](https://classic.yarnpkg.com/en/docs/install) +3. Download and Install [Yarn](https://classic.yarnpkg.com/en/docs/install) This manages all 3rd-party code dependencies ## Installation @@ -23,9 +20,8 @@ To download the repo into the current working directory, run: ``` -git lfs clone https://github.com/IDEMSInternational/open-app-builder.git +git clone https://github.com/IDEMSInternational/open-app-builder.git ``` -Note - if you do a regular git clone, you can always run `git lfs fetch --all` later to sync assets ### Install required dependencies Navigate to the newly cloned directory if you have not done so already: diff --git a/documentation/docs/questions/images/display_group.png b/documentation/docs/questions/images/display_group.png index 2bef052e67..83900b5f23 100644 Binary files a/documentation/docs/questions/images/display_group.png and b/documentation/docs/questions/images/display_group.png differ diff --git a/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png index 4c05cbf007..adf6ba01db 100644 Binary files a/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png and b/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png differ diff --git a/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png b/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png index 6b830e75ec..33ea6c970f 100644 Binary files a/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png and b/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png differ diff --git a/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png b/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png index 6b830e75ec..33ea6c970f 100644 Binary files a/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png and b/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png differ diff --git a/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png b/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png index 6b830e75ec..33ea6c970f 100644 Binary files a/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png and b/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png differ diff --git a/karma.conf.js b/karma.conf.js index 729bbe1847..44b3e987dc 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -5,6 +5,7 @@ module.exports = function (config) { plugins: [ require("karma-jasmine"), require("karma-chrome-launcher"), + require("karma-json-result-reporter"), require("karma-jasmine-html-reporter"), require("karma-coverage"), require("@angular-devkit/build-angular/plugins/karma"), @@ -15,6 +16,7 @@ module.exports = function (config) { // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html // for example, you can disable the random execution with `random: false` // or set a specific seed with `seed: 4321` + random: false, }, clearContext: false, // leave Jasmine Spec Runner output visible in browser }, @@ -26,7 +28,7 @@ module.exports = function (config) { subdir: ".", reporters: [{ type: "html" }, { type: "text-summary" }], }, - reporters: ["progress", "kjhtml"], + reporters: ["progress", "kjhtml", "json-result"], port: 9876, colors: true, logLevel: config.LOG_INFO, @@ -34,5 +36,9 @@ module.exports = function (config) { browsers: ["Chrome"], singleRun: false, restartOnFileChange: true, + jsonResultReporter: { + outputFile: "karma-result.json", + isSynchronous: true, + }, }); }; diff --git a/package.json b/package.json index c915ecc869..001ac14986 100644 --- a/package.json +++ b/package.json @@ -157,6 +157,7 @@ "eslint-plugin-n": "^15.7.0", "eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-promise": "^6.1.1", + "fake-indexeddb": "^6.0.0", "firebase-tools": "^13.6.0", "husky": "^8.0.3", "jasmine-core": "~4.5.0", @@ -166,6 +167,7 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", + "karma-json-result-reporter": "^1.0.0", "lint-staged": "^15.2.2", "prettier": "^3.2.5", "typescript": "5.2.2" diff --git a/packages/@idemsInternational/xlsform-converter/test/form.xlsx b/packages/@idemsInternational/xlsform-converter/test/form.xlsx index c161f9cc8b..b36afe6fa0 100644 Binary files a/packages/@idemsInternational/xlsform-converter/test/form.xlsx and b/packages/@idemsInternational/xlsform-converter/test/form.xlsx differ diff --git a/packages/actions/templates/app-build/template.yml b/packages/actions/templates/app-build/template.yml index fbadc1fe19..6ad35dd90e 100644 --- a/packages/actions/templates/app-build/template.yml +++ b/packages/actions/templates/app-build/template.yml @@ -27,7 +27,6 @@ jobs: steps: - uses: actions/checkout@v3 with: - lfs: true repository: "IDEMSInternational/parenting-app-ui.git" ref: master - uses: actions/checkout@v3 diff --git a/packages/components/plh/parent-point-box/parent-point-box.component.ts b/packages/components/plh/parent-point-box/parent-point-box.component.ts index d4626622f9..ae2b58aa05 100644 --- a/packages/components/plh/parent-point-box/parent-point-box.component.ts +++ b/packages/components/plh/parent-point-box/parent-point-box.component.ts @@ -83,7 +83,7 @@ export class PlhParentPointBoxComponent if (this._row.disabled) { return; } - this._row.value = parseInt(this._row.value) + 1; + this._row.value = parseInt(this._row.value as string) + 1; this._value = this._row.value; this.star.nativeElement.classList.add("on-add"); if (this.play_celebration) { diff --git a/packages/data-models/deployment.model.ts b/packages/data-models/deployment.model.ts index 776b3771ee..d1c1f40053 100644 --- a/packages/data-models/deployment.model.ts +++ b/packages/data-models/deployment.model.ts @@ -3,7 +3,7 @@ import type { IGdriveEntry } from "../@idemsInternational/gdrive-tools"; import type { IAppConfig, IAppConfigOverride } from "./appConfig"; /** Update version to force recompile next time deployment set (e.g. after default config update) */ -export const DEPLOYMENT_CONFIG_VERSION = 20241215.1; +export const DEPLOYMENT_CONFIG_VERSION = 20250202.0; /** Configuration settings available to runtime application */ export interface IDeploymentRuntimeConfig { @@ -46,9 +46,9 @@ export interface IDeploymentRuntimeConfig { /** * Specify if using firebase for auth and crashlytics. * Requires firebase config available through encrypted config */ - firebase: { + firebase?: { /** Project config as specified in firebase console (recommend loading from encrypted environment) */ - config?: { + config: { apiKey: string; authDomain: string; databaseURL: string; @@ -58,8 +58,8 @@ export interface IDeploymentRuntimeConfig { appId: string; measurementId: string; }; - crashlytics: { - /** Enables app crash reports to firebase crashlytics */ + /** Configure app crash reports to firebase crashlytics */ + crashlytics?: { enabled: boolean; }; }; @@ -204,10 +204,6 @@ export const DEPLOYMENT_RUNTIME_CONFIG_DEFAULTS: IDeploymentRuntimeConfig = { }, app_config: {}, auth: {}, - firebase: { - config: null, - crashlytics: { enabled: true }, - }, supabase: { enabled: false, }, diff --git a/packages/data-models/fields.ts b/packages/data-models/fields.ts index 60b4c2b64a..12da5e2e3a 100644 --- a/packages/data-models/fields.ts +++ b/packages/data-models/fields.ts @@ -22,7 +22,25 @@ enum PROTECTED_FIELDS { SERVER_SYNC_LATEST = "server_sync_latest", } +/** Private fields are protected and will not be synced to the server */ +enum PRIVATE_FIELDS { + AUTH_USER_NAME = "auth_user_name", + AUTH_USER_FAMILY_NAME = "auth_user_family_name", + AUTH_USER_GIVEN_NAME = "auth_user_given_name", + AUTH_USER_PICTURE = "auth_user_picture", +} + +const PRIVATE_FIELDS_INVERSE_MAPPING = Object.fromEntries( + Object.entries(PRIVATE_FIELDS).map(([k, v]) => [v, k]) +); + +/** Check whether a string represents the value from a private field, e.g. 'auth_user_name' */ +export const isPrivateFieldName = (key: string) => key in PRIVATE_FIELDS_INVERSE_MAPPING; + /** Whenever retrieving a protected field make sure to include underscore prefix */ -export const getProtectedFieldName = (key: IProtectedFieldName) => `_${PROTECTED_FIELDS[key]}`; +export const getProtectedFieldName = (key: IProtectedFieldName) => { + const mappedName = PRIVATE_FIELDS[key] || PROTECTED_FIELDS[key]; + return `_${mappedName}`; +}; -export type IProtectedFieldName = keyof typeof PROTECTED_FIELDS; +export type IProtectedFieldName = keyof typeof PROTECTED_FIELDS | keyof typeof PRIVATE_FIELDS; diff --git a/packages/data-models/flowTypes.ts b/packages/data-models/flowTypes.ts index b254469c99..33e12bc60a 100644 --- a/packages/data-models/flowTypes.ts +++ b/packages/data-models/flowTypes.ts @@ -291,10 +291,26 @@ export namespace FlowTypes { * */ type TemplateRowDeploymentType = string & {}; + /** Mapping of dynamic variables by prefix to value references */ + export interface TemplateRowEvalContext { + item?: TemplateRowItemEvalContextMetadata & { [key: string]: any }; + local?: { [row_nested_name: string]: FlowTypes.TemplateRow["value"] }; + row?: FlowTypes.TemplateRow; + /** + * HACK - when evaluating dynamic context the "field" value references the column name being evaluated + * and not @field values + * TODO - refactor + */ + field?: string; + // TODO - type definition appears in template-calc service but not easy to import (should refactor) + calc?: any; + // TODO - generic support for all prefixes + } + export interface TemplateRow extends Row_with_translations { type: TemplateRowBaseType | TemplateRowCoreType | TemplateRowDeploymentType; name: string; - value?: any; // TODO - incoming data will be string, so components should handle own parsing + value?: boolean | number | string; action_list?: TemplateRowAction[]; style_list?: string[]; parameter_list?: { [param: string]: string }; @@ -314,7 +330,7 @@ export namespace FlowTypes { /** Keep a list of dynamic dependencies used within a template, by reference (e.g. {@local.var1 : ["text_1"]}) */ _dynamicDependencies?: { [reference: string]: string[] }; _translatedFields?: { [field: string]: any }; - _evalContext?: any; // force specific context variables when calculating eval statements (such as loop items) + _evalContext?: TemplateRowEvalContext; __EMPTY?: any; // empty cells (can be removed after pr 679 merged) } @@ -338,7 +354,7 @@ export namespace FlowTypes { const DYNAMIC_PREFIXES_COMPILER = ["gen", "row", "default"] as const; - const DYNAMIC_PREFIXES_RUNTIME = [ + export const DYNAMIC_PREFIXES_RUNTIME = [ "local", "field", "fields", @@ -350,6 +366,8 @@ export namespace FlowTypes { "raw", ] as const; + type IDynamicPrefixRuntime = (typeof DYNAMIC_PREFIXES_RUNTIME)[number]; + export const DYNAMIC_PREFIXES = [ ...DYNAMIC_PREFIXES_COMPILER, ...DYNAMIC_PREFIXES_RUNTIME, diff --git a/packages/scripts/src/commands/app-data/convert/processors/flowParser/parsers/template.parser.ts b/packages/scripts/src/commands/app-data/convert/processors/flowParser/parsers/template.parser.ts index afbdfbcda9..e0296bb58b 100644 --- a/packages/scripts/src/commands/app-data/convert/processors/flowParser/parsers/template.parser.ts +++ b/packages/scripts/src/commands/app-data/convert/processors/flowParser/parsers/template.parser.ts @@ -37,7 +37,9 @@ export class TemplateParser extends DefaultParser { } if (row.name?.endsWith("_collection") || row.name?.includes("_collection_")) { if (row.value && typeof row.value === "string") { - row.value = parseAppDataCollectionString(row.value); + // TODO - verify if case used and whether it might be better to use a different + // column to store parsed object literal in value (would require type defs update) + row.value = parseAppDataCollectionString(row.value) as any; } } } @@ -164,7 +166,7 @@ export class TemplateParser extends DefaultParser { switch (row.type) { // template row name assigned to target template name case "template": - return row.value; + return row.value as string; // default use combination of row type and row number default: return `${row.type}_${rowNumber}`; diff --git a/packages/scripts/test/data/input/errorChecking/test_duplicate.xlsx b/packages/scripts/test/data/input/errorChecking/test_duplicate.xlsx index 848216f868..1d0761bbfb 100644 Binary files a/packages/scripts/test/data/input/errorChecking/test_duplicate.xlsx and b/packages/scripts/test/data/input/errorChecking/test_duplicate.xlsx differ diff --git a/packages/scripts/test/data/input/errorChecking/test_errorChecking.xlsx b/packages/scripts/test/data/input/errorChecking/test_errorChecking.xlsx index 805693f5ef..e8d7bed686 100644 Binary files a/packages/scripts/test/data/input/errorChecking/test_errorChecking.xlsx and b/packages/scripts/test/data/input/errorChecking/test_errorChecking.xlsx differ diff --git a/packages/scripts/test/data/input/sheets/test_input.xlsx b/packages/scripts/test/data/input/sheets/test_input.xlsx index fd5a215a4e..7ac09bbf08 100644 Binary files a/packages/scripts/test/data/input/sheets/test_input.xlsx and b/packages/scripts/test/data/input/sheets/test_input.xlsx differ diff --git a/packages/scripts/test/data/input/sheets_additional/test_additional_input.xlsx b/packages/scripts/test/data/input/sheets_additional/test_additional_input.xlsx index a47abc6695..23533a46bb 100644 Binary files a/packages/scripts/test/data/input/sheets_additional/test_additional_input.xlsx and b/packages/scripts/test/data/input/sheets_additional/test_additional_input.xlsx differ diff --git a/packages/test-e2e/package.json b/packages/test-e2e/package.json index 1d5cc37ac8..f79deccd78 100644 --- a/packages/test-e2e/package.json +++ b/packages/test-e2e/package.json @@ -16,7 +16,7 @@ "concurrently": "^6.2.1", "cypress": "^8.3.0", "cypress-image-snapshot": "^4.0.1", - "data-models": "1.0.0", + "data-models": "workspace:*", "typescript": "~4.2.4", "wait-on": "^6.0.0" } diff --git a/packages/test-e2e/projects/plh/snapshots/common/example_e2e_test.spec.ts/[Visual testing example] -- [Visits site].snap.png b/packages/test-e2e/projects/plh/snapshots/common/example_e2e_test.spec.ts/[Visual testing example] -- [Visits site].snap.png index de5746db53..c958382717 100644 Binary files a/packages/test-e2e/projects/plh/snapshots/common/example_e2e_test.spec.ts/[Visual testing example] -- [Visits site].snap.png and b/packages/test-e2e/projects/plh/snapshots/common/example_e2e_test.spec.ts/[Visual testing example] -- [Visits site].snap.png differ diff --git a/packages/test-e2e/projects/plh/snapshots/common/example_e2e_test.spec.ts/__diff_output__/[Visual testing example] -- [Visits site].diff.png b/packages/test-e2e/projects/plh/snapshots/common/example_e2e_test.spec.ts/__diff_output__/[Visual testing example] -- [Visits site].diff.png index d62a962872..9768afc366 100644 Binary files a/packages/test-e2e/projects/plh/snapshots/common/example_e2e_test.spec.ts/__diff_output__/[Visual testing example] -- [Visits site].diff.png and b/packages/test-e2e/projects/plh/snapshots/common/example_e2e_test.spec.ts/__diff_output__/[Visual testing example] -- [Visits site].diff.png differ diff --git a/packages/test-e2e/test/input.xlsx b/packages/test-e2e/test/input.xlsx index 90923e29f2..0c8a7411e2 100644 Binary files a/packages/test-e2e/test/input.xlsx and b/packages/test-e2e/test/input.xlsx differ diff --git a/packages/test-visual/.env.sample b/packages/test-visual/.env.sample index 10c3104f3c..0063e44c8c 100644 --- a/packages/test-visual/.env.sample +++ b/packages/test-visual/.env.sample @@ -1,2 +1,2 @@ GH_REPO_ORG=IDEMSInternational -GH_REPO_NAME=parenting-app-ui \ No newline at end of file +GH_REPO_NAME=open-app-builder \ No newline at end of file diff --git a/packages/test-visual/package.json b/packages/test-visual/package.json index f19807ff71..b5ed484f49 100644 --- a/packages/test-visual/package.json +++ b/packages/test-visual/package.json @@ -3,40 +3,39 @@ "version": "1.0.0", "description": "", "main": "lib/index.js", + "type": "module", "bin": { "idems-test-visual": "./lib/index.js" }, "scripts": { "build": "tsc ", - "dev": "ts-node-dev --respawn --watch 'src/**/*.ts' src/index.ts", - "start": "ts-node src/index.ts" + "dev": "tsx watch src/index.ts generate", + "start": "tsx src/index.ts" }, "dependencies": { - "archiver": "^5.3.0", - "axios": "^1.7.4", - "boxen": "^5.1.2", - "chalk": "^4.1.2", - "commander": "^8.2.0", - "data-models": "1.0.0", - "dotenv": "^10.0.0", + "archiver": "^7.0.1", + "boxen": "^8.0.1", + "chalk": "^5.4.1", + "commander": "^13.1.0", + "data-models": "workspace:*", + "dotenv": "^16.4.7", "extract-zip": "^2.0.1", - "fs-extra": "^10.0.0", + "fs-extra": "^11.3.0", "jpeg-js": "^0.4.4", - "log-update": "^4.0.0", - "octokit": "^3.1.2", - "p-queue": "^6.6.2", - "pixelmatch": "^5.2.1", - "pngjs": "^6.0.0", - "puppeteer": "^10.2.0", - "serve": "^13.0.2", - "ts-node": "^10.8.0", - "typescript": "~4.2.4" + "log-update": "^6.1.0", + "octokit": "^4.1.0", + "p-queue": "^8.1.0", + "pixelmatch": "^6.0.0", + "pngjs": "^7.0.0", + "puppeteer": "^24.1.1", + "serve": "^14.2.4", + "tsx": "^4.19.2", + "typescript": "~5.7.3" }, "devDependencies": { - "@types/fs-extra": "^9.0.12", - "@types/node": "^15.12.4", - "@types/pixelmatch": "^5.2.4", - "@types/pngjs": "^6.0.1", - "ts-node-dev": "^1.1.8" + "@types/fs-extra": "^11.0.4", + "@types/node": "^22.13.0", + "@types/pixelmatch": "^5.2.6", + "@types/pngjs": "^6.0.5" } } diff --git a/packages/test-visual/src/commands/download.ts b/packages/test-visual/src/commands/download.ts index 5c857a2410..8ee3ecc39c 100644 --- a/packages/test-visual/src/commands/download.ts +++ b/packages/test-visual/src/commands/download.ts @@ -49,12 +49,16 @@ export class ScreenshotDownload { * TODO - not filtered to specific branch run (assume fine) */ private async getLatestScreenshotsArtifact() { - const artifacts = await getGHRepoArtifacts(); - const latestArtifact = artifacts.find((a) => a.name === SCREENSHOT_ARTIFACT_NAME); + const artifacts = await getGHRepoArtifacts({ + name: SCREENSHOT_ARTIFACT_NAME, + page: 1, + per_page: 1, + }); + // artifacts return in reverse-chronological order so first entry should be latest + const [latestArtifact] = artifacts; if (!latestArtifact) { throw new Error(`No artifacts found with name: ${SCREENSHOT_ARTIFACT_NAME}`); } - console.log("latest artifact", latestArtifact); const browser_download_url = getGHRepoArtifactDLLink(latestArtifact); return { browser_download_url, id: latestArtifact.id }; } diff --git a/packages/test-visual/src/commands/generate.ts b/packages/test-visual/src/commands/generate.ts index b717ec9296..12caffa5c0 100644 --- a/packages/test-visual/src/commands/generate.ts +++ b/packages/test-visual/src/commands/generate.ts @@ -1,21 +1,30 @@ import { Command } from "commander"; -import puppeteer from "puppeteer"; +import puppeteer, { Browser, Page } from "puppeteer"; import path from "path"; import PQueue from "p-queue"; import fs from "fs-extra"; import handler from "serve-handler"; import http from "http"; import logUpdate from "log-update"; +import type { Dexie } from "dexie"; import { DEXIE_SRC_PATH, paths } from "../config"; import { outputCompleteMessage, outputErrorMessage, zipFolder } from "../utils"; import { VISUAL_TEST_CONFIG } from "../config/test"; +import { _wait } from "../../../shared/src/utils/async-utils"; type IPageConfig = (typeof VISUAL_TEST_CONFIG)["pageList"][number]; type IDexieConfig = (typeof VISUAL_TEST_CONFIG)["dexieConfig"]; -// Import Dexie from the src folder so that same instance can be used to seed the DB -// as is used in the app itself. Uses require import syntax for compatibility -const Dexie = require(DEXIE_SRC_PATH); +// HACK - fix tsx issue +// https://github.com/privatenumber/tsx/issues/113 +const { toString } = Function.prototype; +Function.prototype.toString = function () { + const stringified = Reflect.apply(toString, this, arguments); + return `function () { + const __name = (target, value) => Object.defineProperty(target, "name", { value, configurable: true }); + return Reflect.apply(${stringified}, this, arguments); + }`; +}; /*************************************************************************************** * Configuration @@ -79,8 +88,8 @@ export default program *************************************************************************************/ export class ScreenshotGenerate { - browser: puppeteer.Browser; - page: puppeteer.Page; + browser: Browser; + page: Page; server?: http.Server; private options: IProgramOptions; @@ -142,10 +151,16 @@ export class ScreenshotGenerate { /** Create initial puppeteer browser and custom page objects */ private async prepareBrowserRunner() { const { height, width } = VISUAL_TEST_CONFIG.pageDefaults; + const args = ["--disable-notifications"]; + // when running via github actions bypass sandbox + // https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#setting-up-chrome-linux-sandbox + if (process.env.CI) { + args.push("--disable-setuid-sandbox", "--no-sandbox"); + } this.browser = await puppeteer.launch({ headless: !this.options.debug, defaultViewport: { width, height }, - args: ["--disable-notifications"], + args, dumpio: this.options.debug, }); this.page = await this.setupPage(); @@ -184,7 +199,7 @@ export class ScreenshotGenerate { // run an initial request that can be used to check for console errors in debug mode if (this.options.debug) { await this.page.goto(APP_SERVER_URL, { waitUntil: "networkidle2" }); - await this.page.waitForTimeout(10000); + await _wait(10000); } // create a task queue for handling concurrent requests @@ -195,6 +210,9 @@ export class ScreenshotGenerate { autoStart: false, throwOnTimeout: false, }); + // Ensure page unload dialogs are automatically closed + // https://stackoverflow.com/a/68639531 + const acceptBeforeUnload = (dialog) => dialog.type() === "beforeunload" && dialog.accept(); // setup screenshot requests pageList.forEach((pageConfig) => { @@ -203,6 +221,7 @@ export class ScreenshotGenerate { const outputPath = path.resolve(paths.SCREENSHOTS_FOLDER, `${name}.png`); if (!fs.existsSync(outputPath)) { const page = await this.browser.newPage(); + page.on("dialog", acceptBeforeUnload); // resize page viewport if override provided if (height !== pageDefaults.height || width !== pageDefaults.width) { await page.setViewport({ width: pageConfig.width, height: pageConfig.height }); @@ -250,7 +269,7 @@ export class ScreenshotGenerate { } /** Load a template page from within the app and wait for content to render */ - private async goToUrl(pageConfig: IPageConfig, page: puppeteer.Page) { + private async goToUrl(pageConfig: IPageConfig, page: Page) { const { url, selector } = pageConfig; await page.goto(`${APP_SERVER_URL}/${url}`, { waitUntil: "networkidle2", @@ -259,7 +278,7 @@ export class ScreenshotGenerate { await page.waitForSelector(selector); // Additional timeout to support page fully loading // TODO - replace with function call from the app - await page.waitForTimeout(pageConfig.pageWait); + await _wait(pageConfig.pageWait); // Try to ensure all rendering complete by requesting animation frame await page.evaluate(async () => { async function waitForAnimationFrame() { @@ -299,11 +318,14 @@ export class ScreenshotGenerate { * NOTE - requires dexie scripts to be included (handled in init) **/ private async setIndexedDB(dexieConfig: IDexieConfig) { + // Import Dexie from the src folder so that same instance can be used to seed the DB + // as is used in the app itself. Uses require import syntax for compatibility + await import(`file:///${DEXIE_SRC_PATH}`); const { data, tableSchema, version } = dexieConfig; const passedArgs = { tableSchema, version, data }; return this.page.evaluate(async (args: typeof passedArgs) => { const appWindow: IAppWindow = window as any; - const db = new appWindow.Dexie("plh-app-db"); + const db: Dexie = new appWindow.Dexie("plh-app-db"); const { tableSchema, data, version } = args; // when configuring database from seed require setting a lower version // so that it can be configured as the correct version in the app @@ -324,5 +346,5 @@ export class ScreenshotGenerate { } interface IAppWindow extends Window { - Dexie: typeof Dexie; + Dexie: any; } diff --git a/packages/test-visual/src/config/index.ts b/packages/test-visual/src/config/index.ts index c09d1b7e3a..b417ca3bfa 100644 --- a/packages/test-visual/src/config/index.ts +++ b/packages/test-visual/src/config/index.ts @@ -4,6 +4,8 @@ import dotenv from "dotenv"; const env = loadEnvVars(); +const __dirname = import.meta.dirname; + const ROOT_FOLDER = path.resolve(__dirname, "../../../../"); const REPO_FOLDER = path.resolve(__dirname, "../../"); @@ -36,7 +38,7 @@ function loadEnvVars() { if (result.error) { // no env file, just use defaults const defaultEnv: IEnvVars = { - GH_REPO_NAME: "parenting-app-ui", + GH_REPO_NAME: "open-app-builder", GH_REPO_ORG: "IDEMSInternational", }; return defaultEnv; diff --git a/packages/test-visual/src/config/test.ts b/packages/test-visual/src/config/test.ts index 35cfb2ae42..bb35d44ae0 100644 --- a/packages/test-visual/src/config/test.ts +++ b/packages/test-visual/src/config/test.ts @@ -60,7 +60,13 @@ const VISUAL_TEST_TEMPLATE_LIST = template /** Main export for use in test runner */ export const VISUAL_TEST_CONFIG = { - localStorageFields: { _app_language: "za_en", name: "test default user" }, + localStorageFields: { + _app_language: "en", + name: "test default user", + // set language selected to bypass language select on debug deployment + // TODO - make configurable from deployment and action runner + language_selected: "true", + }, dexieConfig: { version: DB_VERSION, tableSchema: DB_TABLES, diff --git a/packages/test-visual/src/helpers/github.ts b/packages/test-visual/src/helpers/github.ts index 63531cdc43..e67bc4343d 100644 --- a/packages/test-visual/src/helpers/github.ts +++ b/packages/test-visual/src/helpers/github.ts @@ -1,6 +1,7 @@ import fs from "fs-extra"; import path from "path"; import { Octokit } from "octokit"; +import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"; import { GH_REPO_NAME, GH_REPO_ORG, paths } from "../config"; import { downloadToFile } from "../utils"; @@ -20,9 +21,11 @@ type PromiseResolveType = T extends PromiseLike ? U : T; export type IGHReleaseData = PromiseResolveType>[0]; /** Retrieve the latest available repo artifacts (NOTE - non-exhaustive, paging not used) */ -export async function getGHRepoArtifacts() { +export async function getGHRepoArtifacts( + params: Partial +) { const octokit = new Octokit({}); - const artifactsRes = await octokit.rest.actions.listArtifactsForRepo({ repo, owner }); + const artifactsRes = await octokit.rest.actions.listArtifactsForRepo({ repo, owner, ...params }); return artifactsRes.data.artifacts; } type IGHArtifact = PromiseResolveType>[0]; diff --git a/packages/test-visual/src/index.ts b/packages/test-visual/src/index.ts index 3e98de5a84..d92dd309a8 100644 --- a/packages/test-visual/src/index.ts +++ b/packages/test-visual/src/index.ts @@ -1,4 +1,3 @@ -#!/usr/bin/env node import { Command } from "commander"; import compareCmd from "./commands/compare"; diff --git a/packages/test-visual/src/utils/index.ts b/packages/test-visual/src/utils/index.ts index bc67ba8449..060d6a5650 100644 --- a/packages/test-visual/src/utils/index.ts +++ b/packages/test-visual/src/utils/index.ts @@ -1,4 +1,3 @@ -import axios from "axios"; import fs from "fs-extra"; import archiver from "archiver"; import extract from "extract-zip"; @@ -6,6 +5,7 @@ import chalk from "chalk"; import boxen from "boxen"; import logUpdate from "log-update"; import { Command } from "commander"; +import { Stream } from "stream"; /** Display an output message in a blue box with 2 lines of text */ export function outputCompleteMessage(text1: string, text2 = "") { @@ -64,28 +64,30 @@ export function unzipFile(zipFilePath: string, outputFolderPath: string) { } export async function downloadToFile(url: string, outputFilePath: string) { - const client = axios.create(); - console.log("downloading", url); - const writer = fs.createWriteStream(outputFilePath); - const { data, headers } = await client.get(url, { - responseType: "stream", - }); - const totalLength = headers["content-length"]; - let bytesReceived = 0; - data.on("data", (chunk: Buffer) => { - bytesReceived += chunk.length; - const progress = Math.round((bytesReceived / totalLength) * 100); - logUpdate(`${progress}%`); - }); - data.pipe(writer); - return new Promise((resolve, reject) => { - writer.on("finish", () => { - logUpdate(`downloaded ${outputFilePath}`); - logUpdate.done(); - resolve(); + const { body, headers } = await fetch(url); + if (body) { + const totalLength = parseInt(headers.get("content-length"), 10); + let bytesReceived = 0; + // create outstream to write to file + const outStream = fs.createWriteStream(outputFilePath); + // convert fetch stream to node readable stream + // additionally log progress on write updates + const stream = Stream.Readable.fromWeb(body as any); + stream.on("data", (chunk) => { + const progress = Math.round((bytesReceived / totalLength) * 100); + bytesReceived += chunk.length; + logUpdate(`${progress}%`); }); - writer.on("error", reject); - }); + // pipe income fetch stream to file write, and resolve promise when complete + stream.pipe(outStream); + return new Promise((resolve) => { + stream.on("close", () => { + logUpdate.done(); + resolve(true); + }); + }); + } + throw new Error(`Download Failed: ${url}`); } export function logProgramHelp(program: Command) { diff --git a/packages/test-visual/tsconfig.json b/packages/test-visual/tsconfig.json index 30b9fb7f44..06967a9601 100644 --- a/packages/test-visual/tsconfig.json +++ b/packages/test-visual/tsconfig.json @@ -1,17 +1,21 @@ { "compilerOptions": { - "target": "ESNext", - "module": "CommonJS", - "moduleResolution": "node", - "lib": ["ESNext", "dom"], - "declaration": true, - "outDir": "lib", - "rootDir": "src", - "strict": true, - "types": ["node"], + // Treat files as modules even if it doesn't use import/export + "moduleDetection": "force", + + // Ignore module structure + "module": "Preserve", + + // Allow JSON modules to be imported "resolveJsonModule": true, + + // Allow JS files to be imported from TS and vice versa + "allowJs": true, + + // Use correct ESM import behavior "esModuleInterop": true, - "noImplicitAny": false, - "strictPropertyInitialization": false + + // Disallow features that require cross-file awareness + "isolatedModules": true } } diff --git a/resources/ic_launcher.png b/resources/ic_launcher.png index 0ab7cb4e51..333534e560 100644 Binary files a/resources/ic_launcher.png and b/resources/ic_launcher.png differ diff --git a/resources/ic_launcher_foreground.png b/resources/ic_launcher_foreground.png index 19fe965101..8ca5cba6c0 100644 Binary files a/resources/ic_launcher_foreground.png and b/resources/ic_launcher_foreground.png differ diff --git a/resources/ic_launcher_round.png b/resources/ic_launcher_round.png index 0ab7cb4e51..333534e560 100644 Binary files a/resources/ic_launcher_round.png and b/resources/ic_launcher_round.png differ diff --git a/resources/splash_land.png b/resources/splash_land.png index 72321c4d17..bb4511b935 100644 Binary files a/resources/splash_land.png and b/resources/splash_land.png differ diff --git a/resources/splash_port.png b/resources/splash_port.png index 4481570f7e..d4f9dfa9a3 100644 Binary files a/resources/splash_port.png and b/resources/splash_port.png differ diff --git a/resources/store/app_icon.png b/resources/store/app_icon.png index 77eb5da258..791e58b02b 100644 Binary files a/resources/store/app_icon.png and b/resources/store/app_icon.png differ diff --git a/resources/store/feature.png b/resources/store/feature.png index a50f9b1c65..e005d3fe5d 100644 Binary files a/resources/store/feature.png and b/resources/store/feature.png differ diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 09d31db814..5a0b113b7d 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,33 +1,48 @@ import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; import { TestBed, waitForAsync } from "@angular/core/testing"; -import { Platform } from "@ionic/angular"; +import { ModalController, Platform } from "@ionic/angular"; import { SplashScreen } from "@capacitor/splash-screen"; import { StatusBar } from "@ionic-native/status-bar/ngx"; import { AppComponent } from "./app.component"; +import { DeploymentService } from "./shared/services/deployment/deployment.service"; +import { MockDeploymentService } from "./shared/services/deployment/deployment.service.mock.spec"; +import { AppDataService } from "./shared/services/data/app-data.service"; +import { MockAppDataService } from "./shared/services/data/app-data.service.mock.spec"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { SkinService } from "./shared/services/skin/skin.service"; +import { AnalyticsService } from "./shared/services/analytics"; +import { FeedbackService } from "./feature/feedback/feedback.service"; +import { AppUpdateService } from "./shared/services/app-update/app-update.service"; describe("AppComponent", () => { let statusBarSpy, splashScreenSpy, platformReadySpy, platformSpy; - beforeEach( - waitForAsync(() => { - statusBarSpy = jasmine.createSpyObj("StatusBar", ["styleDefault"]); - splashScreenSpy = jasmine.createSpyObj("SplashScreen", ["hide"]); - platformReadySpy = Promise.resolve(); - platformSpy = jasmine.createSpyObj("Platform", { ready: platformReadySpy }); - - TestBed.configureTestingModule({ - declarations: [AppComponent], - schemas: [CUSTOM_ELEMENTS_SCHEMA], - providers: [ - { provide: StatusBar, useValue: statusBarSpy }, - { provide: SplashScreen, useValue: splashScreenSpy }, - { provide: Platform, useValue: platformSpy }, - ], - }).compileComponents(); - }) - ); + beforeEach(waitForAsync(() => { + statusBarSpy = jasmine.createSpyObj("StatusBar", ["styleDefault"]); + splashScreenSpy = jasmine.createSpyObj("SplashScreen", ["hide"]); + platformReadySpy = Promise.resolve(); + platformSpy = jasmine.createSpyObj("Platform", { ready: platformReadySpy }); + + TestBed.configureTestingModule({ + declarations: [AppComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + imports: [HttpClientTestingModule], + providers: [ + { provide: StatusBar, useValue: statusBarSpy }, + { provide: SplashScreen, useValue: splashScreenSpy }, + { provide: Platform, useValue: platformSpy }, + { provide: DeploymentService, useValue: new MockDeploymentService() }, + { provide: AppDataService, useValue: new MockAppDataService() }, + { provide: SkinService, useValue: {} }, + { provide: ModalController, useValue: {} }, + { provide: AnalyticsService, useValue: {} }, + { provide: FeedbackService, useValue: {} }, + { provide: AppUpdateService, useValue: {} }, + ], + }).compileComponents(); + })); it("should create the app", () => { const fixture = TestBed.createComponent(AppComponent); @@ -39,8 +54,6 @@ describe("AppComponent", () => { TestBed.createComponent(AppComponent); expect(platformSpy.ready).toHaveBeenCalled(); await platformReadySpy; - expect(statusBarSpy.styleDefault).toHaveBeenCalled(); - expect(splashScreenSpy.hide).toHaveBeenCalled(); }); // TODO: add more tests! diff --git a/src/app/feature/campaign/campaign.service.ts b/src/app/feature/campaign/campaign.service.ts index 41a8136cfe..f248769693 100644 --- a/src/app/feature/campaign/campaign.service.ts +++ b/src/app/feature/campaign/campaign.service.ts @@ -285,7 +285,6 @@ export class CampaignService extends AsyncServiceBase { translatedRow._dynamicFields = extractDynamicFields(translatedRow); const parsedRow = await this.templateVariablesService.evaluatePLHData(translatedRow, { row: translatedRow, - templateRowMap: {}, }); return parsedRow as FlowTypes.Campaign_listRow; } diff --git a/src/app/feature/campaign/pages/campaign-debug/campaign-debug.page.spec.ts b/src/app/feature/campaign/pages/campaign-debug/campaign-debug.page.spec.ts index be629379c2..473c3c8e28 100644 --- a/src/app/feature/campaign/pages/campaign-debug/campaign-debug.page.spec.ts +++ b/src/app/feature/campaign/pages/campaign-debug/campaign-debug.page.spec.ts @@ -1,6 +1,15 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { CampaignDebugPage } from "./campaign-debug.page"; +import { DeploymentService } from "src/app/shared/services/deployment/deployment.service"; +import { MockDeploymentService } from "src/app/shared/services/deployment/deployment.service.mock.spec"; +import { AppDataService } from "src/app/shared/services/data/app-data.service"; +import { MockAppDataService } from "src/app/shared/services/data/app-data.service.mock.spec"; +import { ActivatedRoute, RouterModule } from "@angular/router"; +import { IonicModule, ModalController } from "@ionic/angular"; +import { ObjectValuesPipe } from "src/app/shared/pipes/objectValues.pipe"; +import { ArraySortPipe } from "src/app/shared/pipes/arraySort.pipe"; +import { ObjectKeysPipe } from "src/app/shared/pipes/objectKeys.pipe"; describe("CampaignDebugPage", () => { let component: CampaignDebugPage; @@ -8,7 +17,25 @@ describe("CampaignDebugPage", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [CampaignDebugPage], + declarations: [CampaignDebugPage, ObjectValuesPipe, ArraySortPipe, ObjectKeysPipe], + imports: [IonicModule, RouterModule], + providers: [ + // TODO - use mocked constructor services instead when implementing tests + { + provide: DeploymentService, + useValue: new MockDeploymentService(), + }, + { + provide: AppDataService, + useValue: new MockAppDataService(), + }, + // TODO - update values when implementing tests + { + provide: ActivatedRoute, + useValue: { snapshot: { queryParamMap: new Map([]) } }, + }, + { provide: ModalController, useValue: {} }, + ], }).compileComponents(); }); diff --git a/src/app/feature/feedback/components/feedback-toolbar/feedback-toolbar.component.spec.ts b/src/app/feature/feedback/components/feedback-toolbar/feedback-toolbar.component.spec.ts index 802131a3aa..d21e28f5c9 100644 --- a/src/app/feature/feedback/components/feedback-toolbar/feedback-toolbar.component.spec.ts +++ b/src/app/feature/feedback/components/feedback-toolbar/feedback-toolbar.component.spec.ts @@ -1,16 +1,44 @@ -import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { FeedbackToolbarComponent } from "./feedback-toolbar.component"; +import { IonicModule, PopoverController } from "@ionic/angular"; +import { TemplateService } from "src/app/shared/components/template/services/template.service"; +import { UserMetaService } from "src/app/shared/services/userMeta/userMeta.service"; +import { DBSyncService } from "src/app/shared/services/db/db-sync.service"; +import { AppConfigService } from "src/app/shared/services/app-config/app-config.service"; +import { MockAppConfigService } from "src/app/shared/services/app-config/app-config.service.mock.spec"; +import { MockDeploymentService } from "src/app/shared/services/deployment/deployment.service.mock.spec"; +import { FeedbackService } from "../../feedback.service"; +import { DeploymentService } from "src/app/shared/services/deployment/deployment.service"; +import { FormsModule } from "@angular/forms"; + +// HACK - mock feedback service methods called +class MockFeedbackService implements Partial { + public options = {} as any; + setContentPageWidth() {} + async setEnabled() {} + setNavigationEnabled() {} +} describe("FeedbackToolbarComponent", () => { let component: FeedbackToolbarComponent; let fixture: ComponentFixture; - beforeEach(async () => { + beforeEach(waitForAsync(async () => { await TestBed.configureTestingModule({ declarations: [FeedbackToolbarComponent], + imports: [IonicModule.forRoot(), FormsModule], + providers: [ + { provide: PopoverController, useValue: {} }, + { provide: TemplateService, useValue: {} }, + { provide: UserMetaService, useValue: {} }, + { provide: DBSyncService, useValue: {} }, + { provide: AppConfigService, useValue: new MockAppConfigService() }, + { provide: DeploymentService, useValue: new MockDeploymentService() }, + { provide: FeedbackService, useValue: new MockFeedbackService() }, + ], }).compileComponents(); - }); + })); beforeEach(() => { fixture = TestBed.createComponent(FeedbackToolbarComponent); diff --git a/src/app/feature/nav-stack/components/nav-stack/nav-stack.component.spec.ts b/src/app/feature/nav-stack/components/nav-stack/nav-stack.component.spec.ts index a11f603276..d94db4cffa 100644 --- a/src/app/feature/nav-stack/components/nav-stack/nav-stack.component.spec.ts +++ b/src/app/feature/nav-stack/components/nav-stack/nav-stack.component.spec.ts @@ -2,6 +2,13 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { NavStackComponent } from "./nav-stack.component"; +import { Component, input } from "@angular/core"; + +// HACK - mock child `` component to bypass imports +@Component({ selector: "plh-template-container", template: "
" }) +class MockTemplateContainerComponent { + templatename = input(); +} describe("NavStackComponent", () => { let component: NavStackComponent; @@ -9,11 +16,12 @@ describe("NavStackComponent", () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [NavStackComponent], + declarations: [NavStackComponent, MockTemplateContainerComponent], imports: [IonicModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(NavStackComponent); + fixture.componentRef.setInput("config", {}); component = fixture.componentInstance; fixture.detectChanges(); })); diff --git a/src/app/feature/notification/pages/notifications-debug/notifications-debug.page.spec.ts b/src/app/feature/notification/pages/notifications-debug/notifications-debug.page.spec.ts index 4641e8b0cd..d067086771 100644 --- a/src/app/feature/notification/pages/notifications-debug/notifications-debug.page.spec.ts +++ b/src/app/feature/notification/pages/notifications-debug/notifications-debug.page.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { NotificationsDebugPage } from "./notifications-debug.page"; +import { LocalNotificationService } from "src/app/shared/services/notification/local-notification.service"; +import { DBSyncService } from "src/app/shared/services/db/db-sync.service"; +import { of } from "rxjs"; describe("NotificationsDebugPage", () => { let component: NotificationsDebugPage; @@ -9,6 +12,10 @@ describe("NotificationsDebugPage", () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [NotificationsDebugPage], + providers: [ + { provide: LocalNotificationService, useValue: { pendingNotifications$: of([]) } }, + { provide: DBSyncService, useValue: {} }, + ], }).compileComponents(); }); diff --git a/src/app/feature/template/pages/component/component.page.spec.ts b/src/app/feature/template/pages/component/component.page.spec.ts index 8c6f12d727..9e5dc09aad 100644 --- a/src/app/feature/template/pages/component/component.page.spec.ts +++ b/src/app/feature/template/pages/component/component.page.spec.ts @@ -2,6 +2,9 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { ComponentPage } from "./component.page"; +import { ActivatedRoute } from "@angular/router"; +import { AppDataService } from "src/app/shared/services/data/app-data.service"; +import { MockAppDataService } from "src/app/shared/services/data/app-data.service.mock.spec"; describe("ComponentPage", () => { let component: ComponentPage; @@ -11,6 +14,13 @@ describe("ComponentPage", () => { TestBed.configureTestingModule({ declarations: [ComponentPage], imports: [IonicModule.forRoot()], + providers: [ + { + provide: ActivatedRoute, + useValue: { snapshot: { params: { componentName: "mock_component_name" } } }, + }, + { provide: AppDataService, useValue: new MockAppDataService() }, + ], }).compileComponents(); fixture = TestBed.createComponent(ComponentPage); diff --git a/src/app/feature/template/template.page.spec.ts b/src/app/feature/template/template.page.spec.ts index 4b4b1fc710..a8bcba7fc3 100644 --- a/src/app/feature/template/template.page.spec.ts +++ b/src/app/feature/template/template.page.spec.ts @@ -2,6 +2,18 @@ import { async, ComponentFixture, TestBed } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TemplatePage } from "./template.page"; +import { ActivatedRoute } from "@angular/router"; +import { AppDataService } from "src/app/shared/services/data/app-data.service"; +import { MockAppDataService } from "src/app/shared/services/data/app-data.service.mock.spec"; +import { AppConfigService } from "src/app/shared/services/app-config/app-config.service"; +import { MockAppConfigService } from "src/app/shared/services/app-config/app-config.service.mock.spec"; +import { Component, input } from "@angular/core"; + +// HACK - mock child `` component to bypass imports +@Component({ selector: "plh-template-container", template: "
" }) +class MockTemplateContainerComponent { + templatename = input(); +} describe("TemplatePage", () => { let component: TemplatePage; @@ -9,8 +21,22 @@ describe("TemplatePage", () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [TemplatePage], + declarations: [TemplatePage, MockTemplateContainerComponent], imports: [IonicModule.forRoot()], + providers: [ + { + provide: ActivatedRoute, + useValue: { snapshot: { params: { templateName: "mock_template_name" } } }, + }, + { + provide: AppDataService, + useValue: new MockAppDataService(), + }, + { + provide: AppConfigService, + useValue: new MockAppConfigService(), + }, + ], }).compileComponents(); fixture = TestBed.createComponent(TemplatePage); diff --git a/src/app/feature/theme/components/css-variable-table/css-variable-table.component.spec.ts b/src/app/feature/theme/components/css-variable-table/css-variable-table.component.spec.ts index 6b92e77fd5..246b7b21c9 100644 --- a/src/app/feature/theme/components/css-variable-table/css-variable-table.component.spec.ts +++ b/src/app/feature/theme/components/css-variable-table/css-variable-table.component.spec.ts @@ -2,23 +2,24 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { CssVariableTableComponent } from "./css-variable-table.component"; +import { ThemeService } from "../../services/theme.service"; +import { MockThemeService } from "../../services/theme.service.mock.spec"; describe("CssVariableTableComponent", () => { let component: CssVariableTableComponent; let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [CssVariableTableComponent], - imports: [IonicModule.forRoot()], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [CssVariableTableComponent], + imports: [IonicModule.forRoot()], + providers: [{ provide: ThemeService, useValue: new MockThemeService() }], + }).compileComponents(); - fixture = TestBed.createComponent(CssVariableTableComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }) - ); + fixture = TestBed.createComponent(CssVariableTableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); it("should create", () => { expect(component).toBeTruthy(); diff --git a/src/app/feature/theme/components/css-variable-table/css-variable-table.component.ts b/src/app/feature/theme/components/css-variable-table/css-variable-table.component.ts index 28be8f3d79..975cdfd347 100644 --- a/src/app/feature/theme/components/css-variable-table/css-variable-table.component.ts +++ b/src/app/feature/theme/components/css-variable-table/css-variable-table.component.ts @@ -18,7 +18,10 @@ interface ICustomVariableMeta { export class CssVariableTableComponent implements AfterViewInit { customStyleVariables: ICustomVariableMeta[] = []; - constructor(private themeService: ThemeService, private elementRef: ElementRef) {} + constructor( + private themeService: ThemeService, + private elementRef: ElementRef + ) {} ngAfterViewInit() { this.loadElementCustomVariables(); @@ -29,8 +32,9 @@ export class CssVariableTableComponent implements AfterViewInit { private loadElementCustomVariables() { const currentEl = this.elementRef.nativeElement as HTMLElement; const contentEl = currentEl.closest("ion-content"); + // NOTE - contentEl does not appear in test environment so workaround + if (!contentEl) return; const customVariables = this.themeService.calculateElCustomProperties(contentEl); - console.log("custom variables", customVariables, contentEl); this.customStyleVariables = Object.entries(customVariables) .map(([name, value]) => ({ name, diff --git a/src/app/feature/theme/pages/theme-editor/theme-editor.page.spec.ts b/src/app/feature/theme/pages/theme-editor/theme-editor.page.spec.ts index 424e16f345..5de7fd674e 100644 --- a/src/app/feature/theme/pages/theme-editor/theme-editor.page.spec.ts +++ b/src/app/feature/theme/pages/theme-editor/theme-editor.page.spec.ts @@ -2,23 +2,26 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { ThemeEditorPage } from "./theme-editor.page"; +import { ThemeService } from "../../services/theme.service"; +import { MockThemeService } from "../../services/theme.service.mock.spec"; +import { ColourPaletteComponent } from "../../components/colour-palette/colour-palette.component"; +import { CssVariableTableComponent } from "../../components/css-variable-table/css-variable-table.component"; describe("ThemeEditorPage", () => { let component: ThemeEditorPage; let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ThemeEditorPage], - imports: [IonicModule.forRoot()], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ThemeEditorPage, ColourPaletteComponent, CssVariableTableComponent], + imports: [IonicModule.forRoot()], + providers: [{ provide: ThemeService, useValue: new MockThemeService() }], + }).compileComponents(); - fixture = TestBed.createComponent(ThemeEditorPage); - component = fixture.componentInstance; - fixture.detectChanges(); - }) - ); + fixture = TestBed.createComponent(ThemeEditorPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); it("should create", () => { expect(component).toBeTruthy(); diff --git a/src/app/feature/theme/services/theme.service.mock.spec.ts b/src/app/feature/theme/services/theme.service.mock.spec.ts new file mode 100644 index 0000000000..371d772eaf --- /dev/null +++ b/src/app/feature/theme/services/theme.service.mock.spec.ts @@ -0,0 +1,18 @@ +import { BehaviorSubject } from "rxjs"; +import { ThemeService } from "./theme.service"; +import { MockLocalStorageService } from "src/app/shared/services/local-storage/local-storage.service.mock.spec"; +import { MockAppConfigService } from "src/app/shared/services/app-config/app-config.service.mock.spec"; +import { AppConfigService } from "src/app/shared/services/app-config/app-config.service"; + +export class MockThemeService extends ThemeService { + constructor() { + super( + new MockLocalStorageService(), + new MockAppConfigService({ + APP_THEMES: { available: ["mock_theme"], defaultThemeName: "mock_theme" }, + }) as unknown as AppConfigService + ); + } + + currentTheme$ = new BehaviorSubject("mock_theme"); +} diff --git a/src/app/feature/theme/services/theme.service.spec.ts b/src/app/feature/theme/services/theme.service.spec.ts index b1322e66d5..6055e2b32d 100644 --- a/src/app/feature/theme/services/theme.service.spec.ts +++ b/src/app/feature/theme/services/theme.service.spec.ts @@ -2,21 +2,11 @@ import { TestBed } from "@angular/core/testing"; import { ThemeService } from "./theme.service"; import { LocalStorageService } from "src/app/shared/services/local-storage/local-storage.service"; -import { MockLocalStorageService } from "src/app/shared/services/local-storage/local-storage.service.spec"; +import { MockLocalStorageService } from "src/app/shared/services/local-storage/local-storage.service.mock.spec"; import { AppConfigService } from "src/app/shared/services/app-config/app-config.service"; -import { MockAppConfigService } from "src/app/shared/services/app-config/app-config.service.spec"; +import { MockAppConfigService } from "src/app/shared/services/app-config/app-config.service.mock.spec"; import { IAppConfig } from "packages/data-models"; -export class MockThemeService implements Partial { - ready() { - return true; - } - setTheme() {} - getCurrentTheme() { - return "mock_theme"; - } -} - const MOCK_APP_CONFIG: Partial = { APP_THEMES: { available: ["MOCK_THEME_1", "MOCK_THEME_2"], diff --git a/src/app/feature/tour/tour.service.spec.ts b/src/app/feature/tour/tour.service.spec.ts index 8754f301fd..949b327053 100644 --- a/src/app/feature/tour/tour.service.spec.ts +++ b/src/app/feature/tour/tour.service.spec.ts @@ -1,12 +1,24 @@ import { TestBed } from "@angular/core/testing"; import { TourService } from "./tour.service"; +import { TemplateFieldService } from "src/app/shared/components/template/services/template-field.service"; +import { MockTemplateFieldService } from "src/app/shared/components/template/services/template-field.service.spec"; +import { TemplateTranslateService } from "src/app/shared/components/template/services/template-translate.service"; +import { MockTemplateTranslateService } from "src/app/shared/components/template/services/template-translate.service.spec"; +import { AppDataService } from "src/app/shared/services/data/app-data.service"; +import { MockAppDataService } from "src/app/shared/services/data/app-data.service.mock.spec"; describe("TourService", () => { let service: TourService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + { provide: TemplateFieldService, useValue: new MockTemplateFieldService() }, + { provide: TemplateTranslateService, useValue: new MockTemplateTranslateService() }, + { provide: AppDataService, useValue: new MockAppDataService() }, + ], + }); service = TestBed.inject(TourService); }); diff --git a/src/app/shared/components/template/components/audio/audio.component.spec.ts b/src/app/shared/components/template/components/audio/audio.component.spec.ts index e6776f28e0..9011bec390 100644 --- a/src/app/shared/components/template/components/audio/audio.component.spec.ts +++ b/src/app/shared/components/template/components/audio/audio.component.spec.ts @@ -1,21 +1,27 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplAudioComponent } from "./audio.component"; +import { TemplateAssetService } from "../../services/template-asset.service"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "audio" }; describe("TmplAudioComponent", () => { let component: TmplAudioComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplAudioComponent], imports: [IonicModule.forRoot()], + providers: [{ provide: TemplateAssetService, useValue: {} }], }).compileComponents(); fixture = TestBed.createComponent(TmplAudioComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/audio/audio.component.ts b/src/app/shared/components/template/components/audio/audio.component.ts index b745b92b46..aa52a6e40d 100644 --- a/src/app/shared/components/template/components/audio/audio.component.ts +++ b/src/app/shared/components/template/components/audio/audio.component.ts @@ -120,7 +120,7 @@ export class TmplAudioComponent .split(",") .join(" ") as IAudioParams["variant"]; this.params.src = this.templateAssetService.getTranslatedAssetPath( - this._row.value || getStringParamFromTemplateRow(this._row, "src", null) + (this._row.value as string) || getStringParamFromTemplateRow(this._row, "src", null) ); this.params.playIconAsset = this.getAssetParamFromTemplateRow( "play_icon_asset", diff --git a/src/app/shared/components/template/components/button-google-sign-in/button-google-sign-in.component.html b/src/app/shared/components/template/components/button-google-sign-in/button-google-sign-in.component.html new file mode 100644 index 0000000000..e6497cb0ad --- /dev/null +++ b/src/app/shared/components/template/components/button-google-sign-in/button-google-sign-in.component.html @@ -0,0 +1,36 @@ + + diff --git a/src/app/shared/components/template/components/button-google-sign-in/button-google-sign-in.component.scss b/src/app/shared/components/template/components/button-google-sign-in/button-google-sign-in.component.scss new file mode 100644 index 0000000000..3eab8619a5 --- /dev/null +++ b/src/app/shared/components/template/components/button-google-sign-in/button-google-sign-in.component.scss @@ -0,0 +1,118 @@ +/* CSS copied from https://developers.google.com/identity/branding-guidelines +with Theme: Light; Shape: Pill +The only modification is using `margin-inline-end` instead of `margin-right` to support RTL languages +*/ + +.gsi-material-button { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + -webkit-appearance: none; + background-color: WHITE; + background-image: none; + border: 1px solid #747775; + -webkit-border-radius: 20px; + border-radius: 20px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: #1f1f1f; + cursor: pointer; + font-family: "Roboto", arial, sans-serif; + font-size: 14px; + height: 40px; + letter-spacing: 0.25px; + outline: none; + overflow: hidden; + padding: 0 12px; + position: relative; + text-align: center; + -webkit-transition: + background-color 0.218s, + border-color 0.218s, + box-shadow 0.218s; + transition: + background-color 0.218s, + border-color 0.218s, + box-shadow 0.218s; + vertical-align: middle; + white-space: nowrap; + width: auto; + max-width: 400px; + min-width: min-content; +} + +.gsi-material-button .gsi-material-button-icon { + height: 20px; + margin-inline-end: 12px; + min-width: 20px; + width: 20px; +} + +.gsi-material-button .gsi-material-button-content-wrapper { + -webkit-align-items: center; + align-items: center; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: nowrap; + flex-wrap: nowrap; + height: 100%; + justify-content: space-between; + position: relative; + width: 100%; +} + +.gsi-material-button .gsi-material-button-contents { + -webkit-flex-grow: 1; + flex-grow: 1; + font-family: "Roboto", arial, sans-serif; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: top; +} + +.gsi-material-button .gsi-material-button-state { + -webkit-transition: opacity 0.218s; + transition: opacity 0.218s; + bottom: 0; + left: 0; + opacity: 0; + position: absolute; + right: 0; + top: 0; +} + +.gsi-material-button:disabled { + cursor: default; + background-color: #ffffff61; + border-color: #1f1f1f1f; +} + +.gsi-material-button:disabled .gsi-material-button-contents { + opacity: 38%; +} + +.gsi-material-button:disabled .gsi-material-button-icon { + opacity: 38%; +} + +.gsi-material-button:not(:disabled):active .gsi-material-button-state, +.gsi-material-button:not(:disabled):focus .gsi-material-button-state { + background-color: #303030; + opacity: 12%; +} + +.gsi-material-button:not(:disabled):hover { + -webkit-box-shadow: + 0 1px 2px 0 rgba(60, 64, 67, 0.3), + 0 1px 3px 1px rgba(60, 64, 67, 0.15); + box-shadow: + 0 1px 2px 0 rgba(60, 64, 67, 0.3), + 0 1px 3px 1px rgba(60, 64, 67, 0.15); +} + +.gsi-material-button:not(:disabled):hover .gsi-material-button-state { + background-color: #303030; + opacity: 8%; +} diff --git a/src/app/shared/components/template/components/button-google-sign-in/button-google-sign-in.component.ts b/src/app/shared/components/template/components/button-google-sign-in/button-google-sign-in.component.ts new file mode 100644 index 0000000000..bb9590ffa9 --- /dev/null +++ b/src/app/shared/components/template/components/button-google-sign-in/button-google-sign-in.component.ts @@ -0,0 +1,20 @@ +import { Component } from "@angular/core"; +import { TemplateBaseComponent } from "../base"; +import { AuthService } from "src/app/shared/services/auth/auth.service"; +@Component({ + selector: "tmpl-button-google-sign-in", + templateUrl: "./button-google-sign-in.component.html", + styleUrls: ["./button-google-sign-in.component.scss"], +}) +export class TmplButtonGoogleSignInComponent extends TemplateBaseComponent { + // The button text is set as row value directly in the HTML template + + constructor(private authService: AuthService) { + super(); + } + + public async handleClick() { + await this.authService.provider.signInWithGoogle(); + this.triggerActions("click"); + } +} diff --git a/src/app/shared/components/template/components/button/button.component.html b/src/app/shared/components/template/components/button/button.component.html index 52fb0412d3..0ce5157433 100644 --- a/src/app/shared/components/template/components/button/button.component.html +++ b/src/app/shared/components/template/components/button/button.component.html @@ -1,33 +1,33 @@ - + - @if (params.icon && params.iconAlign === "left") { - + @if (params().icon && params().iconAlign === "left") { + } - @if (params.icon && params.iconAlign === "right") { - + @if (params().icon && params().iconAlign === "right") { + } - @if (params.iconSecondary) { + @if (params().iconSecondary) { - @if (params.iconSecondary.includes(".")) { - + @if (params().iconSecondary.includes(".")) { + } @else { - + } } @@ -47,12 +47,12 @@ *ngSwitchCase="true" class="button-container" (click)="handleClick()" - [attr.data-variant]="params.variant" + [attr.data-variant]="params().variant" [attr.data-has-children]="_row.rows ? true : null" - [attr.data-disabled]="params.disabled" + [attr.data-disabled]="params().disabled" > - -
+ +
{{ _row.value }}
diff --git a/src/app/shared/components/template/components/button/button.component.spec.ts b/src/app/shared/components/template/components/button/button.component.spec.ts index 6c315ba2e8..9f4996f599 100644 --- a/src/app/shared/components/template/components/button/button.component.spec.ts +++ b/src/app/shared/components/template/components/button/button.component.spec.ts @@ -1,21 +1,30 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { async, ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplButtonComponent } from "./button.component"; +import { TemplateTranslateService } from "../../services/template-translate.service"; +import { MockTemplateTranslateService } from "../../services/template-translate.service.spec"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "button" }; describe("TmplButtonComponent", () => { let component: TmplButtonComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplButtonComponent], imports: [IonicModule.forRoot()], + providers: [ + { provide: TemplateTranslateService, useValue: new MockTemplateTranslateService() }, + ], }).compileComponents(); fixture = TestBed.createComponent(TmplButtonComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/button/button.component.ts b/src/app/shared/components/template/components/button/button.component.ts index 6fe3cc88ac..a5005de992 100644 --- a/src/app/shared/components/template/components/button/button.component.ts +++ b/src/app/shared/components/template/components/button/button.component.ts @@ -1,14 +1,13 @@ -import { Component, ElementRef, OnInit } from "@angular/core"; -import { - getStringParamFromTemplateRow, - getBooleanParamFromTemplateRow, -} from "src/app/shared/utils"; +import { Component, computed, effect, ElementRef } from "@angular/core"; +import { getStringParamFromTemplateRow, parseBoolean } from "src/app/shared/utils"; import { TemplateBaseComponent } from "../base"; import { TemplateTranslateService } from "../../services/template-translate.service"; +import { FlowTypes } from "packages/data-models"; interface IButtonParams { /** TEMPLATE PARAMETER: "variant" */ variant: + | "" | "alternative" | "card" | "card-portrait" @@ -41,6 +40,10 @@ interface IButtonParams { iconAlign: "left" | "right"; } +interface IVariantMap { + cardPortrait?: boolean; +} + /** * A general-purpose button component */ @@ -49,10 +52,10 @@ interface IButtonParams { templateUrl: "./button.component.html", styleUrls: ["./button.component.scss"], }) -export class TmplButtonComponent extends TemplateBaseComponent implements OnInit { - params: Partial = {}; +export class TmplButtonComponent extends TemplateBaseComponent { + params = computed(() => this.getParams(this.parameterList())); /** @ignore */ - variantMap: { cardPortrait: boolean }; + variantMap = computed(() => this.populateVariantMap(this.params().variant)); /** @ignore */ constructor( @@ -62,41 +65,27 @@ export class TmplButtonComponent extends TemplateBaseComponent implements OnInit super(); } - ngOnInit() { - this.getParams(); - } - public handleClick() { - if (this.params.disabled) return; + if (this.params().disabled) return; this.triggerActions("click"); } - private getParams() { - this.params.style = `${getStringParamFromTemplateRow(this._row, "style", "information")} ${ - this.isTwoColumns() ? "two_columns" : "" - }` as any; - this.params.variant = getStringParamFromTemplateRow(this._row, "variant", "") - .split(",") - .join(" ") as IButtonParams["variant"]; - this.populateVariantMap(); - this.params.disabled = getBooleanParamFromTemplateRow(this._row, "disabled", false); - this.params.image = getStringParamFromTemplateRow(this._row, "image_asset", null); - if (this._row.disabled) { - this.params.disabled = true; - } - this.params.textAlign = getStringParamFromTemplateRow(this._row, "text_align", null) as any; - this.params.buttonAlign = getStringParamFromTemplateRow( - this._row, - "button_align", - "center" - ) as any; - this.params.icon = getStringParamFromTemplateRow(this._row, "icon", null); - this.params.iconSecondary = getStringParamFromTemplateRow( - this._row, - "icon_secondary_asset", - null - ); - this.params.iconAlign = getStringParamFromTemplateRow(this._row, "icon_align", "left") as any; + private getParams(authorParams: FlowTypes.TemplateRow["parameter_list"]): IButtonParams { + return { + disabled: parseBoolean(this.parameterList().disabled), + style: `${getStringParamFromTemplateRow(this._row, "style", "information")} ${ + this.isTwoColumns() ? "two_columns" : "" + }` as any, + variant: getStringParamFromTemplateRow(this._row, "variant", "") + .split(",") + .join(" ") as IButtonParams["variant"], + image: getStringParamFromTemplateRow(this._row, "image_asset", null), + textAlign: getStringParamFromTemplateRow(this._row, "text_align", null) as any, + buttonAlign: getStringParamFromTemplateRow(this._row, "button_align", "center") as any, + icon: getStringParamFromTemplateRow(this._row, "icon", null), + iconSecondary: getStringParamFromTemplateRow(this._row, "icon_secondary_asset", null), + iconAlign: getStringParamFromTemplateRow(this._row, "icon_align", "left") as any, + }; } /** Determine if the button is inside a display group with the style "two_columns" */ @@ -109,9 +98,9 @@ export class TmplButtonComponent extends TemplateBaseComponent implements OnInit } } - private populateVariantMap() { - const variantArray = this.params.variant.split(" "); - this.variantMap = { + private populateVariantMap(variant: IButtonParams["variant"] = ""): IVariantMap { + const variantArray = variant.split(" "); + return { cardPortrait: variantArray.includes("card-portrait"), }; } diff --git a/src/app/shared/components/template/components/carousel/carousel.component.spec.ts b/src/app/shared/components/template/components/carousel/carousel.component.spec.ts index efabb91660..795390ee08 100644 --- a/src/app/shared/components/template/components/carousel/carousel.component.spec.ts +++ b/src/app/shared/components/template/components/carousel/carousel.component.spec.ts @@ -2,20 +2,40 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplCarouselComponent } from "./carousel.component"; +import { AppConfigService } from "src/app/shared/services/app-config/app-config.service"; +import { MockAppConfigService } from "src/app/shared/services/app-config/app-config.service.mock.spec"; +import { AppDataService } from "src/app/shared/services/data/app-data.service"; +import { MockAppDataService } from "src/app/shared/services/data/app-data.service.mock.spec"; +import { DynamicDataService } from "src/app/shared/services/dynamic-data/dynamic-data.service"; +import { TaskService } from "src/app/shared/services/task/task.service"; +import { DataItemsService } from "../data-items/data-items.service"; +import { SwiperModule } from "swiper/angular"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "carousel" }; describe("CarouselComponent", () => { let component: TmplCarouselComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplCarouselComponent], - imports: [IonicModule.forRoot()], + imports: [IonicModule.forRoot(), SwiperModule], + providers: [ + { provide: AppConfigService, useValue: new MockAppConfigService() }, + { provide: AppDataService, useValue: new MockAppDataService() }, + { provide: DynamicDataService, useValue: {} }, + { provide: TaskService, useValue: {} }, + { provide: DynamicDataService, useValue: {} }, + { provide: DataItemsService, useValue: {} }, + ], }).compileComponents(); fixture = TestBed.createComponent(TmplCarouselComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/dashed-box/dashed-box.component.spec.ts b/src/app/shared/components/template/components/dashed-box/dashed-box.component.spec.ts index 7411fd3a5a..4fa5dbab39 100644 --- a/src/app/shared/components/template/components/dashed-box/dashed-box.component.spec.ts +++ b/src/app/shared/components/template/components/dashed-box/dashed-box.component.spec.ts @@ -1,21 +1,25 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplDashedBoxComponent } from "./dashed-box.component"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "icon_banner" }; describe("TmplDashedBoxComponent", () => { let component: TmplDashedBoxComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplDashedBoxComponent], imports: [IonicModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(TmplDashedBoxComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/data-items/data-items.component.ts b/src/app/shared/components/template/components/data-items/data-items.component.ts index 46cccc10c7..968992d62b 100644 --- a/src/app/shared/components/template/components/data-items/data-items.component.ts +++ b/src/app/shared/components/template/components/data-items/data-items.component.ts @@ -71,7 +71,7 @@ export class TmplDataItemsComponent extends TemplateBaseComponent { */ private hackInterceptComponentActions(_nested_name: string) { this.parent.templateActionService.registerActionsInterceptor(_nested_name, (action) => { - if (action.action_id === "set_self" && action._triggeredBy._evalContext?.itemContext) { + if (action.action_id === "set_self" && action._triggeredBy._evalContext?.item) { return undefined; } return this.dataItemsService.evaluateComponentAction(action); diff --git a/src/app/shared/components/template/components/data-items/data-items.service.spec.ts b/src/app/shared/components/template/components/data-items/data-items.service.spec.ts index 133690cefe..c9ce85892c 100644 --- a/src/app/shared/components/template/components/data-items/data-items.service.spec.ts +++ b/src/app/shared/components/template/components/data-items/data-items.service.spec.ts @@ -9,6 +9,8 @@ import { firstValueFrom } from "rxjs"; import { Injector } from "@angular/core"; import { TemplateVariablesService } from "../../services/template-variables.service"; import { TemplateTranslateService } from "../../services/template-translate.service"; +import { TemplateCalcService } from "../../services/template-calc.service"; +import { MockTemplateCalcService } from "../../services/template-calc.service.mock.spec"; /*************************************************************************************** * Test Setup @@ -95,6 +97,7 @@ describe("DataItemsService", () => { { provide: Injector, useValue: {} }, { provide: TemplateVariablesService, useValue: { evaluateConditionString: (v) => v } }, { provide: TemplateTranslateService, useValue: {} }, + { provide: TemplateCalcService, useValue: new MockTemplateCalcService() }, ], }); service = TestBed.inject(DataItemsService); @@ -121,7 +124,7 @@ describe("DataItemsService", () => { expect(rowProcessorSpy).toHaveBeenCalledTimes(1); const [rowProcessorItemRowsArg] = rowProcessorSpy.calls.first().args; expect(rowProcessorItemRowsArg[0]._evalContext).toEqual({ - itemContext: { + item: { id: "id_1", completed: true, _index: 0, @@ -150,6 +153,7 @@ describe("DataItemsService", () => { console.log({ evaluated }); expect(evaluated.args).toEqual(["my_local_var", 3]); }); + // TODO - fix case where items context refers to generated loop items and not list items xit("evaluates data actions rows with items context", async () => { const obs = service.getItemsObservable(MOCK_DATA_ITEMS_ROW, {}); diff --git a/src/app/shared/components/template/components/data-items/data-items.service.ts b/src/app/shared/components/template/components/data-items/data-items.service.ts index 1e2f3b68e6..abf86f8c30 100644 --- a/src/app/shared/components/template/components/data-items/data-items.service.ts +++ b/src/app/shared/components/template/components/data-items/data-items.service.ts @@ -165,6 +165,11 @@ export class DataItemsService { } as any); // HACK - still want to be able to use localContext from parent rows so copy to child processor processor.templateRowMap = JSON.parse(JSON.stringify(templateRowMap)); + const templateRowMapValues = Object.fromEntries( + Object.entries(templateRowMap).map(([key, { value }]) => [key, value]) + ); + processor.templateRowMapValues = templateRowMapValues; + await processor.processContainerTemplateRows(); return processor.renderedRows(); } diff --git a/src/app/shared/components/template/components/data-items/data-items.utils.spec.ts b/src/app/shared/components/template/components/data-items/data-items.utils.spec.ts index 6d1f1ee9ba..1e32464a00 100644 --- a/src/app/shared/components/template/components/data-items/data-items.utils.spec.ts +++ b/src/app/shared/components/template/components/data-items/data-items.utils.spec.ts @@ -16,16 +16,16 @@ const MOCK_TEMPLATE_ITEM_ROW: FlowTypes.TemplateRow = { { trigger: "click", action_id: "set_item", args: [], params: { completed: false } }, ], _evalContext: { - itemContext: { + item: { _id: "id_1", - }, + } as any, }, }, ], _evalContext: { - itemContext: { + item: { _id: "id_1", - }, + } as any, }, }; @@ -39,9 +39,9 @@ describe("Data Items Utils", () => { const [updatedRow] = res; // should automatically assign index, first and last meta from item list id lookup const expectedItemContext = { _id: "id_1", _index: 1, _first: false, _last: true }; - expect(updatedRow._evalContext.itemContext).toEqual(expectedItemContext); + expect(updatedRow._evalContext.item).toEqual(expectedItemContext); // also check recursive child rows updated - expect(updatedRow.rows[0]._evalContext.itemContext).toEqual(expectedItemContext); + expect(updatedRow.rows[0]._evalContext.item).toEqual(expectedItemContext); }); it("updateItemMeta assigns set_item action context", () => { diff --git a/src/app/shared/components/template/components/data-items/data-items.utils.ts b/src/app/shared/components/template/components/data-items/data-items.utils.ts index d915d8f706..a55cfee938 100644 --- a/src/app/shared/components/template/components/data-items/data-items.utils.ts +++ b/src/app/shared/components/template/components/data-items/data-items.utils.ts @@ -23,12 +23,12 @@ export function updateItemMeta( const itemDataIDs = itemData.map((item) => item.id); // Reassign metadata fields previously assigned by item as rendered row count may have changed return templateRows.map((r) => { - const itemId = r._evalContext.itemContext._id; + const itemId = r._evalContext.item._id; // Map the row item context to the original list of items rendered to know position in item list. const itemIndex = itemDataIDs.indexOf(itemId); // Update metadata fields as _first, _last and index may have changed based on dynamic updates - r._evalContext.itemContext = { - ...r._evalContext.itemContext, + r._evalContext.item = { + ...r._evalContext.item, _index: itemIndex, _first: itemIndex === 0, _last: itemIndex === lastItemIndex, diff --git a/src/app/shared/components/template/components/drawer/drawer.component.spec.ts b/src/app/shared/components/template/components/drawer/drawer.component.spec.ts index b458c6e197..228c7d04a1 100644 --- a/src/app/shared/components/template/components/drawer/drawer.component.spec.ts +++ b/src/app/shared/components/template/components/drawer/drawer.component.spec.ts @@ -2,20 +2,25 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplDrawerComponent } from "./drawer.component"; +import { FilterDisplayComponentPipe } from "../../pipes/filter-display-component.pipe"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "drawer" }; describe("DrawerComponent", () => { let component: TmplDrawerComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ - declarations: [TmplDrawerComponent], + declarations: [TmplDrawerComponent, FilterDisplayComponentPipe], imports: [IonicModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(TmplDrawerComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/html/html.component.ts b/src/app/shared/components/template/components/html/html.component.ts index 310ada0edf..14fb24286a 100644 --- a/src/app/shared/components/template/components/html/html.component.ts +++ b/src/app/shared/components/template/components/html/html.component.ts @@ -15,6 +15,6 @@ export class TemplateHTMLComponent extends TemplateBaseComponent implements OnIn } ngOnInit() { - this.html = this.domSanitizer.bypassSecurityTrustHtml(this._row.value); + this.html = this.domSanitizer.bypassSecurityTrustHtml(this._row.value as string); } } diff --git a/src/app/shared/components/template/components/icon-banner/icon-banner.component.spec.ts b/src/app/shared/components/template/components/icon-banner/icon-banner.component.spec.ts index d3da26a4a3..99827f5eec 100644 --- a/src/app/shared/components/template/components/icon-banner/icon-banner.component.spec.ts +++ b/src/app/shared/components/template/components/icon-banner/icon-banner.component.spec.ts @@ -1,21 +1,25 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplIconBannerComponent } from "./icon-banner.component"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "dashed_box" }; describe("TmplIconBannerComponent", () => { let component: TmplIconBannerComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplIconBannerComponent], imports: [IonicModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(TmplIconBannerComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/index.ts b/src/app/shared/components/template/components/index.ts index 404c38aab0..8773b3cc45 100644 --- a/src/app/shared/components/template/components/index.ts +++ b/src/app/shared/components/template/components/index.ts @@ -24,6 +24,7 @@ import { TmplAdvancedDashedBoxComponent } from "./layout/advanced-dashed-box/adv import { TmplAnimatedSlidesComponent } from "./animated-slides/animated-slides.component"; import { TmplAudioComponent } from "./audio/audio.component"; import { TmplButtonComponent } from "./button/button.component"; +import { TmplButtonGoogleSignInComponent } from "./button-google-sign-in/button-google-sign-in.component"; import { TmplCarouselComponent } from "./carousel/carousel.component"; import { TmplComboBoxComponent } from "./combo-box/combo-box.component"; import { TmplDashedBoxComponent } from "./dashed-box/dashed-box.component"; @@ -87,6 +88,7 @@ export const TEMPLATE_COMPONENTS = [ TmplAnimatedSlidesComponent, TmplAudioComponent, TmplButtonComponent, + TmplButtonGoogleSignInComponent, TmplCarouselComponent, TmplComboBoxComponent, TmplDashedBoxComponent, @@ -161,6 +163,7 @@ const COMMON_COMPONENT_MAPPING = { display_group_sticky: TmplDisplayGroupStickyComponent, drawer: TmplDrawerComponent, form: FormComponent, + google_sign_in_button: TmplButtonGoogleSignInComponent, help_icon: TmplHelpIconComponent, html: TemplateHTMLComponent, icon_banner: TmplIconBannerComponent, diff --git a/src/app/shared/components/template/components/layout/accordion-section/accordion-section.component.ts b/src/app/shared/components/template/components/layout/accordion-section/accordion-section.component.ts index 26a56799bf..0017892799 100644 --- a/src/app/shared/components/template/components/layout/accordion-section/accordion-section.component.ts +++ b/src/app/shared/components/template/components/layout/accordion-section/accordion-section.component.ts @@ -46,7 +46,7 @@ export class AccordionSectionComponent extends TemplateBaseComponent implements this.completedIcon = getStringParamFromTemplateRow(this._row, "completed_icon_asset", null); this.inProgressIcon = getStringParamFromTemplateRow(this._row, "in_progress_icon_asset", null); this.disabledIcon = getStringParamFromTemplateRow(this._row, "disabled_icon_asset", null); - this.percentComplete = this._row.value ? this._row.value : 0; + this.percentComplete = this._row.value ? (this._row.value as number) : 0; this.updateStatus(this.percentComplete); } diff --git a/src/app/shared/components/template/components/layout/display-grid/display-grid.component.spec.ts b/src/app/shared/components/template/components/layout/display-grid/display-grid.component.spec.ts index deb6185c29..6f6c5dbdb8 100644 --- a/src/app/shared/components/template/components/layout/display-grid/display-grid.component.spec.ts +++ b/src/app/shared/components/template/components/layout/display-grid/display-grid.component.spec.ts @@ -1,22 +1,26 @@ -import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { TmplDisplayGridComponent } from "./display-grid.component"; +import { DataItemsService } from "../../data-items/data-items.service"; +import { FilterDisplayComponentPipe } from "../../../pipes/filter-display-component.pipe"; -describe("RadioButtonGridComponent", () => { +describe("TmplDisplayGridComponent", () => { let component: TmplDisplayGridComponent; let fixture: ComponentFixture; - beforeEach(async () => { + beforeEach(waitForAsync(async () => { await TestBed.configureTestingModule({ - declarations: [TmplDisplayGridComponent], + declarations: [TmplDisplayGridComponent, FilterDisplayComponentPipe], + providers: [{ provider: DataItemsService, useValue: {} }], }).compileComponents(); fixture = TestBed.createComponent(TmplDisplayGridComponent); component = fixture.componentInstance; fixture.detectChanges(); - }); + })); - it("should create", () => { + // TODO - requires better DataItemsService mock + xit("should create", () => { expect(component).toBeTruthy(); }); }); diff --git a/src/app/shared/components/template/components/layout/display-group/display-group.component.scss b/src/app/shared/components/template/components/layout/display-group/display-group.component.scss index 2093e0a90c..446be299ce 100644 --- a/src/app/shared/components/template/components/layout/display-group/display-group.component.scss +++ b/src/app/shared/components/template/components/layout/display-group/display-group.component.scss @@ -59,6 +59,7 @@ &[data-variant~="box_gray"], &[data-variant~="box_primary"], &[data-variant~="box_secondary"], + &[data-variant~="box_secondary_alt"], &[data-variant~="box_white"] { margin-top: var(--regular-margin); padding: var(--regular-padding); @@ -84,6 +85,11 @@ --border-color: var(--ion-color-secondary-500); } + &[data-variant~="box_secondary_alt"] { + --background-color: var(--ion-color-secondary-600); + --border-color: var(--ion-color-secondary-200); + } + &[data-variant~="box_white"] { --background-color: white; --border-color: var(--border-color-default, var(--ion-color-primary)); diff --git a/src/app/shared/components/template/components/lottie-animation.ts b/src/app/shared/components/template/components/lottie-animation.ts index 4917a0a0a1..9dca51fa33 100644 --- a/src/app/shared/components/template/components/lottie-animation.ts +++ b/src/app/shared/components/template/components/lottie-animation.ts @@ -37,7 +37,7 @@ export class TmplLottieAnimation extends TemplateBaseComponent implements OnInit // Loop by default const loop = r?.parameter_list?.loop === "false" ? false : true; if (r.value) { - const path = this.templateAssetService.getTranslatedAssetPath(r.value); + const path = this.templateAssetService.getTranslatedAssetPath(r.value as string); this.animOptions = { path, loop, diff --git a/src/app/shared/components/template/components/navigation-bar/navigation-bar.component.spec.ts b/src/app/shared/components/template/components/navigation-bar/navigation-bar.component.spec.ts index 065c896d34..1401119dcc 100644 --- a/src/app/shared/components/template/components/navigation-bar/navigation-bar.component.spec.ts +++ b/src/app/shared/components/template/components/navigation-bar/navigation-bar.component.spec.ts @@ -2,20 +2,24 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplNavigationBarComponent } from "./navigation-bar.component"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "navigation_bar" }; describe("NavigationBarComponent", () => { let component: TmplNavigationBarComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplNavigationBarComponent], imports: [IonicModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(TmplNavigationBarComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/number-selector/number-selector.component.spec.ts b/src/app/shared/components/template/components/number-selector/number-selector.component.spec.ts index 23efef8db9..f789df5b08 100644 --- a/src/app/shared/components/template/components/number-selector/number-selector.component.spec.ts +++ b/src/app/shared/components/template/components/number-selector/number-selector.component.spec.ts @@ -1,21 +1,25 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { async, ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplNumberComponent } from "./number-selector.component"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "pdf" }; describe("NumberSelectorComponent", () => { let component: TmplNumberComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplNumberComponent], imports: [IonicModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(TmplNumberComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/odk-form/odk-form.component.spec.ts b/src/app/shared/components/template/components/odk-form/odk-form.component.spec.ts index b0ec33be5d..4e6f4b6b93 100644 --- a/src/app/shared/components/template/components/odk-form/odk-form.component.spec.ts +++ b/src/app/shared/components/template/components/odk-form/odk-form.component.spec.ts @@ -1,6 +1,8 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { TmplOdkFormComponent } from "./odk-form.component"; +import { TemplateAssetService } from "../../services/template-asset.service"; +import { TemplateTranslateService } from "../../services/template-translate.service"; describe("OdkFormComponent", () => { let component: TmplOdkFormComponent; @@ -9,6 +11,7 @@ describe("OdkFormComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TmplOdkFormComponent], + providers:[{provide:TemplateAssetService, useValue:{}},{provide:TemplateTranslateService, useValue:{}}] }).compileComponents(); fixture = TestBed.createComponent(TmplOdkFormComponent); diff --git a/src/app/shared/components/template/components/pdf/pdf.component.spec.ts b/src/app/shared/components/template/components/pdf/pdf.component.spec.ts index 068506c593..2a6aa757ed 100644 --- a/src/app/shared/components/template/components/pdf/pdf.component.spec.ts +++ b/src/app/shared/components/template/components/pdf/pdf.component.spec.ts @@ -2,12 +2,15 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplPdfComponent } from "./pdf.component"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "pdf" }; describe("PdfComponent", () => { let component: TmplPdfComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplPdfComponent], imports: [IonicModule.forRoot()], @@ -15,7 +18,8 @@ describe("PdfComponent", () => { fixture = TestBed.createComponent(TmplPdfComponent); component = fixture.componentInstance; - fixture.detectChanges(); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/progress-path/progress-path.component.spec.ts b/src/app/shared/components/template/components/progress-path/progress-path.component.spec.ts index 2f965f4ee2..37594419c3 100644 --- a/src/app/shared/components/template/components/progress-path/progress-path.component.spec.ts +++ b/src/app/shared/components/template/components/progress-path/progress-path.component.spec.ts @@ -2,20 +2,27 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplProgressPathComponent } from "./progress-path.component"; +import { TemplateTranslateService } from "../../services/template-translate.service"; +import { FilterDisplayComponentPipe } from "../../pipes/filter-display-component.pipe"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "progress_path" }; describe("TmplProgressPathComponent", () => { let component: TmplProgressPathComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ - declarations: [TmplProgressPathComponent], + declarations: [TmplProgressPathComponent, FilterDisplayComponentPipe], imports: [IonicModule.forRoot()], + providers: [{ provide: TemplateTranslateService, useValue: {} }], }).compileComponents(); fixture = TestBed.createComponent(TmplProgressPathComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/qr-code/qr-code.component.spec.ts b/src/app/shared/components/template/components/qr-code/qr-code.component.spec.ts index 0d25d2f911..09c3dddf5c 100644 --- a/src/app/shared/components/template/components/qr-code/qr-code.component.spec.ts +++ b/src/app/shared/components/template/components/qr-code/qr-code.component.spec.ts @@ -2,20 +2,24 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplQRCodeComponent } from "./qr-code.component"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "qr_code" }; describe("QrCodeComponent", () => { let component: TmplQRCodeComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplQRCodeComponent], imports: [IonicModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(TmplQRCodeComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/radio-button-grid/radio-button-grid.component.spec.ts b/src/app/shared/components/template/components/radio-button-grid/radio-button-grid.component.spec.ts index 2323c9eac7..968d1e2b37 100644 --- a/src/app/shared/components/template/components/radio-button-grid/radio-button-grid.component.spec.ts +++ b/src/app/shared/components/template/components/radio-button-grid/radio-button-grid.component.spec.ts @@ -1,21 +1,33 @@ -import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { TmplRadioButtonGridComponent } from "./radio-button-grid.component"; +import { FlowTypes } from "packages/data-models"; +import { DataItemsService } from "../data-items/data-items.service"; +import { PLHAssetPipe } from "../../pipes/plh-asset.pipe"; +import { TemplateAssetService } from "../../services/template-asset.service"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "radio_button_grid" }; describe("RadioButtonGridComponent", () => { let component: TmplRadioButtonGridComponent; let fixture: ComponentFixture; - beforeEach(async () => { + beforeEach(waitForAsync(async () => { await TestBed.configureTestingModule({ - declarations: [TmplRadioButtonGridComponent], + declarations: [TmplRadioButtonGridComponent, PLHAssetPipe], + providers: [ + { provide: DataItemsService, useValue: {} }, + { provide: TemplateAssetService, useValue: {} }, + ], }).compileComponents(); fixture = TestBed.createComponent(TmplRadioButtonGridComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); - }); + })); + // TODO - DataItemsService not injecting correctly, may require mock it("should create", () => { expect(component).toBeTruthy(); }); diff --git a/src/app/shared/components/template/components/round-icon-button/round-icon-button.component.spec.ts b/src/app/shared/components/template/components/round-icon-button/round-icon-button.component.spec.ts index 9d0bcfc5ab..75b317bb25 100644 --- a/src/app/shared/components/template/components/round-icon-button/round-icon-button.component.spec.ts +++ b/src/app/shared/components/template/components/round-icon-button/round-icon-button.component.spec.ts @@ -1,21 +1,28 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { async, ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { RoundIconButtonComponent } from "./round-icon-button.component"; +import { PLHAssetPipe } from "../../pipes/plh-asset.pipe"; +import { FlowTypes } from "packages/data-models"; +import { TemplateAssetService } from "../../services/template-asset.service"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "round_icon_button" }; describe("RoundIconButtonComponent", () => { let component: RoundIconButtonComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ - declarations: [RoundIconButtonComponent], + declarations: [RoundIconButtonComponent, PLHAssetPipe], imports: [IonicModule.forRoot()], + providers: [{ provide: TemplateAssetService, useValue: {} }], }).compileComponents(); fixture = TestBed.createComponent(RoundIconButtonComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/select-text/select-text.component.spec.ts b/src/app/shared/components/template/components/select-text/select-text.component.spec.ts index b654d1494f..8994bfe35e 100644 --- a/src/app/shared/components/template/components/select-text/select-text.component.spec.ts +++ b/src/app/shared/components/template/components/select-text/select-text.component.spec.ts @@ -2,23 +2,30 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { SelectTextComponent } from "./select-text.component"; +import { TemplateTranslateService } from "../../services/template-translate.service"; +import { MockTemplateTranslateService } from "../../services/template-translate.service.spec"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "select_text" }; describe("SelectTextComponent", () => { let component: SelectTextComponent; let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [SelectTextComponent], - imports: [IonicModule.forRoot()], - }).compileComponents(); + beforeEach(waitForAsync(async () => { + TestBed.configureTestingModule({ + declarations: [SelectTextComponent], + imports: [IonicModule.forRoot()], + providers: [ + { provide: TemplateTranslateService, useValue: new MockTemplateTranslateService() }, + ], + }).compileComponents(); - fixture = TestBed.createComponent(SelectTextComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }) - ); + fixture = TestBed.createComponent(SelectTextComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); + component = fixture.componentInstance; + })); it("should create", () => { expect(component).toBeTruthy(); diff --git a/src/app/shared/components/template/components/select-text/select-text.component.ts b/src/app/shared/components/template/components/select-text/select-text.component.ts index 21a6bf0db3..3c5e6bc830 100644 --- a/src/app/shared/components/template/components/select-text/select-text.component.ts +++ b/src/app/shared/components/template/components/select-text/select-text.component.ts @@ -60,7 +60,7 @@ export class SelectTextComponent this.toggle = !this.toggle; }); - let text = _row.value; + let text = _row.value as string; await Clipboard.write({ string: text }); } diff --git a/src/app/shared/components/template/components/simple-checkbox/simple-checkbox.component.spec.ts b/src/app/shared/components/template/components/simple-checkbox/simple-checkbox.component.spec.ts index 45e945def8..f7d2d25239 100644 --- a/src/app/shared/components/template/components/simple-checkbox/simple-checkbox.component.spec.ts +++ b/src/app/shared/components/template/components/simple-checkbox/simple-checkbox.component.spec.ts @@ -1,21 +1,25 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { async, ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplSimpleCheckboxComponent } from "./simple-checkbox.component"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "simple_checkbox" }; describe("TmplSimpleCheckboxComponent", () => { let component: TmplSimpleCheckboxComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplSimpleCheckboxComponent], imports: [IonicModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(TmplSimpleCheckboxComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/slider/slider.component.spec.ts b/src/app/shared/components/template/components/slider/slider.component.spec.ts index 525108849d..5a1d626646 100644 --- a/src/app/shared/components/template/components/slider/slider.component.spec.ts +++ b/src/app/shared/components/template/components/slider/slider.component.spec.ts @@ -1,24 +1,31 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; +import { NouisliderModule } from "ng2-nouislider"; import { TmplSliderComponent } from "./slider.component"; +import { FlowTypes } from "packages/data-models"; -describe("SliderNewComponent", () => { +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "slider" }; + +describe("SliderComponent", () => { let component: TmplSliderComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplSliderComponent], - imports: [IonicModule.forRoot()], + imports: [IonicModule.forRoot(), NouisliderModule], }).compileComponents(); fixture = TestBed.createComponent(TmplSliderComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); - it("should create", () => { + // HACK - test skipped as NouisliderComponent throws error linked to ngOnDestroy + // assume may be fixed in update so should check again when next working on tests + xit("should create", () => { expect(component).toBeTruthy(); }); }); diff --git a/src/app/shared/components/template/components/slider/slider.component.ts b/src/app/shared/components/template/components/slider/slider.component.ts index c87d8f1c80..3faa5b5ddd 100644 --- a/src/app/shared/components/template/components/slider/slider.component.ts +++ b/src/app/shared/components/template/components/slider/slider.component.ts @@ -80,7 +80,7 @@ export class TmplSliderComponent // if restoring functionality assume the value reverts to last known (if a number) await this.changeValue(typeof this.previousValue === "number" ? this.previousValue : 0); } else { - this.previousValue = this._row.value; + this.previousValue = this._row.value as number; // if removing functionality specify the value as no_value await this.changeValue("no_value"); } diff --git a/src/app/shared/components/template/components/task-card/task-card.component.spec.ts b/src/app/shared/components/template/components/task-card/task-card.component.spec.ts index d8882a2d95..abfb2aadfe 100644 --- a/src/app/shared/components/template/components/task-card/task-card.component.spec.ts +++ b/src/app/shared/components/template/components/task-card/task-card.component.spec.ts @@ -2,23 +2,32 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplTaskCardComponent } from "./task-card.component"; +import { TaskService } from "src/app/shared/services/task/task.service"; +import { TemplateFieldService } from "../../services/template-field.service"; +import { MockTemplateFieldService } from "../../services/template-field.service.spec"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "task_card" }; describe("TmplTaskCardComponent", () => { let component: TmplTaskCardComponent; let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [TmplTaskCardComponent], - imports: [IonicModule.forRoot()], - }).compileComponents(); + beforeEach(waitForAsync(async () => { + TestBed.configureTestingModule({ + declarations: [TmplTaskCardComponent], + imports: [IonicModule.forRoot()], + providers: [ + { provide: TaskService, useValue: {} }, + { provide: TemplateFieldService, useValue: new MockTemplateFieldService() }, + ], + }).compileComponents(); - fixture = TestBed.createComponent(TmplTaskCardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }) - ); + fixture = TestBed.createComponent(TmplTaskCardComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); + component = fixture.componentInstance; + })); it("should create", () => { expect(component).toBeTruthy(); diff --git a/src/app/shared/components/template/components/task-progress-bar/task-progress-bar.component.spec.ts b/src/app/shared/components/template/components/task-progress-bar/task-progress-bar.component.spec.ts index b71a623571..fc24d335f4 100644 --- a/src/app/shared/components/template/components/task-progress-bar/task-progress-bar.component.spec.ts +++ b/src/app/shared/components/template/components/task-progress-bar/task-progress-bar.component.spec.ts @@ -2,25 +2,39 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplTaskProgressBarComponent } from "./task-progress-bar.component"; +import { DynamicDataService } from "src/app/shared/services/dynamic-data/dynamic-data.service"; +import { TaskService } from "src/app/shared/services/task/task.service"; +import { AsyncServiceBase } from "src/app/shared/services/asyncService.base"; + +class MockTaskService extends AsyncServiceBase { + constructor() { + super("MockTaskService"); + this.registerInitFunction(() => null); + } + getTaskGroupDataRows: () => null; +} describe("TmplTaskProgressBarComponent", () => { let component: TmplTaskProgressBarComponent; let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [TmplTaskProgressBarComponent], - imports: [IonicModule.forRoot()], - }).compileComponents(); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [TmplTaskProgressBarComponent], + imports: [IonicModule.forRoot()], + providers: [ + { provide: DynamicDataService, useValue: {} }, + { provide: TaskService, useValue: new MockTaskService() }, + ], + }).compileComponents(); - fixture = TestBed.createComponent(TmplTaskProgressBarComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }) - ); + fixture = TestBed.createComponent(TmplTaskProgressBarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); - it("should create", () => { + // TODO - requires better mock task service to implement + xit("should create", () => { expect(component).toBeTruthy(); }); }); diff --git a/src/app/shared/components/template/components/text/text.component.ts b/src/app/shared/components/template/components/text/text.component.ts index c791ba1aef..750411ba2d 100644 --- a/src/app/shared/components/template/components/text/text.component.ts +++ b/src/app/shared/components/template/components/text/text.component.ts @@ -22,7 +22,7 @@ export class TmplTextComponent extends TemplateBaseComponent implements OnInit { } getParams() { - this.hasTextValue = !["undefined", "NaN", "null", '""'].includes(this._row.value); + this.hasTextValue = !["undefined", "NaN", "null", '""'].includes(this._row.value as string); this.params.textAlign = getStringParamFromTemplateRow(this._row, "text_align", null); this.params.type = this._row.parameter_list?.style?.includes("numbered") ? "numbered" diff --git a/src/app/shared/components/template/components/tile-component/tile-component.component.spec.ts b/src/app/shared/components/template/components/tile-component/tile-component.component.spec.ts index c13825ae3b..139ad78ccc 100644 --- a/src/app/shared/components/template/components/tile-component/tile-component.component.spec.ts +++ b/src/app/shared/components/template/components/tile-component/tile-component.component.spec.ts @@ -1,21 +1,25 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { async, ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TmplTileComponent } from "./tile-component.component"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "tile_component" }; describe("TmplTileComponent", () => { let component: TmplTileComponent; let fixture: ComponentFixture; - beforeEach(async(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [TmplTileComponent], imports: [IonicModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(TmplTileComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/components/timer/timer.component.ts b/src/app/shared/components/template/components/timer/timer.component.ts index 557ed5434e..8cbc9e261c 100644 --- a/src/app/shared/components/template/components/timer/timer.component.ts +++ b/src/app/shared/components/template/components/timer/timer.component.ts @@ -119,7 +119,9 @@ export class TmplTimerComponent extends TemplateBaseComponent implements ITempla } getDurationFromParams() { - return this._row.value ? this._row.value : this.starting_minutes * 60 + this.starting_seconds; + return this._row.value + ? (this._row.value as number) + : this.starting_minutes * 60 + this.starting_seconds; } clickLeftButton() { diff --git a/src/app/shared/components/template/components/youtube/youtube.component.spec.ts b/src/app/shared/components/template/components/youtube/youtube.component.spec.ts index c1b73443db..86c1167bc1 100644 --- a/src/app/shared/components/template/components/youtube/youtube.component.spec.ts +++ b/src/app/shared/components/template/components/youtube/youtube.component.spec.ts @@ -2,20 +2,29 @@ import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { YoutubeComponent } from "./youtube.component"; +import { TemplateTranslateService } from "../../services/template-translate.service"; +import { MockTemplateTranslateService } from "../../services/template-translate.service.spec"; +import { FlowTypes } from "packages/data-models"; + +const MOCK_ROW: FlowTypes.TemplateRow = { _nested_name: "", name: "", type: "task_card" }; describe("YoutubeComponent", () => { let component: YoutubeComponent; let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { + beforeEach(waitForAsync(async () => { TestBed.configureTestingModule({ declarations: [YoutubeComponent], imports: [IonicModule.forRoot()], + providers: [ + { provide: TemplateTranslateService, useValue: new MockTemplateTranslateService() }, + ], }).compileComponents(); fixture = TestBed.createComponent(YoutubeComponent); + fixture.componentRef.setInput("row", MOCK_ROW); + await fixture.whenStable(); component = fixture.componentInstance; - fixture.detectChanges(); })); it("should create", () => { diff --git a/src/app/shared/components/template/pipes/plh-asset.pipe.ts b/src/app/shared/components/template/pipes/plh-asset.pipe.ts index 6310b6714a..ba250c7a22 100644 --- a/src/app/shared/components/template/pipes/plh-asset.pipe.ts +++ b/src/app/shared/components/template/pipes/plh-asset.pipe.ts @@ -10,7 +10,7 @@ import { TemplateAssetService } from "../services/template-asset.service"; export class PLHAssetPipe implements PipeTransform { constructor(private templateAssetService: TemplateAssetService) {} - transform(value: string) { + transform(value: any) { const translatedPath = this.templateAssetService.getTranslatedAssetPath(value); return translatedPath; } diff --git a/src/app/shared/components/template/processors/item.ts b/src/app/shared/components/template/processors/item.ts index 88498b8327..959d63be95 100644 --- a/src/app/shared/components/template/processors/item.ts +++ b/src/app/shared/components/template/processors/item.ts @@ -2,12 +2,6 @@ import { TemplatedData } from "packages/shared/src/models/templatedData/template import { FlowTypes } from "../models"; import { ItemDataPipe } from "./itemPipe"; -type IItemEvalContext = FlowTypes.TemplateRowItemEvalContextMetadata; - -interface ITemplateRowWithItemContext extends FlowTypes.TemplateRow { - _evalContext: { itemContext: IItemEvalContext }; // force specific context variables when calculating eval statements (such as loop items) -} - export class ItemProcessor { constructor( private dataListRows: FlowTypes.Data_listRow[] = [], @@ -44,7 +38,7 @@ export class ItemProcessor { templateRows: FlowTypes.TemplateRow[], itemData: FlowTypes.Data_listRow[] ) { - const loopItemRows: ITemplateRowWithItemContext[] = []; + const loopItemRows: FlowTypes.TemplateRow[] = []; const lastItemIndex = itemData.length - 1; for (const [indexKey, item] of Object.entries(itemData)) { const _index = Number(indexKey); @@ -54,8 +48,8 @@ export class ItemProcessor { _first: _index === 0, _last: _index === lastItemIndex, }; - const evalContext: ITemplateRowWithItemContext["_evalContext"] = { - itemContext: { + const evalContext: FlowTypes.TemplateRow["_evalContext"] = { + item: { ...item, ...itemContextMeta, // Assign row dynamic context to allow reference to rendered row metadata, including @@ -72,11 +66,11 @@ export class ItemProcessor { /** Update the evaluation context of a row and recursively any nested rows */ private setRecursiveRowEvalContext( row: FlowTypes.TemplateRow, - evalContext: ITemplateRowWithItemContext["_evalContext"] - ): ITemplateRowWithItemContext { + evalContext: FlowTypes.TemplateRow["_evalContext"] + ): FlowTypes.TemplateRow { // Workaround destructure for memory allocation issues (applying click action of last item only) const { rows, ...rest } = JSON.parse(JSON.stringify(row)); - const rowWithEvalContext: ITemplateRowWithItemContext = { ...rest, _evalContext: evalContext }; + const rowWithEvalContext: FlowTypes.TemplateRow = { ...rest, _evalContext: evalContext }; // handle child rows independently to avoid accidental property leaks if (row.rows) { rowWithEvalContext.rows = []; @@ -93,14 +87,14 @@ export class ItemProcessor { * so use delimited syntax and parse via newer TemplatedData processor * @see https://github.com/IDEMSInternational/parenting-app-ui/issues/1765 */ - private hackSetNestedName(itemRows: ITemplateRowWithItemContext[]) { + private hackSetNestedName(itemRows: FlowTypes.TemplateRow[]) { const parsedRows = []; for (const row of itemRows) { - const parser = new TemplatedData({ context: { item: row._evalContext.itemContext } }); + const parser = new TemplatedData({ context: { item: row._evalContext.item } }); const { rows, _nested_name } = row; row._nested_name = parser.parse(_nested_name); if (rows) { - row.rows = this.hackSetNestedName(rows as ITemplateRowWithItemContext[]); + row.rows = this.hackSetNestedName(rows as FlowTypes.TemplateRow[]); } parsedRows.push(row); } diff --git a/src/app/shared/components/template/services/instance/README.md b/src/app/shared/components/template/services/instance/README.md index eebbe99a47..c9f79e746e 100644 --- a/src/app/shared/components/template/services/instance/README.md +++ b/src/app/shared/components/template/services/instance/README.md @@ -1,9 +1,9 @@ -# Instance Services - -Some services require being created on demand so that they can operate within a given context, such as a specific template container with rows. - -To improve handling of dependency injection, these instantiable services use a custom injector to register services. This allows them to call global services without need to be inherited, and can also help to prevent cyclic dependency issues - -As such they are not injected, and it is assumed any services required for injection should already be in the global scope (if not should be added to module) - +# Instance Services + +Some services require being created on demand so that they can operate within a given context, such as a specific template container with rows. + +To improve handling of dependency injection, these instantiable services use a custom injector to register services. This allows them to call global services without need to be inherited, and can also help to prevent cyclic dependency issues + +As such they are not injected, and it is assumed any services required for injection should already be in the global scope (if not should be added to module) + TODO - add importable service that ensures instantiated dependencies available \ No newline at end of file diff --git a/src/app/shared/components/template/services/instance/template-action.service.spec.ts b/src/app/shared/components/template/services/instance/template-action.service.spec.ts index 9398bf0d48..21b0cd6c69 100644 --- a/src/app/shared/components/template/services/instance/template-action.service.spec.ts +++ b/src/app/shared/components/template/services/instance/template-action.service.spec.ts @@ -31,11 +31,16 @@ class MockTemplateRowService implements Partial { mock_row_1: { value: "", _nested_name: "mock_row_1", name: "mock_row_1", type: "" }, mock_row_2: { value: "", _nested_name: "mock_row_2", name: "mock_row_2", type: "" }, }; + templateRowMapValues = { + mock_row_1: "", + mock_row_2: "", + }; processRowUpdates = async () => null; } class MockContainer implements Partial { templateRowService = new MockTemplateRowService() as any as TemplateRowService; + get templateRowMap() { return this.templateRowService.templateRowMap; } @@ -74,17 +79,19 @@ describe("TemplateActionService", () => { await service.handleActions([ { trigger: "click", action_id: "set_local", args: ["mock_row_1", "updated"] }, ]); - expect(service.container.templateRowMap.mock_row_1.value).toEqual("updated"); + expect(service.container.templateRowService.templateRowMap.mock_row_1.value).toEqual("updated"); + expect(service.container.templateRowService.templateRowMapValues.mock_row_1).toEqual("updated"); }); it("set_self action", async () => { await service.handleActions([ { trigger: "click", action_id: "set_self", args: ["mock_row_1", "updated"] }, ]); - expect(service.container.templateRowMap.mock_row_1.value).toEqual("updated"); + expect(service.container.templateRowService.templateRowMap.mock_row_1.value).toEqual("updated"); + expect(service.container.templateRowService.templateRowMapValues.mock_row_1).toEqual("updated"); }); - it("Uses updated args when successive actions change same variable", async () => { + it("Uses latest value for `this.value` arg", async () => { const _triggeredBy = { _nested_name: "mock_row_1", name: "mock_row_1", type: "" }; await service.handleActions( [ @@ -97,10 +104,25 @@ describe("TemplateActionService", () => { ], _triggeredBy ); - expect(service.container.templateRowMap.mock_row_2.value).toEqual("updated"); + expect(service.container.templateRowService.templateRowMap.mock_row_2.value).toEqual("updated"); + expect(service.container.templateRowService.templateRowMapValues.mock_row_2).toEqual("updated"); + // also include test case of concatenated expression + await service.handleActions( + [ + { + trigger: "click", + action_id: "set_local", + args: ["mock_row_2", "prefix_{this.value}"], + }, + ], + _triggeredBy + ); + expect(service.container.templateRowService.templateRowMap.mock_row_2.value).toEqual( + "prefix_updated" + ); }); - it("Uses updated params when successive actions change same variable", async () => { + it("Uses latest value for `this.value` param", async () => { const _triggeredBy = { _nested_name: "mock_row_1", name: "mock_row_1", type: "" }; await service.handleActions( [ diff --git a/src/app/shared/components/template/services/instance/template-action.service.ts b/src/app/shared/components/template/services/instance/template-action.service.ts index 8c31cd84f0..b19fc333cc 100644 --- a/src/app/shared/components/template/services/instance/template-action.service.ts +++ b/src/app/shared/components/template/services/instance/template-action.service.ts @@ -337,19 +337,25 @@ export class TemplateActionService extends SyncServiceBase { ): FlowTypes.TemplateRowAction { // Update action.args and action.params const currentValue = this.container?.templateRowMap?.[action._triggeredBy?._nested_name]?.value; - if (action.args) { - action.args = action.args.map((arg) => { - if (arg === "this.value") { + // define a replacer that preserves type if `this.value` specified, replacing as string for + // other expressions `@local.some_field_{this.value}` + function replaceReference(v: any) { + if (typeof v === "string") { + if (v === "this.value") { return currentValue; } - return arg; - }); + if (v.includes("{this.value}")) { + return v.replace("{this.value}", currentValue as string); + } + } + return v; + } + if (action.args) { + action.args = action.args.map((arg) => replaceReference(arg)); } if (action.params) { for (const [key, value] of Object.entries(action.params)) { - if (value === "this.value") { - action.params[key] = currentValue; - } + action.params[key] = replaceReference(value); } } return action; @@ -376,6 +382,8 @@ export class TemplateActionService extends SyncServiceBase { } rowEntry.value = value; this.container.templateRowService.templateRowMap[rowEntry._nested_name] = rowEntry; + this.container.templateRowService.templateRowMapValues[rowEntry._nested_name] = + rowEntry.value; } else { // TODO console.warn("Setting local variable which does not exist", { key, value }, "TODO"); diff --git a/src/app/shared/components/template/services/instance/template-process.service.ts b/src/app/shared/components/template/services/instance/template-process.service.ts index 568f322696..1fc53fee7c 100644 --- a/src/app/shared/components/template/services/instance/template-process.service.ts +++ b/src/app/shared/components/template/services/instance/template-process.service.ts @@ -64,6 +64,7 @@ export class TemplateProcessService extends SyncServiceBase { this.container.template = template; // reset any existing templateRowMap data this.container.templateRowService.templateRowMap = {}; + this.container.templateRowService.templateRowMapValues = {}; // process the template as if it were rendered // TODO - should filter out template rows to only include those used programatically (e.g. set_variable, set_field etc.) await this.container.templateRowService.processContainerTemplateRows(); diff --git a/src/app/shared/components/template/services/instance/template-row.service.ts b/src/app/shared/components/template/services/instance/template-row.service.ts index b91059efa3..5e0e66bf0a 100644 --- a/src/app/shared/components/template/services/instance/template-row.service.ts +++ b/src/app/shared/components/template/services/instance/template-row.service.ts @@ -35,6 +35,10 @@ export class TemplateRowService extends SyncServiceBase { /** List of overrides set by parent templates for access during parent processing */ /** Hashmap of all rows keyed by nested row name (e.g. contentBox1.row1.title) */ public templateRowMap: ITemplateRowMap = {}; + + /** Modified templateRowMap to only include row values, for use in local evalContext */ + public templateRowMapValues: { [row_nested_name: string]: FlowTypes.TemplateRow["value"] } = {}; + public renderedRows = signal([], { equal: isEqual }); // rows processed and filtered by condition constructor( @@ -244,11 +248,12 @@ export class TemplateRowService extends SyncServiceBase { preProcessedRow: FlowTypes.TemplateRow, isNestedTemplate: boolean ) { - const { _nested_name, _evalContext } = preProcessedRow; + const { _nested_name, _evalContext = {} } = preProcessedRow; // Evaluate row variables in context of current local state - const evalContext = { + // TODO - only extract local variables as needed + const evalContext: FlowTypes.TemplateRowEvalContext = { ..._evalContext, - templateRowMap: this.templateRowMap, + local: { ...this.templateRowMapValues, ..._evalContext.local }, row: preProcessedRow, }; @@ -288,7 +293,9 @@ export class TemplateRowService extends SyncServiceBase { // Instead of returning themselves items looped child rows if (type === "items") { // items have their data lists already parsed as hashmap. convert back to array and process - const itemDataList = row.value as Record; + // TODO - ideally should store parsed hashmap as part of evalContext instead of replacing value + // (will make easier to make type-safe and remove need to un-parse) + const itemDataList = row.value as any as Record; const parsedItemDataList = await this.parseDataList(itemDataList); const parsedItemDataRows = Object.values(parsedItemDataList); const { parameter_list, rows } = row; @@ -314,12 +321,13 @@ export class TemplateRowService extends SyncServiceBase { case "set_field": // console.warn("[W] Setting fields from template is not advised", row); - await this.templateFieldService.setField(name, value); + await this.templateFieldService.setField(name, value as string); return; // ensure set_variables are recorded via their name (instead of default nested name) // if a variable is dynamic keep original for future re-evaluation (otherwise discard) case "set_variable": this.templateRowMap[name] = row; + this.templateRowMapValues[name] = row.value; if (_dynamicFields) { return preProcessedRow; } @@ -343,6 +351,7 @@ export class TemplateRowService extends SyncServiceBase { default: // all other types should just set own value for use in future processing this.templateRowMap[_nested_name] = row; + this.templateRowMapValues[_nested_name] = row.value; break; } } diff --git a/src/app/shared/components/template/services/template-calc-functions/plh-calc-functions.spec.ts b/src/app/shared/components/template/services/template-calc-functions/plh-calc-functions.spec.ts index 80918034e6..5975c8701a 100644 --- a/src/app/shared/components/template/services/template-calc-functions/plh-calc-functions.spec.ts +++ b/src/app/shared/components/template/services/template-calc-functions/plh-calc-functions.spec.ts @@ -8,6 +8,9 @@ const MOCK_FAMILES = () => [["Ada", "Blaise"], ["Charles"], ["Daniel", "Eva"]]; * yarn ng test --include src\app\shared\components\template\services\template-calc-functions\plh-calc-functions.spec.ts */ describe("Template Calc - PLH Functions", () => { + beforeEach(() => { + (window as any).calc = PLH_CALC_FUNCTIONS; + }); it("Add family", () => { const res = PLH_CALC_FUNCTIONS.plh_add_family(MOCK_FAMILES(), "Friedrich", "Graham", "Hannah"); expect(res).toEqual([ @@ -35,6 +38,9 @@ describe("Template Calc - PLH Functions", () => { }); describe("Template Calc - PLH Functions QA", () => { + beforeEach(() => { + (window as any).calc = PLH_CALC_FUNCTIONS; + }); it("Handles string input", () => { const stringInput = JSON.stringify(MOCK_FAMILES()); const res = PLH_CALC_FUNCTIONS.plh_add_family(stringInput, "Friedrich", "Graham", "Hannah"); diff --git a/src/app/shared/components/template/services/template-calc-functions/plh-calc-functions.ts b/src/app/shared/components/template/services/template-calc-functions/plh-calc-functions.ts index 7aca5e08e3..23c1d6e49e 100644 --- a/src/app/shared/components/template/services/template-calc-functions/plh-calc-functions.ts +++ b/src/app/shared/components/template/services/template-calc-functions/plh-calc-functions.ts @@ -1,4 +1,4 @@ -import { IFunctionHashmap } from "packages/shared/src"; +import type { IFunctionHashmap } from "packages/shared/src/models/jsEvaluator/jsEvaluator"; /** * Temporary functions used for plh sheets diff --git a/src/app/shared/components/template/services/template-calc.service.mock.spec.ts b/src/app/shared/components/template/services/template-calc.service.mock.spec.ts new file mode 100644 index 0000000000..fb6f66e8f7 --- /dev/null +++ b/src/app/shared/components/template/services/template-calc.service.mock.spec.ts @@ -0,0 +1,23 @@ +import { MockLocalStorageService } from "src/app/shared/services/local-storage/local-storage.service.mock.spec"; +import { ICalcContext, TemplateCalcService } from "./template-calc.service"; +import { MockDataEvaluationService } from "src/app/shared/services/data/data-evaluation.service.mock.spec"; + +export class MockTemplateCalcService extends TemplateCalcService { + constructor(mockCalcContext: Partial = {}) { + super(new MockDataEvaluationService(), new MockLocalStorageService()); + // merge any mock calc context with defaults + super["calcContext"] = { + thisCtxt: { + // ensure default calc included as allows `@calc(...)` to be triggered via `this.calc(...)` + calc: (v: any) => v, + ...mockCalcContext.thisCtxt, + }, + globalConstants: { + ...mockCalcContext.globalConstants, + }, + globalFunctions: { + ...mockCalcContext.globalFunctions, + }, + }; + } +} diff --git a/src/app/shared/components/template/services/template-calc.service.spec.ts b/src/app/shared/components/template/services/template-calc.service.spec.ts index 2752062482..9df9645cb8 100644 --- a/src/app/shared/components/template/services/template-calc.service.spec.ts +++ b/src/app/shared/components/template/services/template-calc.service.spec.ts @@ -1,18 +1,91 @@ -import { ICalcContext, TemplateCalcService } from "./template-calc.service"; - -export class MockTemplateCalcService implements Partial { - public async ready(): Promise { - return true; - } - - public getCalcContext(): ICalcContext { - return { - thisCtxt: {}, - globalConstants: {}, - globalFunctions: {}, - }; - } -} +import { TestBed } from "@angular/core/testing"; +import { TemplateCalcService } from "./template-calc.service"; +import { MockDataEvaluationService } from "src/app/shared/services/data/data-evaluation.service.mock.spec"; +import { MockLocalStorageService } from "src/app/shared/services/local-storage/local-storage.service.mock.spec"; +import { DataEvaluationService } from "src/app/shared/services/data/data-evaluation.service"; +import { LocalStorageService } from "src/app/shared/services/local-storage/local-storage.service"; +import { CORE_CALC_FUNCTIONS } from "./template-calc-functions/core-calc-functions"; +import { PLH_CALC_FUNCTIONS } from "./template-calc-functions/plh-calc-functions"; + +/** + * Call standalone tests via: + * yarn ng test --include src/app/shared/components/template/services/template-calc.service.spec.ts + */ +describe("TemplateCalcService", () => { + let service: TemplateCalcService; + + beforeEach(async () => { + TestBed.configureTestingModule({ + providers: [ + { + provide: DataEvaluationService, + useValue: new MockDataEvaluationService(), + }, + { + provide: LocalStorageService, + useValue: new MockLocalStorageService(), + }, + ], + }); + service = TestBed.inject(TemplateCalcService); + await service.ready(); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("[Deprecated] populates window.calc with calc functions", () => { + const { calc } = service["windowWithCalc"]; + expect(Object.keys(calc)).toEqual([ + ...Object.keys(CORE_CALC_FUNCTIONS), + ...Object.keys(PLH_CALC_FUNCTIONS), + ]); + }); + + it("populates window.date_fns with date_fns lib", () => { + const { date_fns } = service["windowWithCalc"]; + expect(date_fns.hoursToMilliseconds(1)).toEqual(3600000); + }); + + it("Gets calc context", () => { + const calcContext = service.getCalcContext(); + const { globalConstants, globalFunctions, thisCtxt } = calcContext; + // global constants and functions are passed by service + expect(globalConstants).toEqual({}); + // global functions should be same as window but without 3rd party + expect(Object.keys(globalFunctions)).toEqual([ + ...Object.keys(CORE_CALC_FUNCTIONS), + ...Object.keys(PLH_CALC_FUNCTIONS), + ]); + // By default a handful of specific app data context fields always populated + expect(Object.keys(thisCtxt)).toEqual([ + "calc", + "app_day", + "app_first_launch", + "app_user_id", + "device_info", + ]); + }); + + it("evaluates @calc statements", async () => { + const res = await service.evaluate("@calc(2*2)"); + expect(res).toEqual(4); + }); + + it("evaluates @calc statements with window functions", async () => { + const res = await service.evaluate("@calc(window.date_fns.hoursToMilliseconds(1))", {}); + expect(res).toEqual(3600000); + }); + + it("evaluates @calc statements with local context", async () => { + // NOTE - `@local` expression should be converted to `this.local` in template-parser + const res = await service.evaluate("@calc(this.local.test_string)", { + local: { test_string: "hello" }, + }); + expect(res).toEqual("hello"); + }); +}); /** * TODO - Add testing data and methods diff --git a/src/app/shared/components/template/services/template-calc.service.ts b/src/app/shared/components/template/services/template-calc.service.ts index 42e4ce5b81..0e8af9d874 100644 --- a/src/app/shared/components/template/services/template-calc.service.ts +++ b/src/app/shared/components/template/services/template-calc.service.ts @@ -1,14 +1,28 @@ -import { IFunctionHashmap, IConstantHashmap } from "src/app/shared/utils"; +import { IFunctionHashmap, IConstantHashmap, evaluateJSExpression } from "src/app/shared/utils"; import { Injectable } from "@angular/core"; import { Device, DeviceInfo } from "@capacitor/device"; import * as date_fns from "date-fns"; -import { ServerService } from "src/app/shared/services/server/server.service"; import { DataEvaluationService } from "src/app/shared/services/data/data-evaluation.service"; import { AsyncServiceBase } from "src/app/shared/services/asyncService.base"; import { PLH_CALC_FUNCTIONS } from "./template-calc-functions/plh-calc-functions"; import { CORE_CALC_FUNCTIONS } from "./template-calc-functions/core-calc-functions"; -import { UserMetaService } from "src/app/shared/services/userMeta/userMeta.service"; import { LocalStorageService } from "src/app/shared/services/local-storage/local-storage.service"; +import type { FlowTypes } from "packages/data-models"; + +/** Window context with additional calc service properties attached */ +type IWindowWithCalc = Window & { + /** + * @deprecated 0.18.0 Prefer to call directly instead of via window + * + * ✔️ `@calc(pick_random([1,2,3]))` + * ❌ `@calc(window.calc.pick_random([1,2,3]))` + * + * All user-defined calc available globally + * */ + calc: IFunctionHashmap; + /** Specific data_fns lib available globally */ + date_fns: typeof date_fns; +}; @Injectable({ providedIn: "root" }) export class TemplateCalcService extends AsyncServiceBase { @@ -23,17 +37,20 @@ export class TemplateCalcService extends AsyncServiceBase { }; constructor( - private serverService: ServerService, private dataEvaluationService: DataEvaluationService, - private localStorageService: LocalStorageService, - private userMetaService: UserMetaService + private localStorageService: LocalStorageService ) { super("TemplateCalc"); this.registerInitFunction(this.initialise); } + + private get windowWithCalc() { + return window as any as IWindowWithCalc; + } + private async initialise() { - this.ensureSyncServicesReady([this.serverService, this.localStorageService]); - await this.ensureAsyncServicesReady([this.dataEvaluationService, this.userMetaService]); + this.ensureSyncServicesReady([this.localStorageService]); + await this.ensureAsyncServicesReady([this.dataEvaluationService]); await this.setUserMetaData(); this.getCalcContext(); } @@ -45,10 +62,23 @@ export class TemplateCalcService extends AsyncServiceBase { this.calcContext = this.generateCalcContext(); } // Assign all calc functions also to window object to allow calling between functions - (window as any).calc = this.calcFunctions; + this.windowWithCalc.calc = this.calcFunctions; return this.calcContext; } + /** + * Evaluate inner expression provided by `@calc(...)` expression + * The expression is evaluated as JS, with additional access to global constants, function and + * evaluation context variables + * */ + public evaluate(expression: string, evalContext: FlowTypes.TemplateRowEvalContext = {}) { + const calcContext = this.getCalcContext(); + const calcExpression = expression.replace(/@/gi, "this."); + const { thisCtxt, globalFunctions, globalConstants } = calcContext; + const mergedContext = { ...thisCtxt, ...evalContext }; + return evaluateJSExpression(calcExpression, mergedContext, globalFunctions, globalConstants); + } + /** * Main export for use in evaluation statements. Includes all functions listed below * alongside additional a base for variables found at `this.` @@ -95,9 +125,7 @@ export class TemplateCalcService extends AsyncServiceBase { * ``` */ private generateGlobalConstants() { - const globalConstants: IConstantHashmap = { - test_var: "hello", - }; + const globalConstants: IConstantHashmap = {}; return globalConstants; } @@ -109,7 +137,7 @@ export class TemplateCalcService extends AsyncServiceBase { * ``` */ private addWindowCalcFunctions() { - (window as any).date_fns = date_fns; + this.windowWithCalc.date_fns = date_fns; } } @@ -120,6 +148,8 @@ export class TemplateCalcService extends AsyncServiceBase { */ export interface ICalcContext { thisCtxt: { + /** assign `this.calc` variable to handle replaced `this.calc(...)` expressions */ + calc: (v) => any; [name: string]: any; }; globalFunctions: IFunctionHashmap; diff --git a/src/app/shared/components/template/services/template-metadata.service.spec.ts b/src/app/shared/components/template/services/template-metadata.service.spec.ts index 8673fab604..b828fa814d 100644 --- a/src/app/shared/components/template/services/template-metadata.service.spec.ts +++ b/src/app/shared/components/template/services/template-metadata.service.spec.ts @@ -1,12 +1,20 @@ import { TestBed } from "@angular/core/testing"; import { TemplateMetadataService } from "./template-metadata.service"; +import { TemplateService } from "./template.service"; +import { AppConfigService } from "src/app/shared/services/app-config/app-config.service"; +import { MockAppConfigService } from "src/app/shared/services/app-config/app-config.service.mock.spec"; describe("TemplateMetadataService", () => { let service: TemplateMetadataService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + { provide: TemplateService, useValue: {} }, + { provide: AppConfigService, useValue: new MockAppConfigService() }, + ], + }); service = TestBed.inject(TemplateMetadataService); }); diff --git a/src/app/shared/components/template/services/template-translate.service.spec.ts b/src/app/shared/components/template/services/template-translate.service.spec.ts index c5d584dcdc..d97626efbf 100644 --- a/src/app/shared/components/template/services/template-translate.service.spec.ts +++ b/src/app/shared/components/template/services/template-translate.service.spec.ts @@ -3,7 +3,7 @@ import { TemplateTranslateService } from "./template-translate.service"; import { AppDataService } from "src/app/shared/services/data/app-data.service"; import { MockAppDataService } from "src/app/shared/services/data/app-data.service.mock.spec"; import { AppConfigService } from "src/app/shared/services/app-config/app-config.service"; -import { MockAppConfigService } from "src/app/shared/services/app-config/app-config.service.spec"; +import { MockAppConfigService } from "src/app/shared/services/app-config/app-config.service.mock.spec"; const MOCK_DATA_LIST_ROWS = [ { diff --git a/src/app/shared/components/template/services/template-translate.service.ts b/src/app/shared/components/template/services/template-translate.service.ts index 01aaa03da1..9a482dd9e9 100644 --- a/src/app/shared/components/template/services/template-translate.service.ts +++ b/src/app/shared/components/template/services/template-translate.service.ts @@ -6,6 +6,7 @@ import { AsyncServiceBase } from "src/app/shared/services/asyncService.base"; import { AppDataService } from "src/app/shared/services/data/app-data.service"; import { LocalStorageService } from "src/app/shared/services/local-storage/local-storage.service"; import { FlowTypes } from "../models"; +import { isObjectLiteral } from "packages/shared/src/utils/object-utils"; @Injectable({ providedIn: "root" }) /** @@ -105,7 +106,7 @@ export class TemplateTranslateService extends AsyncServiceBase { * Note - for improved efficiency rows with translatable data will usually have * a `translatedFields` property that lists keys for translation */ - translateRow(row: FlowTypes.TemplateRow = {} as any) { + translateRow(row: FlowTypes.TemplateRow = {} as any): FlowTypes.TemplateRow { const translated = { ...row }; // Case 1 - row with translate fields identified (e.g. template row) if (row._translations) { @@ -119,8 +120,8 @@ export class TemplateTranslateService extends AsyncServiceBase { } // Case 2 - row value assigned from data list with translate fields const { value } = row; - if (value && value._translations) { - translated.value = this.translateRow(value); + if (value && isObjectLiteral(value) && (value as any)._translations) { + translated.value = this.translateRow(value as any) as any; } // Note - there is a third case when row value assigned from calculation (e.g. data list child field) // but this is currently manually handled in the template-variables service as required diff --git a/src/app/shared/components/template/services/template-variables.service.spec.ts b/src/app/shared/components/template/services/template-variables.service.spec.ts index fef2322723..b4b1c8e768 100644 --- a/src/app/shared/components/template/services/template-variables.service.spec.ts +++ b/src/app/shared/components/template/services/template-variables.service.spec.ts @@ -1,5 +1,5 @@ import { TestBed } from "@angular/core/testing"; -import { IVariableContext, TemplateVariablesService } from "./template-variables.service"; +import { TemplateVariablesService } from "./template-variables.service"; import { HttpClientTestingModule } from "@angular/common/http/testing"; import { TemplateFieldService } from "./template-field.service"; import { MockTemplateFieldService } from "./template-field.service.spec"; @@ -7,8 +7,9 @@ import { AppDataService } from "src/app/shared/services/data/app-data.service"; import { CampaignService } from "src/app/feature/campaign/campaign.service"; import { MockAppDataService } from "src/app/shared/services/data/app-data.service.mock.spec"; import { TemplateCalcService } from "./template-calc.service"; -import { MockTemplateCalcService } from "./template-calc.service.spec"; +import { MockTemplateCalcService } from "./template-calc.service.mock.spec"; import { TemplateTranslateService } from "./template-translate.service"; +import { FlowTypes } from "packages/data-models"; const MOCK_APP_DATA = {}; @@ -21,7 +22,7 @@ const MOCK_FIELDS = { dynamic_field: "number", }; -const MOCK_CONTEXT_BASE: IVariableContext = { +const MOCK_CONTEXT_BASE: FlowTypes.TemplateRowEvalContext = { // Assume the row will have a dynamic 'field' entry field: "value", row: { @@ -30,8 +31,8 @@ const MOCK_CONTEXT_BASE: IVariableContext = { name: "test_row", _nested_name: "test_row", }, - templateRowMap: {}, - calcContext: { + local: {}, + calc: { globalConstants: {}, globalFunctions: {}, thisCtxt: { @@ -41,7 +42,7 @@ const MOCK_CONTEXT_BASE: IVariableContext = { }, }; -const TEST_FIELD_CONTEXT: IVariableContext = { +const TEST_FIELD_CONTEXT: FlowTypes.TemplateRowEvalContext = { ...MOCK_CONTEXT_BASE, row: { ...MOCK_CONTEXT_BASE.row, @@ -61,7 +62,7 @@ const TEST_FIELD_CONTEXT: IVariableContext = { // Context adapted from this debug template: // https://docs.google.com/spreadsheets/d/1tL6CPHEIW-GPMYjdhVKQToy_hZ1H5qNIBkkh9XnA5QM/edit#gid=114708400 -const TEST_ITEM_CONTEXT: IVariableContext = { +const TEST_ITEM_CONTEXT: FlowTypes.TemplateRowEvalContext = { ...MOCK_CONTEXT_BASE, row: { ...MOCK_CONTEXT_BASE.row, @@ -78,7 +79,7 @@ const TEST_ITEM_CONTEXT: IVariableContext = { ], }, }, - itemContext: { + item: { id: "id1", number: 1, string: "hello", @@ -90,16 +91,11 @@ const TEST_ITEM_CONTEXT: IVariableContext = { }, }; -const TEST_LOCAL_CONTEXT: IVariableContext = { +const TEST_LOCAL_CONTEXT: FlowTypes.TemplateRowEvalContext = { ...MOCK_CONTEXT_BASE, - templateRowMap: { + local: { // Mock row setting a local variable - string_local: { - name: "string_local", - value: "Jasper", - type: "set_variable", - _nested_name: "string_local", - }, + string_local: "Jasper", }, row: { ...MOCK_CONTEXT_BASE.row, @@ -202,7 +198,7 @@ describe("TemplateVariablesService", () => { expect(resWithItemContext).toEqual(1); // Retain raw expression if evaluating outside of item context // https://github.com/IDEMSInternational/parenting-app-ui/pull/2215#discussion_r1514757364 - delete TEST_ITEM_CONTEXT.itemContext; + delete TEST_ITEM_CONTEXT.item; const resWithoutItemContext = await service.evaluatePLHData( MOCK_ITEM_STRING, TEST_ITEM_CONTEXT @@ -225,7 +221,7 @@ describe("TemplateVariablesService", () => { { "@field.dynamic_field": 2 }, { row: { ...MOCK_CONTEXT_BASE.row, _dynamicFields: {} }, - templateRowMap: {}, + local: {}, } ); expect(res).toEqual({ number: 2 }); diff --git a/src/app/shared/components/template/services/template-variables.service.ts b/src/app/shared/components/template/services/template-variables.service.ts index 5f7685022f..446d63d6a1 100644 --- a/src/app/shared/components/template/services/template-variables.service.ts +++ b/src/app/shared/components/template/services/template-variables.service.ts @@ -4,11 +4,11 @@ import { FlowTypes } from "src/app/shared/model"; import { evaluateJSExpression, getNestedProperty, setNestedProperty } from "src/app/shared/utils"; import { ICalcContext, TemplateCalcService } from "./template-calc.service"; import { TemplateTranslateService } from "./template-translate.service"; -import { ITemplateRowMap } from "./instance/template-row.service"; import { extractDynamicEvaluators } from "data-models"; import { TemplateFieldService } from "./template-field.service"; import { AppDataService } from "src/app/shared/services/data/app-data.service"; import { AsyncServiceBase } from "src/app/shared/services/asyncService.base"; +import { isObjectLiteral } from "packages/shared/src/utils/object-utils"; /** Logging Toggle - rewrite default functions to enable or disable inline logs */ const SHOW_DEBUG_LOGS = false; @@ -23,13 +23,6 @@ const { TEMPLATE_ROW_ITEM_METADATA_FIELDS } = FlowTypes; * (e.g.row, variables etc.). Store as a single object to make it easier to pass between methods * @param templateRowMap hashmap containing list of all template rows, keyed by their nested row name */ -export interface IVariableContext { - templateRowMap: ITemplateRowMap; - row: FlowTypes.TemplateRow; - field?: string; - calcContext?: ICalcContext; - itemContext?: any; // used when iterating over items -} @Injectable({ providedIn: "root" }) export class TemplateVariablesService extends AsyncServiceBase { @@ -74,7 +67,7 @@ export class TemplateVariablesService extends AsyncServiceBase { */ public async evaluatePLHData( data: string | number | boolean | any, - context: IVariableContext, + context: FlowTypes.TemplateRowEvalContext, omitFields: string[] = [] ) { const dynamicFields = context.row._dynamicFields; @@ -153,10 +146,7 @@ export class TemplateVariablesService extends AsyncServiceBase { const dynamicEvaluators = extractDynamicEvaluators(conditionString); if (dynamicEvaluators) { // Assumes that no specific row information available (@local undefined) - const context: IVariableContext = { - row: {} as any, - templateRowMap: {} as any, - }; + const context: FlowTypes.TemplateRowEvalContext = {}; return this.evaluatePLHString(dynamicEvaluators, context); } return conditionString; @@ -180,7 +170,7 @@ export class TemplateVariablesService extends AsyncServiceBase { */ private async evaluatePLHString( evaluators: FlowTypes.TemplateRowDynamicEvaluator[], - context: IVariableContext + context: FlowTypes.TemplateRowEvalContext ) { const fullExpression = evaluators[0].fullExpression; log_group(fullExpression); @@ -193,7 +183,7 @@ export class TemplateVariablesService extends AsyncServiceBase { const parsedEvaluators: FlowTypes.TemplateRowDynamicEvaluator[] = []; for (const evaluator of evaluators) { const { type, fieldName, matchedExpression } = evaluator; - context.calcContext = calcContext; + context.calc = calcContext; // If a raw evaluator exists for any part of expression, return full expression unparsed // e.g. "Example syntax is `@field.my_name`" -> "Example syntax is @field.my_name" @@ -204,7 +194,7 @@ export class TemplateVariablesService extends AsyncServiceBase { // If the appropriate context is not available, do not evaluate that particular evaluator // NOTE - this will mean compound expressions will need to be evaluated later // E.g. @item.some_field === @local.other_field -> this.item.id === "local value", which needs further evaluation - if (type === "item" && !context.itemContext) { + if (type === "item" && !context.item) { return this.hackProcessItemEvaluators(evaluators, context); } @@ -257,7 +247,7 @@ export class TemplateVariablesService extends AsyncServiceBase { */ private async hackProcessItemEvaluators( evaluators: FlowTypes.TemplateRowDynamicEvaluator[], - context: IVariableContext + context: FlowTypes.TemplateRowEvalContext ) { let expression = evaluators[0].fullExpression; for (const evaluator of evaluators) { @@ -309,12 +299,11 @@ export class TemplateVariablesService extends AsyncServiceBase { * @param evaluators */ private async parseContextExpression( - context: IVariableContext, + context: FlowTypes.TemplateRowEvalContext, fullExpression: string, evaluators: FlowTypes.TemplateRowDynamicEvaluator[] ) { - const { calcContext } = context; - const { thisCtxt, globalFunctions, globalConstants } = calcContext; + const { thisCtxt, globalFunctions, globalConstants } = context.calc as ICalcContext; let evaluated: any; try { // first pass - full evaluation @@ -367,45 +356,42 @@ export class TemplateVariablesService extends AsyncServiceBase { */ private async processDynamicEvaluator( evaluator: FlowTypes.TemplateRowDynamicEvaluator, - context: IVariableContext + context: FlowTypes.TemplateRowEvalContext ) { let parsedValue: any; let parseSuccess = true; const { type, fieldName } = evaluator; - const { templateRowMap, field } = context; + const { local = {}, field } = context; switch (type) { case "local": - // TODO - assumed 'value' field will be returned but this could be provided instead as an arg - const returnField: keyof FlowTypes.TemplateRow = "value"; - // find any rows where nested path corresponds to match path - let matchedRows: { row: FlowTypes.TemplateRow; nestedName: string }[] = []; - Object.entries(templateRowMap).forEach(([nestedName, row]) => { + let matchedValues: { value: FlowTypes.TemplateRow["value"]; nestedName: string }[] = []; + Object.entries(local).forEach(([nestedName, value]) => { if (nestedName === fieldName || nestedName.endsWith(`.${fieldName}`)) { - matchedRows.push({ row, nestedName }); + matchedValues.push({ nestedName, value }); } }); // no match found. If condition assume this is fine, otherwise authoring error - if (matchedRows.length === 0) { + if (matchedValues.length === 0) { if (field === "condition") { parsedValue = false; } else { parseSuccess = false; console.error(`@local.${fieldName} not found`, { evaluator, - rowMap: templateRowMap, + rowMap: local, }); } } // match found - return least nested (in case of duplicates) else { - matchedRows = matchedRows.sort( + matchedValues = matchedValues.sort( (a, b) => a.nestedName.split(".").length - b.nestedName.split(".").length ); - if (matchedRows.length > 1) { - console.warn(`@local.${fieldName} found multiple`, { matchedRows }); + if (matchedValues.length > 1) { + console.warn(`@local.${fieldName} found multiple`, { matchedValues }); } - parsedValue = matchedRows[0].row[returnField]; + parsedValue = matchedValues[0].value; } break; @@ -440,15 +426,15 @@ export class TemplateVariablesService extends AsyncServiceBase { break; case "calc": const expression = fieldName.replace(/@/gi, "this."); - const { thisCtxt, globalFunctions, globalConstants } = context.calcContext; + const { thisCtxt, globalFunctions, globalConstants } = context.calc as ICalcContext; log("evaluate calc", { expression, thisCtxt, globalFunctions }); // TODO - merge string replacements with above methods parsedValue = evaluateJSExpression(expression, thisCtxt, globalFunctions, globalConstants); break; case "item": // only attempt to evaluate items if context passed, otherwise leave as original unparsed string - if (context?.itemContext) { - parsedValue = context.itemContext[fieldName]; + if (context?.item) { + parsedValue = context.item[fieldName]; } else { parsedValue = evaluator.matchedExpression; } @@ -461,8 +447,8 @@ export class TemplateVariablesService extends AsyncServiceBase { // This will be checked a second time and could cause an infinite loop parsedValue = ""; } - parsedValue = this.ensureValueTranslated(parsedValue); - return { parsedValue, parseSuccess }; + const translatedValue = this.ensureValueTranslated(parsedValue); + return { parsedValue: translatedValue, parseSuccess }; } /** @@ -473,7 +459,7 @@ export class TemplateVariablesService extends AsyncServiceBase { private ensureValueTranslated(value: any) { // If translatable value should be an object with _translations property // TODO - check if case needs to be added to translate arrays - if (value && typeof value === "object" && !Array.isArray(value)) { + if (isObjectLiteral(value)) { if (value.hasOwnProperty("_translations")) { value = this.templateTranslateService.translateRow(value); } else { diff --git a/src/app/shared/components/template/template-container.component.spec.ts b/src/app/shared/components/template/template-container.component.spec.ts index 2bee827743..08a9db1a5a 100644 --- a/src/app/shared/components/template/template-container.component.spec.ts +++ b/src/app/shared/components/template/template-container.component.spec.ts @@ -2,6 +2,10 @@ import { async, ComponentFixture, TestBed } from "@angular/core/testing"; import { IonicModule } from "@ionic/angular"; import { TemplateContainerComponent } from "./template-container.component"; +import { TemplateService } from "./services/template.service"; +import { ActivatedRoute } from "@angular/router"; +import { of } from "rxjs"; +import { FilterDisplayComponentPipe } from "./pipes/filter-display-component.pipe"; describe("TemplateComponent", () => { let component: TemplateContainerComponent; @@ -9,8 +13,19 @@ describe("TemplateComponent", () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [TemplateContainerComponent], + declarations: [TemplateContainerComponent, FilterDisplayComponentPipe], imports: [IonicModule.forRoot()], + providers: [ + // TODO - replace with mock methods when implementing tests + { + provide: TemplateService, + useValue: {}, + }, + { + provide: ActivatedRoute, + useValue: { queryParams: of({}) }, + }, + ], }).compileComponents(); fixture = TestBed.createComponent(TemplateContainerComponent); diff --git a/src/app/shared/services/app-config/app-config.service.mock.spec.ts b/src/app/shared/services/app-config/app-config.service.mock.spec.ts new file mode 100644 index 0000000000..7da368ba16 --- /dev/null +++ b/src/app/shared/services/app-config/app-config.service.mock.spec.ts @@ -0,0 +1,21 @@ +import { AppConfigService } from "./app-config.service"; +import { IAppConfig } from "../../model"; + +import { MockDeploymentService } from "../deployment/deployment.service.mock.spec"; +import { DeploymentService } from "../deployment/deployment.service"; +import { Router } from "@angular/router"; + +/** Mock calls for field values from the template field service to return test data */ +export class MockAppConfigService extends AppConfigService { + constructor(mockAppConfig?: Partial) { + super( + new MockDeploymentService({ app_config: mockAppConfig }) as any as DeploymentService, + { resetConfig: () => null } as any as Router + ); + if (mockAppConfig) { + // When testing use an empty default config (instead of app defaults) for more + // reliable tests (won't break if defaults break) + this.setAppConfig({}, "default"); + } + } +} diff --git a/src/app/shared/services/app-config/app-config.service.spec.ts b/src/app/shared/services/app-config/app-config.service.spec.ts index 69b7340887..5fd3f7e2d8 100644 --- a/src/app/shared/services/app-config/app-config.service.spec.ts +++ b/src/app/shared/services/app-config/app-config.service.spec.ts @@ -1,36 +1,13 @@ import { TestBed } from "@angular/core/testing"; import { AppConfigService } from "./app-config.service"; -import { BehaviorSubject } from "rxjs/internal/BehaviorSubject"; import { IAppConfig } from "../../model"; -import { signal, WritableSignal } from "@angular/core"; +import { WritableSignal } from "@angular/core"; import { DeploymentService } from "../deployment/deployment.service"; -import { IAppConfigOverride, IDeploymentRuntimeConfig } from "packages/data-models"; -import { deepMergeObjects } from "../../utils"; -import { firstValueFrom } from "rxjs/internal/firstValueFrom"; -import { MockDeploymentService } from "../deployment/deployment.service.spec"; - -/** Mock calls for field values from the template field service to return test data */ -export class MockAppConfigService implements Partial { - appConfig = signal(undefined as any); - appConfig$ = new BehaviorSubject(undefined as any); - - // allow additional specs implementing service to provide their own partial appConfig - constructor(private mockAppConfig: Partial = {}) { - this.setAppConfig(); - } +import { IDeploymentRuntimeConfig } from "packages/data-models"; - public ready(timeoutValue?: number) { - return true; - } - - public setAppConfig(overrides: IAppConfigOverride = {}) { - // merge onto empty object to avoid shared references across tests - const mergedConfig = deepMergeObjects({}, this.mockAppConfig, overrides) as IAppConfig; - this.appConfig$.next(mergedConfig); - this.appConfig.set(mergedConfig); - } -} +import { firstValueFrom } from "rxjs/internal/firstValueFrom"; +import { MockDeploymentService } from "../deployment/deployment.service.mock.spec"; const MOCK_DEPLOYMENT_CONFIG: Partial = { app_config: { APP_FOOTER_DEFAULTS: { templateName: "mock_footer" } }, diff --git a/src/app/shared/services/app-events/app-events.service.mock.spec.ts b/src/app/shared/services/app-events/app-events.service.mock.spec.ts new file mode 100644 index 0000000000..c8520c29f3 --- /dev/null +++ b/src/app/shared/services/app-events/app-events.service.mock.spec.ts @@ -0,0 +1,15 @@ +import { AppConfigService } from "../app-config/app-config.service"; +import { MockAppConfigService } from "../app-config/app-config.service.mock.spec"; +import { MockDbService } from "../db/db.service.mock.spec"; +import { MockLocalStorageService } from "../local-storage/local-storage.service.mock.spec"; +import { AppEventService } from "./app-events.service"; + +export class MockAppEventService extends AppEventService { + constructor() { + super( + new MockDbService(), + new MockLocalStorageService(), + new MockAppConfigService() as any as AppConfigService + ); + } +} diff --git a/src/app/shared/services/app-update/app-update.service.spec.ts b/src/app/shared/services/app-update/app-update.service.spec.ts index 8b7f025d1c..596bcb3b77 100644 --- a/src/app/shared/services/app-update/app-update.service.spec.ts +++ b/src/app/shared/services/app-update/app-update.service.spec.ts @@ -1,12 +1,26 @@ import { TestBed } from "@angular/core/testing"; import { AppUpdateService } from "./app-update.service"; +import { AppConfigService } from "../app-config/app-config.service"; +import { MockAppConfigService } from "../app-config/app-config.service.mock.spec"; +import { TemplateNavService } from "../../components/template/services/template-nav.service"; +import { MockSyncServiceBase } from "../syncService.base.mock.spec"; describe("AppUpdateService", () => { let service: AppUpdateService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + { + provide: AppConfigService, + useValue: new MockAppConfigService({ + APP_UPDATES: { enabled: true, completeUpdateTemplate: "" }, + }), + }, + { provide: TemplateNavService, useValue: new MockSyncServiceBase() }, + ], + }); service = TestBed.inject(AppUpdateService); }); diff --git a/src/app/shared/services/asyncService.base.mock.spec.ts b/src/app/shared/services/asyncService.base.mock.spec.ts new file mode 100644 index 0000000000..2600d31d1a --- /dev/null +++ b/src/app/shared/services/asyncService.base.mock.spec.ts @@ -0,0 +1,10 @@ +import { AsyncServiceBase } from "./asyncService.base"; + +export class MockAsyncServiceBase extends AsyncServiceBase { + constructor(name = "MockAsyncServiceBase") { + super(name); + this.registerInitFunction(this.init); + } + + private async init() {} +} diff --git a/src/app/shared/services/auth/auth.service.spec.ts b/src/app/shared/services/auth/auth.service.spec.ts index 0a6cb89eb6..2f100ccb67 100644 --- a/src/app/shared/services/auth/auth.service.spec.ts +++ b/src/app/shared/services/auth/auth.service.spec.ts @@ -1,12 +1,24 @@ import { TestBed } from "@angular/core/testing"; import { AuthService } from "./auth.service"; +import { DeploymentService } from "../deployment/deployment.service"; +import { MockDeploymentService } from "../deployment/deployment.service.mock.spec"; +import { TemplateService } from "../../components/template/services/template.service"; +import { ServerService } from "../server/server.service"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; describe("AuthService", () => { let service: AuthService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + { provide: DeploymentService, useValue: new MockDeploymentService() }, + { provide: TemplateService, useValue: {} }, + { provide: ServerService, useValue: {} }, + ], + }); service = TestBed.inject(AuthService); }); diff --git a/src/app/shared/services/auth/auth.service.ts b/src/app/shared/services/auth/auth.service.ts index 380582b011..74451914b0 100644 --- a/src/app/shared/services/auth/auth.service.ts +++ b/src/app/shared/services/auth/auth.service.ts @@ -12,6 +12,7 @@ import { toObservable } from "@angular/core/rxjs-interop"; import { ServerService } from "../server/server.service"; import { HttpClient } from "@angular/common/http"; import type { IServerUser } from "../server/server.types"; +import { DynamicDataService } from "../dynamic-data/dynamic-data.service"; @Injectable({ providedIn: "root", @@ -30,7 +31,8 @@ export class AuthService extends AsyncServiceBase { private injector: Injector, private templateService: TemplateService, private serverService: ServerService, - private http: HttpClient + private http: HttpClient, + private dynamicDataService: DynamicDataService ) { super("Auth"); this.provider = getAuthProvider(this.config.provider); @@ -38,19 +40,29 @@ export class AuthService extends AsyncServiceBase { effect( async () => { const authUser = this.provider.authUser(); - this.addStorageEntry(authUser); - if (authUser) { + this.addStorageEntry(authUser); // perform immediate sync if user signed in to ensure data backed up await this.serverService.syncUserData(); await this.checkForUserRestore(authUser); } else { // If signed out or no auth user reset previous data and return this.restoreProfiles.set([]); + this.clearUserData(); } }, { allowSignalWrites: true } ); + // expose restore profile data to authoring via `app_auth_profiles` internal collection + effect(async () => { + const profiles = this.restoreProfiles(); + if (profiles.length > 0) { + const collectionData = profiles.map((p) => ({ ...p, id: p.app_user_id })); + await this.dynamicDataService.ready(); + await this.dynamicDataService.setInternalCollection("auth_profiles", collectionData); + console.log("[Auth] Restore Profiles", profiles); + } + }); } private get config() { @@ -123,11 +135,19 @@ export class AuthService extends AsyncServiceBase { } /** Keep id of auth user info in contact fields for db lookup*/ - private addStorageEntry(auth_user?: IAuthUser) { - if (auth_user) { - this.localStorageService.setProtected("AUTH_USER_ID", auth_user.uid); - } else { - this.localStorageService.removeProtected("AUTH_USER_ID"); - } + private addStorageEntry(auth_user: IAuthUser) { + this.localStorageService.setProtected("AUTH_USER_ID", auth_user.uid); + this.localStorageService.setProtected("AUTH_USER_NAME", auth_user.name || ""); + this.localStorageService.setProtected("AUTH_USER_FAMILY_NAME", auth_user.family_name || ""); + this.localStorageService.setProtected("AUTH_USER_GIVEN_NAME", auth_user.given_name || ""); + this.localStorageService.setProtected("AUTH_USER_PICTURE", auth_user.picture || ""); + } + + private clearUserData() { + this.localStorageService.removeProtected("AUTH_USER_ID"); + this.localStorageService.removeProtected("AUTH_USER_NAME"); + this.localStorageService.removeProtected("AUTH_USER_FAMILY_NAME"); + this.localStorageService.removeProtected("AUTH_USER_GIVEN_NAME"); + this.localStorageService.removeProtected("AUTH_USER_PICTURE"); } } diff --git a/src/app/shared/services/auth/providers/firebase.auth.ts b/src/app/shared/services/auth/providers/firebase.auth.ts index 6001373357..36a4b679b5 100644 --- a/src/app/shared/services/auth/providers/firebase.auth.ts +++ b/src/app/shared/services/auth/providers/firebase.auth.ts @@ -1,8 +1,14 @@ import { Injectable, Injector } from "@angular/core"; -import { FirebaseAuthentication } from "@capacitor-firebase/authentication"; +import { FirebaseAuthentication, User } from "@capacitor-firebase/authentication"; import { getAuth } from "firebase/auth"; import { FirebaseService } from "../../firebase/firebase.service"; import { AuthProviderBase } from "./base.auth"; +import { IAuthUser } from "../types"; + +/** LocalStorage field used to store temporary auth profile data */ +const AUTH_METADATA_FIELD = "firebase_auth_openid_profile"; + +export type FirebaseAuthUser = User; @Injectable({ providedIn: "root", @@ -14,33 +20,54 @@ export class FirebaseAuthProvider extends AuthProviderBase { if (!firebaseService.app) { throw new Error("[Firebase Auth] app not configured"); } - this.addAuthListeners(); - // use firebase authStateReady to ensure any previously logged in user is available - await getAuth().authStateReady(); + await this.handleAutomatedLogin(); } public async signInWithGoogle() { - await FirebaseAuthentication.signInWithGoogle(); + const { user, additionalUserInfo } = await FirebaseAuthentication.signInWithGoogle(); + if (user) { + // NOTE - additionalUserInfo is only returned on first signIn so persist to localStorage + // for access on automated sign-in following restart. Use fallback empty object if null + const { profile = {} } = additionalUserInfo; + localStorage.setItem(AUTH_METADATA_FIELD, JSON.stringify(profile)); + + this.setAuthUser(user, profile); + } return this.authUser(); } public async signOut() { await FirebaseAuthentication.signOut(); + this.authUser.set(undefined); + localStorage.removeItem(AUTH_METADATA_FIELD); return this.authUser(); } - public async getCurrentUser() { - const { user } = await FirebaseAuthentication.getCurrentUser(); - return user; + private setAuthUser(user: User, profile: Partial) { + const authUser: IAuthUser = { + ...profile, + uid: user.uid, + }; + this.authUser.set(authUser); } /** - * Listen to auth state changes and update authUser signal - * This helps to ensure the signal is kept in sync with automated user sign-in/out - * */ - private addAuthListeners() { - FirebaseAuthentication.addListener("authStateChange", ({ user }) => { - this.authUser.set(user); - }); + * When a user signs in for the first time a full profile is retrieved which includes openID profile data. + * However, when automated sign-in happens on app reload, only firebase-specific profile information is available. + * As such use localStorage to persist and retrieve openID profile information + */ + private async handleAutomatedLogin() { + // use firebase authStateReady to ensure any previously logged in user is available + // and update the auth user with loaded profile + await getAuth().authStateReady(); + const { user } = await FirebaseAuthentication.getCurrentUser(); + if (user) { + const storedProfile = localStorage.getItem(AUTH_METADATA_FIELD); + if (storedProfile) { + this.setAuthUser(user, JSON.parse(storedProfile)); + } else { + this.signInWithGoogle(); + } + } } } diff --git a/src/app/shared/services/auth/providers/supabase.auth.ts b/src/app/shared/services/auth/providers/supabase.auth.ts index cfedac6236..387036ff18 100644 --- a/src/app/shared/services/auth/providers/supabase.auth.ts +++ b/src/app/shared/services/auth/providers/supabase.auth.ts @@ -1,3 +1,24 @@ import { AuthProviderBase } from "./base.auth"; -export class SupabaseAuthProvider extends AuthProviderBase {} +export class SupabaseAuthProvider extends AuthProviderBase { + /** + * When signing in to google with supabase additional request may be required + * to get personal profile info (TBC) + */ + private async getGoogleUserInfo(accessToken: string) { + if (!accessToken) return; + try { + const response = await fetch("https://www.googleapis.com/oauth2/v3/userinfo", { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + const userInfo = await response.json(); + const { given_name, family_name } = userInfo; + this.authUser.update((v) => ({ ...v, given_name, family_name })); + return userInfo; + } catch (error) { + console.error("[Auth] Error fetching Google user info:", error); + } + } +} diff --git a/src/app/shared/services/auth/types.ts b/src/app/shared/services/auth/types.ts index e65c35f5da..0bdfd146ba 100644 --- a/src/app/shared/services/auth/types.ts +++ b/src/app/shared/services/auth/types.ts @@ -2,6 +2,14 @@ import type { IDeploymentConfig } from "packages/data-models"; export type IAuthProvider = IDeploymentConfig["auth"]["provider"]; -export interface IAuthUser { +/** + * Auth user profile. Should adhere to properties within openid spec + * https://openid.net/specs/openid-connect-basic-1_0.html#StandardClaims + */ +export type IAuthUser = { uid: string; -} + name?: string; + given_name?: string; + family_name?: string; + picture?: string; +}; diff --git a/src/app/shared/services/crashlytics/crashlytics.service.spec.ts b/src/app/shared/services/crashlytics/crashlytics.service.spec.ts index 162751143b..62151ca506 100644 --- a/src/app/shared/services/crashlytics/crashlytics.service.spec.ts +++ b/src/app/shared/services/crashlytics/crashlytics.service.spec.ts @@ -1,12 +1,16 @@ import { TestBed } from "@angular/core/testing"; import { CrashlyticsService } from "./crashlytics.service"; +import { DeploymentService } from "../deployment/deployment.service"; +import { MockDeploymentService } from "../deployment/deployment.service.mock.spec"; describe("CrashlyticsService", () => { let service: CrashlyticsService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [{ provide: DeploymentService, useValue: new MockDeploymentService() }], + }); service = TestBed.inject(CrashlyticsService); }); diff --git a/src/app/shared/services/data/app-data-variable.service.spec.ts b/src/app/shared/services/data/app-data-variable.service.spec.ts index b001f47644..02028a0ff1 100644 --- a/src/app/shared/services/data/app-data-variable.service.spec.ts +++ b/src/app/shared/services/data/app-data-variable.service.spec.ts @@ -4,8 +4,8 @@ import { AppDataVariableService, IVariableContext } from "./app-data-variable.se import { DbService } from "../db/db.service"; import { LocalStorageService } from "../local-storage/local-storage.service"; import { AppDataHandlerBase } from "./variable-handlers"; -import { MockDbService } from "../db/db.service.spec"; -import { MockLocalStorageService } from "../local-storage/local-storage.service.spec"; +import { MockDbService } from "../db/db.service.mock.spec"; +import { MockLocalStorageService } from "../local-storage/local-storage.service.mock.spec"; function getMockHandlers() { /** Mock handler inherits base handler which stores all get/set in-memory */ diff --git a/src/app/shared/services/data/app-data.service.mock.spec.ts b/src/app/shared/services/data/app-data.service.mock.spec.ts index 099fd07b33..1fa694d9cf 100644 --- a/src/app/shared/services/data/app-data.service.mock.spec.ts +++ b/src/app/shared/services/data/app-data.service.mock.spec.ts @@ -1,6 +1,12 @@ -import { FlowTypes } from "packages/data-models"; -import type { AppDataService, IAppDataCache } from "./app-data.service"; +import { AppDataService, IAppDataCache } from "./app-data.service"; import { _wait } from "packages/shared/src/utils/async-utils"; +import { MockErrorHandlerService } from "../error-handler/error-handler.service.mock.spec"; +import { MockAppDataVariableService } from "./app-data-variable.service.spec"; +import { ErrorHandlerService } from "../error-handler/error-handler.service"; +import { AppDataVariableService } from "./app-data-variable.service"; +import { HttpClient } from "@angular/common/http"; +import { delay, of } from "rxjs"; +import { FlowTypes } from "packages/data-models"; /** Base mock data for use with any services calling mock app-data handlers */ const DATA_CACHE_CLEAN: IAppDataCache = { @@ -13,17 +19,29 @@ const DATA_CACHE_CLEAN: IAppDataCache = { tour: {}, }; +const mockHttpClient = (responses: { [url: string]: any }): Partial => ({ + get: (url: string, options) => of(responses[url]).pipe(delay(50)), +}); + /** Mock calls for sheets from the appData service to return test data */ -export class MockAppDataService implements Partial { +export class MockAppDataService extends AppDataService { public appDataCache: IAppDataCache; // allow additional specs implementing service to provide their own data if required constructor( mockData: Partial = {}, /** Bypass methods to load translations from http by providing combined language_codes and strings */ - private translationStrings: { [language_code: string]: { [source_text: string]: string } } = {} + private translationStrings: { [language_code: string]: { [source_text: string]: string } } = {}, + /** List of url-data pairs for http client to return */ + mockHttpResponses: { [url: string]: any } = {} ) { + super( + mockHttpClient(mockHttpResponses) as HttpClient, + new MockErrorHandlerService() as ErrorHandlerService, + new MockAppDataVariableService() as AppDataVariableService + ); this.appDataCache = { ...DATA_CACHE_CLEAN, ...mockData }; + this.sheetContents = { ...DATA_CACHE_CLEAN, ...mockData }; } public ready() { diff --git a/src/app/shared/services/data/app-data.service.spec.ts b/src/app/shared/services/data/app-data.service.spec.ts index f650710079..15aa4f5a5f 100644 --- a/src/app/shared/services/data/app-data.service.spec.ts +++ b/src/app/shared/services/data/app-data.service.spec.ts @@ -8,7 +8,7 @@ import { MockAppDataVariableService } from "./app-data-variable.service.spec"; import { ErrorHandlerService } from "../error-handler/error-handler.service"; import { MockErrorHandlerService } from "../error-handler/error-handler.service.mock.spec"; import { DbService } from "../db/db.service"; -import { MockDbService } from "../db/db.service.spec"; +import { MockDbService } from "../db/db.service.mock.spec"; import { Injectable } from "@angular/core"; import { ISheetContents } from "src/app/data"; import { _wait } from "packages/shared/src/utils/async-utils"; diff --git a/src/app/shared/services/data/data-evaluation.service.mock.spec.ts b/src/app/shared/services/data/data-evaluation.service.mock.spec.ts new file mode 100644 index 0000000000..5f1cda6edf --- /dev/null +++ b/src/app/shared/services/data/data-evaluation.service.mock.spec.ts @@ -0,0 +1,18 @@ +import { MockAppEventService } from "../app-events/app-events.service.mock.spec"; +import { MockDbService } from "../db/db.service.mock.spec"; +import { DataEvaluationService, IDataEvaluationCache } from "./data-evaluation.service"; + +export class MockDataEvaluationService extends DataEvaluationService { + constructor(private mockData: Partial = {}) { + super(new MockDbService(), new MockAppEventService(), null as any); + } + public override async refreshDBCache(): Promise { + this.data = { + app_day: 5, + dbCache: {}, + first_app_launch: "2024-12-22T18:15:20", + ...this.mockData, + }; + return this.data; + } +} diff --git a/src/app/shared/services/db/db.service.mock.spec.ts b/src/app/shared/services/db/db.service.mock.spec.ts new file mode 100644 index 0000000000..b8d6eb165a --- /dev/null +++ b/src/app/shared/services/db/db.service.mock.spec.ts @@ -0,0 +1,13 @@ +import Dexie from "dexie"; +import { indexedDB, IDBKeyRange } from "fake-indexeddb"; +import { DbService } from "./db.service"; +import { EventService } from "../event/event.service"; + +export class MockDbService extends DbService { + constructor() { + super(new EventService()); + // Use a mock indexeddb with Dexie bindings + // https://github.com/dumbmatter/fakeIndexedDB?tab=readme-ov-file#dexie-and-other-indexeddb-api-wrappers + super["db"] = new Dexie("MockDB", { indexedDB, IDBKeyRange }); + } +} diff --git a/src/app/shared/services/db/db.service.spec.ts b/src/app/shared/services/db/db.service.spec.ts index 514e7e04ec..44fa535c0c 100644 --- a/src/app/shared/services/db/db.service.spec.ts +++ b/src/app/shared/services/db/db.service.spec.ts @@ -1,23 +1,7 @@ import { TestBed } from "@angular/core/testing"; -import { IDBMeta } from "packages/data-models/db.model"; -import { generateTimestamp } from "../../utils"; import { DbService } from "./db.service"; -/** - * Minimal set of methods for use in mocking other tests - * TODO - add own test suite - */ -export class MockDbService implements Partial { - async ready() { - await new Promise((resolve) => setTimeout(() => resolve(true), 200)); - return true; - } - public generateDBMeta(syncable?: boolean): IDBMeta { - return { _created: generateTimestamp(), _sync_status: syncable ? "ignored" : "pending" }; - } -} - describe("DbService", () => { beforeEach(() => TestBed.configureTestingModule({})); diff --git a/src/app/shared/services/deployment/deployment.service.mock.spec.ts b/src/app/shared/services/deployment/deployment.service.mock.spec.ts new file mode 100644 index 0000000000..8c5842660f --- /dev/null +++ b/src/app/shared/services/deployment/deployment.service.mock.spec.ts @@ -0,0 +1,11 @@ +import { DEPLOYMENT_RUNTIME_CONFIG_DEFAULTS, IDeploymentRuntimeConfig } from "packages/data-models"; +import { DeploymentService } from "./deployment.service"; + +/** + * Pass `config` constructor arg to provide a custom configuration, or leave empty for defaults + */ +export class MockDeploymentService extends DeploymentService { + constructor(config?: Partial) { + super((config as IDeploymentRuntimeConfig) || DEPLOYMENT_RUNTIME_CONFIG_DEFAULTS); + } +} diff --git a/src/app/shared/services/deployment/deployment.service.spec.ts b/src/app/shared/services/deployment/deployment.service.spec.ts index d337640daa..956a9a4822 100644 --- a/src/app/shared/services/deployment/deployment.service.spec.ts +++ b/src/app/shared/services/deployment/deployment.service.spec.ts @@ -7,17 +7,6 @@ const mockConfig: IDeploymentRuntimeConfig = { name: "test", }; -export class MockDeploymentService implements Partial { - public readonly config: IDeploymentRuntimeConfig; - - constructor(config: Partial) { - this.config = { ...DEPLOYMENT_RUNTIME_CONFIG_DEFAULTS, ...config }; - } - public ready(): boolean { - return true; - } -} - /** * Call standalone tests via: * yarn ng test --include src/app/shared/services/deployment/deployment.service.spec.ts diff --git a/src/app/shared/services/dynamic-data/actions/add_data.action.spec.ts b/src/app/shared/services/dynamic-data/actions/add_data.action.spec.ts index f0498b191b..fbd198dfeb 100644 --- a/src/app/shared/services/dynamic-data/actions/add_data.action.spec.ts +++ b/src/app/shared/services/dynamic-data/actions/add_data.action.spec.ts @@ -9,7 +9,7 @@ import { DynamicDataService } from "../dynamic-data.service"; import { firstValueFrom } from "rxjs"; import { FlowTypes } from "packages/data-models"; import { DeploymentService } from "../../deployment/deployment.service"; -import { MockDeploymentService } from "../../deployment/deployment.service.spec"; +import { MockDeploymentService } from "../../deployment/deployment.service.mock.spec"; import { TemplateActionRegistry } from "../../../components/template/services/instance/template-action.registry"; import { DynamicDataActionFactory, IActionRemoveDataParams } from "./index"; diff --git a/src/app/shared/services/dynamic-data/actions/set_data.action.spec.ts b/src/app/shared/services/dynamic-data/actions/set_data.action.spec.ts index a314362086..354eaca3b6 100644 --- a/src/app/shared/services/dynamic-data/actions/set_data.action.spec.ts +++ b/src/app/shared/services/dynamic-data/actions/set_data.action.spec.ts @@ -9,7 +9,7 @@ import { DynamicDataService } from "../dynamic-data.service"; import { firstValueFrom } from "rxjs"; import { FlowTypes } from "packages/data-models"; import { DeploymentService } from "../../deployment/deployment.service"; -import { MockDeploymentService } from "../../deployment/deployment.service.spec"; +import { MockDeploymentService } from "../../deployment/deployment.service.mock.spec"; import { TemplateActionRegistry } from "../../../components/template/services/instance/template-action.registry"; import { DynamicDataActionFactory } from "./index"; diff --git a/src/app/shared/services/dynamic-data/dynamic-data.service.spec.ts b/src/app/shared/services/dynamic-data/dynamic-data.service.spec.ts index 85090f72b0..07fe66ed1e 100644 --- a/src/app/shared/services/dynamic-data/dynamic-data.service.spec.ts +++ b/src/app/shared/services/dynamic-data/dynamic-data.service.spec.ts @@ -6,7 +6,7 @@ import { DynamicDataService } from "./dynamic-data.service"; import { AppDataService } from "../data/app-data.service"; import { MockAppDataService } from "../data/app-data.service.mock.spec"; import { DeploymentService } from "../deployment/deployment.service"; -import { MockDeploymentService } from "../deployment/deployment.service.spec"; +import { MockDeploymentService } from "../deployment/deployment.service.mock.spec"; import { FlowTypes } from "packages/data-models"; type ITestRow = { id: string; number: number; string: string; boolean: boolean; _meta_field?: any }; @@ -143,13 +143,20 @@ describe("DynamicDataService", () => { ).toBeRejectedWithError(); }); + it("supports internal collections", async () => { + await service.setInternalCollection("mock", [{ id: "1", string: "hello" }]); + const obs = await service.query$("data_list", "_mock"); + const data = await firstValueFrom(obs); + expect(data).toEqual([{ id: "1", string: "hello" }]); + }); + // QA it("prevents query of non-existent data lists", async () => { let errMsg: string; await service.query$("data_list", "fakeData").catch((err) => { errMsg = err.message; }); - expect(errMsg).toEqual("No data exists for collection [fakeData], cannot initialise"); + expect(errMsg).toEqual(`No data exists for collection [fakeData], cannot initialise`); }); it("ignores cached data where initial data no longer exists", async () => { diff --git a/src/app/shared/services/dynamic-data/dynamic-data.service.ts b/src/app/shared/services/dynamic-data/dynamic-data.service.ts index feeef6370a..d80035057c 100644 --- a/src/app/shared/services/dynamic-data/dynamic-data.service.ts +++ b/src/app/shared/services/dynamic-data/dynamic-data.service.ts @@ -210,6 +210,19 @@ export class DynamicDataService extends AsyncServiceBase { return this.db.getCollection(collectionName)?.count().exec(); } + /** + * Set the data for an internal data collection + * All internal collections are prefixed by `_app_` and are only stored ephemerally (not persisted) + * Data that is set will override any pre-existing data + **/ + public async setInternalCollection(name: string, data: any[]) { + const { collectionName } = await this.ensureCollection("data_list", `_${name}`); + const collection = this.db.getCollection(collectionName); + const docs = await collection.find().exec(); + await collection.bulkRemove(docs.map((d) => d.id)); + await this.db.bulkInsert(collectionName, data); + } + /** Ensure a collection exists, creating if not and populating with corresponding list data */ private async ensureCollection(flow_type: FlowTypes.FlowType, flow_name: string) { const collectionName = this.normaliseCollectionName(flow_type, flow_name); @@ -244,6 +257,11 @@ export class DynamicDataService extends AsyncServiceBase { * compatible in case of schema changes * */ private async prepareInitialData(flow_type: FlowTypes.FlowType, flow_name: string) { + // Internal tables, prefixed by `_` are in-memory read-only and do not have preloaded data or schema + if (flow_name.startsWith("_")) { + return { data: [], schema: { ...REACTIVE_SCHEMA_BASE } }; + } + const flowData = await this.appDataService.getSheet(flow_type, flow_name); if (!flowData || flowData.rows.length === 0) { throw new Error(`No data exists for collection [${flow_name}], cannot initialise`); diff --git a/src/app/shared/services/error-handler/error-handler.service.spec.ts b/src/app/shared/services/error-handler/error-handler.service.spec.ts index f377e0c6b3..0c1317178e 100644 --- a/src/app/shared/services/error-handler/error-handler.service.spec.ts +++ b/src/app/shared/services/error-handler/error-handler.service.spec.ts @@ -2,6 +2,8 @@ import { TestBed } from "@angular/core/testing"; import { ErrorHandlerService } from "./error-handler.service"; import { FirebaseService } from "../firebase/firebase.service"; +import { DeploymentService } from "../deployment/deployment.service"; +import { MockDeploymentService } from "../deployment/deployment.service.mock.spec"; describe("ErrorHandlerService", () => { let service: ErrorHandlerService; @@ -13,6 +15,7 @@ describe("ErrorHandlerService", () => { provide: FirebaseService, useValue: {}, }, + { provide: DeploymentService, useValue: new MockDeploymentService() }, ], }); service = TestBed.inject(ErrorHandlerService); diff --git a/src/app/shared/services/file-manager/file-manager.service.spec.ts b/src/app/shared/services/file-manager/file-manager.service.spec.ts index ac122cb658..a1d070334f 100644 --- a/src/app/shared/services/file-manager/file-manager.service.spec.ts +++ b/src/app/shared/services/file-manager/file-manager.service.spec.ts @@ -1,6 +1,11 @@ import { TestBed } from "@angular/core/testing"; import { FileManagerService } from "./file-manager.service"; import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { ErrorHandlerService } from "../error-handler/error-handler.service"; +import { MockErrorHandlerService } from "../error-handler/error-handler.service.mock.spec"; +import { TemplateAssetService } from "../../components/template/services/template-asset.service"; +import { DeploymentService } from "../deployment/deployment.service"; +import { MockDeploymentService } from "../deployment/deployment.service.mock.spec"; /** * Call standalone tests via: @@ -12,6 +17,11 @@ describe("FileManagerService", () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], + providers: [ + { provide: ErrorHandlerService, useValue: new MockErrorHandlerService() }, + { provide: TemplateAssetService, useValue: {} }, + { provide: DeploymentService, useValue: new MockDeploymentService() }, + ], }); service = TestBed.inject(FileManagerService); }); diff --git a/src/app/shared/services/firebase/firebase.service.ts b/src/app/shared/services/firebase/firebase.service.ts index e8ca57e734..8832669ba7 100644 --- a/src/app/shared/services/firebase/firebase.service.ts +++ b/src/app/shared/services/firebase/firebase.service.ts @@ -20,15 +20,14 @@ export class FirebaseService extends SyncServiceBase { private initialise() { const { firebase } = this.deploymentService.config; - // Check if any services are enabled, simply return if not - const enabledServices = Object.entries(firebase) - .filter(([key, v]) => v && v.constructor === {}.constructor && v["enabled"]) - .map(([key]) => key); - if (enabledServices.length === 0) return; + // Skip init if top-level firebase config not provided + if (!firebase) return; - // Check config exists if services are enabled - if (!firebase.config) { - console.warn(`[Firebase] config missing, services disabled:\n`, enabledServices.join(", ")); + // Provide warning if firebase app config not available (e.g. encrypted import failed) + const { config, ...services } = firebase; + if (!config) { + const configuredServices = Object.keys(services).join(", "); + console.warn(`[Firebase] config missing, services disabled:\n`, configuredServices); return; } diff --git a/src/app/shared/services/lifecycle-actions/lifecycle-actions.service.spec.ts b/src/app/shared/services/lifecycle-actions/lifecycle-actions.service.spec.ts index 58c42d4420..dd2dc50383 100644 --- a/src/app/shared/services/lifecycle-actions/lifecycle-actions.service.spec.ts +++ b/src/app/shared/services/lifecycle-actions/lifecycle-actions.service.spec.ts @@ -1,12 +1,20 @@ import { TestBed } from "@angular/core/testing"; import { LifecycleActionsService } from "./lifecycle-actions.service"; +import { AppDataService } from "../data/app-data.service"; +import { MockAppDataService } from "../data/app-data.service.mock.spec"; +import { TemplateVariablesService } from "../../components/template/services/template-variables.service"; describe("LifecycleActionsService", () => { let service: LifecycleActionsService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + { provide: AppDataService, useValue: new MockAppDataService() }, + { provide: TemplateVariablesService, useValue: {} }, + ], + }); service = TestBed.inject(LifecycleActionsService); }); diff --git a/src/app/shared/services/local-storage/local-storage.service.mock.spec.ts b/src/app/shared/services/local-storage/local-storage.service.mock.spec.ts new file mode 100644 index 0000000000..f92bd08a8d --- /dev/null +++ b/src/app/shared/services/local-storage/local-storage.service.mock.spec.ts @@ -0,0 +1,16 @@ +import { LocalStorageService } from "./local-storage.service"; + +/** + * Mock calls to localStorage service + * The mock still requires window localstorage, but passes custom prefix to store mock values + * + * TODO - would be better to override window.localstorage methods however difficult to restore + * after use (needs to be implemented in individual specs and not mock service) + * */ +export class MockLocalStorageService extends LocalStorageService { + constructor(prefix = "mock") { + super(); + this.prefix = prefix; + this.clear(); + } +} diff --git a/src/app/shared/services/local-storage/local-storage.service.spec.ts b/src/app/shared/services/local-storage/local-storage.service.spec.ts index f1a80fb262..0b77c992a4 100644 --- a/src/app/shared/services/local-storage/local-storage.service.spec.ts +++ b/src/app/shared/services/local-storage/local-storage.service.spec.ts @@ -1,27 +1,6 @@ import { TestBed } from "@angular/core/testing"; import { LocalStorageService } from "./local-storage.service"; -import { IProtectedFieldName } from "packages/data-models"; - -/** Mock calls to localstorage to store values in-memory */ -export class MockLocalStorageService implements Partial { - private values: Record = {}; - public getString(key: string): string { - return this.values[key]; - } - public setString(key: string, value: string): void { - this.values[key] = value; - } - public ready(): boolean { - return true; - } - public getProtected(field: IProtectedFieldName): string { - return this.getString(`_${field}`); - } - public setProtected(field: IProtectedFieldName, value: string) { - return this.setString(`_${field}`, value); - } -} /** * Call standalone tests via: @@ -72,6 +51,18 @@ describe("LocalStorageService", () => { expect(service.isProtected("rp-contact-field._app_user_id")).toEqual(true); }); + it("sets and gets private entries", () => { + service.setProtected("AUTH_USER_NAME", "private_user_name"); + expect(service.getProtected("AUTH_USER_NAME")).toEqual("private_user_name"); + expect(localStorage.getItem("rp-contact-field._auth_user_name")).toEqual("private_user_name"); + }); + + it("omits private entries from getAll method", () => { + service.setProtected("AUTH_USER_NAME", "private_user_name"); + const res = service.getAll(); + expect(res).toEqual({}); + }); + // TODO - currently deprecated but not throwing error // it("prevents setting protected fields", () => {}); }); diff --git a/src/app/shared/services/local-storage/local-storage.service.ts b/src/app/shared/services/local-storage/local-storage.service.ts index 6f9f606edb..3d857dcbf0 100644 --- a/src/app/shared/services/local-storage/local-storage.service.ts +++ b/src/app/shared/services/local-storage/local-storage.service.ts @@ -1,21 +1,22 @@ import { Injectable } from "@angular/core"; -import { IProtectedFieldName, getProtectedFieldName } from "data-models"; +import { IProtectedFieldName, getProtectedFieldName, isPrivateFieldName } from "data-models"; import { SyncServiceBase } from "../syncService.base"; -export const LOCAL_STORAGE_PREFIX = "rp-contact-field"; - @Injectable({ providedIn: "root", }) export class LocalStorageService extends SyncServiceBase { + /** Prefix applied to all localStorage entries */ + public prefix = "rp-contact-field"; + constructor() { super("LocalStorage"); } private get(key: string): string | null { if (!key) return null; - if (!key.startsWith(LOCAL_STORAGE_PREFIX)) { - key = `${LOCAL_STORAGE_PREFIX}.${key}`; + if (!key.startsWith(this.prefix)) { + key = `${this.prefix}.${key}`; } return localStorage.getItem(key); } @@ -25,16 +26,16 @@ export class LocalStorageService extends SyncServiceBase { if (!allowProtected && this.isProtected(key)) { console.warn(`[DEPRECATED] - set local-storage with protected name: ${key}`); } - if (!key.startsWith(LOCAL_STORAGE_PREFIX)) { - key = `${LOCAL_STORAGE_PREFIX}.${key}`; + if (!key.startsWith(this.prefix)) { + key = `${this.prefix}.${key}`; } return localStorage.setItem(key, value); } private remove(key: string) { if (!key) return; - if (!key.startsWith(LOCAL_STORAGE_PREFIX)) { - key = `${LOCAL_STORAGE_PREFIX}.${key}`; + if (!key.startsWith(this.prefix)) { + key = `${this.prefix}.${key}`; } return localStorage.removeItem(key); } @@ -66,10 +67,12 @@ export class LocalStorageService extends SyncServiceBase { } } + /** Retrieve all key-values pairs that have been stored by app to localStorage, excluding private */ getAll() { const values = {}; Object.keys(localStorage) - .filter((k) => k.startsWith(LOCAL_STORAGE_PREFIX)) + .filter((k) => k.startsWith(this.prefix)) + .filter((k) => !this.isPrivate(k)) .forEach((k) => (values[k] = localStorage.getItem(k))); return values; } @@ -92,9 +95,17 @@ export class LocalStorageService extends SyncServiceBase { } /** Check if a field name is protected (starts with underscore prefixed or non-prefixed) */ isProtected(key: string) { - if (key.startsWith(LOCAL_STORAGE_PREFIX)) { - key = key.replace(`${LOCAL_STORAGE_PREFIX}.`, ""); + if (key.startsWith(this.prefix)) { + key = key.replace(`${this.prefix}.`, ""); } return key.startsWith("_"); } + + /** Check if a field name has been marked as private */ + isPrivate(key: string) { + if (key.startsWith(this.prefix)) { + key = key.replace(`${this.prefix}._`, ""); + } + return isPrivateFieldName(key); + } } diff --git a/src/app/shared/services/notification/local-notification.service.spec.ts b/src/app/shared/services/notification/local-notification.service.spec.ts index f7b6d6566b..346b02aa82 100644 --- a/src/app/shared/services/notification/local-notification.service.spec.ts +++ b/src/app/shared/services/notification/local-notification.service.spec.ts @@ -1,9 +1,15 @@ import { TestBed } from "@angular/core/testing"; import { LocalNotificationService } from "./local-notification.service"; +import { AppConfigService } from "../app-config/app-config.service"; +import { MockAppConfigService } from "../app-config/app-config.service.mock.spec"; describe("LocalNotificationService", () => { - beforeEach(() => TestBed.configureTestingModule({})); + beforeEach(() => + TestBed.configureTestingModule({ + providers: [{ provide: AppConfigService, useValue: new MockAppConfigService() }], + }) + ); it("should be created", () => { const service: LocalNotificationService = TestBed.get(LocalNotificationService); diff --git a/src/app/shared/services/remote-asset/remote-asset.service.spec.ts b/src/app/shared/services/remote-asset/remote-asset.service.spec.ts index 1bdd5bc618..b214a90954 100644 --- a/src/app/shared/services/remote-asset/remote-asset.service.spec.ts +++ b/src/app/shared/services/remote-asset/remote-asset.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from "@angular/core/testing"; import { RemoteAssetService } from "./remote-asset.service"; -import { MockDeploymentService } from "../deployment/deployment.service.spec"; +import { MockDeploymentService } from "../deployment/deployment.service.mock.spec"; import { HttpClientTestingModule } from "@angular/common/http/testing"; import { IAssetContents } from "src/app/data"; import { FlowTypes } from "../../model"; diff --git a/src/app/shared/services/screen-orientation/screen-orientation.service.spec.ts b/src/app/shared/services/screen-orientation/screen-orientation.service.spec.ts index 6d13da74de..107f3e3f74 100644 --- a/src/app/shared/services/screen-orientation/screen-orientation.service.spec.ts +++ b/src/app/shared/services/screen-orientation/screen-orientation.service.spec.ts @@ -1,12 +1,15 @@ import { TestBed } from "@angular/core/testing"; import { ScreenOrientationService } from "./screen-orientation.service"; +import { TemplateMetadataService } from "../../components/template/services/template-metadata.service"; describe("ScreenOrientationService", () => { let service: ScreenOrientationService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [{ provide: TemplateMetadataService, useValue: {} }], + }); service = TestBed.inject(ScreenOrientationService); }); diff --git a/src/app/shared/services/seo/seo.service.spec.ts b/src/app/shared/services/seo/seo.service.spec.ts index e671e8c3eb..dc62b33a14 100644 --- a/src/app/shared/services/seo/seo.service.spec.ts +++ b/src/app/shared/services/seo/seo.service.spec.ts @@ -1,12 +1,16 @@ import { TestBed } from "@angular/core/testing"; import { SeoService } from "./seo.service"; +import { DeploymentService } from "../deployment/deployment.service"; +import { MockDeploymentService } from "../deployment/deployment.service.mock.spec"; describe("SeoService", () => { let service: SeoService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [{ provide: DeploymentService, useValue: new MockDeploymentService() }], + }); service = TestBed.inject(SeoService); }); diff --git a/src/app/shared/services/server/server.service.ts b/src/app/shared/services/server/server.service.ts index 25140634cd..72a8d27330 100644 --- a/src/app/shared/services/server/server.service.ts +++ b/src/app/shared/services/server/server.service.ts @@ -71,6 +71,7 @@ export class ServerService extends SyncServiceBase { } console.log("[SERVER] sync data"); const contact_fields = this.localStorageService.getAll(); + const dynamic_data = await this.dynamicDataService.getState(); // apply temp timestamp to contact fields to sync as latest diff --git a/src/app/shared/services/share/share.service.spec.ts b/src/app/shared/services/share/share.service.spec.ts index de4f548d35..0c1ccfa070 100644 --- a/src/app/shared/services/share/share.service.spec.ts +++ b/src/app/shared/services/share/share.service.spec.ts @@ -1,12 +1,22 @@ import { TestBed } from "@angular/core/testing"; import { ShareService } from "./share.service"; +import { ErrorHandlerService } from "../error-handler/error-handler.service"; +import { MockErrorHandlerService } from "../error-handler/error-handler.service.mock.spec"; +import { FileManagerService } from "../file-manager/file-manager.service"; +import { TemplateAssetService } from "../../components/template/services/template-asset.service"; describe("ShareService", () => { let service: ShareService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + { provide: ErrorHandlerService, useValue: new MockErrorHandlerService() }, + { provide: FileManagerService, useValue: {} }, + { provide: TemplateAssetService, useValue: {} }, + ], + }); service = TestBed.inject(ShareService); }); diff --git a/src/app/shared/services/share/share.service.ts b/src/app/shared/services/share/share.service.ts index 2dca5edd8f..cd8e785277 100644 --- a/src/app/shared/services/share/share.service.ts +++ b/src/app/shared/services/share/share.service.ts @@ -7,13 +7,22 @@ import { FileManagerService } from "../file-manager/file-manager.service"; import { TemplateAssetService } from "../../components/template/services/template-asset.service"; import { Capacitor } from "@capacitor/core"; -const SHARE_NOT_SUPPORTED_ON_PLATFORM_ERROR_MESSAGE = - "[SHARE] Sharing is not supported on this platform"; +interface IShareActionParams { + file?: string; + text?: string; + url?: string; + title?: string; + dialog_title?: string; +} + +type IShareParams = Omit & { dialogTitle?: string }; @Injectable({ providedIn: "root", }) export class ShareService extends SyncServiceBase { + /** Temporary local storage path on native devices for file being shared */ + private localFilepath: string; constructor( private errorHandler: ErrorHandlerService, private fileManagerService: FileManagerService, @@ -30,87 +39,127 @@ export class ShareService extends SyncServiceBase { private registerTemplateActionHandlers() { this.templateActionRegistry.register({ - share: async ({ args }) => { - const [actionId, ...shareArgs] = args; - const childActions = { - file: async () => await this.shareFile(shareArgs[0]), - text: () => this.share({ text: shareArgs[0] }), - url: () => this.share({ url: shareArgs[0] }), - }; - // To support deprecated "share" action (previously used to share text only), - // assume text is being shared if first arg is not an actionId - if (!(actionId in childActions)) { - return await this.share({ text: args[0] }); + share: async (action) => { + let { args, params } = action as { args: string[]; params: IShareActionParams }; + + // Handle legacy arg-based syntax, where action is called as `share: data_type: data` + if (args) { + console.warn("[SHARE] Deprecated action syntax. Use `share | data_type: data` instead."); + const [dataType, ...shareArgs] = args; + if (dataType && shareArgs?.[0]) { + params = { + [dataType]: shareArgs[0], + }; + } + } + + if (params) { + await this.handleShare(params); + } else { + return console.error("[SHARE] No params provided to `share` action"); } - return childActions[actionId](); }, }); } - async share(options: ShareOptions) { - const { value: canShare } = await Share.canShare(); - if (canShare) { - try { - const { activityType } = await Share.share(options); - console.log("[SHARE] Content shared to", activityType); - } catch (error) { - this.handleShareError(error); - } - } else console.error(SHARE_NOT_SUPPORTED_ON_PLATFORM_ERROR_MESSAGE); + private async handleShare(options: IShareActionParams) { + // Rename `dialog_title` to `dialogTitle` + const { dialog_title, ...shareOptions } = options; + let parsedOptions = { dialogTitle: dialog_title, ...shareOptions }; + + // Convert file reference to platform-relative shareable file data + if (parsedOptions?.file) { + const fileData = await this.getFileData(parsedOptions.file); + delete parsedOptions.file; + parsedOptions = { ...parsedOptions, ...fileData }; + } + await this.share(parsedOptions); } - async shareFile(relativePath: string) { - let localFilepath: string; + private async share(options: IShareParams) { try { - if (relativePath) { - await this.templateAssetService.ready(); - // On native platforms, try to share file using @capacitor/share - if (Capacitor.isNativePlatform()) { - const { value: canShare } = await Share.canShare(); - if (canShare) { - this.fileManagerService.ready(); - const blob = (await this.templateAssetService.fetchAsset(relativePath, "blob")) as Blob; - // @capacitor/share can only share files saved to "Cache" directory - ({ localFilepath } = await this.fileManagerService.saveFile({ - data: blob, - targetPath: relativePath, - directory: "Cache", - })); - if (localFilepath) { - const { activityType } = await Share.share({ url: localFilepath }); - console.log("[SHARE] Content shared to", activityType); - } - } else console.error(SHARE_NOT_SUPPORTED_ON_PLATFORM_ERROR_MESSAGE); - } - // On web platforms, try to share file using Web Share API - else { - if (navigator.canShare) { - const blob = (await this.templateAssetService.fetchAsset(relativePath, "blob")) as Blob; - const filename = relativePath.split("/").pop(); - const data = { files: [new File([blob], filename, { type: blob.type })] }; - if (navigator.canShare(data)) { - await navigator.share(data); - } else { - console.error("[SHARE] Unable to share file:", data); - } - } else { - console.error(SHARE_NOT_SUPPORTED_ON_PLATFORM_ERROR_MESSAGE); - } - } + if (Capacitor.isNativePlatform()) { + await this.shareNative(options); + } + // Capacitor's Share API does not support sharing files on web, so use Web Share API directly + else { + await this.shareWeb(options); } } catch (error) { this.handleShareError(error); } finally { - // If a temporary file was saved for sharing, delete it - if (localFilepath) { - this.fileManagerService.deleteFile(localFilepath); + this.cleanupLocalFile(); + } + } + + private async shareNative(options: ShareOptions) { + const { value: canShare } = await Share.canShare(); + if (canShare) { + const { activityType } = await Share.share(options); + console.log("[SHARE] Content shared to", activityType); + } else { + console.error("[SHARE] Sharing is not supported on this platform"); + } + } + + private async shareWeb(options: ShareData) { + if (navigator.canShare(options)) { + await navigator.share(options); + } else { + console.error("[SHARE] Unable to share this data on this platform,", options); + } + } + + /** + * Fetch the requested file and format file data for sharing, appropriate to platform + * - On Web platforms, the file is shared directly as a blob + * - On Native platforms, file is temporarily saved to app's internal cache, and a local URL is shared + */ + private async getFileData(relativePath: string) { + if (!relativePath) return {}; + + let shareAbleFileData: { url?: string; files?: File[] } = {}; + + await this.templateAssetService.ready(); + + // On native platforms, temporarily save file locally in order to share URL + if (Capacitor.isNativePlatform()) { + this.fileManagerService.ready(); + const blob = (await this.templateAssetService.fetchAsset(relativePath, "blob")) as Blob; + const saveFileResponse = await this.fileManagerService.saveFile({ + data: blob, + targetPath: relativePath, + // @capacitor/share can only share files saved to "Cache" directory + directory: "Cache", + }); + this.localFilepath = saveFileResponse.localFilepath; + if (this.localFilepath) { + shareAbleFileData = { url: this.localFilepath }; } } + // On web platforms, format the data for the Web Share API + else { + const blob = (await this.templateAssetService.fetchAsset(relativePath, "blob")) as Blob; + const filename = relativePath.split("/").pop(); + shareAbleFileData = { files: [new File([blob], filename, { type: blob.type })] }; + } + return shareAbleFileData; + } + + /** + * If a local file was saved temporarily for sharing, delete it + * */ + private async cleanupLocalFile() { + if (this.localFilepath) { + await this.fileManagerService.deleteFile(this.localFilepath); + this.localFilepath = null; + } } private handleShareError(error: Error) { - const cancellationMessages = ["Abort due to cancellation of share.", "Share canceled"]; - if (cancellationMessages.includes(error.message)) { + // Handle known errors resulting from user cancelling share + const CANCELLATION_MESSAGES = ["Abort due to cancellation of share.", "Share canceled"]; + if (CANCELLATION_MESSAGES.includes(error.message)) { console.warn("[SHARE] Share cancelled by user"); } else { this.errorHandler.handleError(error); diff --git a/src/app/shared/services/skin/skin.service.spec.ts b/src/app/shared/services/skin/skin.service.spec.ts index 2c86f90693..44a0005040 100644 --- a/src/app/shared/services/skin/skin.service.spec.ts +++ b/src/app/shared/services/skin/skin.service.spec.ts @@ -2,12 +2,12 @@ import { TestBed } from "@angular/core/testing"; import { SkinService } from "./skin.service"; import { LocalStorageService } from "../local-storage/local-storage.service"; -import { MockLocalStorageService } from "../local-storage/local-storage.service.spec"; +import { MockLocalStorageService } from "../local-storage/local-storage.service.mock.spec"; import { AppConfigService } from "../app-config/app-config.service"; -import { MockAppConfigService } from "../app-config/app-config.service.spec"; +import { MockAppConfigService } from "../app-config/app-config.service.mock.spec"; import { TemplateService } from "../../components/template/services/template.service"; import { ThemeService } from "src/app/feature/theme/services/theme.service"; -import { MockThemeService } from "src/app/feature/theme/services/theme.service.spec"; +import { MockThemeService } from "src/app/feature/theme/services/theme.service.mock.spec"; import { IAppConfig, IAppSkin } from "packages/data-models"; import { deepMergeObjects } from "../../utils"; import clone from "clone"; diff --git a/src/app/shared/services/syncService.base.mock.spec.ts b/src/app/shared/services/syncService.base.mock.spec.ts new file mode 100644 index 0000000000..7b84ec598c --- /dev/null +++ b/src/app/shared/services/syncService.base.mock.spec.ts @@ -0,0 +1,7 @@ +import { SyncServiceBase } from "./syncService.base"; + +export class MockSyncServiceBase extends SyncServiceBase { + constructor(name = "MockSyncServiceBase") { + super(name); + } +} diff --git a/src/app/shared/services/task/task.service.spec.ts b/src/app/shared/services/task/task.service.spec.ts index 01be22bf5e..2ab0d8d0a9 100644 --- a/src/app/shared/services/task/task.service.spec.ts +++ b/src/app/shared/services/task/task.service.spec.ts @@ -7,7 +7,7 @@ import { TaskService } from "./task.service"; // Mock Services import { MockTemplateFieldService } from "../../components/template/services/template-field.service.spec"; -import { MockAppConfigService } from "../app-config/app-config.service.spec"; +import { MockAppConfigService } from "../app-config/app-config.service.mock.spec"; import { MockAppDataService } from "../data/app-data.service.mock.spec"; // Mocked Services import { AppDataService, IAppDataCache } from "../data/app-data.service"; @@ -15,6 +15,8 @@ import { AppConfigService } from "../app-config/app-config.service"; import { CampaignService } from "../../../feature/campaign/campaign.service"; import { TemplateFieldService } from "../../components/template/services/template-field.service"; import { _wait } from "packages/shared/src/utils/async-utils"; +import { DynamicDataService } from "../dynamic-data/dynamic-data.service"; +import { MockDynamicDataService } from "../dynamic-data/dynamic-data.service.mock.spec"; // This must match the corresponding value in the deployment config, if the default value is overridden const highlightedTaskFieldName = "_task_highlighted_group_id"; @@ -96,6 +98,10 @@ describe("TaskService", () => { provide: AppConfigService, useValue: new MockAppConfigService(MOCK_CONFIG), }, + { + provide: DynamicDataService, + useValue: new MockDynamicDataService(MOCK_DATA), + }, // Mock single method from campaign service called { provide: CampaignService, diff --git a/src/app/shared/services/userMeta/userMeta.service.ts b/src/app/shared/services/userMeta/userMeta.service.ts index 782367b7b5..b9d1cb176d 100644 --- a/src/app/shared/services/userMeta/userMeta.service.ts +++ b/src/app/shared/services/userMeta/userMeta.service.ts @@ -7,7 +7,7 @@ import { AsyncServiceBase } from "../asyncService.base"; import { DbService } from "../db/db.service"; import { TemplateActionRegistry } from "../../components/template/services/instance/template-action.registry"; import { TemplateFieldService } from "../../components/template/services/template-field.service"; -import { LOCAL_STORAGE_PREFIX, LocalStorageService } from "../local-storage/local-storage.service"; +import { LocalStorageService } from "../local-storage/local-storage.service"; import { DynamicDataService } from "../dynamic-data/dynamic-data.service"; import { getProtectedFieldName, IProtectedFieldName } from "packages/data-models"; @@ -77,6 +77,9 @@ export class UserMetaService extends AsyncServiceBase { /** Import existing user contact fields and replace current user */ private async importUser(id: string) { + if (!id) { + throw new Error(`[User Import] no id provided`); + } try { // TODO - get type-safe return types using openapi http client const profile = await firstValueFrom( @@ -87,7 +90,7 @@ export class UserMetaService extends AsyncServiceBase { return; } const { contact_fields, dynamic_data } = profile as any; - console.log("[User Import]", { contact_fields, dynamic_data }); + console.log("[User Import]", profile); await this.importUserContactFields(contact_fields); await this.importUserDynamicData(dynamic_data); } catch (error) { @@ -99,8 +102,9 @@ export class UserMetaService extends AsyncServiceBase { // create a reverse mapping of protected fields that are allowed to be imported // to allow setting protected fields such as rp-contact-field._app_skin const protectedFieldMapping: Record = {}; + const { prefix } = this.localStorageService; for (const fieldName of IMPORTABLE_PROTECTED_FIELD_NAMES) { - const mappedName = `${LOCAL_STORAGE_PREFIX}.${getProtectedFieldName(fieldName)}`; + const mappedName = `${prefix}.${getProtectedFieldName(fieldName)}`; protectedFieldMapping[mappedName] = fieldName; } for (const [key, value] of Object.entries(contact_fields)) { diff --git a/src/assets/fonts/README.md b/src/assets/fonts/README.md index 208a377a19..77ce488cb3 100644 --- a/src/assets/fonts/README.md +++ b/src/assets/fonts/README.md @@ -1,3 +1,3 @@ -Fonts generate from https://google-webfonts-helper.herokuapp.com/fonts/roboto?subsets=latin - +Fonts generate from https://google-webfonts-helper.herokuapp.com/fonts/roboto?subsets=latin + These have been copied to the assets folder \ No newline at end of file diff --git a/src/assets/icon/favicon.svg b/src/assets/icon/favicon.svg index e1158e0c41..a57d16ea41 100644 --- a/src/assets/icon/favicon.svg +++ b/src/assets/icon/favicon.svg @@ -1,3 +1,32 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:638632bc6fbb1caf15a5bb3d89a95addaf44e9c08d1fd2b757b355772b5f4170 -size 5922 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/icon/shared/cross.svg b/src/assets/icon/shared/cross.svg index 4c287df481..b8a322a8f1 100644 --- a/src/assets/icon/shared/cross.svg +++ b/src/assets/icon/shared/cross.svg @@ -1,3 +1,7 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57481bfe3c2b70a540000463a8fcfcd6abc65823851f39c3d54258787cb53a4d -size 431 + + + + + + diff --git a/src/assets/icon/shared/tick.svg b/src/assets/icon/shared/tick.svg index 3f03c5f66e..e936f03d94 100644 --- a/src/assets/icon/shared/tick.svg +++ b/src/assets/icon/shared/tick.svg @@ -1,3 +1,7 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1926665db7cc5e7a01c103f083c8e39b7150104be3f8ecde296889603b59e0ae -size 463 + + + + + + diff --git a/src/theme/themes/_index.scss b/src/theme/themes/_index.scss index 6516eddf7c..c3bc08607f 100644 --- a/src/theme/themes/_index.scss +++ b/src/theme/themes/_index.scss @@ -1,5 +1,6 @@ @forward "./default.scss"; @forward "./professional.scss"; +@forward "./plh_kids_tz.scss"; @forward "./pfr.scss"; @forward "./plh_facilitator_mx/index"; @forward "./early_family_math.scss"; diff --git a/src/theme/themes/plh_facilitator_mx/_overrides.scss b/src/theme/themes/plh_facilitator_mx/_overrides.scss index 274c92a2e5..ede04890ad 100644 --- a/src/theme/themes/plh_facilitator_mx/_overrides.scss +++ b/src/theme/themes/plh_facilitator_mx/_overrides.scss @@ -947,6 +947,9 @@ body[data-theme="plh_facilitator_mx"] { border: none; border-inline-start: 3px solid var(--color-accent-orange-40); } + &[data-variant~="box_secondary_alt"] { + border-radius: var(--ion-border-radius-secondary) !important; + } &[data-variant~="box_gray"] { --background-color: var(--ion-color-gray-100) !important; border: none; diff --git a/src/theme/themes/plh_kids_tz.scss b/src/theme/themes/plh_kids_tz.scss new file mode 100644 index 0000000000..a28fbf12d7 --- /dev/null +++ b/src/theme/themes/plh_kids_tz.scss @@ -0,0 +1,63 @@ +@use "./utils"; +@use "sass:color"; + +@mixin theme-plh_kids_tz { + [data-theme="plh_kids_tz"] { + /** Authoring variables **/ + $color-primary: hsl(203, 76%, 21%); // #0d3f5e + $color-secondary: #edb135; // #EDB135 + $page-background: white; + + /** Global and component variables **/ + $variable-overrides: ( + // BORDERS + border-color-default: var(--ion-color-gray-200), + border-width-default: 1px, + border-standard: var(--border-width-default) solid var(--border-color-default), + border-thin-standard: 1px solid var(--border-color-default), + button-background-primary: var(--ion-color-primary-500), + button-background-secondary: var(--ion-color-secondary), + button-background-option: var(--ion-color-primary-800), + round-button-background-secondary-light: var(--ion-color-yellow), + // round-button-background-secondary-mid: #fa9529, + // round-button-background-secondary-dark: #F87023, + // tile-button-background-default: #a3d9fa, + tile-button-background-primary: var(--ion-color-primary-500), + tile-button-background-primary-light: var(--ion-color-primary-300), + tile-button-background-secondary: var(--ion-color-secondary), + tile-button-background-secondary-light: var(--ion-color-yellow), + // audio-control-background: #1980d2, + points-item-background: var(--ion-background-color), + points-item-background-complete: var(--ion-color-primary-200), + points-item-border: rgba(black, 0.07), + display-group-background-banner-primary: var(--ion-color-primary-200), + display-group-background-banner-secondary: var(--ion-color-secondary-300), + // display-group-background-tool-1: #fa8e29, + // display-group-background-tool-2: #ff7b00, + // display-group-background-tool-3: #108ab2, + // display-group-background-tool-4: #096a8b, + // display-group-background-tool-5: $color-primary, + display-group-background-home-light: var(--ion-color-primary-300), + display-group-background-home-mid: var(--ion-color-primary-600), + display-group-background-home-dark: var(--ion-color-primary-800), + // timer-button-background: #1985d2, + // combo-box-placeholder-text: rgba(13, 64, 96, 0.5), + // combo-box-background-no-value: transparent, + combo-box-background-with-value: var(--ion-color-primary-300), + // slider-ui-color: #096e90, + accordion-background-highlight: var(--ion-color-primary-300), + tour-next-button-background: var(--ion-color-secondary), + radio-group-background-selected: var(--ion-color-primary-300), + // radio-button-font-size: 1.25rem, + // radio-button-font-color: var(--ion-color-primary), + ion-item-background: var(--ion-color-gray-light), + task-progress-bar-color: var(--ion-color-primary-500), + // checkbox-background-color: white, + progress-path-line-background: var(--ion-color-gray-100) + ); + @include utils.generateTheme($color-primary, $color-secondary, $page-background); + @each $name, $value in $variable-overrides { + --#{$name}: #{$value}; + } + } +} diff --git a/src/theme/variables.scss b/src/theme/variables.scss index d7d734cad2..98c7abcf51 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -11,6 +11,7 @@ @include themes.theme-default; @include themes.theme-professional; +@include themes.theme-plh_kids_tz; @include themes.theme-pfr; @include themes.theme-plh_facilitator_mx; @include themes.theme-early_family_math; diff --git a/yarn.lock b/yarn.lock index 324f357308..6065d510bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2864,6 +2864,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/aix-ppc64@npm:0.23.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm64@npm:0.18.20" @@ -2885,6 +2892,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-arm64@npm:0.23.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm@npm:0.18.20" @@ -2906,6 +2920,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-arm@npm:0.23.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-x64@npm:0.18.20" @@ -2927,6 +2948,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-x64@npm:0.23.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-arm64@npm:0.18.20" @@ -2948,6 +2976,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/darwin-arm64@npm:0.23.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-x64@npm:0.18.20" @@ -2969,6 +3004,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/darwin-x64@npm:0.23.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-arm64@npm:0.18.20" @@ -2990,6 +3032,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/freebsd-arm64@npm:0.23.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-x64@npm:0.18.20" @@ -3011,6 +3060,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/freebsd-x64@npm:0.23.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm64@npm:0.18.20" @@ -3032,6 +3088,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-arm64@npm:0.23.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm@npm:0.18.20" @@ -3053,6 +3116,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-arm@npm:0.23.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ia32@npm:0.18.20" @@ -3074,6 +3144,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-ia32@npm:0.23.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-loong64@npm:0.18.20" @@ -3095,6 +3172,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-loong64@npm:0.23.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-mips64el@npm:0.18.20" @@ -3116,6 +3200,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-mips64el@npm:0.23.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ppc64@npm:0.18.20" @@ -3137,6 +3228,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-ppc64@npm:0.23.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-riscv64@npm:0.18.20" @@ -3158,6 +3256,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-riscv64@npm:0.23.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-s390x@npm:0.18.20" @@ -3179,6 +3284,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-s390x@npm:0.23.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-x64@npm:0.18.20" @@ -3200,6 +3312,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-x64@npm:0.23.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/netbsd-x64@npm:0.18.20" @@ -3221,6 +3340,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/netbsd-x64@npm:0.23.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/openbsd-arm64@npm:0.23.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/openbsd-x64@npm:0.18.20" @@ -3242,6 +3375,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/openbsd-x64@npm:0.23.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/sunos-x64@npm:0.18.20" @@ -3263,6 +3403,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/sunos-x64@npm:0.23.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-arm64@npm:0.18.20" @@ -3284,6 +3431,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-arm64@npm:0.23.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-ia32@npm:0.18.20" @@ -3305,6 +3459,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-ia32@npm:0.23.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-x64@npm:0.18.20" @@ -3326,6 +3487,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-x64@npm:0.23.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -5983,286 +6151,279 @@ __metadata: languageName: node linkType: hard -"@octokit/app@npm:^14.0.2": - version: 14.0.2 - resolution: "@octokit/app@npm:14.0.2" +"@octokit/app@npm:^15.1.2": + version: 15.1.2 + resolution: "@octokit/app@npm:15.1.2" dependencies: - "@octokit/auth-app": ^6.0.0 - "@octokit/auth-unauthenticated": ^5.0.0 - "@octokit/core": ^5.0.0 - "@octokit/oauth-app": ^6.0.0 - "@octokit/plugin-paginate-rest": ^9.0.0 - "@octokit/types": ^12.0.0 - "@octokit/webhooks": ^12.0.4 - checksum: 1d6745302f81694c85a47997640be1e509a61da1de4ec31f8fed3715daf03cbcf39d6f80a146e2d2a0ff0ebc987fcda5dba9d53f1357f2e8c93edd5675db3780 + "@octokit/auth-app": ^7.1.4 + "@octokit/auth-unauthenticated": ^6.1.1 + "@octokit/core": ^6.1.3 + "@octokit/oauth-app": ^7.1.5 + "@octokit/plugin-paginate-rest": ^11.3.6 + "@octokit/types": ^13.6.2 + "@octokit/webhooks": ^13.4.2 + checksum: b06db1cb66e26d71609112abbf63c51efa209be2ceb7a6ac800db4f8538c3bb83a422640042866f509cf1f8f60fc4b5c5de0974adc3783f61d508798d0a740de languageName: node linkType: hard -"@octokit/auth-app@npm:^6.0.0": - version: 6.0.3 - resolution: "@octokit/auth-app@npm:6.0.3" +"@octokit/auth-app@npm:^7.1.4": + version: 7.1.4 + resolution: "@octokit/auth-app@npm:7.1.4" dependencies: - "@octokit/auth-oauth-app": ^7.0.0 - "@octokit/auth-oauth-user": ^4.0.0 - "@octokit/request": ^8.0.2 - "@octokit/request-error": ^5.0.0 - "@octokit/types": ^12.0.0 - deprecation: ^2.3.1 - lru-cache: ^10.0.0 - universal-github-app-jwt: ^1.1.2 - universal-user-agent: ^6.0.0 - checksum: 797cb14bef8d20c36ac9a242ca01203abaf6d130e58daf179f45124cd2f1c6a28971f14a82de0ada4e5e488f6e2218dad09f610aaf04cf0ac99b8f3720460ff6 + "@octokit/auth-oauth-app": ^8.1.2 + "@octokit/auth-oauth-user": ^5.1.2 + "@octokit/request": ^9.1.4 + "@octokit/request-error": ^6.1.6 + "@octokit/types": ^13.6.2 + toad-cache: ^3.7.0 + universal-github-app-jwt: ^2.2.0 + universal-user-agent: ^7.0.0 + checksum: 2ce839187da2dbf8e0f0b73c94ccb6a7f400032ce898f15322e69a51571143f1d12b9fc4839e03d15751f8b81fc972dd671d8c2c7b830e1f17225b9237e6136f languageName: node linkType: hard -"@octokit/auth-oauth-app@npm:^7.0.0": - version: 7.0.1 - resolution: "@octokit/auth-oauth-app@npm:7.0.1" +"@octokit/auth-oauth-app@npm:^8.1.2": + version: 8.1.2 + resolution: "@octokit/auth-oauth-app@npm:8.1.2" dependencies: - "@octokit/auth-oauth-device": ^6.0.0 - "@octokit/auth-oauth-user": ^4.0.0 - "@octokit/request": ^8.0.2 - "@octokit/types": ^12.0.0 - "@types/btoa-lite": ^1.0.0 - btoa-lite: ^1.0.0 - universal-user-agent: ^6.0.0 - checksum: a96486150bc5b4ddd7167ef4d6554886b1949631aa76fde92a21424ffe2810b87e4ae413d52ed07c2c6c015ef697a93e716e9ef7b11be26539d5ccc8eeb7226b + "@octokit/auth-oauth-device": ^7.1.2 + "@octokit/auth-oauth-user": ^5.1.2 + "@octokit/request": ^9.1.4 + "@octokit/types": ^13.6.2 + universal-user-agent: ^7.0.0 + checksum: 29a43b2087c3093e2293b7baba5256ad2512398c74276019e6ca4ae0d1230c0419eaa435e01e87d25a0243f235129bb1a8a057c90037276317ac55856db232c6 languageName: node linkType: hard -"@octokit/auth-oauth-device@npm:^6.0.0": - version: 6.0.1 - resolution: "@octokit/auth-oauth-device@npm:6.0.1" +"@octokit/auth-oauth-device@npm:^7.1.2": + version: 7.1.2 + resolution: "@octokit/auth-oauth-device@npm:7.1.2" dependencies: - "@octokit/oauth-methods": ^4.0.0 - "@octokit/request": ^8.0.0 - "@octokit/types": ^12.0.0 - universal-user-agent: ^6.0.0 - checksum: 2639b07430023c23fc117035ac775a74709a0b05abcad4a29f1f0fdb808775acac4fb410fa45fd18f89da1fb73b743ef7382a3cd39d29bbe059d2802aeaa3aa4 + "@octokit/oauth-methods": ^5.1.3 + "@octokit/request": ^9.1.4 + "@octokit/types": ^13.6.2 + universal-user-agent: ^7.0.0 + checksum: 230aa354487586c70f7d82a538422fc787824b7dc0179ac3a8f6bb285f16aa7d1b618cd336e935be2e1505a89582c4b424c3b6dd2130fc8a47481b83fb4664ee languageName: node linkType: hard -"@octokit/auth-oauth-user@npm:^4.0.0": - version: 4.0.1 - resolution: "@octokit/auth-oauth-user@npm:4.0.1" +"@octokit/auth-oauth-user@npm:^5.1.2": + version: 5.1.2 + resolution: "@octokit/auth-oauth-user@npm:5.1.2" dependencies: - "@octokit/auth-oauth-device": ^6.0.0 - "@octokit/oauth-methods": ^4.0.0 - "@octokit/request": ^8.0.2 - "@octokit/types": ^12.0.0 - btoa-lite: ^1.0.0 - universal-user-agent: ^6.0.0 - checksum: 38b0edb58d4396a8a3782fea56b7691dc8dc28bdb195c4dfe8fe83578c4daa4afa941a524e6a44a45c47b2af9863703087e18dacc6e396c6c032862eec266b4c + "@octokit/auth-oauth-device": ^7.1.2 + "@octokit/oauth-methods": ^5.1.2 + "@octokit/request": ^9.1.4 + "@octokit/types": ^13.6.2 + universal-user-agent: ^7.0.0 + checksum: 1aef03d7d4949bf72c1ea7bd5c53756239bfd8daeaefbbe3004c49bffdba83e37d9459297d98e695dd8e3784345ff125f83d89ab1f21669f293174256cfb6c63 languageName: node linkType: hard -"@octokit/auth-token@npm:^4.0.0": - version: 4.0.0 - resolution: "@octokit/auth-token@npm:4.0.0" - checksum: d78f4dc48b214d374aeb39caec4fdbf5c1e4fd8b9fcb18f630b1fe2cbd5a880fca05445f32b4561f41262cb551746aeb0b49e89c95c6dd99299706684d0cae2f +"@octokit/auth-token@npm:^5.0.0": + version: 5.1.2 + resolution: "@octokit/auth-token@npm:5.1.2" + checksum: 1f02305bd75cabc7aadce7e0a707f84775fe067b81e4b325744acad2a125a88fbbc1df1e707caa782425e8afd8728d9ed3d6085fc15a38937777404de1f6c22c languageName: node linkType: hard -"@octokit/auth-unauthenticated@npm:^5.0.0": - version: 5.0.1 - resolution: "@octokit/auth-unauthenticated@npm:5.0.1" +"@octokit/auth-unauthenticated@npm:^6.1.1": + version: 6.1.1 + resolution: "@octokit/auth-unauthenticated@npm:6.1.1" dependencies: - "@octokit/request-error": ^5.0.0 - "@octokit/types": ^12.0.0 - checksum: b6eed1fc15d47f45411c0229dd6613dd8fd4b79afbac23b8c47818da692a35d54f57e088294d9b71ce4dcc0f58ce0c77d12cd2700370d87770059248b9a8fbba + "@octokit/request-error": ^6.1.6 + "@octokit/types": ^13.6.2 + checksum: b91e89ef5faf0da95e4b5f381023af7fa6ec1d5e9ac670576b18b5f4daee3c06baf1a7059e7798eb438078eb4cd8711499387bd1d2178fe9500c405f4f6fa366 languageName: node linkType: hard -"@octokit/core@npm:^5.0.0": - version: 5.0.2 - resolution: "@octokit/core@npm:5.0.2" +"@octokit/core@npm:^6.1.3": + version: 6.1.3 + resolution: "@octokit/core@npm:6.1.3" dependencies: - "@octokit/auth-token": ^4.0.0 - "@octokit/graphql": ^7.0.0 - "@octokit/request": ^8.0.2 - "@octokit/request-error": ^5.0.0 - "@octokit/types": ^12.0.0 - before-after-hook: ^2.2.0 - universal-user-agent: ^6.0.0 - checksum: 9ce060d61577f6805901ae5c33b2764a441db119ae0cca09104adf37b119cce68b656220de56c0c5004c9c9c1c892a7fdfbe9c0b1f5e398cb359dfd39c57eca8 + "@octokit/auth-token": ^5.0.0 + "@octokit/graphql": ^8.1.2 + "@octokit/request": ^9.1.4 + "@octokit/request-error": ^6.1.6 + "@octokit/types": ^13.6.2 + before-after-hook: ^3.0.2 + universal-user-agent: ^7.0.0 + checksum: 9174c8658f362a34a42dba77681b9ee8724b13c2231690dccbc2664a4fe64da8339b0875f73917e09f67ef370d59d9aee499fe0855f1eba55535383af1018e8f languageName: node linkType: hard -"@octokit/endpoint@npm:^9.0.0": - version: 9.0.4 - resolution: "@octokit/endpoint@npm:9.0.4" +"@octokit/endpoint@npm:^10.0.0": + version: 10.1.2 + resolution: "@octokit/endpoint@npm:10.1.2" dependencies: - "@octokit/types": ^12.0.0 - universal-user-agent: ^6.0.0 - checksum: ed1b64a448f478e5951a043ef816d634a5a1f584519cbf2f374ceac058f82a16e52f078f156aa8b8cbcab7b0590348d94294fc83c9b4eebd42a820a5f10db81c + "@octokit/types": ^13.6.2 + universal-user-agent: ^7.0.2 + checksum: 425f4b0f12e2565d7270522e2e42d0595bd16c2c16fe262b540d50fc94d279e93b37b670370ae23dfe6117a2b74c69ffd7d3644e4dea5e6fc576a562ed75fba4 languageName: node linkType: hard -"@octokit/graphql@npm:^7.0.0": - version: 7.0.2 - resolution: "@octokit/graphql@npm:7.0.2" +"@octokit/graphql@npm:^8.1.2": + version: 8.2.0 + resolution: "@octokit/graphql@npm:8.2.0" dependencies: - "@octokit/request": ^8.0.1 - "@octokit/types": ^12.0.0 - universal-user-agent: ^6.0.0 - checksum: 05a752c4c2d84fc2900d8e32e1c2d1ee98a5a14349e651cb1109d0741e821e7417a048b1bb40918534ed90a472314aabbda35688868016f248098925f82a3bfa + "@octokit/request": ^9.1.4 + "@octokit/types": ^13.8.0 + universal-user-agent: ^7.0.0 + checksum: dc18c3dcb4607d89850cd392b198ffe278ac8e7fd22d3f610e8efdc760bb90b6f1fb70a41a149c47c46122af65771898ed3dd66b261dc03f96f869f90cded404 languageName: node linkType: hard -"@octokit/oauth-app@npm:^6.0.0": - version: 6.0.0 - resolution: "@octokit/oauth-app@npm:6.0.0" - dependencies: - "@octokit/auth-oauth-app": ^7.0.0 - "@octokit/auth-oauth-user": ^4.0.0 - "@octokit/auth-unauthenticated": ^5.0.0 - "@octokit/core": ^5.0.0 - "@octokit/oauth-authorization-url": ^6.0.2 - "@octokit/oauth-methods": ^4.0.0 +"@octokit/oauth-app@npm:^7.1.4, @octokit/oauth-app@npm:^7.1.5": + version: 7.1.5 + resolution: "@octokit/oauth-app@npm:7.1.5" + dependencies: + "@octokit/auth-oauth-app": ^8.1.2 + "@octokit/auth-oauth-user": ^5.1.2 + "@octokit/auth-unauthenticated": ^6.1.1 + "@octokit/core": ^6.1.3 + "@octokit/oauth-authorization-url": ^7.1.1 + "@octokit/oauth-methods": ^5.1.3 "@types/aws-lambda": ^8.10.83 - universal-user-agent: ^6.0.0 - checksum: 5d07c6fe15d4a670a3ca0c7c0d37c973912b3ac993375966a6bed0e084edbda972f575e2ab2dc17aa9718e5aeefbec7489f5aeb2dbc6e47768ad9633f27f842d + universal-user-agent: ^7.0.0 + checksum: d8bbe0a889b47f48ca907a372f2464a629c97b86bc5c5c4beb97b844e679e4b0ad59e88b5027c7771d8c049e3fdb12cce9ee8828023d109fc02a500c0d561e49 languageName: node linkType: hard -"@octokit/oauth-authorization-url@npm:^6.0.2": - version: 6.0.2 - resolution: "@octokit/oauth-authorization-url@npm:6.0.2" - checksum: 0f11169a3eeb782cc08312c923de1a702b25ae033b972ba40380b6d72cb3f684543c8b6a5cf6f05936fdc6b8892070d4f7581138d8efc1b4c4a55ae6d7762327 +"@octokit/oauth-authorization-url@npm:^7.0.0, @octokit/oauth-authorization-url@npm:^7.1.1": + version: 7.1.1 + resolution: "@octokit/oauth-authorization-url@npm:7.1.1" + checksum: 02ad29fa4540c6b4b3a1e9f6936d40057174be91e9c7cad1afcd09d027fa2a50598dad5857699d1be25568bf70d86123dc9cd3874afe044ce6791e6805e97542 languageName: node linkType: hard -"@octokit/oauth-methods@npm:^4.0.0": - version: 4.0.1 - resolution: "@octokit/oauth-methods@npm:4.0.1" +"@octokit/oauth-methods@npm:^5.1.2, @octokit/oauth-methods@npm:^5.1.3": + version: 5.1.3 + resolution: "@octokit/oauth-methods@npm:5.1.3" dependencies: - "@octokit/oauth-authorization-url": ^6.0.2 - "@octokit/request": ^8.0.2 - "@octokit/request-error": ^5.0.0 - "@octokit/types": ^12.0.0 - btoa-lite: ^1.0.0 - checksum: 35fb00f9ef8aa77cb00e398d4a423ff4b928d1d51462cdbdd4d3975b862355cbddb6777d10bdf43b0ac5c23f7038e07a9ff495731dbfe6aa0241799cbf5042f0 + "@octokit/oauth-authorization-url": ^7.0.0 + "@octokit/request": ^9.1.4 + "@octokit/request-error": ^6.1.6 + "@octokit/types": ^13.6.2 + checksum: 61c9deb272052fb3c6cdc3fb2f5b40b711e2943f07569a8fb6fe12c0a8ffe1a1015a883645fa917bee49c740dff50b062d7270e01b47a37694dffe225fa5cf0a languageName: node linkType: hard -"@octokit/openapi-types@npm:^19.1.0": - version: 19.1.0 - resolution: "@octokit/openapi-types@npm:19.1.0" - checksum: 9d1b188741609a9832b964df2bc337ee77c1fc89d5f686faebb743c7cb27721e214180d623ee28227427b4c43719b79ee4890e338a709b78a9f249a7c369ac3e +"@octokit/openapi-types@npm:^23.0.1": + version: 23.0.1 + resolution: "@octokit/openapi-types@npm:23.0.1" + checksum: 1e6766c60375375d85ecabded67d9ee313cf9401c18a44534b942717cf840d41b5a9d42035522efffe6b811ee2204d4615f72c333e984e81b25545926eb77989 languageName: node linkType: hard -"@octokit/plugin-paginate-graphql@npm:^4.0.0": - version: 4.0.0 - resolution: "@octokit/plugin-paginate-graphql@npm:4.0.0" - peerDependencies: - "@octokit/core": ">=5" - checksum: 368121d74fc40a4cee96f2febc29ae43abd8f6b7d0b06d3520847827675128028c4fa10d0534c5f0466658e81257d103092154778625c886a9fcdd01c302e50e +"@octokit/openapi-webhooks-types@npm:8.5.1": + version: 8.5.1 + resolution: "@octokit/openapi-webhooks-types@npm:8.5.1" + checksum: 810d5261b512b174fe2bccae9669223c588258e90871a31c9ba612807a416742e6a4ba3679daa561995d924081fc32b953f4a150d7cd380a19b24da60dd467f6 languageName: node linkType: hard -"@octokit/plugin-paginate-rest@npm:^9.0.0": - version: 9.1.5 - resolution: "@octokit/plugin-paginate-rest@npm:9.1.5" - dependencies: - "@octokit/types": ^12.4.0 +"@octokit/plugin-paginate-graphql@npm:^5.2.4": + version: 5.2.4 + resolution: "@octokit/plugin-paginate-graphql@npm:5.2.4" peerDependencies: - "@octokit/core": ">=5" - checksum: ee5bc62e3175b61fdd3e65cc26410e1130931e729591003b302167cb02d0cec0746fd58220800d07de179e36077b9d675c794d0859457b701a7692b9fcde49a0 + "@octokit/core": ">=6" + checksum: f119999c8872f8c24eff653c3af53dea9d06b6863491ea52b888c1a9489019fcaa47423321b857073c609baaaf43fecf97ef335d780042334217abfe24b68bed languageName: node linkType: hard -"@octokit/plugin-rest-endpoint-methods@npm:^10.0.0": - version: 10.2.0 - resolution: "@octokit/plugin-rest-endpoint-methods@npm:10.2.0" +"@octokit/plugin-paginate-rest@npm:^11.3.6, @octokit/plugin-paginate-rest@npm:^11.4.0": + version: 11.4.0 + resolution: "@octokit/plugin-paginate-rest@npm:11.4.0" dependencies: - "@octokit/types": ^12.3.0 + "@octokit/types": ^13.7.0 peerDependencies: - "@octokit/core": ">=5" - checksum: 3209688bf508d22a525fe32d632ff928b048688c1859c7e4bbb08bd181aa07f580b375a502e34368628103e5d5cccf7f9fb0ff0c8fd4262470ac8eeffb80ac6b + "@octokit/core": ">=6" + checksum: f4d2a290c9c1ff6655b135b43721a6f8e0260ec101ba0818ebd0ac5aab74f935681e721b461ac9f915703431309f7e5f8e708b6eb3dab0b8b00ba5c585b4b12a languageName: node linkType: hard -"@octokit/plugin-retry@npm:^6.0.0": - version: 6.0.1 - resolution: "@octokit/plugin-retry@npm:6.0.1" +"@octokit/plugin-rest-endpoint-methods@npm:^13.3.0": + version: 13.3.0 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.3.0" dependencies: - "@octokit/request-error": ^5.0.0 - "@octokit/types": ^12.0.0 - bottleneck: ^2.15.3 + "@octokit/types": ^13.7.0 peerDependencies: - "@octokit/core": ">=5" - checksum: 9c8663b5257cf4fa04cc737c064e9557501719d6d3af7cf8f46434a2117e1cf4b8d25d9eb4294ed255ad17a0ede853542649870612733f4b8ece97e24e391d22 + "@octokit/core": ">=6" + checksum: 89de851184384575b578c9f9afb45cde3b2db4d3ed748b85437ce5c7b18f1fb696f0cc974c4aeefdeb0b1103b302320c7e41171a9fa9884e09d4ff829b4aa677 languageName: node linkType: hard -"@octokit/plugin-throttling@npm:^8.0.0": - version: 8.1.3 - resolution: "@octokit/plugin-throttling@npm:8.1.3" +"@octokit/plugin-retry@npm:^7.1.3": + version: 7.1.3 + resolution: "@octokit/plugin-retry@npm:7.1.3" dependencies: - "@octokit/types": ^12.2.0 + "@octokit/request-error": ^6.1.6 + "@octokit/types": ^13.6.2 bottleneck: ^2.15.3 peerDependencies: - "@octokit/core": ^5.0.0 - checksum: 98963ef2eab825015702b1ca1ef4ccbda0c009242e93001144e51014d3b53d3ecb1282b67488680c7f5f4e805d0c3af4020ad673079fff92c6353f04903a9a64 + "@octokit/core": ">=6" + checksum: d8ee8cf72d348cd52ef86906264528c047aaf49517f8e3d61bf74629d8852f1ec0a1ce535f07dcc0437422bad8a0c9b47af712457b9347419a81f8caa2a99033 languageName: node linkType: hard -"@octokit/request-error@npm:^5.0.0": - version: 5.0.1 - resolution: "@octokit/request-error@npm:5.0.1" +"@octokit/plugin-throttling@npm:^9.4.0": + version: 9.4.0 + resolution: "@octokit/plugin-throttling@npm:9.4.0" dependencies: - "@octokit/types": ^12.0.0 - deprecation: ^2.0.0 - once: ^1.4.0 - checksum: a681341e43b4da7a8acb19e1a6ba0355b1af146fa0191f2554a98950cf85f898af6ae3ab0b0287d6c871f5465ec57cb38363b96b5019f9f77ba6f30eca39ede5 + "@octokit/types": ^13.7.0 + bottleneck: ^2.15.3 + peerDependencies: + "@octokit/core": ^6.1.3 + checksum: 84f774a40b9a5e4b0c1a405b28368490bd104ef2bf27006da2e319cd62b096b70c00a00d89d6705b8ac31bc2633a95c0cd3c7ca4e66c85c10d9470113aa6d3c3 languageName: node linkType: hard -"@octokit/request@npm:^8.0.0, @octokit/request@npm:^8.0.1, @octokit/request@npm:^8.0.2": - version: 8.1.6 - resolution: "@octokit/request@npm:8.1.6" +"@octokit/request-error@npm:^6.0.1, @octokit/request-error@npm:^6.1.6": + version: 6.1.6 + resolution: "@octokit/request-error@npm:6.1.6" dependencies: - "@octokit/endpoint": ^9.0.0 - "@octokit/request-error": ^5.0.0 - "@octokit/types": ^12.0.0 - universal-user-agent: ^6.0.0 - checksum: df90204586ee7db5adf69c3007c5d9c0a866de488c9ba8756f98083208726ed360d5a541e68204c413fa10e6f17e171dc9868b18768b9799df0003bc84c59cf2 + "@octokit/types": ^13.6.2 + checksum: 5b4e2637c7c6ef3ce9dfb2b2a054a2b821b2b750b9ddedfef277699d774db5103bdb697717ed462d085ca66860079b5b01210ca8a855bbafeb016b1a69dd276b languageName: node linkType: hard -"@octokit/types@npm:^12.0.0, @octokit/types@npm:^12.2.0, @octokit/types@npm:^12.3.0, @octokit/types@npm:^12.4.0": - version: 12.4.0 - resolution: "@octokit/types@npm:12.4.0" +"@octokit/request@npm:^9.1.4": + version: 9.2.0 + resolution: "@octokit/request@npm:9.2.0" dependencies: - "@octokit/openapi-types": ^19.1.0 - checksum: 17bca450efc5433f14e1f93a24232316a0fb490aec8d886c3a430e853f10a74e6664751a44e0e199336b23c20658c4afcb3590e422ba7c155c2cb31f433ebbb7 + "@octokit/endpoint": ^10.0.0 + "@octokit/request-error": ^6.0.1 + "@octokit/types": ^13.6.2 + fast-content-type-parse: ^2.0.0 + universal-user-agent: ^7.0.2 + checksum: 24056e2c3c634bfca5f72277b6cd69f69ad2a58c033a0e96c2fa626e3e6a028f6ca58702aecceb5f2f4ed23a583b16df2dd728fa02dce910dc081bfcbc64ad92 languageName: node linkType: hard -"@octokit/webhooks-methods@npm:^4.0.0": - version: 4.0.0 - resolution: "@octokit/webhooks-methods@npm:4.0.0" - checksum: 07010438e53a6a659f0d7d3596bf89e6795776165066553e76384d90cef077a1e259122733913468299a1a76c71536914eb871d0508fcbbd453468b21eeb30c7 +"@octokit/types@npm:^13.6.2, @octokit/types@npm:^13.7.0, @octokit/types@npm:^13.8.0": + version: 13.8.0 + resolution: "@octokit/types@npm:13.8.0" + dependencies: + "@octokit/openapi-types": ^23.0.1 + checksum: be5fb327d0e39765e06f5a314556a273ff2bfb9ce4fd5a6e52c237d2f20a4c329493a8bde2c595cb82a5022f07ee6495dfff07ce24e3de4660c9ead913e3db0d languageName: node linkType: hard -"@octokit/webhooks-types@npm:7.1.0": - version: 7.1.0 - resolution: "@octokit/webhooks-types@npm:7.1.0" - checksum: 5aea38c38e97cb1b8d54c805c17c4015ee937d0b1ad550adc64eaf2e90bfbaf1e00c878490c10b43e31a11563e8d02183b86268ed588b04e39b22d5fd27807cf +"@octokit/webhooks-methods@npm:^5.0.0": + version: 5.1.0 + resolution: "@octokit/webhooks-methods@npm:5.1.0" + checksum: 6b0185f62b30b1d267456c449732d1c381e22533bcfeea3002bb88bc9f50a6ec5e4863be092473e7c47bee8c01b863ebd93980dd378495860dfd8d762044a212 languageName: node linkType: hard -"@octokit/webhooks@npm:^12.0.4": - version: 12.0.11 - resolution: "@octokit/webhooks@npm:12.0.11" +"@octokit/webhooks@npm:^13.4.2": + version: 13.5.0 + resolution: "@octokit/webhooks@npm:13.5.0" dependencies: - "@octokit/request-error": ^5.0.0 - "@octokit/webhooks-methods": ^4.0.0 - "@octokit/webhooks-types": 7.1.0 - aggregate-error: ^3.1.0 - checksum: 8c4c26ad00a368086e444a730eef3f044d554abd3d91eb0afbf02a317841211282f3b3a5dc7bfcd2a85f4e7c96949f6950a75feee1dc431ff136fe26d5fe3020 + "@octokit/openapi-webhooks-types": 8.5.1 + "@octokit/request-error": ^6.1.6 + "@octokit/webhooks-methods": ^5.0.0 + checksum: 02c7fc17c0863d0f292b021db03c9cad1643f7e42861586ec4f9fdfe3ede0e73195643b79f513f3fde7e137e0b90fec925c0ccfd751e4d828870c419d9fd6823 languageName: node linkType: hard @@ -6401,6 +6562,24 @@ __metadata: languageName: node linkType: hard +"@puppeteer/browsers@npm:2.7.0": + version: 2.7.0 + resolution: "@puppeteer/browsers@npm:2.7.0" + dependencies: + debug: ^4.4.0 + extract-zip: ^2.0.1 + progress: ^2.0.3 + proxy-agent: ^6.5.0 + semver: ^7.6.3 + tar-fs: ^3.0.6 + unbzip2-stream: ^1.4.3 + yargs: ^17.7.2 + bin: + browsers: lib/cjs/main-cli.js + checksum: 65b1a67268f6276f2ffd9a4d9f6637e083734c87a551acd87acb3d9ad9a0516fbf285889494b826087ec9907e215805df279e3847f6558d323255b713c271752 + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.12.0": version: 4.12.0 resolution: "@rollup/rollup-android-arm-eabi@npm:4.12.0" @@ -7263,13 +7442,6 @@ __metadata: languageName: node linkType: hard -"@types/btoa-lite@npm:^1.0.0": - version: 1.0.2 - resolution: "@types/btoa-lite@npm:1.0.2" - checksum: 4c46b163c881a75522c7556dd7a7df8a0d4c680a45e8bac34e50864e1c2d9df8dc90b99f75199154c60ef2faff90896b7e5f11df6936c94167a3e5e1c6f4d935 - languageName: node - linkType: hard - "@types/chai@npm:^4.2.22": version: 4.3.11 resolution: "@types/chai@npm:4.3.11" @@ -7462,6 +7634,16 @@ __metadata: languageName: node linkType: hard +"@types/fs-extra@npm:^11.0.4": + version: 11.0.4 + resolution: "@types/fs-extra@npm:11.0.4" + dependencies: + "@types/jsonfile": "*" + "@types/node": "*" + checksum: 242cb84157631f057f76495c8220707541882c00a00195b603d937fb55e471afecebcb089bab50233ed3a59c69fd68bf65c1f69dd7fafe2347e139cc15b9b0e5 + languageName: node + linkType: hard + "@types/fs-extra@npm:^8.0.0": version: 8.1.5 resolution: "@types/fs-extra@npm:8.1.5" @@ -7626,12 +7808,12 @@ __metadata: languageName: node linkType: hard -"@types/jsonwebtoken@npm:^9.0.0": - version: 9.0.5 - resolution: "@types/jsonwebtoken@npm:9.0.5" +"@types/jsonfile@npm:*": + version: 6.1.4 + resolution: "@types/jsonfile@npm:6.1.4" dependencies: "@types/node": "*" - checksum: 07ab6fee602e5bd3fb5c6dfe4ec400769dc20f1d7fce901feecb4c3af5f5f08323b03ea55de3e49b1aa41e171a59008f6f4318738a735588c5268a63eba25337 + checksum: 309fda20eb5f1cf68f2df28931afdf189c5e7e6bec64ac783ce737bb98908d57f6f58757ad5da9be37b815645a6f914e2d4f3ac66c574b8fe1ba6616284d0e97 languageName: node linkType: hard @@ -7810,6 +7992,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.13.0": + version: 22.13.0 + resolution: "@types/node@npm:22.13.0" + dependencies: + undici-types: ~6.20.0 + checksum: 934122ad4c20bd583cae1cf5350f911350a99ca2fd2a4c1902c1db97af8bb4c496675d592a45f9ce8aced3006fe80ca075b7e9da030a61b2d9008b7259383a76 + languageName: node + linkType: hard + "@types/node@npm:^22.6.0": version: 22.10.4 resolution: "@types/node@npm:22.10.4" @@ -7860,7 +8051,7 @@ __metadata: languageName: node linkType: hard -"@types/pixelmatch@npm:^5.2.4": +"@types/pixelmatch@npm:^5.2.6": version: 5.2.6 resolution: "@types/pixelmatch@npm:5.2.6" dependencies: @@ -7869,12 +8060,12 @@ __metadata: languageName: node linkType: hard -"@types/pngjs@npm:^6.0.1": - version: 6.0.4 - resolution: "@types/pngjs@npm:6.0.4" +"@types/pngjs@npm:^6.0.5": + version: 6.0.5 + resolution: "@types/pngjs@npm:6.0.5" dependencies: "@types/node": "*" - checksum: ffc8b96f86561289964012043ef517625252a99e9faeebe9d9f5492f53c995699981ec2e77d85966906f116866b9423792b34fbabc5ae3973ca09b727d131ebb + checksum: 132fce25817d47a784ed48aa678332521b0f7e6edbaa76f3fa4e9ca1228078788ae712f99ad4d1a324d9ba0b14829958677eabf3ebef1fb6e120816f433f0cd8 languageName: node linkType: hard @@ -8829,10 +9020,10 @@ __metadata: languageName: node linkType: hard -"@zeit/schemas@npm:2.6.0": - version: 2.6.0 - resolution: "@zeit/schemas@npm:2.6.0" - checksum: 7f2175ee34fad1a37da20882f9cda038ebb43a99ceaf30877f1676044669adde714ee56de6f1fcb57214dfa4479995a63fb2d053fe9f877b6852cdc1e4da574c +"@zeit/schemas@npm:2.36.0": + version: 2.36.0 + resolution: "@zeit/schemas@npm:2.36.0" + checksum: 9a5939a14ffa1e3a2c73ccb7c06b7bbc932baf1012d9191d3df641f26a2c3f7a6f25ea0213aad02a2011602ded3410cbcdc47633ec9ed28400485625b432945f languageName: node linkType: hard @@ -9020,7 +9211,14 @@ __metadata: languageName: node linkType: hard -"aggregate-error@npm:^3.0.0, aggregate-error@npm:^3.1.0": +"agent-base@npm:^7.1.2": + version: 7.1.3 + resolution: "agent-base@npm:7.1.3" + checksum: 87bb7ee54f5ecf0ccbfcba0b07473885c43ecd76cb29a8db17d6137a19d9f9cd443a2a7c5fd8a3f24d58ad8145f9eb49116344a66b107e1aeab82cf2383f4753 + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" dependencies: @@ -9064,18 +9262,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:6.12.6, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:^6.12.6": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" - dependencies: - fast-deep-equal: ^3.1.1 - fast-json-stable-stringify: ^2.0.0 - json-schema-traverse: ^0.4.1 - uri-js: ^4.2.2 - checksum: 874972efe5c4202ab0a68379481fbd3d1b5d0a7bd6d3cc21d40d3536ebff3352a2a1fabb632d4fd2cc7fe4cbdcd5ed6782084c9bbf7f32a1536d18f9da5007d4 - languageName: node - linkType: hard - "ajv@npm:8.11.0": version: 8.11.0 resolution: "ajv@npm:8.11.0" @@ -9100,6 +9286,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:^6.12.6": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: ^3.1.1 + fast-json-stable-stringify: ^2.0.0 + json-schema-traverse: ^0.4.1 + uri-js: ^4.2.2 + checksum: 874972efe5c4202ab0a68379481fbd3d1b5d0a7bd6d3cc21d40d3536ebff3352a2a1fabb632d4fd2cc7fe4cbdcd5ed6782084c9bbf7f32a1536d18f9da5007d4 + languageName: node + linkType: hard + "amdefine@npm:>=0.0.4": version: 1.0.1 resolution: "amdefine@npm:1.0.1" @@ -9107,7 +9305,7 @@ __metadata: languageName: node linkType: hard -"ansi-align@npm:^3.0.0": +"ansi-align@npm:^3.0.0, ansi-align@npm:^3.0.1": version: 3.0.1 resolution: "ansi-align@npm:3.0.1" dependencies: @@ -9148,6 +9346,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^7.0.0": + version: 7.0.0 + resolution: "ansi-escapes@npm:7.0.0" + dependencies: + environment: ^1.0.0 + checksum: 19baa61e68d1998c03b3b8bd023653a6c2667f0ed6caa9a00780ffd6f0a14f4a6563c57a38b3c0aba71bd704cd49c4c8df41be60bd81c957409f91e9dd49051f + languageName: node + linkType: hard + "ansi-html-community@npm:^0.0.8": version: 0.0.8 resolution: "ansi-html-community@npm:0.0.8" @@ -9336,7 +9543,7 @@ __metadata: languageName: node linkType: hard -"arch@npm:^2.1.1, arch@npm:^2.2.0": +"arch@npm:^2.2.0": version: 2.2.0 resolution: "arch@npm:2.2.0" checksum: e21b7635029fe8e9cdd5a026f9a6c659103e63fff423834323cdf836a1bb240a72d0c39ca8c470f84643385cf581bd8eda2cad8bf493e27e54bd9783abe9101f @@ -9379,7 +9586,22 @@ __metadata: languageName: node linkType: hard -"archiver@npm:^5.0.0, archiver@npm:^5.3.0": +"archiver-utils@npm:^5.0.0, archiver-utils@npm:^5.0.2": + version: 5.0.2 + resolution: "archiver-utils@npm:5.0.2" + dependencies: + glob: ^10.0.0 + graceful-fs: ^4.2.0 + is-stream: ^2.0.1 + lazystream: ^1.0.0 + lodash: ^4.17.15 + normalize-path: ^3.0.0 + readable-stream: ^4.0.0 + checksum: 7dc4f3001dc373bd0fa7671ebf08edf6f815cbc539c78b5478a2eaa67e52e3fc0e92f562cdef2ba016c4dcb5468d3d069eb89535c6844da4a5bb0baf08ad5720 + languageName: node + linkType: hard + +"archiver@npm:^5.0.0": version: 5.3.2 resolution: "archiver@npm:5.3.2" dependencies: @@ -9394,6 +9616,21 @@ __metadata: languageName: node linkType: hard +"archiver@npm:^7.0.1": + version: 7.0.1 + resolution: "archiver@npm:7.0.1" + dependencies: + archiver-utils: ^5.0.2 + async: ^3.2.4 + buffer-crc32: ^1.0.0 + readable-stream: ^4.0.0 + readdir-glob: ^1.1.2 + tar-stream: ^3.0.0 + zip-stream: ^6.0.1 + checksum: f93bcc00f919e0bbb6bf38fddf111d6e4d1ed34721b73cc073edd37278303a7a9f67aa4abd6fd2beb80f6c88af77f2eb4f60276343f67605e3aea404e5ad93ea + languageName: node + linkType: hard + "archy@npm:^1.0.0": version: 1.0.0 resolution: "archy@npm:1.0.0" @@ -9401,10 +9638,10 @@ __metadata: languageName: node linkType: hard -"arg@npm:2.0.0": - version: 2.0.0 - resolution: "arg@npm:2.0.0" - checksum: eeadcfa6160847452ac1973d1c6990e2133e50972d56f80f3601f83a465daa88431cb430cc12101d90b01719361a55a166b03f489143b6ba2acd2304714ebe74 +"arg@npm:5.0.2": + version: 5.0.2 + resolution: "arg@npm:5.0.2" + checksum: 6c69ada1a9943d332d9e5382393e897c500908d91d5cb735a01120d5f71daf1b339b7b8980cbeaba8fd1afc68e658a739746179e4315a26e8a28951ff9930078 languageName: node linkType: hard @@ -9776,17 +10013,6 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.7.4": - version: 1.7.4 - resolution: "axios@npm:1.7.4" - dependencies: - follow-redirects: ^1.15.6 - form-data: ^4.0.0 - proxy-from-env: ^1.1.0 - checksum: 0c17039a9acfe6a566fca8431ba5c1b455c83d30ea6157fec68a6722878fcd30f3bd32d172f6bee0c51fe75ca98e6414ddcd968a87b5606b573731629440bfaf - languageName: node - linkType: hard - "axobject-query@npm:2.0.2": version: 2.0.2 resolution: "axobject-query@npm:2.0.2" @@ -9805,6 +10031,13 @@ __metadata: languageName: node linkType: hard +"b4a@npm:^1.6.4": + version: 1.6.7 + resolution: "b4a@npm:1.6.7" + checksum: afe4e239b49c0ef62236fe0d788ac9bd9d7eac7e9855b0d1835593cd0efcc7be394f9cc28a747a2ed2cdcb0a48c3528a551a196f472eb625457c711169c9efa2 + languageName: node + linkType: hard + "babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -9937,6 +10170,57 @@ __metadata: languageName: node linkType: hard +"bare-events@npm:^2.0.0, bare-events@npm:^2.2.0": + version: 2.5.4 + resolution: "bare-events@npm:2.5.4" + checksum: 522a5401caaede9d8c857c2fd346c993bf43995e958e8ebfa79d32b1e086032800e0639f3559d7ad85788fae54f6d9605685de507eec54298ea2aa2c8c9cb2c3 + languageName: node + linkType: hard + +"bare-fs@npm:^4.0.1": + version: 4.0.1 + resolution: "bare-fs@npm:4.0.1" + dependencies: + bare-events: ^2.0.0 + bare-path: ^3.0.0 + bare-stream: ^2.0.0 + checksum: 80ae7ed1304182633252ce20f69d53bffd39e1a4f1387b309c2f2cf2a48732e8a5440405eb4a7250a3d8d1d2fb923a50bbb8aa67f85729c8a82e08dd09637a17 + languageName: node + linkType: hard + +"bare-os@npm:^3.0.1": + version: 3.4.0 + resolution: "bare-os@npm:3.4.0" + checksum: a473da6219f901b2101fac176ff271b28b9aee7e2d01b13b96320a56656c2f83a7d8275db89238ff46ed348638d86338761b6682684fbd0c4bb6b09201446047 + languageName: node + linkType: hard + +"bare-path@npm:^3.0.0": + version: 3.0.0 + resolution: "bare-path@npm:3.0.0" + dependencies: + bare-os: ^3.0.1 + checksum: 51d559515f332f62cf9c37c38f2640c1b84b5e8c9de454b70baf029f806058cf94c51d6a0dfec0025cc7760f2069dc3e16c82f0d24f4a9ddb18c829bf9c0206d + languageName: node + linkType: hard + +"bare-stream@npm:^2.0.0": + version: 2.6.5 + resolution: "bare-stream@npm:2.6.5" + dependencies: + streamx: ^2.21.0 + peerDependencies: + bare-buffer: "*" + bare-events: "*" + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + checksum: 6a3d4baf8ded0bdc465b7b0b65dfbb8e40f7520ee8899adcae5fd37949d5c520412164116659750ad841215b03ce761fe252a626cd4fe3ec9df0440c6fd07a96 + languageName: node + linkType: hard + "base64-js@npm:1.3.1": version: 1.3.1 resolution: "base64-js@npm:1.3.1" @@ -10006,10 +10290,10 @@ __metadata: languageName: node linkType: hard -"before-after-hook@npm:^2.2.0": - version: 2.2.3 - resolution: "before-after-hook@npm:2.2.3" - checksum: a1a2430976d9bdab4cd89cb50d27fa86b19e2b41812bf1315923b0cba03371ebca99449809226425dd3bcef20e010db61abdaff549278e111d6480034bebae87 +"before-after-hook@npm:^3.0.2": + version: 3.0.2 + resolution: "before-after-hook@npm:3.0.2" + checksum: 5f76a9d31909f7f1f7125b7e017ff018799308f5c1fc5a5bfeba9986149da77e6a5cdde0d151671cf374a7fa6452533237bb1de62dfd6c235c20e7c61cc9569d languageName: node linkType: hard @@ -10156,7 +10440,23 @@ __metadata: languageName: node linkType: hard -"boxen@npm:5.1.2, boxen@npm:^5.0.0, boxen@npm:^5.0.1, boxen@npm:^5.1.2": +"boxen@npm:7.0.0": + version: 7.0.0 + resolution: "boxen@npm:7.0.0" + dependencies: + ansi-align: ^3.0.1 + camelcase: ^7.0.0 + chalk: ^5.0.1 + cli-boxes: ^3.0.0 + string-width: ^5.1.2 + type-fest: ^2.13.0 + widest-line: ^4.0.1 + wrap-ansi: ^8.0.1 + checksum: b917cf7a168ef3149635a8c02d5c9717d66182348bd27038d85328ad12655151e3324db0f2815253846c33e5f0ddf28b6cd52d56a12b9f88617b7f8f722b946a + languageName: node + linkType: hard + +"boxen@npm:^5.0.0, boxen@npm:^5.0.1": version: 5.1.2 resolution: "boxen@npm:5.1.2" dependencies: @@ -10172,6 +10472,22 @@ __metadata: languageName: node linkType: hard +"boxen@npm:^8.0.1": + version: 8.0.1 + resolution: "boxen@npm:8.0.1" + dependencies: + ansi-align: ^3.0.1 + camelcase: ^8.0.0 + chalk: ^5.3.0 + cli-boxes: ^3.0.0 + string-width: ^7.2.0 + type-fest: ^4.21.0 + widest-line: ^5.0.0 + wrap-ansi: ^9.0.0 + checksum: f42d9e628e03e5c84ac9cda3173f75cadbdf60ed94fc06aaeef79f7c84a8181c4d79a8f40253192a1613993036c81811ad6957f346e5aa6abb7e9d1d799cbfd5 + languageName: node + linkType: hard + "bplist-parser@npm:^0.3.2": version: 0.3.2 resolution: "bplist-parser@npm:0.3.2" @@ -10317,13 +10633,6 @@ __metadata: languageName: node linkType: hard -"btoa-lite@npm:^1.0.0": - version: 1.0.0 - resolution: "btoa-lite@npm:1.0.0" - checksum: c2d61993b801f8e35a96f20692a45459c753d9baa29d86d1343e714f8d6bbe7069f1a20a5ae868488f3fb137d5bd0c560f6fbbc90b5a71050919d2d2c97c0475 - languageName: node - linkType: hard - "buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13, buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -10331,6 +10640,13 @@ __metadata: languageName: node linkType: hard +"buffer-crc32@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-crc32@npm:1.0.0" + checksum: bc114c0e02fe621249e0b5093c70e6f12d4c2b1d8ddaf3b1b7bbe3333466700100e6b1ebdc12c050d0db845bc582c4fce8c293da487cc483f97eea027c480b23 + languageName: node + linkType: hard + "buffer-equal-constant-time@npm:1.0.1": version: 1.0.1 resolution: "buffer-equal-constant-time@npm:1.0.1" @@ -10515,6 +10831,20 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^7.0.0": + version: 7.0.1 + resolution: "camelcase@npm:7.0.1" + checksum: 86ab8f3ebf08bcdbe605a211a242f00ed30d8bfb77dab4ebb744dd36efbc84432d1c4adb28975ba87a1b8be40a80fbd1e60e2f06565315918fa7350011a26d3d + languageName: node + linkType: hard + +"camelcase@npm:^8.0.0": + version: 8.0.0 + resolution: "camelcase@npm:8.0.0" + checksum: 6da7abe997af29e80052f17aa21628c7cce14af364cef9f07a2a44d59614dd6f361d405f121938e673424d673697a8c53ad17be8c4b03b0a727307c4db8b5b5e + languageName: node + linkType: hard + "caniuse-lite@npm:^1.0.30001578": version: 1.0.30001589 resolution: "caniuse-lite@npm:1.0.30001589" @@ -10592,14 +10922,12 @@ __metadata: languageName: node linkType: hard -"chalk@npm:2.4.1": - version: 2.4.1 - resolution: "chalk@npm:2.4.1" +"chalk-template@npm:0.4.0": + version: 0.4.0 + resolution: "chalk-template@npm:0.4.0" dependencies: - ansi-styles: ^3.2.1 - escape-string-regexp: ^1.0.5 - supports-color: ^5.3.0 - checksum: 196eb8e99a0c00c6fcef216c794b109fd071931b10ec0f8f305c368fb6d776d0d4857d0028d2cc3561632428d6d94a41d7edf33b506f7540b7c8492f4224d89e + chalk: ^4.1.2 + checksum: 6c706802a79a7963cbce18f022b046fe86e438a67843151868852f80ea7346e975a6a9749991601e7e5d3b6a6c4852a04c53dc966a9a3d04031bd0e0ed53c819 languageName: node linkType: hard @@ -10613,6 +10941,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:5.0.1": + version: 5.0.1 + resolution: "chalk@npm:5.0.1" + checksum: 7b45300372b908f0471fbf7389ce2f5de8d85bb949026fd51a1b95b10d0ed32c7ed5aab36dd5e9d2bf3191867909b4404cef75c5f4d2d1daeeacd301dd280b76 + languageName: node + linkType: hard + "chalk@npm:5.3.0, chalk@npm:^5.2.0, chalk@npm:^5.3.0": version: 5.3.0 resolution: "chalk@npm:5.3.0" @@ -10644,6 +10979,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.0.1, chalk@npm:^5.4.1": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: 0c656f30b782fed4d99198825c0860158901f449a6b12b818b0aabad27ec970389e7e8767d0e00762175b23620c812e70c4fd92c0210e55fc2d993638b74e86e + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -10769,6 +11111,18 @@ __metadata: languageName: node linkType: hard +"chromium-bidi@npm:1.1.0": + version: 1.1.0 + resolution: "chromium-bidi@npm:1.1.0" + dependencies: + mitt: 3.0.1 + zod: 3.24.1 + peerDependencies: + devtools-protocol: "*" + checksum: 1c2824c96eebe27e7e1b91241af361245a1978a51b03a66cf09203c9082255398ca702f75222444d44089598279268e415aa66af431922f610182a92596968de + languageName: node + linkType: hard + "ci-info@npm:^2.0.0": version: 2.0.0 resolution: "ci-info@npm:2.0.0" @@ -10823,6 +11177,13 @@ __metadata: languageName: node linkType: hard +"cli-boxes@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-boxes@npm:3.0.0" + checksum: 637d84419d293a9eac40a1c8c96a2859e7d98b24a1a317788e13c8f441be052fc899480c6acab3acc82eaf1bccda6b7542d7cdcf5c9c3cc39227175dc098d5b2 + languageName: node + linkType: hard + "cli-color@npm:^2.0.3": version: 2.0.3 resolution: "cli-color@npm:2.0.3" @@ -10854,6 +11215,15 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: ^5.0.0 + checksum: 1eb9a3f878b31addfe8d82c6d915ec2330cec8447ab1f117f4aa34f0137fbb3137ec3466e1c9a65bcb7557f6e486d343f2da57f253a2f668d691372dfa15c090 + languageName: node + linkType: hard + "cli-spinners@npm:2.6.1": version: 2.6.1 resolution: "cli-spinners@npm:2.6.1" @@ -10924,14 +11294,14 @@ __metadata: languageName: node linkType: hard -"clipboardy@npm:2.3.0": - version: 2.3.0 - resolution: "clipboardy@npm:2.3.0" +"clipboardy@npm:3.0.0": + version: 3.0.0 + resolution: "clipboardy@npm:3.0.0" dependencies: - arch: ^2.1.1 - execa: ^1.0.0 - is-wsl: ^2.1.1 - checksum: 2733790bc8bbb76a5be7706fa4632f655010774e579a9d3ebe31dc10cf44a2b82cf07b0b6f74162e63048ce32d912193c08c5b5311dce5c19fc641a3bda1292b + arch: ^2.2.0 + execa: ^5.1.1 + is-wsl: ^2.2.0 + checksum: 2c292acb59705494cbe07d7df7c8becff4f01651514d32ebd80f4aec2d20946d8f3824aac67ecdf2d09ef21fdf0eb24b6a7f033c137ccdceedc4661c54455c94 languageName: node linkType: hard @@ -11193,6 +11563,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^13.1.0": + version: 13.1.0 + resolution: "commander@npm:13.1.0" + checksum: 8ca2fcb33caf2aa06fba3722d7a9440921331d54019dabf906f3603313e7bf334b009b862257b44083ff65d5a3ab19e83ad73af282bd5319f01dc228bdf87ef0 + languageName: node + linkType: hard + "commander@npm:^2.11.0, commander@npm:^2.19.0, commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -11207,7 +11584,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^8.1.0, commander@npm:^8.2.0, commander@npm:^8.3.0": +"commander@npm:^8.1.0, commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" checksum: 0f82321821fc27b83bd409510bb9deeebcfa799ff0bf5d102128b500b7af22872c0c92cb6a0ebc5a4cf19c6b550fba9cedfa7329d18c6442a625f851377bacf0 @@ -11301,31 +11678,29 @@ __metadata: languageName: node linkType: hard -"compressible@npm:~2.0.14, compressible@npm:~2.0.16": - version: 2.0.18 - resolution: "compressible@npm:2.0.18" +"compress-commons@npm:^6.0.2": + version: 6.0.2 + resolution: "compress-commons@npm:6.0.2" dependencies: - mime-db: ">= 1.43.0 < 2" - checksum: 58321a85b375d39230405654721353f709d0c1442129e9a17081771b816302a012471a9b8f4864c7dbe02eef7f2aaac3c614795197092262e94b409c9be108f0 + crc-32: ^1.2.0 + crc32-stream: ^6.0.0 + is-stream: ^2.0.1 + normalize-path: ^3.0.0 + readable-stream: ^4.0.0 + checksum: 37d79a54f91344ecde352588e0a128f28ce619b085acd4f887defd76978a0640e3454a42c7dcadb0191bb3f971724ae4b1f9d6ef9620034aa0427382099ac946 languageName: node linkType: hard -"compression@npm:1.7.3": - version: 1.7.3 - resolution: "compression@npm:1.7.3" +"compressible@npm:~2.0.16": + version: 2.0.18 + resolution: "compressible@npm:2.0.18" dependencies: - accepts: ~1.3.5 - bytes: 3.0.0 - compressible: ~2.0.14 - debug: 2.6.9 - on-headers: ~1.0.1 - safe-buffer: 5.1.2 - vary: ~1.1.2 - checksum: f1c24d9d3f30f6ae7ac57a41078ec90ca514112e6d21fc992d1d79d904a2eedb2a96620806f8de9ab85a75dbec94ef9b6dded9a06a6d72faa9bc8c4e3c375072 + mime-db: ">= 1.43.0 < 2" + checksum: 58321a85b375d39230405654721353f709d0c1442129e9a17081771b816302a012471a9b8f4864c7dbe02eef7f2aaac3c614795197092262e94b409c9be108f0 languageName: node linkType: hard -"compression@npm:^1.7.0, compression@npm:^1.7.4": +"compression@npm:1.7.4, compression@npm:^1.7.0, compression@npm:^1.7.4": version: 1.7.4 resolution: "compression@npm:1.7.4" dependencies: @@ -11658,6 +12033,16 @@ __metadata: languageName: node linkType: hard +"crc32-stream@npm:^6.0.0": + version: 6.0.0 + resolution: "crc32-stream@npm:6.0.0" + dependencies: + crc-32: ^1.2.0 + readable-stream: ^4.0.0 + checksum: e6edc2f81bc387daef6d18b2ac18c2ffcb01b554d3b5c7d8d29b177505aafffba574658fdd23922767e8dab1183d1962026c98c17e17fb272794c33293ef607c + languageName: node + linkType: hard + "create-jest@npm:^29.7.0": version: 29.7.0 resolution: "create-jest@npm:29.7.0" @@ -12112,7 +12497,7 @@ __metadata: languageName: node linkType: hard -"data-models@1.0.0, data-models@workspace:*, data-models@workspace:packages/data-models": +"data-models@workspace:*, data-models@workspace:packages/data-models": version: 0.0.0-use.local resolution: "data-models@workspace:packages/data-models" dependencies: @@ -12219,6 +12604,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.4.0": + version: 4.4.0 + resolution: "debug@npm:4.4.0" + dependencies: + ms: ^2.1.3 + peerDependenciesMeta: + supports-color: + optional: true + checksum: fb42df878dd0e22816fc56e1fdca9da73caa85212fbe40c868b1295a6878f9101ae684f4eeef516c13acfc700f5ea07f1136954f43d4cd2d477a811144136479 + languageName: node + linkType: hard + "decache@npm:^4.6.2": version: 4.6.2 resolution: "decache@npm:4.6.2" @@ -12434,13 +12831,6 @@ __metadata: languageName: node linkType: hard -"deprecation@npm:^2.0.0, deprecation@npm:^2.3.1": - version: 2.3.1 - resolution: "deprecation@npm:2.3.1" - checksum: f56a05e182c2c195071385455956b0c4106fe14e36245b00c689ceef8e8ab639235176a96977ba7c74afb173317fac2e0ec6ec7a1c6d1e6eaa401c586c714132 - languageName: node - linkType: hard - "dequal@npm:^2.0.3": version: 2.0.3 resolution: "dequal@npm:2.0.3" @@ -12485,10 +12875,10 @@ __metadata: languageName: node linkType: hard -"devtools-protocol@npm:0.0.901419": - version: 0.0.901419 - resolution: "devtools-protocol@npm:0.0.901419" - checksum: de68331ddfb35b828ad743d939d9237e122f76d4a6cbf1e64f6c6d8e9c2c5cc65a5f1994db0fead90192cca1aa9dbed2ea822a7da7b58104cd041a90e215b9a3 +"devtools-protocol@npm:0.0.1380148": + version: 0.0.1380148 + resolution: "devtools-protocol@npm:0.0.1380148" + checksum: 2d9e86bc3699ba634410a385ebaede8ce51ddc2290f985ebf527e151369a990f955e192c334fef05af1031eb083cddb20d642cd56ea59ff442e3b0234ba6aca8 languageName: node linkType: hard @@ -12779,6 +13169,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.4.7": + version: 16.4.7 + resolution: "dotenv@npm:16.4.7" + checksum: c27419b5875a44addcc56cc69b7dc5b0e6587826ca85d5b355da9303c6fc317fc9989f1f18366a16378c9fdd9532d14117a1abe6029cc719cdbbef6eaef2cea4 + languageName: node + linkType: hard + "dotenv@npm:~16.3.1": version: 16.3.2 resolution: "dotenv@npm:16.3.2" @@ -13098,6 +13495,13 @@ __metadata: languageName: node linkType: hard +"environment@npm:^1.0.0": + version: 1.1.0 + resolution: "environment@npm:1.1.0" + checksum: dd3c1b9825e7f71f1e72b03c2344799ac73f2e9ef81b78ea8b373e55db021786c6b9f3858ea43a436a2c4611052670ec0afe85bc029c384cc71165feee2f4ba6 + languageName: node + linkType: hard + "err-code@npm:^2.0.2": version: 2.0.3 resolution: "err-code@npm:2.0.3" @@ -13573,6 +13977,89 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:~0.23.0": + version: 0.23.1 + resolution: "esbuild@npm:0.23.1" + dependencies: + "@esbuild/aix-ppc64": 0.23.1 + "@esbuild/android-arm": 0.23.1 + "@esbuild/android-arm64": 0.23.1 + "@esbuild/android-x64": 0.23.1 + "@esbuild/darwin-arm64": 0.23.1 + "@esbuild/darwin-x64": 0.23.1 + "@esbuild/freebsd-arm64": 0.23.1 + "@esbuild/freebsd-x64": 0.23.1 + "@esbuild/linux-arm": 0.23.1 + "@esbuild/linux-arm64": 0.23.1 + "@esbuild/linux-ia32": 0.23.1 + "@esbuild/linux-loong64": 0.23.1 + "@esbuild/linux-mips64el": 0.23.1 + "@esbuild/linux-ppc64": 0.23.1 + "@esbuild/linux-riscv64": 0.23.1 + "@esbuild/linux-s390x": 0.23.1 + "@esbuild/linux-x64": 0.23.1 + "@esbuild/netbsd-x64": 0.23.1 + "@esbuild/openbsd-arm64": 0.23.1 + "@esbuild/openbsd-x64": 0.23.1 + "@esbuild/sunos-x64": 0.23.1 + "@esbuild/win32-arm64": 0.23.1 + "@esbuild/win32-ia32": 0.23.1 + "@esbuild/win32-x64": 0.23.1 + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 0413c3b9257327fb598427688b7186ea335bf1693746fe5713cc93c95854d6388b8ed4ad643fddf5b5ace093f7dcd9038dd58e087bf2da1f04dfb4c5571660af + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.2 resolution: "escalade@npm:3.1.2" @@ -14207,7 +14694,7 @@ __metadata: languageName: node linkType: hard -"events@npm:^3.2.0": +"events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780 @@ -14263,7 +14750,7 @@ __metadata: languageName: node linkType: hard -"execa@npm:^5.0.0": +"execa@npm:^5.0.0, execa@npm:^5.1.1": version: 5.1.1 resolution: "execa@npm:5.1.1" dependencies: @@ -14470,6 +14957,13 @@ __metadata: languageName: node linkType: hard +"fake-indexeddb@npm:^6.0.0": + version: 6.0.0 + resolution: "fake-indexeddb@npm:6.0.0" + checksum: c55bb1f54c1c910a6ca9e18b376ebecc51a29eceb829550d415aa34c2e246730a76dd35dc4fd6d8ae5c5790b9b4f180859e9457bcb73f2d167cc87aa04b6201e + languageName: node + linkType: hard + "fancy-log@npm:^2.0.0": version: 2.0.0 resolution: "fancy-log@npm:2.0.0" @@ -14479,6 +14973,13 @@ __metadata: languageName: node linkType: hard +"fast-content-type-parse@npm:^2.0.0": + version: 2.0.1 + resolution: "fast-content-type-parse@npm:2.0.1" + checksum: 0ea4c7dce77c579d19805ea874d128832f535086465c57994a49a28a4784538ea4eeaa49261a5c675a4764c634e12a74bae26e09d64e886cb826c0b97e4c621d + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -14500,6 +15001,13 @@ __metadata: languageName: node linkType: hard +"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": + version: 1.3.2 + resolution: "fast-fifo@npm:1.3.2" + checksum: 6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 + languageName: node + linkType: hard + "fast-glob@npm:3.3.2, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" @@ -14541,7 +15049,7 @@ __metadata: languageName: node linkType: hard -"fast-url-parser@npm:1.1.3, fast-url-parser@npm:^1.1.3": +"fast-url-parser@npm:^1.1.3": version: 1.1.3 resolution: "fast-url-parser@npm:1.1.3" dependencies: @@ -15187,6 +15695,7 @@ __metadata: eslint-plugin-prefer-arrow: 1.2.3 eslint-plugin-promise: ^6.1.1 extract-math: ^1.2.3 + fake-indexeddb: ^6.0.0 file-saver: ^2.0.5 firebase: ^10.9.0 firebase-tools: ^13.6.0 @@ -15204,6 +15713,7 @@ __metadata: karma-coverage: ~2.2.0 karma-jasmine: ~5.1.0 karma-jasmine-html-reporter: ~2.0.0 + karma-json-result-reporter: ^1.0.0 katex: ^0.16.21 lint-staged: ^15.2.2 lottie-web: ^5.12.2 @@ -15260,6 +15770,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.3.0": + version: 11.3.0 + resolution: "fs-extra@npm:11.3.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: f983c706e0c22b0c0747a8e9c76aed6f391ba2d76734cf2757cd84da13417b402ed68fe25bace65228856c61d36d3b41da198f1ffbf33d0b34283a2f7a62c6e9 + languageName: node + linkType: hard + "fs-extra@npm:^7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" @@ -15355,6 +15876,13 @@ __metadata: languageName: node linkType: hard +"fun-map@npm:^3.3.1": + version: 3.3.1 + resolution: "fun-map@npm:3.3.1" + checksum: 317cce13550b54563d9c4b85276ff97095b91fc2cff2be6f39e47c92b8a7d6ef7cc4d167b451630423fa0d25cca639173f1d3398a0903af51ac371fcb6ffecc8 + languageName: node + linkType: hard + "function-bind@npm:^1.1.2": version: 1.1.2 resolution: "function-bind@npm:1.1.2" @@ -15589,6 +16117,15 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.7.5": + version: 4.10.0 + resolution: "get-tsconfig@npm:4.10.0" + dependencies: + resolve-pkg-maps: ^1.0.0 + checksum: cebf14d38ecaa9a1af25fc3f56317402a4457e7e20f30f52a0ab98b4c85962a259f75065e483824f73a1ce4a8e4926c149ead60f0619842b8cd13b94e15fbdec + languageName: node + linkType: hard + "get-uri@npm:^6.0.1": version: 6.0.3 resolution: "get-uri@npm:6.0.3" @@ -15710,6 +16247,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.0.0": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^3.1.2 + minimatch: ^9.0.4 + minipass: ^7.1.2 + package-json-from-dist: ^1.0.0 + path-scurry: ^1.11.1 + bin: + glob: dist/esm/bin.mjs + checksum: 0bc725de5e4862f9f387fd0f2b274baf16850dcd2714502ccf471ee401803997983e2c05590cb65f9675a3c6f2a58e7a53f9e365704108c6ad3cbf1d60934c4a + languageName: node + linkType: hard + "glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.3, glob@npm:^10.3.7": version: 10.3.10 resolution: "glob@npm:10.3.10" @@ -16361,16 +16914,6 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:5.0.0": - version: 5.0.0 - resolution: "https-proxy-agent@npm:5.0.0" - dependencies: - agent-base: 6 - debug: 4 - checksum: 165bfb090bd26d47693597661298006841ab733d0c7383a8cb2f17373387a94c903a3ac687090aa739de05e379ab6f868bae84ab4eac288ad85c328cd1ec9e53 - languageName: node - linkType: hard - "https-proxy-agent@npm:7.0.2": version: 7.0.2 resolution: "https-proxy-agent@npm:7.0.2" @@ -16401,6 +16944,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^7.0.6": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: ^7.1.2 + debug: 4 + checksum: b882377a120aa0544846172e5db021fa8afbf83fea2a897d397bd2ddd8095ab268c24bc462f40a15f2a8c600bf4aa05ce52927f70038d4014e68aefecfa94e8d + languageName: node + linkType: hard + "human-signals@npm:^1.1.1": version: 1.1.1 resolution: "human-signals@npm:1.1.1" @@ -17130,6 +17683,13 @@ __metadata: languageName: node linkType: hard +"is-port-reachable@npm:4.0.0": + version: 4.0.0 + resolution: "is-port-reachable@npm:4.0.0" + checksum: 47b7e10db8edcef27fbf9e50f0de85ad368d35688790ca64a13db67260111ac5f4b98989b11af06199fa93f25d810bd09a5b21b2c2646529668638f7c34d3c04 + languageName: node + linkType: hard + "is-potential-custom-element-name@npm:^1.0.1": version: 1.0.1 resolution: "is-potential-custom-element-name@npm:1.0.1" @@ -17184,7 +17744,7 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^2.0.0": +"is-stream@npm:^2.0.0, is-stream@npm:^2.0.1": version: 2.0.1 resolution: "is-stream@npm:2.0.1" checksum: b8e05ccdf96ac330ea83c12450304d4a591f9958c11fd17bed240af8d5ffe08aedafa4c0f4cfccd4d28dc9d4d129daca1023633d5c11601a6cbc77521f6fae66 @@ -17508,6 +18068,19 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: be31027fc72e7cc726206b9f560395604b82e0fddb46c4cbf9f97d049bcef607491a5afc0699612eaa4213ca5be8fd3e1e7cd187b3040988b65c9489838a7c00 + languageName: node + linkType: hard + "jake@npm:^10.8.5": version: 10.8.7 resolution: "jake@npm:10.8.7" @@ -18457,7 +19030,7 @@ __metadata: languageName: node linkType: hard -"jsonwebtoken@npm:^9.0.0, jsonwebtoken@npm:^9.0.2": +"jsonwebtoken@npm:^9.0.0": version: 9.0.2 resolution: "jsonwebtoken@npm:9.0.2" dependencies: @@ -18574,6 +19147,15 @@ __metadata: languageName: node linkType: hard +"karma-json-result-reporter@npm:^1.0.0": + version: 1.0.0 + resolution: "karma-json-result-reporter@npm:1.0.0" + dependencies: + fun-map: ^3.3.1 + checksum: 0f97c7016f964e1df2ef8a98a649bc061f81d7222fcaedfde57772aff3a5097daa109fdc7d28955cf72af41c0e44e694d866fea2f878a2012a7a6b40b1665420 + languageName: node + linkType: hard + "karma-safari-launcher@npm:^1.0.0": version: 1.0.0 resolution: "karma-safari-launcher@npm:1.0.0" @@ -19310,6 +19892,19 @@ __metadata: languageName: node linkType: hard +"log-update@npm:^6.1.0": + version: 6.1.0 + resolution: "log-update@npm:6.1.0" + dependencies: + ansi-escapes: ^7.0.0 + cli-cursor: ^5.0.0 + slice-ansi: ^7.1.0 + strip-ansi: ^7.1.0 + wrap-ansi: ^9.0.0 + checksum: 817a9ba6c5cbc19e94d6359418df8cfe8b3244a2903f6d53354e175e243a85b782dc6a98db8b5e457ee2f09542ca8916c39641b9cd3b0e6ef45e9481d50c918a + languageName: node + linkType: hard + "log4js@npm:^6.4.1": version: 6.9.1 resolution: "log4js@npm:6.9.1" @@ -19388,13 +19983,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.0.0": - version: 10.1.0 - resolution: "lru-cache@npm:10.1.0" - checksum: 58056d33e2500fbedce92f8c542e7c11b50d7d086578f14b7074d8c241422004af0718e08a6eaae8705cee09c77e39a61c1c79e9370ba689b7010c152e6a76ab - languageName: node - linkType: hard - "lru-cache@npm:^10.0.1, lru-cache@npm:^9.1.1 || ^10.0.0": version: 10.2.0 resolution: "lru-cache@npm:10.2.0" @@ -19402,6 +19990,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 6476138d2125387a6d20f100608c2583d415a4f64a0fecf30c9e2dda976614f09cad4baa0842447bd37dd459a7bd27f57d9d8f8ce558805abd487c583f3d774a + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -19860,6 +20455,13 @@ __metadata: languageName: node linkType: hard +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: eb5893c99e902ccebbc267c6c6b83092966af84682957f79313311edb95e8bb5f39fb048d77132b700474d1c86d90ccc211e99bae0935447a4834eb4c882982c + languageName: node + linkType: hard + "mimic-response@npm:^3.1.0": version: 3.1.0 resolution: "mimic-response@npm:3.1.0" @@ -19893,21 +20495,21 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:3.0.4": - version: 3.0.4 - resolution: "minimatch@npm:3.0.4" +"minimatch@npm:3.0.5": + version: 3.0.5 + resolution: "minimatch@npm:3.0.5" dependencies: brace-expansion: ^1.1.7 - checksum: 66ac295f8a7b59788000ea3749938b0970344c841750abd96694f80269b926ebcafad3deeb3f1da2522978b119e6ae3a5869b63b13a7859a456b3408bd18a078 + checksum: a3b84b426eafca947741b864502cee02860c4e7b145de11ad98775cfcf3066fef422583bc0ffce0952ddf4750c1ccf4220b1556430d4ce10139f66247d87d69e languageName: node linkType: hard -"minimatch@npm:3.0.5": - version: 3.0.5 - resolution: "minimatch@npm:3.0.5" +"minimatch@npm:3.1.2, minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" dependencies: brace-expansion: ^1.1.7 - checksum: a3b84b426eafca947741b864502cee02860c4e7b145de11ad98775cfcf3066fef422583bc0ffce0952ddf4750c1ccf4220b1556430d4ce10139f66247d87d69e + checksum: c154e566406683e7bcb746e000b84d74465b3a832c45d59912b9b55cd50dee66e5c4b1e5566dba26154040e51672f9aa450a9aef0c97cfc7336b78b7afb9540a languageName: node linkType: hard @@ -19947,15 +20549,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: ^1.1.7 - checksum: c154e566406683e7bcb746e000b84d74465b3a832c45d59912b9b55cd50dee66e5c4b1e5566dba26154040e51672f9aa450a9aef0c97cfc7336b78b7afb9540a - languageName: node - linkType: hard - "minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": version: 5.1.6 resolution: "minimatch@npm:5.1.6" @@ -20099,6 +20692,13 @@ __metadata: languageName: node linkType: hard +"minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 2bfd325b95c555f2b4d2814d49325691c7bee937d753814861b0b49d5edcda55cbbf22b6b6a60bb91eddac8668771f03c5ff647dcd9d0f798e9548b9cdc46ee3 + languageName: node + linkType: hard + "minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": version: 2.1.2 resolution: "minizlib@npm:2.1.2" @@ -20109,6 +20709,13 @@ __metadata: languageName: node linkType: hard +"mitt@npm:3.0.1": + version: 3.0.1 + resolution: "mitt@npm:3.0.1" + checksum: b55a489ac9c2949ab166b7f060601d3b6d893a852515ae9eca4e11df01c013876df777ea109317622b5c1c60e8aae252558e33c8c94e14124db38f64a39614b1 + languageName: node + linkType: hard + "mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": version: 0.5.3 resolution: "mkdirp-classic@npm:0.5.3" @@ -20322,7 +20929,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -20637,13 +21244,6 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:2.6.1": - version: 2.6.1 - resolution: "node-fetch@npm:2.6.1" - checksum: 91075bedd57879117e310fbcc36983ad5d699e522edb1ebcdc4ee5294c982843982652925c3532729fdc86b2d64a8a827797a745f332040d91823c8752ee4d7c - languageName: node - linkType: hard - "node-fetch@npm:2.6.7": version: 2.6.7 resolution: "node-fetch@npm:2.6.7" @@ -21185,21 +21785,21 @@ __metadata: languageName: node linkType: hard -"octokit@npm:^3.1.2": - version: 3.1.2 - resolution: "octokit@npm:3.1.2" +"octokit@npm:^4.1.0": + version: 4.1.0 + resolution: "octokit@npm:4.1.0" dependencies: - "@octokit/app": ^14.0.2 - "@octokit/core": ^5.0.0 - "@octokit/oauth-app": ^6.0.0 - "@octokit/plugin-paginate-graphql": ^4.0.0 - "@octokit/plugin-paginate-rest": ^9.0.0 - "@octokit/plugin-rest-endpoint-methods": ^10.0.0 - "@octokit/plugin-retry": ^6.0.0 - "@octokit/plugin-throttling": ^8.0.0 - "@octokit/request-error": ^5.0.0 - "@octokit/types": ^12.0.0 - checksum: 8ef0d57a0e0129da5a3fddf2b2150c3214fd19b5cbefdd2fe7fa4604f45aae90587080ce1498fadef6714d385835ee00b7c86c526f31d67c93fcd545e041568c + "@octokit/app": ^15.1.2 + "@octokit/core": ^6.1.3 + "@octokit/oauth-app": ^7.1.4 + "@octokit/plugin-paginate-graphql": ^5.2.4 + "@octokit/plugin-paginate-rest": ^11.4.0 + "@octokit/plugin-rest-endpoint-methods": ^13.3.0 + "@octokit/plugin-retry": ^7.1.3 + "@octokit/plugin-throttling": ^9.4.0 + "@octokit/request-error": ^6.1.6 + "@octokit/types": ^13.7.0 + checksum: 625d81c77288dc8cb3f6d9a976677734499edcee33b8e155914c2bc4a26c36e8c1bd7fe7ae0e66bea396714294b2b0bd82b17052a5c0322333a0a181e231e3ef languageName: node linkType: hard @@ -21228,7 +21828,7 @@ __metadata: languageName: node linkType: hard -"on-headers@npm:^1.0.0, on-headers@npm:~1.0.1, on-headers@npm:~1.0.2": +"on-headers@npm:^1.0.0, on-headers@npm:~1.0.2": version: 1.0.2 resolution: "on-headers@npm:1.0.2" checksum: 2bf13467215d1e540a62a75021e8b318a6cfc5d4fc53af8e8f84ad98dbcea02d506c6d24180cd62e1d769c44721ba542f3154effc1f7579a8288c9f7873ed8e5 @@ -21271,6 +21871,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: ^5.0.0 + checksum: eb08d2da9339819e2f9d52cab9caf2557d80e9af8c7d1ae86e1a0fef027d00a88e9f5bd67494d350df360f7c559fbb44e800b32f310fb989c860214eacbb561c + languageName: node + linkType: hard + "open@npm:8.4.0": version: 8.4.0 resolution: "open@npm:8.4.0" @@ -21504,6 +22113,16 @@ __metadata: languageName: node linkType: hard +"p-queue@npm:^8.1.0": + version: 8.1.0 + resolution: "p-queue@npm:8.1.0" + dependencies: + eventemitter3: ^5.0.1 + p-timeout: ^6.1.2 + checksum: b6602ef0f3bd22ebfd916e0fe42ae1388090c63d8fa667fd7b29137e44970653adc5a9f39bedcbb0980ad4ab2cca8c04dbfdd9085b241d12c13f1d9dd02c497e + languageName: node + linkType: hard + "p-retry@npm:^4.5.0": version: 4.6.2 resolution: "p-retry@npm:4.6.2" @@ -21530,6 +22149,13 @@ __metadata: languageName: node linkType: hard +"p-timeout@npm:^6.1.2": + version: 6.1.4 + resolution: "p-timeout@npm:6.1.4" + checksum: 0fb7bcac2cf49a97b44f881accfdd1057560a4d8657d75c32c4ebc9d75c0a4a09107f32491bcfedb3d8c0b95d06407beb004d880d6386fa58492ab40cd85a1c5 + languageName: node + linkType: hard + "p-try@npm:^2.0.0": version: 2.2.0 resolution: "p-try@npm:2.2.0" @@ -21553,7 +22179,23 @@ __metadata: languageName: node linkType: hard -"pac-resolver@npm:^7.0.0": +"pac-proxy-agent@npm:^7.1.0": + version: 7.1.0 + resolution: "pac-proxy-agent@npm:7.1.0" + dependencies: + "@tootallnate/quickjs-emscripten": ^0.23.0 + agent-base: ^7.1.2 + debug: ^4.3.4 + get-uri: ^6.0.1 + http-proxy-agent: ^7.0.0 + https-proxy-agent: ^7.0.6 + pac-resolver: ^7.0.1 + socks-proxy-agent: ^8.0.5 + checksum: 0ed8ebca239b5c78f7c5039ec0e33aaf6ce8de2fb53d00996b5b7b362e655af9793721008ddf56c4b1d30fb5202b2cb5baee97e374ed1285c0cfb5be7c4574a5 + languageName: node + linkType: hard + +"pac-resolver@npm:^7.0.0, pac-resolver@npm:^7.0.1": version: 7.0.1 resolution: "pac-resolver@npm:7.0.1" dependencies: @@ -21575,6 +22217,13 @@ __metadata: languageName: node linkType: hard +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 58ee9538f2f762988433da00e26acc788036914d57c71c246bf0be1b60cdbd77dd60b6a3e1a30465f0b248aeb80079e0b34cb6050b1dfa18c06953bb1cbc7602 + languageName: node + linkType: hard + "packet-reader@npm:1.0.0": version: 1.0.0 resolution: "packet-reader@npm:1.0.0" @@ -21801,6 +22450,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: ^10.2.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: 890d5abcd593a7912dcce7cf7c6bf7a0b5648e3dee6caf0712c126ca0a65c7f3d7b9d769072a4d1baf370f61ce493ab5b038d59988688e0c5f3f646ee3c69023 + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.7": version: 0.1.7 resolution: "path-to-regexp@npm:0.1.7" @@ -21808,13 +22467,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:2.2.1": - version: 2.2.1 - resolution: "path-to-regexp@npm:2.2.1" - checksum: b921a74e7576e25b06ad1635abf7e8125a29220d2efc2b71d74b9591f24a27e6f09078fa9a1b27516a097ea0637b7cab79d19b83d7f36a8ef3ef5422770e89d9 - languageName: node - linkType: hard - "path-to-regexp@npm:3.2.0": version: 3.2.0 resolution: "path-to-regexp@npm:3.2.0" @@ -21822,6 +22474,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:3.3.0": + version: 3.3.0 + resolution: "path-to-regexp@npm:3.3.0" + checksum: bb249d08804f7961dd44fb175466c900b893c56e909db8e2a66ec12b9d9a964af269eb7a50892c933f52b47315953dfdb4279639fbce20977c3625a9ef3055fe + languageName: node + linkType: hard + "path-to-regexp@npm:^1.8.0": version: 1.8.0 resolution: "path-to-regexp@npm:1.8.0" @@ -22076,7 +22735,7 @@ __metadata: languageName: node linkType: hard -"pixelmatch@npm:^5.1.0, pixelmatch@npm:^5.2.1": +"pixelmatch@npm:^5.1.0": version: 5.3.0 resolution: "pixelmatch@npm:5.3.0" dependencies: @@ -22087,12 +22746,14 @@ __metadata: languageName: node linkType: hard -"pkg-dir@npm:4.2.0, pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0": - version: 4.2.0 - resolution: "pkg-dir@npm:4.2.0" +"pixelmatch@npm:^6.0.0": + version: 6.0.0 + resolution: "pixelmatch@npm:6.0.0" dependencies: - find-up: ^4.0.0 - checksum: 9863e3f35132bf99ae1636d31ff1e1e3501251d480336edb1c211133c8d58906bed80f154a1d723652df1fda91e01c7442c2eeaf9dc83157c7ae89087e43c8d6 + pngjs: ^7.0.0 + bin: + pixelmatch: bin/pixelmatch + checksum: 89066e7904083f6e7de86afd26a51ec63fa311f51ee2ca2019eb2284e2b25d84f826da3f86699f857f4e13633dcc7e55912d5de2698f3128b63c14a633f85dcf languageName: node linkType: hard @@ -22105,6 +22766,15 @@ __metadata: languageName: node linkType: hard +"pkg-dir@npm:^4.1.0, pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: ^4.0.0 + checksum: 9863e3f35132bf99ae1636d31ff1e1e3501251d480336edb1c211133c8d58906bed80f154a1d723652df1fda91e01c7442c2eeaf9dc83157c7ae89087e43c8d6 + languageName: node + linkType: hard + "pkg-dir@npm:^7.0.0": version: 7.0.0 resolution: "pkg-dir@npm:7.0.0" @@ -22167,6 +22837,13 @@ __metadata: languageName: node linkType: hard +"pngjs@npm:^7.0.0": + version: 7.0.0 + resolution: "pngjs@npm:7.0.0" + checksum: b19a018930d27de26229c1b3ff250b3a25d09caa22cbb0b0459987d91eb0a560a18ab5d67da45a38ed7514140f26d1db58de83c31159ec101f2bb270a3c707f1 + languageName: node + linkType: hard + "pony-cause@npm:^2.1.2": version: 2.1.10 resolution: "pony-cause@npm:2.1.10" @@ -22485,10 +23162,10 @@ __metadata: languageName: node linkType: hard -"progress@npm:2.0.1": - version: 2.0.1 - resolution: "progress@npm:2.0.1" - checksum: 46d1f5a5df9c331f6402d856a4239f90a8fde8f9fcff0426ceb4edca7a7a3b4256d83adcfb3d4176a1dd239536a43e547bd0f325f5e8c4ac2881169361028426 +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: bfcce49814f7d172a6e6a14d5fa3ac92cc3d0c3b9feb1279774708a719e19acd673995226351a082a9ae99978254e320ccda4240ddc474ba31a76c79491ca7c3 languageName: node linkType: hard @@ -22671,6 +23348,22 @@ __metadata: languageName: node linkType: hard +"proxy-agent@npm:^6.5.0": + version: 6.5.0 + resolution: "proxy-agent@npm:6.5.0" + dependencies: + agent-base: ^7.1.2 + debug: ^4.3.4 + http-proxy-agent: ^7.0.1 + https-proxy-agent: ^7.0.6 + lru-cache: ^7.14.1 + pac-proxy-agent: ^7.1.0 + proxy-from-env: ^1.1.0 + socks-proxy-agent: ^8.0.5 + checksum: d03ad2d171c2768280ade7ea6a7c5b1d0746215d70c0a16e02780c26e1d347edd27b3f48374661ae54ec0f7b41e6e45175b687baf333b36b1fd109a525154806 + languageName: node + linkType: hard + "proxy-from-env@npm:1.0.0": version: 1.0.0 resolution: "proxy-from-env@npm:1.0.0" @@ -22678,7 +23371,7 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:1.1.0, proxy-from-env@npm:^1.1.0": +"proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 @@ -22746,23 +23439,33 @@ __metadata: languageName: node linkType: hard -"puppeteer@npm:^10.2.0": - version: 10.4.0 - resolution: "puppeteer@npm:10.4.0" +"puppeteer-core@npm:24.1.1": + version: 24.1.1 + resolution: "puppeteer-core@npm:24.1.1" dependencies: - debug: 4.3.1 - devtools-protocol: 0.0.901419 - extract-zip: 2.0.1 - https-proxy-agent: 5.0.0 - node-fetch: 2.6.1 - pkg-dir: 4.2.0 - progress: 2.0.1 - proxy-from-env: 1.1.0 - rimraf: 3.0.2 - tar-fs: 2.0.0 - unbzip2-stream: 1.3.3 - ws: 7.4.6 - checksum: 3b7b628f07b3e1f960110691363c1eb07a485fa2f24b5a083558a56841cc46bfcac7a3ab8ae5c687f39621972b35327ce934ea731c831dcd8b75d897920bdc26 + "@puppeteer/browsers": 2.7.0 + chromium-bidi: 1.1.0 + debug: ^4.4.0 + devtools-protocol: 0.0.1380148 + typed-query-selector: ^2.12.0 + ws: ^8.18.0 + checksum: f02d41733e7c206bd2d09b1bf1caa7c4b87114642360e7cfda02a689c99d9854ca81ad7c512de9a430a9106a6a65e3a803e1b61c8a46af2146a7a2f092cb8b47 + languageName: node + linkType: hard + +"puppeteer@npm:^24.1.1": + version: 24.1.1 + resolution: "puppeteer@npm:24.1.1" + dependencies: + "@puppeteer/browsers": 2.7.0 + chromium-bidi: 1.1.0 + cosmiconfig: ^9.0.0 + devtools-protocol: 0.0.1380148 + puppeteer-core: 24.1.1 + typed-query-selector: ^2.12.0 + bin: + puppeteer: lib/cjs/puppeteer/node/cli.js + checksum: 628c38f76b63bebe6a6708605a62e5b1f14358f589ce56389b0349b187f747702b6ee7d9bff86800f0377aae9a7c460b0c70eb3682e931feaef5e289f266e795 languageName: node linkType: hard @@ -22999,6 +23702,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.0.0": + version: 4.7.0 + resolution: "readable-stream@npm:4.7.0" + dependencies: + abort-controller: ^3.0.0 + buffer: ^6.0.3 + events: ^3.3.0 + process: ^0.11.10 + string_decoder: ^1.3.0 + checksum: 03ec762faed8e149dc6452798b60394a8650861a1bb4bf936fa07b94044826bc25abe73696f5f45372abc404eec01876c560f64b479eba108b56397312dbe2ae + languageName: node + linkType: hard + "readdir-glob@npm:^1.1.2": version: 1.1.3 resolution: "readdir-glob@npm:1.1.3" @@ -23277,6 +23993,13 @@ __metadata: languageName: node linkType: hard +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 1012afc566b3fdb190a6309cc37ef3b2dcc35dff5fa6683a9d00cd25c3247edfbc4691b91078c97adc82a29b77a2660c30d791d65dab4fc78bfc473f60289977 + languageName: node + linkType: hard + "resolve-url-loader@npm:5.0.0": version: 5.0.0 resolution: "resolve-url-loader@npm:5.0.0" @@ -23357,6 +24080,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" + dependencies: + onetime: ^7.0.0 + signal-exit: ^4.1.0 + checksum: 838dd54e458d89cfbc1a923b343c1b0f170a04100b4ce1733e97531842d7b440463967e521216e8ab6c6f8e89df877acc7b7f4c18ec76e99fb9bf5a60d358d2c + languageName: node + linkType: hard + "ret@npm:~0.1.10": version: 0.1.15 resolution: "ret@npm:0.1.15" @@ -23409,17 +24142,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:3.0.2, rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" - dependencies: - glob: ^7.1.3 - bin: - rimraf: bin.js - checksum: 87f4164e396f0171b0a3386cc1877a817f572148ee13a7e113b238e48e8a9f2f31d009a92ec38a591ff1567d9662c6b67fd8818a2dbbaed74bc26a87a2a4a9a0 - languageName: node - linkType: hard - "rimraf@npm:4.4.1, rimraf@npm:^4.4.1": version: 4.4.1 resolution: "rimraf@npm:4.4.1" @@ -23442,6 +24164,17 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: ^7.1.3 + bin: + rimraf: bin.js + checksum: 87f4164e396f0171b0a3386cc1877a817f572148ee13a7e113b238e48e8a9f2f31d009a92ec38a591ff1567d9662c6b67fd8818a2dbbaed74bc26a87a2a4a9a0 + languageName: node + linkType: hard + "rimraf@npm:^5.0.0": version: 5.0.5 resolution: "rimraf@npm:5.0.5" @@ -24052,19 +24785,18 @@ __metadata: languageName: node linkType: hard -"serve-handler@npm:6.1.3": - version: 6.1.3 - resolution: "serve-handler@npm:6.1.3" +"serve-handler@npm:6.1.6": + version: 6.1.6 + resolution: "serve-handler@npm:6.1.6" dependencies: bytes: 3.0.0 content-disposition: 0.5.2 - fast-url-parser: 1.1.3 mime-types: 2.1.18 - minimatch: 3.0.4 + minimatch: 3.1.2 path-is-inside: 1.0.2 - path-to-regexp: 2.2.1 + path-to-regexp: 3.3.0 range-parser: 1.2.0 - checksum: 384c1bc10add07a554207f918acaa75af47fcfd8fb89e070faa3468ab45ec5bbc9f976e62d659b6b63404edcf5c54efb7e0a48f3f55946eec83b62b283b9837e + checksum: eb26201e699ac4694fb16f9aaf932330f6b1159e9d9496261baa23caf1e81322afcfd2b5f5f2b306b133298c03a8395a3c13b56fde5d70b331014b3a5ab7217f languageName: node linkType: hard @@ -24095,22 +24827,24 @@ __metadata: languageName: node linkType: hard -"serve@npm:^13.0.2": - version: 13.0.4 - resolution: "serve@npm:13.0.4" +"serve@npm:^14.2.4": + version: 14.2.4 + resolution: "serve@npm:14.2.4" dependencies: - "@zeit/schemas": 2.6.0 - ajv: 6.12.6 - arg: 2.0.0 - boxen: 5.1.2 - chalk: 2.4.1 - clipboardy: 2.3.0 - compression: 1.7.3 - serve-handler: 6.1.3 - update-check: 1.5.2 + "@zeit/schemas": 2.36.0 + ajv: 8.12.0 + arg: 5.0.2 + boxen: 7.0.0 + chalk: 5.0.1 + chalk-template: 0.4.0 + clipboardy: 3.0.0 + compression: 1.7.4 + is-port-reachable: 4.0.0 + serve-handler: 6.1.6 + update-check: 1.5.4 bin: - serve: bin/serve.js - checksum: 5fc40ef49f2aea47bb126118d6f00d56ad832cabbfcf7c96bff30e6aebec1866ae652b19a865f33388167f16c7de632d1d24d058e292dc227a77bdd89e916147 + serve: build/main.js + checksum: 9d396609214d6d368e95943cd556be76a6918d8522b401115a109fa8e40e1b8740d55cc930b9ee2980540da852c7d54750d00d232b903c88c6471c504c55e62c languageName: node linkType: hard @@ -24439,7 +25173,7 @@ __metadata: languageName: node linkType: hard -"slice-ansi@npm:^7.0.0": +"slice-ansi@npm:^7.0.0, slice-ansi@npm:^7.1.0": version: 7.1.0 resolution: "slice-ansi@npm:7.1.0" dependencies: @@ -24524,6 +25258,17 @@ __metadata: languageName: node linkType: hard +"socks-proxy-agent@npm:^8.0.5": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: ^7.1.2 + debug: ^4.3.4 + socks: ^2.8.3 + checksum: b4fbcdb7ad2d6eec445926e255a1fb95c975db0020543fbac8dfa6c47aecc6b3b619b7fb9c60a3f82c9b2969912a5e7e174a056ae4d98cb5322f3524d6036e1d + languageName: node + linkType: hard + "socks@npm:^2.7.1": version: 2.8.0 resolution: "socks@npm:2.8.0" @@ -24534,6 +25279,16 @@ __metadata: languageName: node linkType: hard +"socks@npm:^2.8.3": + version: 2.8.3 + resolution: "socks@npm:2.8.3" + dependencies: + ip-address: ^9.0.5 + smart-buffer: ^4.2.0 + checksum: 7a6b7f6eedf7482b9e4597d9a20e09505824208006ea8f2c49b71657427f3c137ca2ae662089baa73e1971c62322d535d9d0cf1c9235cf6f55e315c18203eadd + languageName: node + linkType: hard + "sort-any@npm:^2.0.0": version: 2.0.0 resolution: "sort-any@npm:2.0.0" @@ -25004,6 +25759,20 @@ __metadata: languageName: node linkType: hard +"streamx@npm:^2.15.0, streamx@npm:^2.21.0": + version: 2.22.0 + resolution: "streamx@npm:2.22.0" + dependencies: + bare-events: ^2.2.0 + fast-fifo: ^1.3.2 + text-decoder: ^1.1.0 + dependenciesMeta: + bare-events: + optional: true + checksum: 9b2772a084281129d402f298bddf8d5f3c09b6b3d9b5c93df942e886b0b963c742a89736415cc53ffb8fc1f6f5b0b3ea171ed0ba86f1b31cde6ed35db5e07f6d + languageName: node + linkType: hard + "string-argv@npm:0.3.2, string-argv@npm:~0.3.1": version: 0.3.2 resolution: "string-argv@npm:0.3.2" @@ -25054,6 +25823,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: ^10.3.0 + get-east-asian-width: ^1.0.0 + strip-ansi: ^7.1.0 + checksum: 42f9e82f61314904a81393f6ef75b832c39f39761797250de68c041d8ba4df2ef80db49ab6cd3a292923a6f0f409b8c9980d120f7d32c820b4a8a84a2598a295 + languageName: node + linkType: hard + "string.prototype.trim@npm:^1.2.8": version: 1.2.8 resolution: "string.prototype.trim@npm:1.2.8" @@ -25431,18 +26211,6 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:2.0.0": - version: 2.0.0 - resolution: "tar-fs@npm:2.0.0" - dependencies: - chownr: ^1.1.1 - mkdirp: ^0.5.1 - pump: ^3.0.0 - tar-stream: ^2.0.0 - checksum: f15079cd7e5b38b7982d3a1c2f0cf0eac58e1c622f0191b12d90440a4e97ea0f63bf31467f6ad9cb5ffdd47d9fc251682f9456e36c6d5e2488f49f14a9d28a75 - languageName: node - linkType: hard - "tar-fs@npm:^2.0.0, tar-fs@npm:^2.1.1": version: 2.1.1 resolution: "tar-fs@npm:2.1.1" @@ -25455,7 +26223,24 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.0.0, tar-stream@npm:^2.1.4, tar-stream@npm:^2.2.0, tar-stream@npm:~2.2.0": +"tar-fs@npm:^3.0.6": + version: 3.0.8 + resolution: "tar-fs@npm:3.0.8" + dependencies: + bare-fs: ^4.0.1 + bare-path: ^3.0.0 + pump: ^3.0.0 + tar-stream: ^3.1.5 + dependenciesMeta: + bare-fs: + optional: true + bare-path: + optional: true + checksum: 5bebadd68e7a10cc3aa9c30b579c295e158cef7b1f42a73ee1bb1992925027aa8ef6cbcdb0d03e202e7f3850799391de30adf2585f7f240b606faa65df1a6b68 + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4, tar-stream@npm:^2.2.0, tar-stream@npm:~2.2.0": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -25468,6 +26253,17 @@ __metadata: languageName: node linkType: hard +"tar-stream@npm:^3.0.0, tar-stream@npm:^3.1.5": + version: 3.1.7 + resolution: "tar-stream@npm:3.1.7" + dependencies: + b4a: ^1.6.4 + fast-fifo: ^1.2.0 + streamx: ^2.15.0 + checksum: 6393a6c19082b17b8dcc8e7fd349352bb29b4b8bfe1075912b91b01743ba6bb4298f5ff0b499a3bbaf82121830e96a1a59d4f21a43c0df339e54b01789cb8cc6 + languageName: node + linkType: hard + "tar@npm:^6.0.1, tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.2.1 resolution: "tar@npm:6.2.1" @@ -25546,7 +26342,7 @@ __metadata: concurrently: ^6.2.1 cypress: ^8.3.0 cypress-image-snapshot: ^4.0.1 - data-models: 1.0.0 + data-models: "workspace:*" typescript: ~4.2.4 wait-on: ^6.0.0 languageName: unknown @@ -25567,35 +26363,42 @@ __metadata: version: 0.0.0-use.local resolution: "test-visual@workspace:packages/test-visual" dependencies: - "@types/fs-extra": ^9.0.12 - "@types/node": ^15.12.4 - "@types/pixelmatch": ^5.2.4 - "@types/pngjs": ^6.0.1 - archiver: ^5.3.0 - axios: ^1.7.4 - boxen: ^5.1.2 - chalk: ^4.1.2 - commander: ^8.2.0 - data-models: 1.0.0 - dotenv: ^10.0.0 + "@types/fs-extra": ^11.0.4 + "@types/node": ^22.13.0 + "@types/pixelmatch": ^5.2.6 + "@types/pngjs": ^6.0.5 + archiver: ^7.0.1 + boxen: ^8.0.1 + chalk: ^5.4.1 + commander: ^13.1.0 + data-models: "workspace:*" + dotenv: ^16.4.7 extract-zip: ^2.0.1 - fs-extra: ^10.0.0 + fs-extra: ^11.3.0 jpeg-js: ^0.4.4 - log-update: ^4.0.0 - octokit: ^3.1.2 - p-queue: ^6.6.2 - pixelmatch: ^5.2.1 - pngjs: ^6.0.0 - puppeteer: ^10.2.0 - serve: ^13.0.2 - ts-node: ^10.8.0 - ts-node-dev: ^1.1.8 - typescript: ~4.2.4 + log-update: ^6.1.0 + octokit: ^4.1.0 + p-queue: ^8.1.0 + pixelmatch: ^6.0.0 + pngjs: ^7.0.0 + puppeteer: ^24.1.1 + serve: ^14.2.4 + tsx: ^4.19.2 + typescript: ~5.7.3 bin: idems-test-visual: ./lib/index.js languageName: unknown linkType: soft +"text-decoder@npm:^1.1.0": + version: 1.2.3 + resolution: "text-decoder@npm:1.2.3" + dependencies: + b4a: ^1.6.4 + checksum: d7642a61f9d72330eac52ff6b6e8d34dea03ebbb1e82749a8734e7892e246cf262ed70730d20c4351c5dc5334297b9cc6c0b6a8725a204a63a197d7728bb35e5 + languageName: node + linkType: hard + "text-hex@npm:1.0.x": version: 1.0.0 resolution: "text-hex@npm:1.0.0" @@ -25742,6 +26545,13 @@ __metadata: languageName: node linkType: hard +"toad-cache@npm:^3.7.0": + version: 3.7.0 + resolution: "toad-cache@npm:3.7.0" + checksum: d0f2092ab2c0f3355d3537c41b13888a12996f38080e6c39907e715eb382d997ccf61baab9e8eda3f202b6c07e304728106be3631c9fe3b6c001aaf15b7bdb8f + languageName: node + linkType: hard + "toidentifier@npm:1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" @@ -26305,6 +27115,22 @@ __metadata: languageName: node linkType: hard +"tsx@npm:^4.19.2": + version: 4.19.2 + resolution: "tsx@npm:4.19.2" + dependencies: + esbuild: ~0.23.0 + fsevents: ~2.3.3 + get-tsconfig: ^4.7.5 + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 7f9f1b338a73297725a9217cedaaad862f7c81d5264093c74b98a71491ad5413b11248d604c0e650f4f7da6f365249f1426fdb58a1325ab9e15448156b1edff6 + languageName: node + linkType: hard + "tuf-js@npm:^2.2.0": version: 2.2.0 resolution: "tuf-js@npm:2.2.0" @@ -26385,7 +27211,7 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^2.18.0": +"type-fest@npm:^2.13.0, type-fest@npm:^2.18.0": version: 2.19.0 resolution: "type-fest@npm:2.19.0" checksum: a4ef07ece297c9fba78fc1bd6d85dff4472fe043ede98bd4710d2615d15776902b595abf62bd78339ed6278f021235fb28a96361f8be86ed754f778973a0d278 @@ -26399,6 +27225,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.21.0": + version: 4.33.0 + resolution: "type-fest@npm:4.33.0" + checksum: 42c9a4e305ef86826f6c3e9fb0d230765523eba248b7927580e76fa0384a4a12dfcde3ba04ac94b3cfab664b16608f1f9b8fb6116a48c728b87350e8252fd32c + languageName: node + linkType: hard + "type-is@npm:^1.6.4, type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -26484,6 +27317,13 @@ __metadata: languageName: node linkType: hard +"typed-query-selector@npm:^2.12.0": + version: 2.12.0 + resolution: "typed-query-selector@npm:2.12.0" + checksum: c4652f2eec16112d69e0da30c2effab3f03d1710f9559da1e1209bbfc9a20990d5de4ba97890c11f9d17d85c8ae3310953a86c198166599d4c36abc63664f169 + languageName: node + linkType: hard + "typedarray-to-buffer@npm:^3.1.5": version: 3.1.5 resolution: "typedarray-to-buffer@npm:3.1.5" @@ -26540,6 +27380,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:~5.7.3": + version: 5.7.3 + resolution: "typescript@npm:5.7.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 6c38b1e989918e576f0307e6ee013522ea480dfce5f3ca85c9b2d8adb1edeffd37f4f30cd68de0c38a44563d12ba922bdb7e36aa2dac9c51de5d561e6e9a2e9c + languageName: node + linkType: hard + "typescript@patch:typescript@4.9.5#~builtin, typescript@patch:typescript@^4.1.2#~builtin": version: 4.9.5 resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=ad5954" @@ -26580,6 +27430,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@~5.7.3#~builtin": + version: 5.7.3 + resolution: "typescript@patch:typescript@npm%3A5.7.3#~builtin::version=5.7.3&hash=ad5954" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 633cd749d6cd7bc842c6b6245847173bba99742a60776fae3c0fbcc0d1733cd51a733995e5f4dadd8afb0e64e57d3c7dbbeae953a072ee303940eca69e22f311 + languageName: node + linkType: hard + "ua-parser-js@npm:^0.7.30": version: 0.7.37 resolution: "ua-parser-js@npm:0.7.37" @@ -26647,13 +27507,13 @@ __metadata: languageName: node linkType: hard -"unbzip2-stream@npm:1.3.3": - version: 1.3.3 - resolution: "unbzip2-stream@npm:1.3.3" +"unbzip2-stream@npm:^1.4.3": + version: 1.4.3 + resolution: "unbzip2-stream@npm:1.4.3" dependencies: buffer: ^5.2.1 through: ^2.3.8 - checksum: 5ae179e60971023c1ee2be2d3f02dd81e4dae8c506fe2367f63a2c126073c2f08c101005d328c34b13cb1d691627e4c6c528d1e273ea3a3dda15d906bac66391 + checksum: 0e67c4a91f4fa0fc7b4045f8b914d3498c2fc2e8c39c359977708ec85ac6d6029840e97f508675fdbdf21fcb8d276ca502043406f3682b70f075e69aae626d1d languageName: node linkType: hard @@ -26789,20 +27649,17 @@ __metadata: languageName: node linkType: hard -"universal-github-app-jwt@npm:^1.1.2": - version: 1.1.2 - resolution: "universal-github-app-jwt@npm:1.1.2" - dependencies: - "@types/jsonwebtoken": ^9.0.0 - jsonwebtoken: ^9.0.2 - checksum: 1bc069c57d319607d4b52143ba89de18cdff2b6afb63107e6972dff9574c7fc453f1a6bb1714817c72898a55c37fa38783be965ebd1c61de661231ca061440d1 +"universal-github-app-jwt@npm:^2.2.0": + version: 2.2.0 + resolution: "universal-github-app-jwt@npm:2.2.0" + checksum: 09f8e9710453749bd669fb6511157f03683674066f04696b10d42c18d87cb40d77a5b7504b5bd6f4e329229fff8715e01958217560accd941381c6b4cb7a46fe languageName: node linkType: hard -"universal-user-agent@npm:^6.0.0": - version: 6.0.1 - resolution: "universal-user-agent@npm:6.0.1" - checksum: fdc8e1ae48a05decfc7ded09b62071f571c7fe0bd793d700704c80cea316101d4eac15cc27ed2bb64f4ce166d2684777c3198b9ab16034f547abea0d3aa1c93c +"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": + version: 7.0.2 + resolution: "universal-user-agent@npm:7.0.2" + checksum: 3f02cb6de0bb9fbaf379566bd0320d8e46af6e4358a2e88fce7e70687ed7b48b37f479d728bb22f4204a518e363f3038ac4841c033af1ee2253f6428a6c67e53 languageName: node linkType: hard @@ -26869,13 +27726,13 @@ __metadata: languageName: node linkType: hard -"update-check@npm:1.5.2": - version: 1.5.2 - resolution: "update-check@npm:1.5.2" +"update-check@npm:1.5.4": + version: 1.5.4 + resolution: "update-check@npm:1.5.4" dependencies: registry-auth-token: 3.3.2 registry-url: 3.1.0 - checksum: 82b42978610ef616afd374153bcbff5055c6482454f3391fe5df48c0bd9fe63de16733f100f8b8d12cea7b33d094d15bdd01ef329ff123f127ca3dcf2b7dfce5 + checksum: 2c9f7de6f030364c5ea02a341e5ae2dfe76da6559b32d40dd3b047b3ac0927408cf92d322c51cd8e009688210a85ccbf1eba449762a65a0d1b14f3cdf1ea5c48 languageName: node linkType: hard @@ -27609,6 +28466,24 @@ __metadata: languageName: node linkType: hard +"widest-line@npm:^4.0.1": + version: 4.0.1 + resolution: "widest-line@npm:4.0.1" + dependencies: + string-width: ^5.0.1 + checksum: 64c48cf27171221be5f86fc54b94dd29879165bdff1a7aa92dde723d9a8c99fb108312768a5d62c8c2b80b701fa27bbd36a1ddc58367585cd45c0db7920a0cba + languageName: node + linkType: hard + +"widest-line@npm:^5.0.0": + version: 5.0.0 + resolution: "widest-line@npm:5.0.0" + dependencies: + string-width: ^7.0.0 + checksum: 07f6527b961b88d40ac250596c06fada00cbe049080c6cc8ef4d7bc4f4ab03d7eb1a1c2e5585dd0d8b6ec99ba6f168d5b236edd8ba9221aeb8d914451f0235f9 + languageName: node + linkType: hard + "wildcard@npm:^2.0.0": version: 2.0.1 resolution: "wildcard@npm:2.0.1" @@ -27748,7 +28623,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^8.1.0": +"wrap-ansi@npm:^8.0.1, wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" dependencies: @@ -27799,21 +28674,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:7.4.6": - version: 7.4.6 - resolution: "ws@npm:7.4.6" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 3a990b32ed08c72070d5e8913e14dfcd831919205be52a3ff0b4cdd998c8d554f167c9df3841605cde8b11d607768cacab3e823c58c96a5c08c987e093eb767a - languageName: node - linkType: hard - "ws@npm:8.14.2": version: 8.14.2 resolution: "ws@npm:8.14.2" @@ -27874,6 +28734,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 91d4d35bc99ff6df483bdf029b9ea4bfd7af1f16fc91231a96777a63d263e1eabf486e13a2353970efc534f9faa43bdbf9ee76525af22f4752cbc5ebda333975 + languageName: node + linkType: hard + "ws@npm:~8.11.0": version: 8.11.0 resolution: "ws@npm:8.11.0" @@ -28224,6 +29099,24 @@ __metadata: languageName: node linkType: hard +"zip-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "zip-stream@npm:6.0.1" + dependencies: + archiver-utils: ^5.0.0 + compress-commons: ^6.0.2 + readable-stream: ^4.0.0 + checksum: aa5abd6a89590eadeba040afbc375f53337f12637e5e98330012a12d9886cde7a3ccc28bd91aafab50576035bbb1de39a9a316eecf2411c8b9009c9f94f0db27 + languageName: node + linkType: hard + +"zod@npm:3.24.1": + version: 3.24.1 + resolution: "zod@npm:3.24.1" + checksum: dcd5334725b29555593c186fd6505878bb7ccb4f5954f728d2de24bf71f9397492d83bdb69d5b8a376eb500a02273ae0691b57deb1eb8718df3f64c77cc5534a + languageName: node + linkType: hard + "zone.js@npm:~0.10.3": version: 0.10.3 resolution: "zone.js@npm:0.10.3"