diff --git a/.github/workflows/cypress-snapshot-update.yml b/.github/workflows/cypress-snapshot-update.yml deleted file mode 100644 index cec2d5a8a..000000000 --- a/.github/workflows/cypress-snapshot-update.yml +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT -name: Cypress snapshot update - -on: - workflow_dispatch: - schedule: - # At 2:30 on Sundays - - cron: '20 1 * * 0' - -permissions: - contents: read - issues: write - -jobs: - update: - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - branches: ["master", "stable31", "stable32"] - - name: cypress-snapshot-update-${{ matrix.branches }} - - steps: - - name: Checkout app - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - ref: ${{ matrix.branches }} - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: versions - with: - fallbackNode: "^20" - fallbackNpm: "^10" - - - name: Set up node ${{ steps.versions.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ steps.versions.outputs.nodeVersion }} - - - name: Set up npm ${{ steps.versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - - - name: Install node dependencies & build app - run: | - npm ci - npm run build --if-present - - - name: Snapshot update - run: npm run cypress:update-snapshots - - - name: Create Pull Request - if: always() - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 - with: - token: ${{ secrets.COMMAND_BOT_PAT }} - commit-message: "chore(deps): cypress snapshot update" - committer: GitHub - author: nextcloud-command - signoff: true - branch: automated/noid/${{ matrix.branches }}-cypress-snapshot-update - title: "[${{ matrix.branches }}] Update cypress snapshots" - body: | - Auto-generated update of cypress snapshots - labels: | - dependencies - 3. to review - add-paths: | - cypress/snapshots diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml deleted file mode 100644 index 26819dde9..000000000 --- a/.github/workflows/cypress.yml +++ /dev/null @@ -1,171 +0,0 @@ -# This workflow is provided via the organization template repository -# -# https://github.com/nextcloud/.github -# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization -# -# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Cypress - -on: pull_request - -concurrency: - group: cypress-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - # Adjust APP_NAME if your repository name is different - APP_NAME: ${{ github.event.repository.name }} - - # This represents the server branch to checkout. - # Usually it's the base branch of the PR, but for pushes it's the branch itself. - # e.g. 'main', 'stable27' or 'feature/my-feature' - # n.b. server will use head_ref, as we want to test the PR branch. - BRANCH: ${{ github.base_ref || github.ref_name }} - - -permissions: - contents: read - -jobs: - init: - runs-on: ubuntu-latest - outputs: - nodeVersion: ${{ steps.versions.outputs.nodeVersion }} - npmVersion: ${{ steps.versions.outputs.npmVersion }} - - env: - PUPPETEER_SKIP_DOWNLOAD: true - - steps: - - name: Disabled on forks - if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} - run: | - echo 'Can not run cypress on forks' - exit 1 - - - name: Checkout app - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Check composer.json - id: check_composer - uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v3.1.0 - with: - files: 'composer.json' - - - name: Install composer dependencies - if: steps.check_composer.outputs.files_exists == 'true' - run: composer install --no-dev - - - name: Read package.json node and npm engines version - uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 - id: versions - with: - fallbackNode: '^24' - fallbackNpm: '^11.3' - - - name: Set up node ${{ steps.versions.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ steps.versions.outputs.nodeVersion }} - - - name: Set up npm ${{ steps.versions.outputs.npmVersion }} - run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' - - - name: Install node dependencies & build app - run: | - npm ci - TESTING=true npm run build --if-present - - - name: Save context - uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - with: - key: cypress-context-${{ github.run_id }} - path: ./ - - cypress: - runs-on: ubuntu-latest - needs: init - - strategy: - fail-fast: false - matrix: - # Please increase the number or runners as your tests suite grows (0 based index for e2e tests) - containers: [0, 1, 2, 3, 4, 5, 6, 7] - # Hack as strategy.job-total includes the component and GitHub does not allow math expressions - # Always align this number with the total of e2e runners (max. index + 1) - total-containers: [8] - - name: runner ${{ matrix.containers }} - - steps: - - name: Restore context - uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - with: - fail-on-cache-miss: true - key: cypress-context-${{ github.run_id }} - path: ./ - - - name: Set up node ${{ needs.init.outputs.nodeVersion }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: ${{ needs.init.outputs.nodeVersion }} - - - name: Set up npm ${{ needs.init.outputs.npmVersion }} - run: npm i -g 'npm@${{ needs.init.outputs.npmVersion }}' - - - name: Install cypress - run: ./node_modules/cypress/bin/cypress install - - - name: Run ${{ matrix.containers == 'component' && 'component' || 'E2E' }} cypress tests - uses: cypress-io/github-action@b7a7441d775af8f8b9d19945c10dd689a51dba68 # v7.2.0 - with: - # We already installed the dependencies in the init job - install: false - # cypress run type - component: ${{ matrix.containers == 'component' }} - # Do not add Cypress record key config as this conflicts with cypress-split - # Cypress again tries to force users to buy their dashboard... - env: - # Needs to be prefixed with CYPRESS_ - CYPRESS_BRANCH: ${{ env.BRANCH }} - # https://github.com/cypress-io/github-action/issues/124 - COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }} - # Needed for some specific code workarounds - TESTING: true - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SPLIT: ${{ matrix.total-containers }} - SPLIT_INDEX: ${{ matrix.containers == 'component' && 0 || matrix.containers }} - - - name: Upload snapshots - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - if: always() - with: - name: snapshots_${{ matrix.containers }} - path: cypress/snapshots - - - name: Extract NC logs - if: failure() && matrix.containers != 'component' - run: docker logs nextcloud-cypress-tests-${{ env.APP_NAME }} > nextcloud.log - - - name: Upload NC logs - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - if: failure() && matrix.containers != 'component' - with: - name: nc_logs_${{ matrix.containers }} - path: nextcloud.log - - summary: - runs-on: ubuntu-latest-low - needs: [init, cypress] - - if: always() - - name: cypress-summary - - steps: - - name: Summary status - run: if ${{ needs.init.result != 'success' || ( needs.cypress.result != 'success' && needs.cypress.result != 'skipped' ) }}; then exit 1; fi diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 000000000..347a869d4 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + +name: Playwright + +on: pull_request + +concurrency: + group: playwright-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +env: + APP_NAME: ${{ github.event.repository.name }} + BRANCH: ${{ github.base_ref || github.ref_name }} + +jobs: + playwright: + runs-on: ubuntu-latest + + name: Playwright E2E tests + + steps: + - name: Disabled on forks + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + run: | + echo 'Can not run Playwright on forks' + exit 1 + + - name: Checkout app + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Check composer.json + id: check_composer + uses: andstor/file-existence-action@558493d6c74bf472d87c84eab196434afc2fa029 # v3.1.0 + with: + files: 'composer.json' + + - name: Install composer dependencies + if: steps.check_composer.outputs.files_exists == 'true' + run: composer install --no-dev + + - name: Read package.json node and npm engines version + uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 + id: versions + with: + fallbackNode: '^24' + fallbackNpm: '^11' + + - name: Set up node ${{ steps.versions.outputs.nodeVersion }} + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ steps.versions.outputs.nodeVersion }} + + - name: Set up npm ${{ steps.versions.outputs.npmVersion }} + run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' + + - name: Install dependencies & build app + run: | + npm ci + TESTING=true npm run build --if-present + + - name: Install Playwright browsers + run: npx playwright install chromium --with-deps + + - name: Run Playwright tests + run: npx playwright test + env: + BRANCH: ${{ env.BRANCH }} + CI: true + + - name: Show Nextcloud logs on failure + if: failure() + run: | + docker exec nextcloud-e2e-test-server_${{ env.APP_NAME }} cat data/nextcloud.log || true + + - name: Upload Playwright report + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 + if-no-files-found: ignore + + - name: Upload Playwright traces + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: failure() + with: + name: playwright-traces + path: test-results/ + retention-days: 7 + if-no-files-found: ignore + + summary: + runs-on: ubuntu-latest-low + needs: playwright + if: always() + name: playwright-summary + steps: + - name: Summary status + run: if ${{ needs.playwright.result != 'success' && needs.playwright.result != 'skipped' }}; then exit 1; fi diff --git a/.gitignore b/.gitignore index feb19e486..1200c39ab 100644 --- a/.gitignore +++ b/.gitignore @@ -26,14 +26,12 @@ coverage/ vendor .php_cs.cache -# Cypress files +# Playwright files js/*roboto* -cypress/downloads -cypress/screenshots -cypress/snapshots -cypress/videos -cypress/snapshots/actual -cypress/snapshots/diff +playwright-report/ +test-results/ +blob-report/ +playwright/.cache/ # RelativeCI webpack bundle stats webpack-stats.json diff --git a/README.md b/README.md index 176f1f028..a1889eb6e 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,19 @@ Show your latest holiday photos and videos like in the movies. Show a glimpse of ### 🧙 Advanced development stuff To build the Javascript whenever you make changes, you can also run `npm run dev` for development builds. -### 📷 Running cypress tests -To run e2e cypress tests, execute `npm run cypress`. +### 📷 Running Playwright tests +The end-to-end tests use [Playwright](https://playwright.dev/) and a disposable Nextcloud Docker container. -The `visual-regression` tests require additional care as they depend on installation of fonts in the application. To achieve repeatable results run the tests using `npm run cypress:visual-regression`. This will build the app with the required fonts and run the tests. +Build the app once, then run the tests: -If changes are required to the reference (base) screenshots used by the `visual-regression` tests, run `cypress:update-snapshots` and commit the updated screenshots. +```sh +npm ci +npx playwright install chromium +TESTING=true npm run build +npm run test:e2e +``` + +`npm run test:e2e:ui` opens the interactive UI. The Nextcloud container is started automatically by `playwright/start-server.mjs` (port 8081) and reused between local runs; set `PLAYWRIGHT_BASE_URL` to target an existing instance instead. ## API diff --git a/REUSE.toml b/REUSE.toml index 6a7e9678e..8b02d3eca 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -12,7 +12,7 @@ SPDX-FileCopyrightText = "2019 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "AGPL-3.0-or-later" [[annotations]] -path = ["cypress/fixtures/image.gif", "cypress/fixtures/image.ico", "cypress/fixtures/image.svg", "cypress/fixtures/image-small.png", "cypress/fixtures/image-apng.png"] +path = ["playwright/fixtures/image.gif", "playwright/fixtures/image.ico", "playwright/fixtures/image.svg", "playwright/fixtures/image-small.png", "playwright/fixtures/image-apng.png"] precedence = "aggregate" SPDX-FileCopyrightText = "2019 Nextcloud GmbH" SPDX-License-Identifier = "LicenseRef-NextcloudTrademarks" @@ -24,7 +24,7 @@ SPDX-FileCopyrightText = "2020 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "AGPL-3.0-or-later" [[annotations]] -path = ["psalm.xml", "tsconfig.json", "tests/psalm-baseline.xml", "cypress/tsconfig.json"] +path = ["psalm.xml", "tsconfig.json", "tests/psalm-baseline.xml", "playwright/tsconfig.json"] precedence = "aggregate" SPDX-FileCopyrightText = "2022 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "AGPL-3.0-or-later" @@ -36,7 +36,7 @@ SPDX-FileCopyrightText = "2023 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "AGPL-3.0-or-later" [[annotations]] -path = [".eslintrc.json", "cypress/.eslintrc.json", "cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/image.png", "cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/image2.png", "cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/non-dav.png", "cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/video.png"] +path = [".eslintrc.json"] precedence = "aggregate" SPDX-FileCopyrightText = "2024 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "AGPL-3.0-or-later" @@ -60,55 +60,55 @@ SPDX-FileCopyrightText = "2019 Nextcloud translators { - if (browser.family === 'chromium' && browser.name !== 'electron') { - launchOptions.preferences.default['browser.enable_spellchecking'] = false - return launchOptions - } - - if (browser.family === 'firefox') { - launchOptions.preferences['layout.spellcheckDefault'] = 0 - return launchOptions - } - - if (browser.name === 'electron') { - launchOptions.preferences.spellcheck = false - return launchOptions - } - }) - - if (process.env.CYPRESS_baseUrl) { - return config - } - - // Remove container after run - on('after:run', () => { - if (!process.env.CI) { - stopNextcloud() - } - }) - - // Before the browser launches - // starting Nextcloud testing container - const ip = await startNextcloud(process.env.BRANCH) - // Setting container's IP as base Url - config.baseUrl = `http://${ip}/index.php` - await waitOnNextcloud(ip) - await configureNextcloud([]) // pass empty array as WE are already the viewer - return config - }, - }, -}) diff --git a/cypress/.eslintrc.json b/cypress/.eslintrc.json deleted file mode 100644 index fb78972d2..000000000 --- a/cypress/.eslintrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "env": { - "cypress/globals": true - }, - "plugins": [ - "cypress" - ], - "extends": [ - "plugin:cypress/recommended" - ] -} diff --git a/cypress/e2e/a11y.cy.ts b/cypress/e2e/a11y.cy.ts deleted file mode 100644 index de1415c0b..000000000 --- a/cypress/e2e/a11y.cy.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -describe('A11y tests', function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, 'image1.jpg', 'image/jpeg') - cy.uploadFile(user, 'image2.jpg', 'image/jpeg') - cy.uploadFile(user, 'video1.mp4', 'video/mp4') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - - after(function() { - cy.logout() - }) - - it('See files in the list', function() { - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - cy.getFile('image2.jpg', { timeout: 10000 }) - .should('contain', 'image2 .jpg') - cy.getFile('video1.mp4', { timeout: 10000 }) - .should('contain', 'video1 .mp4') - }) - - it('Open the viewer on file click', function() { - cy.openFile('image2.jpg') - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image2.jpg') - }) - - it('Should have rendered the previous video and the next image', function() { - // There are buttons to navigate to the previous and next image - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - // The previous and the next image - cy.get('body > .viewer .modal-container img').should('have.length', 2) - // The next video - cy.get('body > .viewer .modal-container video').should('have.length', 1) - }) - - it('Should make the previous and the next slides hidden for assistive technologies', function() { - // Cypress doesn't respect aria-hidden in should.be.visible - cy.get('body > .viewer .modal-container .viewer__file:not(.viewer__file--active) video') - .parents('[aria-hidden="true"]') - .should('exist') - cy.get('body > .viewer .modal-container .viewer__file:not(.viewer__file--active) video') - .parents('[inert]') - .should('exist') - cy.get('body > .viewer .modal-container .viewer__file:not(.viewer__file--active) img') - .parents('[aria-hidden="true"]') - .should('exist') - cy.get('body > .viewer .modal-container .viewer__file:not(.viewer__file--active) img') - .parents('[inert]') - .should('exist') - }) - - it('Should make vido controls on the next slide not focusable', function() { - cy.get('body > .viewer .modal-container .viewer__file:not(.viewer__file--active):has(video) button') - .first() - .focus() - cy.get('body > .viewer .modal-container .viewer__file:not(.viewer__file--active):has(video) button') - .first() - .should('not.have.focus') - }) -}) diff --git a/cypress/e2e/actions/delete.cy.ts b/cypress/e2e/actions/delete.cy.ts deleted file mode 100644 index 099ea885e..000000000 --- a/cypress/e2e/actions/delete.cy.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Delete image.png in viewer', function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, 'image.png', 'image/png') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('See image.png in the list', function() { - cy.getFile('image.png', { timeout: 10000 }) - .should('contain', 'image .png') - }) - - it('Open the viewer on file click', function() { - cy.openFile('image.png') - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Delete the image and close viewer', function() { - // open the menu - cy.get('body > .viewer .modal-header button.action-item__menutoggle').click() - // delete the file - cy.get('.action-button:contains(\'Delete\')').click() - }) - - it('Does not see the viewer anymore', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('not.exist') - }) - - it('Does not see image.png in the list anymore', function() { - cy.visit('/apps/files') - cy.getFile('image.png', { timeout: 10000 }) - .should('not.exist') - }) -}) diff --git a/cypress/e2e/actions/download.cy.ts b/cypress/e2e/actions/download.cy.ts deleted file mode 100644 index 6a61dc457..000000000 --- a/cypress/e2e/actions/download.cy.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import * as path from 'path' - -const fileName = 'image.png' - -describe(`Download ${fileName} in viewer`, function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, fileName, 'image/png') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - - after(function() { - cy.logout() - }) - - it(`See "${fileName}" in the list`, function() { - cy.getFile(fileName, { timeout: 10000 }) - .should('contain', fileName.replace(/(.*)\./, '$1 .')) - }) - - it('Open the viewer on file click', function() { - cy.openFile(fileName) - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Download the image', function() { - // open the menu - cy.get('body > .viewer .modal-header button.action-item__menutoggle').click() - // download the - cy.findByRole('menuitem', { name: 'Download' }).click() - }) - - it('Compare downloaded file with asset by size', function() { - const downloadsFolder = Cypress.config('downloadsFolder') - const fixturesFolder = Cypress.config('fixturesFolder') - - const downloadedFilePath = path.join(downloadsFolder, fileName) - const fixtureFilePath = path.join(fixturesFolder, fileName) - - cy.readFile(fixtureFilePath, 'binary', { timeout: 5000 }).then(fixtureBuffer => { - cy.readFile(downloadedFilePath, 'binary', { timeout: 5000 }) - .should(downloadedBuffer => { - if (downloadedBuffer.length !== fixtureBuffer.length) { - throw new Error(`File size ${downloadedBuffer.length} is not ${fixtureBuffer.length}`) - } - }) - }) - }) -}) diff --git a/cypress/e2e/actions/edit.cy.ts b/cypress/e2e/actions/edit.cy.ts deleted file mode 100644 index 2e9f48511..000000000 --- a/cypress/e2e/actions/edit.cy.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Open the new saved as image', function() { - before(function() { - cy.createRandomUser().then(user => { - cy.uploadFile(user, 'image1.jpg', 'image/jpeg') - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('See images in the list', function() { - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - }) - it('Open the viewer on file click', function() { - cy.openFile('image1.jpg') - cy.get('body > .viewer').should('be.visible') - }) - it('open the image editor', function() { - cy.get('button[aria-label="Edit"]').click() - }) - it('Save the image', function() { - cy.get('.FIE_topbar-save-button').click() - cy.get('input[type="text"].SfxInput-Base').clear() - cy.get('input[type="text"].SfxInput-Base').type('imageSave') - cy.get('.SfxModal-Container button[color="primary"].SfxButton-root').contains('Save').click() - cy.get('.FIE_topbar-close-button').click() - cy.get('.modal-header__name').should('contain', 'imageSave.jpg') - cy.get('.modal-header button[aria-label="Close"]').click() - }) - it('See the new saved image in the list', function() { - - cy.getFile('imageSave.jpg', { timeout: 10000 }) - .should('contain', 'imageSave .jpg') - }) - -}) diff --git a/cypress/e2e/actions/sidebar.cy.ts b/cypress/e2e/actions/sidebar.cy.ts deleted file mode 100644 index ceb20974f..000000000 --- a/cypress/e2e/actions/sidebar.cy.ts +++ /dev/null @@ -1,127 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Open the sidebar from the viewer and open viewer with sidebar already opened', function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, 'image1.jpg', 'image/jpeg') - cy.uploadFile(user, 'image2.jpg', 'image/jpeg') - cy.uploadFile(user, 'image3.jpg', 'image/jpeg') - cy.uploadFile(user, 'image4.jpg', 'image/jpeg') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('See images in the list', function() { - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - cy.getFile('image2.jpg', { timeout: 10000 }) - .should('contain', 'image2 .jpg') - cy.getFile('image3.jpg', { timeout: 10000 }) - .should('contain', 'image3 .jpg') - cy.getFile('image4.jpg', { timeout: 10000 }) - .should('contain', 'image4 .jpg') - }) - - it('Open the viewer on file click', function() { - cy.openFile('image1.jpg') - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image1.jpg') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Open the sidebar', function() { - // open the menu - cy.get('body > .viewer .modal-header button.action-item__menutoggle').click() - // open the sidebar - cy.findByRole('menuitem', { name: 'Open sidebar' }).click() - cy.get('aside.app-sidebar').should('be.visible') - // we hide the sidebar button if opened - cy.get('.action-button__icon.icon-menu-sidebar').should('not.exist') - // check the sidebar is opened for the correct file - cy.get('aside.app-sidebar .app-sidebar-header .app-sidebar-header__mainname').should('contain', 'image1.jpg') - }) - - it('Change to next image with sidebar open', function() { - cy.get('aside.app-sidebar').should('be.visible') - - // check the sidebar is opened for the correct file - cy.get('aside.app-sidebar .app-sidebar-header .app-sidebar-header__mainname').should('contain', 'image1.jpg') - - // open the next file (image2.png) using the arrow - cy.get('body > .viewer .button-vue.next').click() - cy.get('aside.app-sidebar .app-sidebar-header .app-sidebar-header__mainname').should('contain', 'image2.jpg') - }) - - it('Change to previous image with sidebar open', function() { - cy.get('aside.app-sidebar').should('be.visible') - - // check the sidebar is opened for the correct file - cy.get('aside.app-sidebar .app-sidebar-header .app-sidebar-header__mainname').should('contain', 'image2.jpg') - - // open the previous file (image1.png) using the arrow - cy.get('body > .viewer .button-vue.prev').click() - cy.get('aside.app-sidebar .app-sidebar-header .app-sidebar-header__mainname').should('contain', 'image1.jpg') - }) - - it('Close the sidebar', function() { - cy.get('aside.app-sidebar .app-sidebar-header .app-sidebar__close').click() - cy.get('aside.app-sidebar').should('not.be.visible') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').click() - // The button to show the sidebar is shown again - cy.findByRole('menuitem', { name: 'Open sidebar' }).should('be.visible') - }) - - it('Open the viewer with the sidebar open', function() { - cy.get('body > .viewer .modal-header button.header-close').click() - cy.get('body > .viewer').should('not.exist') - - // open the sidebar without viewer open - cy.getFile('image1.jpg') - .find('[data-cy-files-list-row-mtime]') - .click() - cy.get('aside.app-sidebar').should('be.visible') - cy.url().should('contain', 'opendetails=true') - - // eslint-disable-next-line - cy.wait(500) - - cy.openFile('image1.jpg') - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - - // eslint-disable-next-line - cy.wait(500) - - // close the sidebar again - cy.get('aside.app-sidebar') - .findByRole('button', { name: 'Close sidebar' }) - .click() - cy.get('aside.app-sidebar').should('not.be.visible') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - }) -}) diff --git a/cypress/e2e/audios/audio.mpeg.cy.ts b/cypress/e2e/audios/audio.mpeg.cy.ts deleted file mode 100644 index 38b37d798..000000000 --- a/cypress/e2e/audios/audio.mpeg.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import audioTest from '../mixins/audio' - -describe('Open audio.mp3 in viewer', function() { - audioTest('audio.mp3', 'audio/mpeg') -}) diff --git a/cypress/e2e/audios/audio.ogg.cy.ts b/cypress/e2e/audios/audio.ogg.cy.ts deleted file mode 100644 index f5a0ffc2f..000000000 --- a/cypress/e2e/audios/audio.ogg.cy.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import audioTest from '../mixins/audio' - -describe('Open audio.ogg in viewer', function() { - audioTest('audio.ogg', 'audio/ogg') -}) diff --git a/cypress/e2e/audios/audios.cy.ts b/cypress/e2e/audios/audios.cy.ts deleted file mode 100644 index 16c777d4d..000000000 --- a/cypress/e2e/audios/audios.cy.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Open mp3 and ogg audio in viewer', function() { - let randUser - - before(function() { - // Init user - cy.createRandomUser().then(user => { - randUser = user - - // Upload test files - cy.uploadFile(user, 'audio.mp3', 'audio/mpeg') - cy.uploadFile(user, 'audio.ogg', 'audio/ogg') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('See audios in the list', function() { - cy.getFile('audio.mp3', { timeout: 10000 }) - .should('contain', 'audio .mp3') - cy.getFile('audio.ogg', { timeout: 10000 }) - .should('contain', 'audio .ogg') - }) - - it('Open the viewer on file click', function() { - cy.openFile('audio.mp3') - cy.get('body > .viewer').should('be.visible') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'audio.mp3') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does see next navigation arrows', function() { - cy.get('body > .viewer .modal-container audio').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('The audio source is the remote url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active audio') - .should('have.attr', 'src') - .and('contain', `/remote.php/dav/files/${randUser.userId}/audio.mp3`) - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Show audio.ogg on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container audio').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('The audio source is the remote url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active audio') - .should('have.attr', 'src') - .and('contain', `/remote.php/dav/files/${randUser.userId}/audio.ogg`) - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) -}) diff --git a/cypress/e2e/download-forbidden.cy.ts b/cypress/e2e/download-forbidden.cy.ts deleted file mode 100644 index 2822a8e50..000000000 --- a/cypress/e2e/download-forbidden.cy.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import type { User } from '@nextcloud/cypress' -import { ShareType } from '@nextcloud/sharing' - -describe('Disable download button if forbidden', { testIsolation: true }, () => { - let sharee: User - - before(() => { - cy.createRandomUser().then((user) => { sharee = user }) - cy.createRandomUser().then((user) => { - // Upload test files - cy.createFolder(user, '/Photos') - cy.uploadFile(user, 'image1.jpg', 'image/jpeg', '/Photos/image1.jpg') - - cy.login(user) - cy.createShare('/Photos', - { shareWith: sharee.userId, shareType: ShareType.User, attributes: [{ scope: 'permissions', key: 'download', value: false }] }, - ) - cy.logout() - }) - }) - - beforeEach(() => { - cy.login(sharee) - cy.visit('/apps/files') - cy.openFile('Photos') - }) - - it('See the shared folder and images in files list', () => { - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - }) - - // TODO: Fix no-download files on server - it.skip('See the image can be shown', () => { - cy.getFile('image1.jpg').should('be.visible') - cy.openFile('image1.jpg') - cy.get('body > .viewer').should('be.visible') - - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the title on the viewer header but not the Download nor the menu button', () => { - cy.getFile('image1.jpg').should('be.visible') - // eslint-disable-next-line - cy.wait(250) - cy.openFile('image1.jpg') - cy.get('body > .viewer .modal-header__name').should('contain', 'image1.jpg') - - cy.get('[role="dialog"]') - .should('be.visible') - .find('button[aria-label="Actions"]') - .click() - - cy.get('[role="menu"]:visible') - .find('button') - .should('have.length', 2) - .each(($el) => { - expect($el.text()).to.match(/(Full screen|Open sidebar)/i) - }) - }) -}) diff --git a/cypress/e2e/files.cy.ts b/cypress/e2e/files.cy.ts deleted file mode 100644 index 6354bced2..000000000 --- a/cypress/e2e/files.cy.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { User } from '@nextcloud/cypress' - -describe('Files default view', function() { - const user = new User('admin', 'admin') - - before(function() { - cy.login(user) - }) - - after(function() { - cy.logout() - }) - - it('See the default files list', function() { - cy.visit('/apps/files') - cy.getFile('welcome.txt').should('contain', 'welcome .txt') - }) - - it('Take screenshot', function() { - cy.screenshot() - }) -}) diff --git a/cypress/e2e/images/image-apng.cy.ts b/cypress/e2e/images/image-apng.cy.ts deleted file mode 100644 index 935ccf1f6..000000000 --- a/cypress/e2e/images/image-apng.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import imageTest from '../mixins/image' - -describe('Open image-apng.png in viewer', function() { - imageTest('image-apng.png', 'image/png') -}) diff --git a/cypress/e2e/images/image-small.png.cy.ts b/cypress/e2e/images/image-small.png.cy.ts deleted file mode 100644 index 92437d08b..000000000 --- a/cypress/e2e/images/image-small.png.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import imageTest from '../mixins/image' - -describe('Open image-small.png in viewer', function() { - imageTest('image-small.png', 'image/png') -}) diff --git a/cypress/e2e/images/image.gif.cy.ts b/cypress/e2e/images/image.gif.cy.ts deleted file mode 100644 index db7d42e23..000000000 --- a/cypress/e2e/images/image.gif.cy.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import imageTest from '../mixins/image' - -describe('Open image.gif in viewer', function() { - imageTest('image.gif', 'image/gif', '/remote.php/dav/files') -}) diff --git a/cypress/e2e/images/image.ico.cy.ts b/cypress/e2e/images/image.ico.cy.ts deleted file mode 100644 index c02764a1a..000000000 --- a/cypress/e2e/images/image.ico.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import imageTest from '../mixins/image' - -describe('Open image.ico in viewer', function() { - imageTest('image.ico', 'image/x-icon', '/remote.php/dav/files') -}) diff --git a/cypress/e2e/images/image.png.cy.ts b/cypress/e2e/images/image.png.cy.ts deleted file mode 100644 index cb8dcb407..000000000 --- a/cypress/e2e/images/image.png.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import imageTest from '../mixins/image' - -describe('Open image.png in viewer', function() { - imageTest('image.png', 'image/png') -}) diff --git a/cypress/e2e/images/image.svg.cy.ts b/cypress/e2e/images/image.svg.cy.ts deleted file mode 100644 index 81ceaccc8..000000000 --- a/cypress/e2e/images/image.svg.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import imageTest from '../mixins/image' - -describe('Open image.svg in viewer', function() { - imageTest('image.svg', 'image/svg+xml', 'data:image/svg+xml;base64') -}) diff --git a/cypress/e2e/images/image.webp.cy.ts b/cypress/e2e/images/image.webp.cy.ts deleted file mode 100644 index 2c5822679..000000000 --- a/cypress/e2e/images/image.webp.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import imageTest from '../mixins/image' - -describe('Open image.webp in viewer', function() { - imageTest('image.webp', 'image/webp') -}) diff --git a/cypress/e2e/images/images-custom-list-loadmore.cy.ts b/cypress/e2e/images/images-custom-list-loadmore.cy.ts deleted file mode 100644 index b7314d25b..000000000 --- a/cypress/e2e/images/images-custom-list-loadmore.cy.ts +++ /dev/null @@ -1,201 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Open custom list of images in viewer with pagination', function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, 'image1.jpg', 'image/jpeg') - cy.uploadFile(user, 'image2.jpg', 'image/jpeg') - cy.uploadFile(user, 'image3.jpg', 'image/jpeg') - cy.uploadFile(user, 'image4.jpg', 'image/jpeg') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('See images in the list', function() { - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - cy.getFile('image2.jpg', { timeout: 10000 }) - .should('contain', 'image2 .jpg') - cy.getFile('image3.jpg', { timeout: 10000 }) - .should('contain', 'image3 .jpg') - cy.getFile('image4.jpg', { timeout: 10000 }) - .should('contain', 'image4 .jpg') - }) - - it('Open the viewer with a specific list', function() { - // make sure we only loadMore once - let loaded = false - - // get the files fileids - cy.getFileId('image1.jpg').then(fileID1 => { - cy.getFileId('image2.jpg').then(fileID2 => { - cy.getFileId('image3.jpg').then(fileID3 => { - cy.getFileId('image4.jpg').then(fileID4 => { - - // open the viewer with custom list of fileinfo - cy.window().then((win) => { - win.OCA.Viewer.open({ - path: '/image1.jpg', - list: [ - { - basename: 'image1.jpg', - filename: '/image1.jpg', - hasPreview: true, - fileid: parseInt(fileID1), - mime: 'image/jpeg', - permissions: 'RWD', - etag: 'etag123', - }, - { - basename: 'image2.jpg', - filename: '/image2.jpg', - hasPreview: true, - fileid: parseInt(fileID2), - mime: 'image/jpeg', - permissions: 'RWD', - etag: 'etag456', - }, - ], - // This will be triggered when we get to the end of the list - loadMore() { - // make sure we only loadMore once - if (loaded) { - return [] - } - - loaded = true - return [ - { - basename: 'image3.jpg', - filename: '/image3.jpg', - hasPreview: true, - fileid: parseInt(fileID3), - mime: 'image/jpeg', - permissions: 'RWD', - etag: 'etag123', - }, - { - basename: 'image4.jpg', - filename: '/image4.jpg', - hasPreview: true, - fileid: parseInt(fileID4), - mime: 'image/jpeg', - permissions: 'RWD', - etag: 'etag456', - }, - ] - }, - }) - }) - }) - }) - }) - }) - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image1.jpg') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does see next navigation arrows', function() { - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer .modal-container img').should('have.attr', 'src') - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Show image2 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) - - it('Show image3 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 3) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Show image4 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) - - it('Show image1 again on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) -}) diff --git a/cypress/e2e/images/images-custom-list.cy.ts b/cypress/e2e/images/images-custom-list.cy.ts deleted file mode 100644 index 36b2494d9..000000000 --- a/cypress/e2e/images/images-custom-list.cy.ts +++ /dev/null @@ -1,156 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Open custom images list in viewer', function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, 'image1.jpg', 'image/jpeg') - cy.uploadFile(user, 'image2.jpg', 'image/jpeg') - cy.uploadFile(user, 'image3.jpg', 'image/jpeg') - cy.uploadFile(user, 'image4.jpg', 'image/jpeg') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('See images in the list', function() { - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - cy.getFile('image2.jpg', { timeout: 10000 }) - .should('contain', 'image2 .jpg') - cy.getFile('image3.jpg', { timeout: 10000 }) - .should('contain', 'image3 .jpg') - cy.getFile('image4.jpg', { timeout: 10000 }) - .should('contain', 'image4 .jpg') - }) - - it('Open the viewer with a specific list', function() { - // get the two files fileids - cy.getFileId('image1.jpg').then(fileID1 => { - cy.getFileId('image3.jpg').then(fileID3 => { - - // open the viewer with custom list of fileinfo - cy.window().then((win) => { - win.OCA.Viewer.open({ - path: '/image1.jpg', - list: [ - { - basename: 'image1.jpg', - filename: '/image1.jpg', - hasPreview: true, - fileid: parseInt(fileID1), - permissions: 'RWD', - mime: 'image/jpeg', - etag: '123456789', - }, - { - basename: 'image3.jpg', - filename: '/image3.jpg', - hasPreview: true, - fileid: parseInt(fileID3), - permissions: 'R', - mime: 'image/jpeg', - etag: '987654321', - }, - ], - }) - }) - - }) - }) - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image1.jpg') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does see next navigation arrows', function() { - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer .modal-container img').should('have.attr', 'src') - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) - - it('Show image3 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image3.jpg') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Show image1 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image1.jpg') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) -}) diff --git a/cypress/e2e/images/images.cy.ts b/cypress/e2e/images/images.cy.ts deleted file mode 100644 index d23f0ab69..000000000 --- a/cypress/e2e/images/images.cy.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Open images in viewer', function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, 'image1.jpg', 'image/jpeg') - cy.uploadFile(user, 'image2.jpg', 'image/jpeg') - cy.uploadFile(user, 'image3.jpg', 'image/jpeg') - cy.uploadFile(user, 'image4.jpg', 'image/jpeg') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('See images in the list', function() { - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - cy.getFile('image2.jpg', { timeout: 10000 }) - .should('contain', 'image2 .jpg') - cy.getFile('image3.jpg', { timeout: 10000 }) - .should('contain', 'image3 .jpg') - cy.getFile('image4.jpg', { timeout: 10000 }) - .should('contain', 'image4 .jpg') - }) - - it('Open the viewer on file click', function() { - cy.openFile('image1.jpg') - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image1.jpg') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does see next navigation arrows', function() { - // only 2 because we don't know if we're at the end of the slideshow, current img and next one - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer .modal-container img').should('have.attr', 'src') - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Show image2 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 3) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) - - it('Show image3 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 3) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) - - it('Show image4 on next', function() { - cy.get('body > .viewer button.next').click() - // only 2 because we don't know if we're at the end of the slideshow, current img and previous one - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) - - it('Show image1 again on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('The image source is the preview url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/index.php/core/preview') - }) -}) diff --git a/cypress/e2e/mixins/audio.ts b/cypress/e2e/mixins/audio.ts deleted file mode 100644 index 03462f091..000000000 --- a/cypress/e2e/mixins/audio.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -/** - * Generate an audio cypress test - * - * @param {string} fileName the audio to upload and test against - * @param {string} mimeType the audio mime type - */ -export default function(fileName = 'audio.ogg', mimeType = 'audio/ogg') { - let randUser - - before(function() { - // Init user - cy.createRandomUser().then(user => { - randUser = user - - // Upload test files - cy.uploadFile(user, fileName, mimeType) - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it(`See ${fileName} in the list`, function() { - cy.getFile(fileName, { timeout: 10000 }) - .should('contain', fileName.replace(/(.*)\./, '$1 .')) - }) - - it('Open the viewer on file click and wait for loading to end', function() { - // Match audio request - cy.intercept('GET', `/remote.php/dav/files/${randUser.userId}/${fileName}`).as('source') - - // Open the file and check Viewer existence - cy.openFile(fileName) - cy.get('body > .viewer').should('be.visible') - - // Make sure loading is finished - cy.wait('@source').its('response.statusCode').should('eq', 206) - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', fileName) - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does not see navigation arrows', function() { - cy.get('body > .viewer button.prev').should('not.be.visible') - cy.get('body > .viewer button.next').should('not.be.visible') - }) - - it('The audio source is the remote url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active audio') - .should('have.attr', 'src') - .and('contain', `/remote.php/dav/files/${randUser.userId}/${fileName}`) - }) -} diff --git a/cypress/e2e/mixins/image.ts b/cypress/e2e/mixins/image.ts deleted file mode 100644 index 9f7b79e67..000000000 --- a/cypress/e2e/mixins/image.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -/** - * Generate an image cypress test - * - * @param {string} fileName the image to upload and test against - * @param {string} mimeType the image mime type - * @param {string} source the optional custom source to check against - */ -export default function(fileName = 'image1.jpg', mimeType = 'image/jpeg', source = null) { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, fileName, mimeType) - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it(`See ${fileName} in the list`, function() { - cy.getFile(fileName, { timeout: 10000 }) - .should('contain', fileName.replace(/(.*)\./, '$1 .')) - }) - - it('Open the viewer on file click and wait for loading to end', function() { - // Match image request - cy.getFileId(fileName).then(fileId => { - const matchRoute = source - ? `/remote.php/dav/files/*/${fileName}` - : `/index.php/core/preview*fileId=${fileId}*` - cy.intercept('GET', matchRoute).as('image') - }) - - // Open the file and check Viewer existence - cy.openFile(fileName) - cy.get('body > .viewer').should('be.visible') - - // Make sure loading is finished - cy.wait('@image').its('response.statusCode').should('eq', 200) - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', fileName) - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does not see navigation arrows', function() { - cy.get('body > .viewer button.prev').should('not.be.visible') - cy.get('body > .viewer button.next').should('not.be.visible') - }) - - it(`The image source is the ${source ? 'remote' : 'preview'} url`, function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', source ?? '/index.php/core/preview') - }) -} diff --git a/cypress/e2e/mixins/oddname.ts b/cypress/e2e/mixins/oddname.ts deleted file mode 100644 index 60023a7d9..000000000 --- a/cypress/e2e/mixins/oddname.ts +++ /dev/null @@ -1,141 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -/** - * Make a name aimed to break the viewer in case of escaping errors - * - * @param {string} realName the file original name - * @return {string} a name for the file to be uploaded as - */ -function naughtyFileName(realName) { - const ext = realName.split('.').pop() - return ( - '~⛰️ shot of a $[big} mountain`, ' - + "realy #1's " - + '" #_+="%2520%27%22%60%25%21%23 was this called ' - + realName - + 'in the' - + '☁️' - + '👩‍💻' - + '? :* .' - + ext.toUpperCase() - ) -} - -let failsLeft = 5 -Cypress.on('fail', (error) => { - failsLeft-- - throw error // throw error to have test still fail -}) - -/** - * - * @param file - * @param type - * @param sidebar - */ -export default function(file, type, sidebar = false) { - const placedName = naughtyFileName(file) - - const folderName - = 'Nextcloud "%27%22%60%25%21%23" >`⛰️<' + file + "><` e*'rocks!#?#%~" - - describe(`Open ${file} in viewer with a naughty name ${sidebar ? 'with sidebar' : ''}`, function() { - before(function() { - // fail fast - if (failsLeft < 0) { - throw new Error('Too many previous tests failed') - } - - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.createFolder(user, `/${folderName}`) - cy.uploadFile(user, file, type, `/${folderName}/${placedName}`) - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - - // wait a bit for things to be settled - cy.openFile(folderName) - }) - - /** - * - */ - function noLoadingAnimation() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - } - - /** - * - */ - function menuOk() { - cy.get('body > .viewer .icon-error').should('not.exist') - cy.get('body > .viewer .modal-header__name').should('contain', placedName) - cy.get('body > .viewer .modal-header button.header-close').should( - 'be.visible', - ) - } - - /** - * - */ - function arrowsOK() { - cy.get('body > .viewer button.prev').should('not.be.visible') - cy.get('body > .viewer button.next').should('not.be.visible') - } - - it(`See ${file} as ${placedName} in the list`, function() { - // cy.getFile will escape all the characters in the name to match it with css - cy.getFile(placedName, { timeout: 10000 }) - .should('contain', placedName.replace(/(.*)\./, '$1 .')) - }) - - it('Open the viewer on file click', function() { - cy.openFile(placedName) - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', noLoadingAnimation) - it('See the menu icon and title on the viewer header', menuOk) - it('Does not see navigation arrows', arrowsOK) - - if (sidebar) { - it('Open the sidebar', function() { - // open the menu - cy.get('body > .viewer .modal-header button.action-item__menutoggle').click() - // open the sidebar - cy.findByRole('menuitem', { name: 'Open sidebar' }).should('be.visible').click() - cy.get('aside.app-sidebar').should('be.visible') - // we hide the sidebar button if opened - cy.get('.action-button__icon.icon-menu-sidebar').should('not.exist') - // check the sidebar is opened for the correct file - cy.get('aside.app-sidebar .app-sidebar-header .app-sidebar-header__mainname').should('contain', placedName) - }) - } - - it('Share the folder with a share link and access the share link', function() { - cy.createLinkShare(folderName).then((token) => { - cy.logout() - cy.visit(`/s/${token}`) - }) - }) - - it('Open the viewer on file click (public)', function() { - cy.openFile(placedName) - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation (public)', noLoadingAnimation) - it('See the menu icon and title on the viewer header (public)', menuOk) - it('Does not see navigation arrows (public)', arrowsOK) - }) -} diff --git a/cypress/e2e/mixins/video.ts b/cypress/e2e/mixins/video.ts deleted file mode 100644 index f5a59929d..000000000 --- a/cypress/e2e/mixins/video.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -/** - * Generate a video cypress test - * - * @param {string} fileName the video to upload and test against - * @param {string} mimeType the video mime type - */ -export default function(fileName = 'image1.jpg', mimeType = 'image/jpeg') { - let randUser - - before(function() { - // Init user - cy.createRandomUser().then(user => { - randUser = user - - // Upload test files - cy.uploadFile(user, fileName, mimeType) - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it(`See ${fileName} in the list`, function() { - cy.getFile(fileName, { timeout: 10000 }) - .should('contain', fileName.replace(/(.*)\./, '$1 .')) - }) - - it('Open the viewer on file click and wait for loading to end', function() { - // Match audio request - cy.intercept('GET', `/remote.php/dav/files/${randUser.userId}/${fileName}`).as('source') - - // Open the file and check Viewer existence - cy.openFile(fileName) - cy.get('body > .viewer').should('be.visible') - - // Make sure loading is finished - cy.wait('@source').its('response.statusCode').should('eq', 206) - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', fileName) - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does not see navigation arrows', function() { - cy.get('body > .viewer button.prev').should('not.be.visible') - cy.get('body > .viewer button.next').should('not.be.visible') - }) - - it('The video source is the remote url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active video') - .should('have.attr', 'src') - .and('contain', `/remote.php/dav/files/${randUser.userId}/${fileName}`) - }) -} diff --git a/cypress/e2e/navigation.cy.ts b/cypress/e2e/navigation.cy.ts deleted file mode 100644 index f5dc1d3c6..000000000 --- a/cypress/e2e/navigation.cy.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Browser navigation', function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, 'image.png', 'image/png', '/image1.png') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('Navigating back to the files overview', function() { - cy.getFile('image1.png', { timeout: 10000 }) - cy.openFile('image1.png') - cy.get('body > .viewer').should('be.visible') - cy.go('back') - cy.get('body > .viewer').should('not.exist') - }) -}) diff --git a/cypress/e2e/non-dav-files.cy.ts b/cypress/e2e/non-dav-files.cy.ts deleted file mode 100644 index 0bb5764ea..000000000 --- a/cypress/e2e/non-dav-files.cy.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { basename as pathBasename } from '@nextcloud/paths' - -const source = '/apps/theming/img/background/anatoly-mikhaltsov-butterfly-wing-scale.jpg' -const basename = pathBasename(source) - -describe('Open non-dav files in viewer', function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, 'test-card.mp4', 'video/mp4') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('Open background', function() { - const fileInfo = { - filename: source, - basename, - mime: 'image/jpeg', - source, - etag: 'abc', - hasPreview: false, - fileid: 123, - } - - cy.window().then((win) => { - win.OCA.Viewer.open({ - fileInfo, - list: [fileInfo], - }) - }) - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the title and close button on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', basename) - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does not see navigation arrows', function() { - cy.get('body > .viewer button.prev').should('not.be.visible') - cy.get('body > .viewer button.next').should('not.be.visible') - }) - - it('See the menu but does not see the sidebar button', function() { - // Menu exists - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('.action-button__icon.icon-menu-sidebar').should('not.exist') - }) - - it('The image source is the remote url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', source) - }) -}) diff --git a/cypress/e2e/oddname/oddname-audio.cy.ts b/cypress/e2e/oddname/oddname-audio.cy.ts deleted file mode 100644 index f293e6356..000000000 --- a/cypress/e2e/oddname/oddname-audio.cy.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import runTest from '../mixins/oddname' - -for (const [file, type] of [ - ['audio.mp3', 'audio/mpeg'], - ['audio.ogg', 'audio/ogg'], -]) { - runTest(file, type) -} diff --git a/cypress/e2e/oddname/oddname-image.cy.ts b/cypress/e2e/oddname/oddname-image.cy.ts deleted file mode 100644 index 238614ee7..000000000 --- a/cypress/e2e/oddname/oddname-image.cy.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import runTest from '../mixins/oddname' - -for (const [file, type] of [ - ['image1.jpg', 'image/jpeg'], - ['image.gif', 'image/gif'], - ['image.png', 'image/png'], - ['image-small.png', 'image/png'], - ['image.svg', 'image/svg'], -]) { - runTest(file, type) -} diff --git a/cypress/e2e/oddname/oddname-sidebar.cy.ts b/cypress/e2e/oddname/oddname-sidebar.cy.ts deleted file mode 100644 index 2672a7e73..000000000 --- a/cypress/e2e/oddname/oddname-sidebar.cy.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import runTest from '../mixins/oddname' - -runTest('image.png', 'image/png', true) diff --git a/cypress/e2e/oddname/oddname-video.cy.ts b/cypress/e2e/oddname/oddname-video.cy.ts deleted file mode 100644 index 6d1b3973d..000000000 --- a/cypress/e2e/oddname/oddname-video.cy.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import runTest from '../mixins/oddname' - -for (const [file, type] of [ - ['video1.mp4', 'video/mp4'], - ['video.mkv', 'video/mkv'], - ['video.ogv', 'video/ogv'], - ['video.webm', 'video/webm'], -]) { - runTest(file, type) -} diff --git a/cypress/e2e/sharing/download-share-disabled.cy.ts b/cypress/e2e/sharing/download-share-disabled.cy.ts deleted file mode 100644 index af4c8f516..000000000 --- a/cypress/e2e/sharing/download-share-disabled.cy.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -const fileName = 'image1.jpg' - -describe(`Download ${fileName} in viewer`, function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.createFolder(user, '/Photos') - cy.uploadFile(user, 'image1.jpg', 'image/jpeg', '/Photos/image1.jpg') - cy.uploadFile(user, 'image2.jpg', 'image/jpeg', '/Photos/image2.jpg') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - - it('See the default files list', function() { - cy.getFile('welcome.txt').should('contain', 'welcome .txt') - cy.getFile('Photos').should('contain', 'Photos') - }) - - it('See shared files in the list', function() { - cy.openFile('Photos') - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - cy.getFile('image2.jpg', { timeout: 10000 }) - .should('contain', 'image2 .jpg') - }) - - it('Share the Photos folder with a share link, disable download and access the share link', function() { - cy.createLinkShare('/Photos').then((token: string) => { - cy.intercept('GET', '**/apps/files_sharing/api/v1/shares*').as('sharingAPI') - - // Open the sidebar from the breadcrumbs - cy.findByRole('navigation', { name: 'Current directory path' }) - .findByRole('button', { name: 'Photos' }) - .click() - cy.findByRole('menuitem', { name: 'Share' }).click() - cy.get('aside.app-sidebar').should('be.visible') - - // Wait for the sidebar to be done loading - cy.wait('@sharingAPI', { timeout: 10000 }) - - // Open the share menu - cy.get('.sharing-link-list > .sharing-entry button[aria-label*="Actions for "]').click() - cy.get('.action-button:contains(\'Customize link\')').click() - cy.findByRole('button', { name: 'Advanced settings' }).click() - - cy.get('.checkbox-radio-switch-checkbox').contains('Hide download').as('hideDownloadBtn') - // click the label - cy.get('@hideDownloadBtn').get('span').contains('Hide download').click() - cy.get('@hideDownloadBtn').get('input[type=checkbox]').should('be.checked') - - cy.intercept('PUT', '/ocs/v2.php/apps/files_sharing/api/v1/shares/*').as('updateShare') - cy.contains('button', 'Update share').click() - cy.wait('@updateShare') - - // Log out and access link share - cy.logout() - cy.visit(`/s/${token}`) - }) - }) - - it('See only view action', () => { - for (const file of ['image1.jpg', 'image2.jpg']) { - cy.get(`[data-cy-files-list-row-name="${CSS.escape(file)}"]`) - .find('[data-cy-files-list-row-actions]') - .find('button') - .click() - // Only view action - cy.get('[role="menu"]:visible') - .find('button') - .should('have.length', 1) - .first() - .should('contain.text', 'View') - cy.get(`[data-cy-files-list-row-name="${CSS.escape(file)}"]`) - .find('[data-cy-files-list-row-actions]') - .find('button') - .click() - } - }) - - it('Open the viewer on file click', function() { - cy.openFile('image1.jpg') - cy.get('body > .viewer').should('be.visible') - }) - - // TODO: FIX DOWNLOAD DISABLED SHARES - it.skip('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - // TODO: FIX DOWNLOAD DISABLED SHARES - it.skip('See the title on the viewer header but not the Download nor the menu button', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image1.jpg') - cy.get('body a[download="image1.jpg"]').should('not.exist') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('not.exist') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - -}) diff --git a/cypress/e2e/sharing/download-share.cy.ts b/cypress/e2e/sharing/download-share.cy.ts deleted file mode 100644 index 6a04b81e4..000000000 --- a/cypress/e2e/sharing/download-share.cy.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import * as path from 'path' - -const fileName = 'image1.jpg' - -describe(`Download ${fileName} from viewer in link share`, function() { - let token = null - - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.createFolder(user, '/Photos') - cy.uploadFile(user, 'image1.jpg', 'image/jpeg', '/Photos/image1.jpg') - cy.uploadFile(user, 'image2.jpg', 'image/jpeg', '/Photos/image2.jpg') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - // already logged out after visiting share link - // cy.logout() - }) - - it('See the default files list', function() { - cy.getFile('welcome.txt').should('contain', 'welcome .txt') - cy.getFile('Photos').should('contain', 'Photos') - }) - - it('See shared files in the list', function() { - cy.openFile('Photos') - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - cy.getFile('image2.jpg', { timeout: 10000 }) - .should('contain', 'image2 .jpg') - }) - - it('Share the Photos folder with a share link and access the share link', function() { - cy.createLinkShare('/Photos').then(newToken => { - token = newToken - cy.logout() - cy.visit(`/s/${token}`) - }) - }) - - it('Open the viewer on file click', function() { - cy.openFile('image1.jpg') - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the title and the close icon on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image1.jpg') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('See the menu on the viewer header and open it', function() { - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible').click() - }) - - it('See the full screen and download icons in the menu', function() { - cy.findByRole('menuitem', { name: 'Full screen' }).should('be.visible') - cy.findByRole('menuitem', { name: 'Download' }).should('be.visible') - }) - - it('Download the image', function() { - // https://github.com/cypress-io/cypress/issues/14857 - cy.window().then((win) => { setTimeout(() => { win.location.reload() }, 5000) }) - // download the file - cy.findByRole('menuitem', { name: 'Download' }).click() - }) - - it('Compare downloaded file with asset by size', function() { - const downloadsFolder = Cypress.config('downloadsFolder') - const fixturesFolder = Cypress.config('fixturesFolder') - - const downloadedFilePath = path.join(downloadsFolder, fileName) - const fixtureFilePath = path.join(fixturesFolder, fileName) - - cy.readFile(fixtureFilePath, 'binary', { timeout: 5000 }).then(fixtureBuffer => { - cy.readFile(downloadedFilePath, 'binary', { timeout: 5000 }) - .should(downloadedBuffer => { - if (downloadedBuffer.length !== fixtureBuffer.length) { - throw new Error(`File size ${downloadedBuffer.length} is not ${fixtureBuffer.length}`) - } - }) - }) - }) -}) diff --git a/cypress/e2e/sharing/files-shares.cy.ts b/cypress/e2e/sharing/files-shares.cy.ts deleted file mode 100644 index f07c5fc19..000000000 --- a/cypress/e2e/sharing/files-shares.cy.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('See shared folder with link share', function() { - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.createFolder(user, '/Photos') - cy.uploadFile(user, 'image1.jpg', 'image/jpeg', '/Photos/image1.jpg') - cy.uploadFile(user, 'image2.jpg', 'image/jpeg', '/Photos/image2.jpg') - cy.uploadFile(user, 'image3.jpg', 'image/jpeg', '/Photos/image3.jpg') - cy.uploadFile(user, 'image4.jpg', 'image/jpeg', '/Photos/image4.jpg') - cy.uploadFile(user, 'video1.mp4', 'video/mp4', '/Photos/video1.mp4') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - // already logged out after visiting share link - // cy.logout() - }) - - it('See the default files list', function() { - cy.getFile('welcome.txt').should('contain', 'welcome .txt') - cy.getFile('Photos').should('contain', 'Photos') - }) - - it('See shared files in the list', function() { - cy.openFile('Photos') - cy.getFile('image1.jpg', { timeout: 10000 }) - .should('contain', 'image1 .jpg') - cy.getFile('image2.jpg', { timeout: 10000 }) - .should('contain', 'image2 .jpg') - cy.getFile('image3.jpg', { timeout: 10000 }) - .should('contain', 'image3 .jpg') - cy.getFile('image4.jpg', { timeout: 10000 }) - .should('contain', 'image4 .jpg') - cy.getFile('video1.mp4', { timeout: 10000 }) - .should('contain', 'video1 .mp4') - }) - - it('Share the Photos folder with a share link and access the share link', function() { - cy.createLinkShare('/Photos').then(token => { - cy.logout() - cy.visit(`/s/${token}`) - }) - }) - - it('Open the viewer on file click', function() { - cy.openFile('image1.jpg') - cy.get('body > .viewer').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'image1.jpg') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does see next navigation arrows', function() { - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer .modal-container img').should('have.attr', 'src') - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Show image2 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 3) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Show image3 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 3) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Show image4 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Show video1 on next', function() { - cy.get('body > .viewer button.next').click() - // only 2 because we don't know if we're at the end of the slideshow, current vid and prev img - cy.get('body > .viewer .modal-container img').should('have.length', 1) - cy.get('body > .viewer .modal-container video').should('have.length', 1) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - cy.get('body > .viewer .modal-header__name').should('contain', 'video1.mp4') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Show image1 again on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) -}) diff --git a/cypress/e2e/sharing/single-file-share.cy.ts b/cypress/e2e/sharing/single-file-share.cy.ts deleted file mode 100644 index 1b5a99efe..000000000 --- a/cypress/e2e/sharing/single-file-share.cy.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('See shared folder with link share', function() { - let imageToken - let videoToken - - before(function() { - // Init user - cy.createRandomUser().then(user => { - // Upload test files - cy.uploadFile(user, 'image1.jpg', 'image/jpeg') - cy.uploadFile(user, 'video1.mp4', 'video/mp4') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - - // Create shares - cy.createLinkShare('/image1.jpg').then(token => { imageToken = token }) - cy.createLinkShare('/video1.mp4').then(token => { videoToken = token }) - - // Done - cy.logout() - }) - }) - - it('Opens the shared image in the viewer', function() { - cy.visit(`/s/${imageToken}`) - - cy.contains('image1.jpg').should('be.visible') - - cy.intercept('GET', '**/apps/files_sharing/publicpreview/**').as('getImage') - cy.openFileInSingleShare() - cy.wait('@getImage') - .its('response.statusCode') - .should('eq', 200) - - // Make sure loading is finished - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - - // The image source is the preview url - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active img') - .should('have.attr', 'src') - .and('contain', '/apps/files_sharing/publicpreview/') - - // See the menu icon and close button - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Opens the shared video in the viewer', function() { - cy.visit(`/s/${videoToken}`) - - cy.contains('video1.mp4').should('be.visible') - - cy.intercept('GET', '**/public.php/dav/files/**').as('loadVideo') - cy.openFileInSingleShare() - cy.wait('@loadVideo') - - // Make sure loading is finished - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - - // The video source is the preview url - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active video') - .should('have.attr', 'src') - .and('contain', `/public.php/dav/files/${videoToken}`) - - // See the menu icon and close button - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) -}) diff --git a/cypress/e2e/videos/video.mkv.cy.ts b/cypress/e2e/videos/video.mkv.cy.ts deleted file mode 100644 index 2adfb4916..000000000 --- a/cypress/e2e/videos/video.mkv.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import videoTest from '../mixins/video' - -describe('Open video.mkv in viewer', function() { - videoTest('video.mkv', 'image/mkv') -}) diff --git a/cypress/e2e/videos/video.mp4.cy.ts b/cypress/e2e/videos/video.mp4.cy.ts deleted file mode 100644 index 88ed4b9c1..000000000 --- a/cypress/e2e/videos/video.mp4.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import videoTest from '../mixins/video' - -describe('Open video1.mp4 in viewer', function() { - videoTest('video1.mp4', 'video/mp4') -}) diff --git a/cypress/e2e/videos/video.ogv.cy.ts b/cypress/e2e/videos/video.ogv.cy.ts deleted file mode 100644 index c0e9b74fa..000000000 --- a/cypress/e2e/videos/video.ogv.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import videoTest from '../mixins/video' - -describe('Open video.ogv in viewer', function() { - videoTest('video.ogv', 'video/ogv') -}) diff --git a/cypress/e2e/videos/video.webm.cy.ts b/cypress/e2e/videos/video.webm.cy.ts deleted file mode 100644 index 228448d59..000000000 --- a/cypress/e2e/videos/video.webm.cy.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import videoTest from '../mixins/video' - -describe('Open video.webm in viewer', function() { - videoTest('video.webm', 'video/webm') -}) diff --git a/cypress/e2e/videos/videos.cy.ts b/cypress/e2e/videos/videos.cy.ts deleted file mode 100644 index 0c54dc668..000000000 --- a/cypress/e2e/videos/videos.cy.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Open mp4 videos in viewer', function() { - let randUser - - before(function() { - // Init user - cy.createRandomUser().then(user => { - randUser = user - - // Upload test files - cy.uploadFile(user, 'video1.mp4', 'video/mp4') - cy.uploadFile(user, 'video2.mp4', 'video/mp4') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - }) - after(function() { - cy.logout() - }) - - it('See videos in the list', function() { - cy.getFile('video1.mp4', { timeout: 10000 }) - .should('contain', 'video1 .mp4') - cy.getFile('video2.mp4', { timeout: 10000 }) - .should('contain', 'video2 .mp4') - }) - - it('Open the viewer on file click', function() { - cy.openFile('video1.mp4') - cy.get('body > .viewer').should('be.visible') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'video1.mp4') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does see next navigation arrows', function() { - cy.get('body > .viewer .modal-container video').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('The video source is the remote url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active video') - .should('have.attr', 'src') - .and('contain', `/remote.php/dav/files/${randUser.userId}/video1.mp4`) - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Show video 2 on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container video').should('have.length', 2) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('The video source is the remote url', function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active video') - .should('have.attr', 'src') - .and('contain', `/remote.php/dav/files/${randUser.userId}/video2.mp4`) - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) -}) diff --git a/cypress/e2e/visual-regression.cy.ts b/cypress/e2e/visual-regression.cy.ts deleted file mode 100644 index a2e6529ee..000000000 --- a/cypress/e2e/visual-regression.cy.ts +++ /dev/null @@ -1,163 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -describe('Visual regression tests', function() { - let randUser - - before(function() { - // Init user - cy.createRandomUser().then(user => { - randUser = user - - // Upload test files - cy.uploadFile(user, 'test-card.mp4', 'video/mp4') - cy.uploadFile(user, 'test-card.png', 'image/png') - - // Visit nextcloud - cy.login(user) - cy.visit('/apps/files') - }) - - cy.window().then((win) => { - // Load roboto font for visual regression consistency - win.loadRoboto = true - win.document.body.style.setProperty('--font-face', 'Roboto') - win.document.body.style.setProperty('font-family', 'Roboto') - }) - }) - - it('See files in the list', function() { - cy.getFile('test-card.mp4', { timeout: 10000 }) - .should('contain', 'test-card .mp4') - cy.getFile('test-card.png', { timeout: 10000 }) - .should('contain', 'test-card .png') - }) - - it('Open the viewer on file click', function() { - cy.intercept('GET', '**/viewer/css/fonts/roboto-*').as('roboto-font') - cy.intercept('GET', '**/core/preview*').as('image1') - cy.intercept('GET', '/remote.php/dav/files/*/test-card.mp4').as('video') - cy.openFile('test-card.mp4') - cy.wait('@roboto-font') - cy.wait('@video') - // We preload images, so we can check its loading here and not when clicking next - cy.wait('@image1') - cy.get('body > .viewer').should('be.visible') - }) - - it('See the menu icon and title on the viewer header', function() { - cy.get('body > .viewer .modal-header__name').should('contain', 'test-card.mp4') - cy.get('body > .viewer .modal-header button.action-item__menutoggle').should('be.visible') - cy.get('body > .viewer .modal-header button.header-close').should('be.visible') - }) - - it('Does see next navigation arrows', function() { - cy.get('body > .viewer .modal-container video').should('have.length', 1) - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active video') - .should('have.attr', 'src') - .and('contain', `/remote.php/dav/files/${randUser.userId}/test-card.mp4`) - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Take test-card.mp4 screenshot', { retries: 0 }, function() { - cy.get('body > .viewer .modal-container .viewer__file.viewer__file--active video').then(video => { - video.get(0).pause() - video.get(0).currentTime = 1 - }) - // wait a bit for things to be settled - // eslint-disable-next-line - cy.wait(250) - cy.compareSnapshot('video', 0.02) - }) - - it('Show second file on next', function() { - cy.get('body > .viewer button.next').click() - cy.get('body > .viewer .modal-container img').should('have.length', 1) - cy.get('body > .viewer .modal-container img').should('have.attr', 'src') - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Take test-card.png screenshot', { retries: 0 }, function() { - cy.compareSnapshot('image') - }) - - it('Close and open image again', function() { - cy.get('body > .viewer button.header-close').click() - cy.get('body > .viewer').should('not.exist') - - // No need to intercept the request again, it's cached - cy.openFile('test-card.png') - - cy.get('body > .viewer').should('be.visible') - cy.get('body > .viewer .modal-header__name').should('contain', 'test-card.png') - cy.get('body > .viewer .modal-container img').should('have.length', 1) - cy.get('body > .viewer .modal-container img').should('have.attr', 'src') - cy.get('body > .viewer button.prev').should('be.visible') - cy.get('body > .viewer button.next').should('be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Take test-card.png screenshot 2', { retries: 0 }, function() { - cy.compareSnapshot('image2') - }) - - it('Open non-dav image', function() { - const fileInfo = { - filename: '/core/img/favicon.png', - basename: 'favicon.png', - mime: 'image/png', - source: '/core/img/favicon.png', - etag: 'abc', - hasPreview: false, - fileid: 123, - } - cy.intercept('GET', '/core/img/favicon.png').as('favicon') - cy.window().then((win) => { - win.OCA.Viewer.open({ - fileInfo, - list: [fileInfo], - }) - }) - cy.wait('@favicon') - - cy.get('body > .viewer .modal-container img').should('have.length', 1) - cy.get('body > .viewer .modal-container img').should('have.attr', 'src') - cy.get('body > .viewer button.prev').should('not.be.visible') - cy.get('body > .viewer button.next').should('not.be.visible') - }) - - it('Does not see a loading animation', function() { - cy.get('body > .viewer', { timeout: 10000 }) - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - }) - - it('Take non-dav logo.png screenshot', { retries: 0 }, function() { - cy.compareSnapshot('non-dav') - }) -}) diff --git a/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/image.png b/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/image.png deleted file mode 100644 index ba2d88803..000000000 Binary files a/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/image.png and /dev/null differ diff --git a/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/image2.png b/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/image2.png deleted file mode 100644 index 1bcd211a5..000000000 Binary files a/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/image2.png and /dev/null differ diff --git a/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/non-dav.png b/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/non-dav.png deleted file mode 100644 index c437bd908..000000000 Binary files a/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/non-dav.png and /dev/null differ diff --git a/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/video.png b/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/video.png deleted file mode 100644 index b1127ff85..000000000 Binary files a/cypress/snapshots/base/cypress/e2e/visual-regression.cy.ts/video.png and /dev/null differ diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts deleted file mode 100644 index 5eefac18f..000000000 --- a/cypress/support/commands.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { addCommands } from '@nextcloud/cypress' -import { Permission } from '@nextcloud/files' -import { ShareType } from '@nextcloud/sharing' -import { addCompareSnapshotCommand } from 'cypress-visual-regression/dist/command' -import { basename } from 'path' - -import '@testing-library/cypress/add-commands' - -addCommands() -addCompareSnapshotCommand({ - errorThreshold: 0.01, -}) - -const url = Cypress.config('baseUrl')!.replace(/\/index.php\/?$/g, '') -Cypress.env('baseUrl', url) - -/** - * cy.uploadedFile - uploads a file from the fixtures folder - * - * @param {User} user the owner of the file, e.g. admin - * @param {string} fixture the fixture file name, e.g. image1.jpg - * @param {string} mimeType e.g. image/png - * @param {string} [target] the target of the file relative to the user root - */ -Cypress.Commands.add('uploadFile', (user, fixture, mimeType, target = `/${fixture}`) => { - cy.clearCookies() - - // get fixture - return cy.fixture(fixture, 'binary').then(file => { - // convert the binary to a blob - const blob = Cypress.Blob.binaryStringToBlob(file, mimeType) - - const fileName = basename(target) - const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}` - const filePath = target.split('/').map(encodeURIComponent).join('/') - - cy.request({ - url: `${rootPath}${filePath}`, - method: 'PUT', - body: blob, - headers: { - 'Content-Type': mimeType, - }, - auth: { - username: user.userId, - password: user.password, - }, - }).then(response => { - cy.log(`Uploaded file ${fileName}`, response) - }) - }) -}) - -Cypress.Commands.add('createFolder', (user, target) => { - cy.clearCookies() - - const dirName = basename(target) - const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}` - const dirPath = target.split('/').map(encodeURIComponent).join('/') - - cy.request({ - url: `${rootPath}${dirPath}`, - method: 'MKCOL', - auth: { - username: user.userId, - password: user.password, - }, - }).then(response => { - cy.log(`Created folder ${dirName}`, response) - }) -}) - -Cypress.Commands.add('getFile', fileName => { - return cy.get(`[data-cy-files-list] tr[data-cy-files-list-row-name="${CSS.escape(fileName)}"]`) -}) - -Cypress.Commands.add('openFile', fileName => { - cy.getFile(fileName) - .find('button') - .first() - .should('be.visible') - .click() - // eslint-disable-next-line - cy.wait(250) -}) - -Cypress.Commands.add('openFileInSingleShare', () => { - cy.get('tr[data-cy-files-list-row-name]') - .should('have.length', 1) - // eslint-disable-next-line - cy.wait(250) -}) - -Cypress.Commands.add('getFileId', fileName => { - return cy.getFile(fileName) - .should('have.attr', 'data-cy-files-list-row-fileid') -}) - -Cypress.Commands.add('deleteFile', fileName => { - cy.getFile(fileName).clickAction('delete') -}) - -Cypress.Commands.add('reloadCurrentFilesList', () => { - cy.get('[data-cy-files-content-breadcrumbs] a[title="Reload current directory"]').click() -}) - -Cypress.Commands.add( - 'clickAction', - { prevSubject: 'element' }, - (subject, action) => { - subject.find('[data-cy-files-list-row-actions] button').click() - cy.get(`[data-cy-files-list-row-action="${action}"]`).click() - }, -) - -interface ShareOptions { - shareType: number - shareWith?: string - permissions: number - attributes?: { value: boolean, key: string, scope: string}[] -} - -Cypress.Commands.add('createShare', (path: string, shareOptions?: ShareOptions) => { - return cy.request('/csrftoken').then(({ body }) => { - const requesttoken = body.token - - return cy.request({ - method: 'POST', - url: '../ocs/v2.php/apps/files_sharing/api/v1/shares?format=json', - headers: { - requesttoken, - }, - body: { - path, - permissions: Permission.READ, - ...shareOptions, - attributes: shareOptions?.attributes && JSON.stringify(shareOptions.attributes), - }, - }).then(({ body }) => { - const shareToken = body.ocs?.data?.token - if (shareToken === undefined) { - throw new Error('Invalid OCS response') - } - cy.log('Share link created', shareToken) - return cy.wrap(shareToken) - }) - }) -}) - -/** - * Create a share link and return the share url - * - * @param {string} path the file/folder path - * @return {string} the share link url - */ -Cypress.Commands.add('createLinkShare', path => { - return cy.createShare(path, { shareType: ShareType.Link }) -}) - -Cypress.Commands.overwrite('compareSnapshot', (originalFn, subject, name, options) => { - // hide avatar because random colour break the visual regression tests - cy.window().then(window => { - const avatarDiv = window.document.querySelector('.avatardiv') - if (avatarDiv) { - avatarDiv.remove() - } - }) - return originalFn(subject, name, options) -}) diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts deleted file mode 100644 index 0a5f71f43..000000000 --- a/cypress/support/e2e.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands' - -Cypress.on('uncaught:exception', () => { - return false -}) diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json deleted file mode 100644 index 8e156cf39..000000000 --- a/cypress/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.json", - "include": ["../*.ts", "."], - "compilerOptions": { - "rootDir": "..", - "types": [ - "@testing-library/cypress", - "cypress", - "dockerode", - "node" - ] - } -} diff --git a/package-lock.json b/package-lock.json index 90934be29..34be56be0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,20 +41,13 @@ }, "devDependencies": { "@nextcloud/browserslist-config": "^3.0.1", - "@nextcloud/cypress": "^1.0.0-beta.15", + "@nextcloud/e2e-test-server": "^0.5.0", "@nextcloud/eslint-config": "^8.4.2", "@nextcloud/sharing": "^0.2.5", "@nextcloud/stylelint-config": "^3.1.0", "@nextcloud/vite-config": "^1.5.6", - "@testing-library/cypress": "^10.1.0", - "@types/dockerode": "^3.3.47", + "@playwright/test": "^1.61.0", "@vue/tsconfig": "^0.5.1", - "cypress": "^13.17.0", - "cypress-split": "^1.24.28", - "cypress-visual-regression": "^5.3.0", - "dockerode": "^4.0.10", - "eslint-plugin-cypress": "^3.6.0", - "ts-node": "^10.9.2", "tslib": "^2.8.1", "typescript": "^5.9.3", "vite": "^6.0.0", @@ -65,45 +58,6 @@ "npm": "^11.3.0" } }, - "node_modules/@actions/core": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.3.tgz", - "integrity": "sha512-Od9Thc3T1mQJYddvVPM4QGiLUewdh+3txmDYHHxoNdkqysR1MbCT+rFOtNUxYAz+7+6RIsqipVahY2GJqGPyxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@actions/exec": "^2.0.0", - "@actions/http-client": "^3.0.2" - } - }, - "node_modules/@actions/exec": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-2.0.0.tgz", - "integrity": "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@actions/io": "^2.0.0" - } - }, - "node_modules/@actions/http-client": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.2.tgz", - "integrity": "sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tunnel": "^0.0.6", - "undici": "^6.23.0" - } - }, - "node_modules/@actions/io": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz", - "integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==", - "dev": true, - "license": "MIT" - }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -420,41 +374,6 @@ "node-fetch": "^3.3.0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@csstools/css-parser-algorithms": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", @@ -534,71 +453,6 @@ "node": ">=10" } }, - "node_modules/@cypress/request": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz", - "integrity": "sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~4.0.4", - "http-signature": "~1.4.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "~6.14.1", - "safe-buffer": "^5.1.2", - "tough-cookie": "^5.0.0", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" - } - }, - "node_modules/@cypress/xvfb/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@dependents/detective-less": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.1.tgz", - "integrity": "sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "gonzales-pe": "^4.3.0", - "node-source-walk": "^7.0.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@dual-bundle/import-meta-resolve": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", @@ -1331,21 +1185,48 @@ "node": ">=6" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "node_modules/@hapi/address": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", + "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/address/node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "node_modules/@hapi/formula": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", + "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/pinpoint": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", + "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/tlds": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.7.tgz", + "integrity": "sha512-MgNjRwy9Ti92yVAixLmDc8dd1bJIKwO9qlWCfFQRwRmUEDPQHYn4G6hwPFvFGUTzAa0FsS+inMjLin7GnyBRhA==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" + "engines": { + "node": ">=14.0.0" } }, "node_modules/@humanwhocodes/config-array": { @@ -1808,25 +1689,6 @@ "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, - "node_modules/@nextcloud/cypress": { - "version": "1.0.0-beta.15", - "resolved": "https://registry.npmjs.org/@nextcloud/cypress/-/cypress-1.0.0-beta.15.tgz", - "integrity": "sha512-0pTweoFmw9x0J2R1Ou+ycIw8aGuJGTrbudaorl4vFXUEDEIo4GAx136RzH1TzYkRn1zPxDloFi2qmHh54NMdng==", - "dev": true, - "license": "AGPL-3.0-or-later", - "dependencies": { - "dockerode": "^4.0.2", - "fast-xml-parser": "^5.2.2", - "wait-on": "^8.0.0" - }, - "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" - }, - "peerDependencies": { - "cypress": "^13.9.0" - } - }, "node_modules/@nextcloud/dialogs": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-7.3.0.tgz", @@ -2331,6 +2193,125 @@ "vue": "3.x" } }, + "node_modules/@nextcloud/e2e-test-server": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@nextcloud/e2e-test-server/-/e2e-test-server-0.5.0.tgz", + "integrity": "sha512-N1naJXOUYDyLy1JmjZtCmTqKGnGHs+CREytXzQVV7riEL171tXHw6GaB2Vi4kHWF5SMlGMnfv7r0yR0qlnUW4w==", + "dev": true, + "license": "AGPL-3.0-or-later", + "dependencies": { + "@nextcloud/paths": "^3.1.0", + "dockerode": "^5.0.0", + "fast-xml-parser": "^5.2.2", + "tar-stream": "^3.2.0", + "wait-on": "^9.0.1" + }, + "engines": { + "node": "^22.0.0 || ^24.0.0 || >=26.0.0" + } + }, + "node_modules/@nextcloud/e2e-test-server/node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@nextcloud/e2e-test-server/node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@nextcloud/e2e-test-server/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/@nextcloud/e2e-test-server/node_modules/dockerode": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-5.0.1.tgz", + "integrity": "sha512-avsq/xk4YPIrn0CgleX5bjT9Y8IT1p9PxrNQ++RBQ2WEyFfHCTDsT9kmyxz+H/axnjAwg8wJWEIuPGOUuNupiA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "@grpc/grpc-js": "^1.11.1", + "@grpc/proto-loader": "^0.7.13", + "docker-modem": "^5.0.7", + "protobufjs": "^7.3.2", + "tar-fs": "^2.1.4" + }, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/@nextcloud/e2e-test-server/node_modules/joi": { + "version": "18.2.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.3.tgz", + "integrity": "sha512-N5A3KTWQpPWT4ExxxPlUx7WmykGXRzhNidWhV41d6Abu9YfI2NyWCJuxdPnslJCPWtbRpSVOWSnSS6GakLM/Rg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/address": "^5.1.1", + "@hapi/formula": "^3.0.2", + "@hapi/hoek": "^11.0.7", + "@hapi/pinpoint": "^2.0.1", + "@hapi/tlds": "^1.1.1", + "@hapi/topo": "^6.0.2", + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nextcloud/e2e-test-server/node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/@nextcloud/e2e-test-server/node_modules/wait-on": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.10.tgz", + "integrity": "sha512-rCoJEhvMr0X6alHmwc9abbrA5ZrLZFKpFQVKPNFwl2h7DapXOGdmimIHDtLOWhT4PjhZhxFEtZoQgEXbkDWdZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.16.0", + "joi": "^18.2.1", + "lodash": "^4.18.1", + "minimist": "^1.2.8", + "rxjs": "^7.8.2" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@nextcloud/eslint-config": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/@nextcloud/eslint-config/-/eslint-config-8.4.2.tgz", @@ -2788,6 +2769,7 @@ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2802,6 +2784,7 @@ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 8" } @@ -2812,6 +2795,7 @@ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -3155,6 +3139,22 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -3888,30 +3888,6 @@ "styled-components": ">=5.0.0" } }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/@skjnldsv/vue-plyr": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@skjnldsv/vue-plyr/-/vue-plyr-7.5.0.tgz", @@ -3960,43 +3936,12 @@ } } }, - "node_modules/@testing-library/cypress": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@testing-library/cypress/-/cypress-10.1.0.tgz", - "integrity": "sha512-tNkNtYRqPQh71xXKuMizr146zlellawUfDth7A/urYU4J66g0VGZ063YsS0gqS79Z58u1G/uo9UxN05qvKXMag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.14.6", - "@testing-library/dom": "^10.1.0" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "cypress": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0" - } - }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } + "license": "MIT" }, "node_modules/@tippyjs/react": { "version": "4.2.6", @@ -4017,34 +3962,6 @@ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "license": "MIT" }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -4064,13 +3981,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -4080,29 +3990,6 @@ "@types/ms": "*" } }, - "node_modules/@types/docker-modem": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", - "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/ssh2": "*" - } - }, - "node_modules/@types/dockerode": { - "version": "3.3.47", - "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.47.tgz", - "integrity": "sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/docker-modem": "*", - "@types/node": "*", - "@types/ssh2": "*" - } - }, "node_modules/@types/escape-html": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-1.0.4.tgz", @@ -4225,46 +4112,12 @@ "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", "license": "MIT" }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/sizzle": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", "license": "MIT" }, - "node_modules/@types/ssh2": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz", - "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^18.11.18" - } - }, - "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.110", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.110.tgz", - "integrity": "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/ssh2/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/toastify-js": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/@types/toastify-js/-/toastify-js-1.12.4.tgz", @@ -4290,17 +4143,6 @@ "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", @@ -4366,39 +4208,6 @@ } } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.0.tgz", - "integrity": "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.0", - "@typescript-eslint/types": "^8.33.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", - "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/scope-manager": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", @@ -4418,23 +4227,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz", - "integrity": "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, "node_modules/@typescript-eslint/type-utils": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", @@ -5313,31 +5105,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "debug": "4" }, "engines": { - "node": ">=8" + "node": ">= 6.0.0" } }, "node_modules/ajv": { @@ -5407,32 +5184,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -5486,34 +5237,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/app-module-path": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", - "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/are-docs-informative": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", @@ -5525,13 +5248,6 @@ "node": ">=14" } }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -5540,22 +5256,13 @@ "license": "Python-2.0", "peer": true }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" @@ -5605,6 +5312,7 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -5738,16 +5446,6 @@ "util": "^0.12.5" } }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/ast-kit": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", @@ -5764,16 +5462,6 @@ "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/ast-module-types": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.1.tgz", - "integrity": "sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/ast-walker-scope": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.8.3.tgz", @@ -5796,17 +5484,11 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -5824,16 +5506,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -5850,31 +5522,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", - "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", - "dev": true, - "license": "MIT" - }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, @@ -5922,6 +5578,106 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", + "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-stream/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", + "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base-64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", @@ -5993,20 +5749,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true, - "license": "MIT" - }, "node_modules/blurhash": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", @@ -6277,16 +6019,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -6379,16 +6111,6 @@ "@keyv/serialize": "^1.0.3" } }, - "node_modules/cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -6497,13 +6219,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -6520,6 +6235,7 @@ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -6537,6 +6253,7 @@ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -6602,16 +6319,6 @@ "node": "*" } }, - "node_modules/check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -6636,22 +6343,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ci-info": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", - "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/cipher-base": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", @@ -6672,73 +6363,17 @@ "integrity": "sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==", "license": "MIT" }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, "engines": { "node": ">=12" } @@ -6780,13 +6415,6 @@ "license": "MIT", "peer": true }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6809,16 +6437,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/comment-parser": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", @@ -6837,16 +6455,6 @@ "dev": true, "license": "MIT" }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/compare-versions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", @@ -6859,7 +6467,8 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/confbox": { "version": "0.2.4", @@ -6873,19 +6482,6 @@ "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", "dev": true }, - "node_modules/console.table": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz", - "integrity": "sha512-dPyZofqggxuvSf7WXvNjuRfnsOk1YazkVP8FdxH4tcH2c37wc79/Yl6Bhr7Lsu00KMgy2ql/qCMuNu8xctZM8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "easy-table": "1.1.0" - }, - "engines": { - "node": "> 0.10" - } - }, "node_modules/constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -7035,6 +6631,7 @@ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -7152,138 +6749,6 @@ "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==", "license": "MIT" }, - "node_modules/cypress": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", - "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@cypress/request": "^3.0.6", - "@cypress/xvfb": "^1.2.4", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.7.1", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "ci-info": "^4.0.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.3", - "tree-kill": "1.2.2", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "bin": { - "cypress": "bin/cypress" - }, - "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" - } - }, - "node_modules/cypress-split": { - "version": "1.24.28", - "resolved": "https://registry.npmjs.org/cypress-split/-/cypress-split-1.24.28.tgz", - "integrity": "sha512-6s6fAzZ2QG+p78ti2kBsOsLi+Pa1wEg+Z9uDj8B1I09QQm1JYZOsNo3fYtP2m3eWSQ9/iQhJ4VMlP63DALU7xQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@actions/core": "2.0.3", - "arg": "^5.0.2", - "console.table": "^0.10.0", - "debug": "^4.3.4", - "fast-shuffle": "^6.1.0", - "find-cypress-specs": "1.54.9", - "globby": "^11.1.0", - "humanize-duration": "^3.28.0" - }, - "bin": { - "cypress-split-merge": "bin/merge.js", - "cypress-split-preview": "bin/preview.js" - } - }, - "node_modules/cypress-visual-regression": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/cypress-visual-regression/-/cypress-visual-regression-5.3.0.tgz", - "integrity": "sha512-vnGOcoty61JibQ6LMYmYXQ0MIdoiQzuRtLpL+iuGE3aalVaYecC9AUfQfrOJL1eRtC2hsvrmPRfgHBZ/MXe75A==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "4.1.2", - "pixelmatch": "5.3.0", - "pngjs": "7.0.0", - "sanitize-filename": "1.6.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "cypress": ">=12" - } - }, - "node_modules/cypress/node_modules/proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cypress/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -7366,13 +6831,6 @@ "integrity": "sha512-/+lyMUKoRogMuTeOVii6lUwjbVlesN9YRYLzZT/g3TEZ3uD9QnpjResujeEqUW+OSNbT7T1+SYdyEkTcRv+KDQ==", "license": "MIT" }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "dev": true, - "license": "MIT" - }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -7422,39 +6880,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -7463,31 +6888,6 @@ "license": "MIT", "peer": true }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -7533,35 +6933,6 @@ "node": ">=0.4.0" } }, - "node_modules/dependency-tree": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-11.1.1.tgz", - "integrity": "sha512-pnkCd8VGOq70EVaEQxDC9mZCjCwYj4yG4j8h+PEJswuWp+rdE6p8zbtVvWk+yPwaVimOjlhNi782U9K5KOU9MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^12.1.0", - "filing-cabinet": "^5.0.3", - "precinct": "^12.2.0", - "typescript": "^5.7.3" - }, - "bin": { - "dependency-tree": "bin/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/dependency-tree/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -7597,247 +6968,6 @@ "node": ">=0.10" } }, - "node_modules/detective-amd": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-6.0.1.tgz", - "integrity": "sha512-TtyZ3OhwUoEEIhTFoc1C9IyJIud3y+xYkSRjmvCt65+ycQuc3VcBrPRTMWoO/AnuCyOB8T5gky+xf7Igxtjd3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-module-types": "^6.0.1", - "escodegen": "^2.1.0", - "get-amd-module-type": "^6.0.1", - "node-source-walk": "^7.0.1" - }, - "bin": { - "detective-amd": "bin/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/detective-cjs": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-6.0.1.tgz", - "integrity": "sha512-tLTQsWvd2WMcmn/60T2inEJNhJoi7a//PQ7DwRKEj1yEeiQs4mrONgsUtEJKnZmrGWBBmE0kJ1vqOG/NAxwaJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-module-types": "^6.0.1", - "node-source-walk": "^7.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/detective-es6": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-5.0.1.tgz", - "integrity": "sha512-XusTPuewnSUdoxRSx8OOI6xIA/uld/wMQwYsouvFN2LAg7HgP06NF1lHRV3x6BZxyL2Kkoih4ewcq8hcbGtwew==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-source-walk": "^7.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/detective-postcss": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-7.0.1.tgz", - "integrity": "sha512-bEOVpHU9picRZux5XnwGsmCN4+8oZo7vSW0O0/Enq/TO5R2pIAP2279NsszpJR7ocnQt4WXU0+nnh/0JuK4KHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-url": "^1.2.4", - "postcss-values-parser": "^6.0.2" - }, - "engines": { - "node": "^14.0.0 || >=16.0.0" - }, - "peerDependencies": { - "postcss": "^8.4.47" - } - }, - "node_modules/detective-sass": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-6.0.1.tgz", - "integrity": "sha512-jSGPO8QDy7K7pztUmGC6aiHkexBQT4GIH+mBAL9ZyBmnUIOFbkfZnO8wPRRJFP/QP83irObgsZHCoDHZ173tRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "gonzales-pe": "^4.3.0", - "node-source-walk": "^7.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/detective-scss": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-5.0.1.tgz", - "integrity": "sha512-MAyPYRgS6DCiS6n6AoSBJXLGVOydsr9huwXORUlJ37K3YLyiN0vYHpzs3AdJOgHobBfispokoqrEon9rbmKacg==", - "dev": true, - "license": "MIT", - "dependencies": { - "gonzales-pe": "^4.3.0", - "node-source-walk": "^7.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/detective-stylus": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-5.0.1.tgz", - "integrity": "sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/detective-typescript": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-14.0.0.tgz", - "integrity": "sha512-pgN43/80MmWVSEi5LUuiVvO/0a9ss5V7fwVfrJ4QzAQRd3cwqU1SfWGXJFcNKUqoD5cS+uIovhw5t/0rSeC5Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "^8.23.0", - "ast-module-types": "^6.0.1", - "node-source-walk": "^7.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "typescript": "^5.4.4" - } - }, - "node_modules/detective-typescript/node_modules/@typescript-eslint/types": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.0.tgz", - "integrity": "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/detective-typescript/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz", - "integrity": "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.33.0", - "@typescript-eslint/tsconfig-utils": "8.33.0", - "@typescript-eslint/types": "8.33.0", - "@typescript-eslint/visitor-keys": "8.33.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/detective-typescript/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz", - "integrity": "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.33.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/detective-typescript/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/detective-typescript/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/detective-typescript/node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/detective-vue2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/detective-vue2/-/detective-vue2-2.2.0.tgz", - "integrity": "sha512-sVg/t6O2z1zna8a/UIV6xL5KUa2cMTQbdTIIvqNM0NIPswp52fe43Nwmbahzj3ww4D844u/vC2PYfiGLvD3zFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@dependents/detective-less": "^5.0.1", - "@vue/compiler-sfc": "^3.5.13", - "detective-es6": "^5.0.1", - "detective-sass": "^6.0.1", - "detective-scss": "^5.0.1", - "detective-stylus": "^5.0.1", - "detective-typescript": "^14.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "typescript": "^5.4.4" - } - }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -7851,16 +6981,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -7886,6 +7006,7 @@ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "path-type": "^4.0.0" }, @@ -7909,39 +7030,6 @@ "node": ">= 8.0" } }, - "node_modules/dockerode": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.10.tgz", - "integrity": "sha512-8L/P9JynLBiG7/coiA4FlQXegHltRqS0a+KqI44P1zgQh8QLHTg7FKOwhkBgSJwZTeHsq30WRoVFLuwkfK0YFg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@balena/dockerignore": "^1.0.2", - "@grpc/grpc-js": "^1.11.1", - "@grpc/proto-loader": "^0.7.13", - "docker-modem": "^5.0.7", - "protobufjs": "^7.3.2", - "tar-fs": "^2.1.4", - "uuid": "^10.0.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/dockerode/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -7956,13 +7044,6 @@ "node": ">=6.0.0" } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT" - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -8062,27 +7143,6 @@ "node": ">= 0.4" } }, - "node_modules/easy-table": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", - "integrity": "sha512-oq33hWOSSnl2Hoh00tZWaIPi1ievrD9aFG82/IgjlycAnW9hHx5PkJiXpxPsgEE+H7BsbVQXFVFST8TEXS6/pA==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "wcwidth": ">=1.0.1" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.161", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", @@ -8142,34 +7202,6 @@ "once": "^1.4.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -8292,27 +7324,6 @@ "node": ">= 0.4" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -8443,38 +7454,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -8700,48 +7679,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-cypress": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-3.6.0.tgz", - "integrity": "sha512-7IAMcBbTVu5LpWeZRn5a9mQ30y4hKp3AfTz+6nSD/x/7YyLMoBI6X7XjDLYI6zFvuy4Q4QVGl563AGEXGW/aSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "globals": "^13.20.0" - }, - "peerDependencies": { - "eslint": ">=7" - } - }, - "node_modules/eslint-plugin-cypress/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-cypress/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-plugin-es-x": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", @@ -9236,20 +8173,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -9344,17 +8267,11 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true, - "license": "MIT" - }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -9371,6 +8288,16 @@ "node": ">=0.8.x" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -9382,43 +8309,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^2.2.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/exsolve": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", @@ -9431,37 +8321,6 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9469,12 +8328,20 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -9492,6 +8359,7 @@ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -9515,16 +8383,6 @@ "license": "MIT", "peer": true }, - "node_modules/fast-shuffle": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/fast-shuffle/-/fast-shuffle-6.1.1.tgz", - "integrity": "sha512-HPxFJxEi18KPmVQuK5Hi5l4KSl3u50jtaxseRrPqrxewqfvU+sTPTaUpP33Hj+NdJoLuJP5ipx3ybTr+fa6dEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pcg": "1.1.0" - } - }, "node_modules/fast-uri": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", @@ -9594,20 +8452,11 @@ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "reusify": "^1.0.4" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -9636,196 +8485,59 @@ }, { "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/filerobot-image-editor": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/filerobot-image-editor/-/filerobot-image-editor-4.8.1.tgz", - "integrity": "sha512-p5p0bCVe8WcV+V3Rn+jjn4Ly1KzWeGX2B8Nepw/G4KRsg0DgJJ8VZTGqXJHdcrKrELvkdKhO93ZeHNQnId8zRA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.17.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-konva": "^18.2.10", - "styled-components": "^5.3.5" - }, - "peerDependencies": { - "react-filerobot-image-editor": "^4.7.0" - } - }, - "node_modules/filing-cabinet": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-5.0.3.tgz", - "integrity": "sha512-PlPcMwVWg60NQkhvfoxZs4wEHjhlOO/y7OAm4sKM60o1Z9nttRY4mcdQxp/iZ+kg/Vv6Hw1OAaTbYVM9DA9pYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "app-module-path": "^2.2.0", - "commander": "^12.1.0", - "enhanced-resolve": "^5.18.0", - "module-definition": "^6.0.1", - "module-lookup-amd": "^9.0.3", - "resolve": "^1.22.10", - "resolve-dependency-path": "^4.0.1", - "sass-lookup": "^6.1.0", - "stylus-lookup": "^6.1.0", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.7.3" - }, - "bin": { - "filing-cabinet": "bin/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/filing-cabinet/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/filing-cabinet/node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "url": "https://paypal.me/jimmywarting" + } + ], "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, "engines": { - "node": ">=8" + "node": "^12.20 || >= 14.13" } }, - "node_modules/find-cypress-specs": { - "version": "1.54.9", - "resolved": "https://registry.npmjs.org/find-cypress-specs/-/find-cypress-specs-1.54.9.tgz", - "integrity": "sha512-Wc4SDIUV3Gtb+PblVLMLHP6xkcUzqkTOPRkSott0hIYVGl714jDztr3Blgt4x0dq7AKXaKCFT+8Bngrnd/yNBw==", + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@actions/core": "^2.0.2", - "arg": "^5.0.1", - "console.table": "^0.10.0", - "debug": "^4.3.3", - "find-test-names": "1.29.19", - "minimatch": "^5.1.4", - "pluralize": "^8.0.0", - "require-and-forget": "^1.0.1", - "shelljs": "^0.10.0", - "spec-change": "^1.11.17", - "tinyglobby": "^0.2.15", - "tsx": "^4.19.3" - }, - "bin": { - "find-cypress-specs": "bin/find.js" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=18" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/find-cypress-specs/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", + "node_modules/filerobot-image-editor": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/filerobot-image-editor/-/filerobot-image-editor-4.8.1.tgz", + "integrity": "sha512-p5p0bCVe8WcV+V3Rn+jjn4Ly1KzWeGX2B8Nepw/G4KRsg0DgJJ8VZTGqXJHdcrKrELvkdKhO93ZeHNQnId8zRA==", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "@babel/runtime": "^7.17.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-konva": "^18.2.10", + "styled-components": "^5.3.5" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "react-filerobot-image-editor": "^4.7.0" } }, - "node_modules/find-test-names": { - "version": "1.29.19", - "resolved": "https://registry.npmjs.org/find-test-names/-/find-test-names-1.29.19.tgz", - "integrity": "sha512-fSO2GXgOU6dH+FdffmRXYN/kLdnd8zkBGIZrKsmAdfLSFUUDLpDFF7+F/h+wjmjDWQmMgD8hPfJZR+igiEUQHQ==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.2", - "@babel/plugin-syntax-jsx": "^7.27.1", - "acorn-walk": "^8.2.0", - "debug": "^4.3.3", - "simple-bin-help": "^1.8.0", - "tinyglobby": "^0.2.13" + "to-regex-range": "^5.0.1" }, - "bin": { - "find-test-names": "bin/find-test-names.js", - "print-tests": "bin/print-tests.js", - "update-test-count": "bin/update-test-count.js" + "engines": { + "node": ">=8" } }, "node_modules/find-up": { @@ -9942,16 +8654,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "*" - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -9987,28 +8689,13 @@ "dev": true, "license": "MIT" }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -10062,6 +8749,7 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10076,20 +8764,6 @@ "node": ">=6.9.0" } }, - "node_modules/get-amd-module-type": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-6.0.1.tgz", - "integrity": "sha512-MtjsmYiCXcYDDrGqtNbeIYdAl85n+5mSv2r3FbzER/YV3ZILw4HNNIw34HuV5pyl0jzs6GFYU1VHVEefhgcNHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-module-types": "^6.0.1", - "node-source-walk": "^7.0.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -10124,13 +8798,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true, - "license": "ISC" - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -10144,22 +8811,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -10185,6 +8836,7 @@ "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -10192,26 +8844,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "async": "^3.2.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -10219,6 +8851,7 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -10254,6 +8887,7 @@ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10265,6 +8899,7 @@ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10272,22 +8907,6 @@ "node": "*" } }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -10373,6 +8992,7 @@ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -10396,22 +9016,6 @@ "license": "MIT", "peer": true }, - "node_modules/gonzales-pe": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", - "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "gonzales": "bin/gonzales.js" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -10445,6 +9049,7 @@ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -10770,21 +9375,6 @@ "entities": "^4.4.0" } }, - "node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -10792,23 +9382,19 @@ "dev": true, "license": "MIT" }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">=8.12.0" + "node": ">= 6" } }, - "node_modules/humanize-duration": { - "version": "3.32.2", - "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.32.2.tgz", - "integrity": "sha512-jcTwWYeCJf4dN5GJnjBmHd42bNyK94lY49QTkrsAQrMTUoIYLevvDpmQtg5uv8ZrdIRIbzdasmSNZ278HHUPEg==", - "dev": true, - "license": "Unlicense" - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -10836,6 +9422,7 @@ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -10887,16 +9474,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -10904,6 +9481,7 @@ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -10916,16 +9494,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/inline-style-parser": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", @@ -10938,6 +9506,7 @@ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", @@ -11006,6 +9575,7 @@ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -11053,6 +9623,7 @@ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-bigints": "^1.0.2" }, @@ -11082,6 +9653,7 @@ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -11195,6 +9767,7 @@ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -11295,29 +9868,13 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -11372,6 +9929,7 @@ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -11383,22 +9941,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -11445,22 +9994,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-set": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -11474,6 +10014,7 @@ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3" }, @@ -11484,25 +10025,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-string": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -11535,6 +10064,7 @@ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", @@ -11563,52 +10093,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-url-superb": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", - "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -11639,6 +10130,7 @@ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" @@ -11662,7 +10154,8 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/isomorphic-timers-promises": { "version": "1.0.1", @@ -11674,13 +10167,6 @@ "node": ">=10" } }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "license": "MIT" - }, "node_modules/its-fine": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz", @@ -11698,21 +10184,7 @@ "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true, - "license": "MIT" - }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -11734,13 +10206,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" - }, "node_modules/jsdoc-type-pratt-parser": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", @@ -11780,13 +10245,6 @@ "license": "MIT", "peer": true }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -11803,13 +10261,6 @@ "license": "MIT", "peer": true }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -11835,22 +10286,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -11915,16 +10350,6 @@ "integrity": "sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==", "license": "MIT" }, - "node_modules/lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "> 0.8" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -11963,34 +10388,6 @@ "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", "license": "MIT" }, - "node_modules/listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, "node_modules/loadjs": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loadjs/-/loadjs-4.3.0.tgz", @@ -12049,13 +10446,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", @@ -12070,75 +10460,6 @@ "license": "MIT", "peer": true }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -12193,16 +10514,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -12227,13 +10538,6 @@ "url": "https://github.com/sponsors/sxzz" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, "node_modules/material-colors": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", @@ -12514,19 +10818,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 8" } @@ -12979,6 +11277,7 @@ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -12993,6 +11292,7 @@ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8.6" }, @@ -13042,16 +11342,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -13127,52 +11417,6 @@ "pathe": "^2.0.1" } }, - "node_modules/module-definition": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.1.tgz", - "integrity": "sha512-FeVc50FTfVVQnolk/WQT8MX+2WVcDnTGiq6Wo+/+lJ2ET1bRVi3HG3YlJUfqagNMc/kUlFSoR96AJkxGpKz13g==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-module-types": "^6.0.1", - "node-source-walk": "^7.0.1" - }, - "bin": { - "module-definition": "bin/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/module-lookup-amd": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-9.0.4.tgz", - "integrity": "sha512-DWJEuLVvjxh5b8wrvJC5wr2a7qo7pOWXIgdCBNazU416kcIyzO4drxvlqKhsHzYwxcC4cWuhoK+MiWCKCGnv7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^12.1.0", - "glob": "^7.2.3", - "requirejs": "^2.3.7", - "requirejs-config-file": "^4.0.0" - }, - "bin": { - "lookup-amd": "bin/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/module-lookup-amd/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -13320,19 +11564,6 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, - "node_modules/node-source-walk": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-7.0.1.tgz", - "integrity": "sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.26.7" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/node-stdlib-browser": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-stdlib-browser/-/node-stdlib-browser-1.3.1.tgz", @@ -13389,19 +11620,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -13552,22 +11770,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -13594,13 +11796,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true, - "license": "MIT" - }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -13652,22 +11847,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-queue": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", @@ -13831,6 +12010,7 @@ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13841,6 +12021,7 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -13863,6 +12044,7 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -13891,44 +12073,12 @@ "node": ">= 0.10" } }, - "node_modules/pcg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pcg/-/pcg-1.1.0.tgz", - "integrity": "sha512-S+bYs8CV6l2lj01PRN4g9EiHDktcXJKD9FdE/FqpdXSuy1zImsRq8A8T5UK6gkXdI9O5YFdAgH40uPoR8Bk8aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "long": "5.2.3", - "ramda": "0.29.1" - } - }, - "node_modules/pcg/node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, "node_modules/perfect-debounce": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", "license": "MIT" }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -13947,39 +12097,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pixelmatch": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", - "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "pngjs": "^6.0.0" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/pixelmatch/node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.13.0" - } - }, "node_modules/pkg-dir": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", @@ -14004,14 +12121,51 @@ "pathe": "^2.0.3" } }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/plyr": { @@ -14027,16 +12181,6 @@ "url-polyfill": "^1.1.12" } }, - "node_modules/pngjs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", - "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.19.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -14183,64 +12327,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, - "node_modules/postcss-values-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz", - "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "color-name": "^1.1.4", - "is-url-superb": "^4.0.0", - "quote-unquote": "^1.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "postcss": "^8.2.9" - } - }, - "node_modules/precinct": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.2.0.tgz", - "integrity": "sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@dependents/detective-less": "^5.0.1", - "commander": "^12.1.0", - "detective-amd": "^6.0.1", - "detective-cjs": "^6.0.1", - "detective-es6": "^5.0.1", - "detective-postcss": "^7.0.1", - "detective-sass": "^6.0.1", - "detective-scss": "^5.0.1", - "detective-stylus": "^5.0.1", - "detective-typescript": "^14.0.0", - "detective-vue2": "^2.2.0", - "module-definition": "^6.0.1", - "node-source-walk": "^7.0.1", - "postcss": "^8.5.1", - "typescript": "^5.7.3" - }, - "bin": { - "precinct": "bin/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/precinct/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -14268,54 +12354,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT" - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -14504,25 +12542,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" - }, - "node_modules/quote-unquote": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz", - "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", - "dev": true, - "license": "MIT" - }, - "node_modules/ramda": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", - "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", - "dev": true, "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } + "peer": true }, "node_modules/randombytes": { "version": "2.1.0", @@ -14729,6 +12750,7 @@ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -14973,54 +12995,6 @@ "unist-util-visit": "^5.0.0" } }, - "node_modules/request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "throttleit": "^1.0.0" - } - }, - "node_modules/require-and-forget": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-and-forget/-/require-and-forget-1.0.1.tgz", - "integrity": "sha512-Sea861D/seGo3cptxc857a34Df0oEijXit8Q3IDodiwZMzVmyXrRI9EgQQa3hjkhoEjNzCBvv0t/0fMgebmWLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4.3.4" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/require-and-forget/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/require-and-forget/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -15052,34 +13026,6 @@ "node": ">=0.10.5" } }, - "node_modules/requirejs": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.7.tgz", - "integrity": "sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==", - "dev": true, - "license": "MIT", - "bin": { - "r_js": "bin/r.js", - "r.js": "bin/r.js" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/requirejs-config-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz", - "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esprima": "^4.0.0", - "stringify-object": "^3.2.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -15107,16 +13053,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-dependency-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-4.0.1.tgz", - "integrity": "sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -15134,6 +13070,7 @@ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -15149,38 +13086,18 @@ "node": ">=10" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -15423,6 +13340,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "queue-microtask": "^1.2.2" } @@ -15522,16 +13440,6 @@ "dev": true, "license": "MIT" }, - "node_modules/sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "dev": true, - "license": "WTFPL OR ISC", - "dependencies": { - "truncate-utf8-bytes": "^1.0.0" - } - }, "node_modules/sass": { "version": "1.89.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.1.tgz", @@ -15554,33 +13462,6 @@ "@parcel/watcher": "^2.4.1" } }, - "node_modules/sass-lookup": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-6.1.0.tgz", - "integrity": "sha512-Zx+lVyoWqXZxHuYWlTA17Z5sczJ6braNT2C7rmClw+c4E7r/n911Zwss3h1uHI9reR5AgHZyNHF7c2+VIp5AUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^12.1.0", - "enhanced-resolve": "^5.18.0" - }, - "bin": { - "sass-lookup": "bin/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/sass-lookup/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -15636,6 +13517,7 @@ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -15701,6 +13583,7 @@ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -15714,71 +13597,11 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } }, - "node_modules/shelljs": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.10.0.tgz", - "integrity": "sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "execa": "^5.1.1", - "fast-glob": "^3.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/shelljs/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/shelljs/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/shelljs/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -15855,44 +13678,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/simple-bin-help": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/simple-bin-help/-/simple-bin-help-1.8.0.tgz", - "integrity": "sha512-0LxHn+P1lF5r2WwVB/za3hLRIsYoLaNq1CXqjbrs3ZvLuvlWnRKrUjEWzV7umZL7hpQ7xULiQMV+0iXdRa5iFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, + "peer": true, "engines": { "node": ">=8" } @@ -16013,43 +13805,15 @@ "spdx-ranges": "^2.0.0" } }, - "node_modules/spdx-satisfies/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spec-change": { - "version": "1.11.20", - "resolved": "https://registry.npmjs.org/spec-change/-/spec-change-1.11.20.tgz", - "integrity": "sha512-N9V0tptwsYs6WRvO3CYdHaptwj+EAaTAktTwIx9cEKkdboX4BBWY5//EhV9EoOzpGYO/4zHhs6lY3dHQPcMB6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "arg": "^5.0.2", - "debug": "^4.3.4", - "deep-equal": "^2.2.3", - "dependency-tree": "^11.1.1", - "lazy-ass": "^2.0.3", - "tinyglobby": "^0.2.0" - }, - "bin": { - "spec-change": "bin/spec-change.js" - } - }, - "node_modules/spec-change/node_modules/lazy-ass": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-2.0.3.tgz", - "integrity": "sha512-/O3/DoQmI1XAhklDvF1dAjFf/epE8u3lzOZegQfLZ8G7Ud5bTRSZiFOpukHCu6jODrCA4gtIdwUCC7htxcDACA==", + "node_modules/spdx-satisfies/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", - "engines": { - "node": "> 0.8" + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "node_modules/split-ca": { @@ -16090,32 +13854,6 @@ "nan": "^2.20.0" } }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -16130,6 +13868,7 @@ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" @@ -16162,6 +13901,18 @@ "xtend": "^4.0.2" } }, + "node_modules/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -16316,21 +14067,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -16350,20 +14086,11 @@ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -16844,32 +14571,6 @@ "node": ">=8" } }, - "node_modules/stylus-lookup": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-6.1.0.tgz", - "integrity": "sha512-5QSwgxAzXPMN+yugy61C60PhoANdItfdjSEZR8siFwz7yL9jTmV0UBKDCfn3K8GkGB4g0Y9py7vTCX8rFu4/pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^12.1.0" - }, - "bin": { - "stylus-lookup": "bin/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/stylus-lookup/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -17007,16 +14708,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -17046,30 +14737,48 @@ "node": ">=6" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "streamx": "^2.12.5" + } }, - "node_modules/throttleit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", - "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "node_modules/text-decoder/node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/timers-browserify": { "version": "2.0.12", @@ -17115,35 +14824,6 @@ "@popperjs/core": "^2.9.0" } }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tmp": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", - "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", - "dev": true, - "engines": { - "node": ">=14.14" - } - }, "node_modules/to-buffer": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", @@ -17177,29 +14857,6 @@ "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==", "license": "MIT" }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/tributejs": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/tributejs/-/tributejs-5.1.3.tgz", @@ -17226,16 +14883,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", - "dev": true, - "license": "WTFPL", - "dependencies": { - "utf8-byte-length": "^1.0.1" - } - }, "node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -17259,57 +14906,6 @@ "node": ">=18" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -17345,26 +14941,6 @@ "dev": true, "license": "0BSD" }, - "node_modules/tsx": { - "version": "4.19.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", - "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.25.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, "node_modules/tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", @@ -17372,29 +14948,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -17416,19 +14969,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -17556,16 +15096,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undici": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", - "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -17761,16 +15291,6 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.7.8" } }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -17858,13 +15378,6 @@ "dev": true, "license": "MIT" }, - "node_modules/utf8-byte-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", - "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", - "dev": true, - "license": "(WTFPL OR MIT)" - }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -17886,38 +15399,6 @@ "dev": true, "license": "MIT" }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -18377,37 +15858,6 @@ "vue": "^2.5.0" } }, - "node_modules/wait-on": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.3.tgz", - "integrity": "sha512-nQFqAFzZDeRxsu7S3C7LbuxslHhk+gnJZHyethuGKAn2IVleIbTB9I3vJSQiSR+DifUqmdzfPMoMPJfLqMF2vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "axios": "^1.8.2", - "joi": "^17.13.3", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "rxjs": "^7.8.2" - }, - "bin": { - "wait-on": "bin/wait-on" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", @@ -18476,6 +15926,7 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -18492,6 +15943,7 @@ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", @@ -18541,6 +15993,7 @@ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", @@ -18723,27 +16176,6 @@ "node": ">=12" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 2c45f5b39..2173f859c 100644 --- a/package.json +++ b/package.json @@ -27,16 +27,13 @@ "type": "module", "scripts": { "build": "vite --mode production build", - "cypress": "npm run cypress:e2e", - "cypress:e2e": "cypress run --e2e", - "cypress:gui": "cypress open --e2e", - "cypress:update-snapshots": "cypress run --env visualRegressionType=base --spec cypress/e2e/visual-regression.cy.ts --config screenshotsFolder=cypress/snapshots/base", - "cypress:visual-regression": "cypress run --spec cypress/e2e/visual-regression.cy.ts", "dev": "export NODE_ENV=development; vite --mode development build", - "lint": "eslint cypress src", - "lint:fix": "eslint cypress src --fix", + "lint": "eslint playwright src", + "lint:fix": "eslint playwright src --fix", "stylelint": "stylelint src", "stylelint:fix": "stylelint src --fix", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", "watch": "export NODE_ENV=development; vite --mode development build --watch" }, "browserslist": [ @@ -75,20 +72,13 @@ }, "devDependencies": { "@nextcloud/browserslist-config": "^3.0.1", - "@nextcloud/cypress": "^1.0.0-beta.15", + "@nextcloud/e2e-test-server": "^0.5.0", "@nextcloud/eslint-config": "^8.4.2", "@nextcloud/sharing": "^0.2.5", "@nextcloud/stylelint-config": "^3.1.0", "@nextcloud/vite-config": "^1.5.6", - "@testing-library/cypress": "^10.1.0", - "@types/dockerode": "^3.3.47", + "@playwright/test": "^1.61.0", "@vue/tsconfig": "^0.5.1", - "cypress": "^13.17.0", - "cypress-split": "^1.24.28", - "cypress-visual-regression": "^5.3.0", - "dockerode": "^4.0.10", - "eslint-plugin-cypress": "^3.6.0", - "ts-node": "^10.9.2", "tslib": "^2.8.1", "typescript": "^5.9.3", "vite": "^6.0.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..16777cfc1 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,62 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { defineConfig, devices } from '@playwright/test' + +// Defaults to the isolated test container on port 8081 started by start-server.mjs. +// Override with PLAYWRIGHT_BASE_URL to run against an existing instance. +const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:8081' + +export default defineConfig({ + testDir: './playwright', + + // Files run in parallel (specs isolate by createRandomUser()), but tests + // within a file stay ordered — the stateful viewer flows depend on it, and + // full within-file parallelism overwhelms the single shared test container. + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: 2, + + reporter: process.env.CI ? [['blob'], ['dot'], ['github']] : 'html', + + // The Cypress suite waited up to 10s for file-list rows; bump it further so + // slower list renders / DAV responses under parallel load don't flake. + expect: { timeout: 15000 }, + + use: { + baseURL: baseURL + '/index.php/', + trace: 'on-first-retry', + video: 'on-first-retry', + // 16/9 viewport, matching the previous Cypress setup. + viewport: { width: 1280, height: 720 }, + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'node playwright/start-server.mjs', + // Wait for the ready log line instead of polling the URL — avoids false + // positives where the HTTP port opens before Nextcloud finishes booting. + wait: { + stdout: /Nextcloud ready at/, + }, + gracefulShutdown: { + signal: 'SIGTERM', + timeout: 10000, + }, + // Reuse a container left running from a previous local run. + // In CI the container won't exist yet, so start-server.mjs will create it. + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + timeout: 5 * 60 * 1000, + }, +}) diff --git a/playwright/e2e/a11y.spec.ts b/playwright/e2e/a11y.spec.ts new file mode 100644 index 000000000..cbbbf62ec --- /dev/null +++ b/playwright/e2e/a11y.spec.ts @@ -0,0 +1,79 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { expect, setupFilesPage, test } from '../support/fixtures.ts' +import { getRowForFile, openFile } from '../support/filesUtils.ts' +import { + expectNavArrows, + expectViewerLoaded, + getMediaInContainer, + getViewer, + getViewerName, +} from '../support/viewerUtils.ts' + +/** Media element of an inactive (neighbour) slide. */ +const inactiveMedia = (page: Page, type: 'img' | 'video') => + getViewer(page).locator(`.modal-container .viewer__file:not(.viewer__file--active) ${type}`) + +test.describe.serial('A11y tests', () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [ + { fixture: 'image1.jpg', mimeType: 'image/jpeg' }, + { fixture: 'image2.jpg', mimeType: 'image/jpeg' }, + { fixture: 'video1.mp4', mimeType: 'video/mp4' }, + ])) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See files in the list', async () => { + await expect(getRowForFile(page, 'image1.jpg')).toContainText('image1 .jpg') + await expect(getRowForFile(page, 'image2.jpg')).toContainText('image2 .jpg') + await expect(getRowForFile(page, 'video1.mp4')).toContainText('video1 .mp4') + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'image2.jpg') + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation', async () => { + await expectViewerLoaded(page) + }) + + test('See the title on the viewer header', async () => { + await expect(getViewerName(page)).toContainText('image2.jpg') + }) + + test('Should have rendered the previous video and the next image', async () => { + await expectNavArrows(page) + // The previous and the next image slides are preloaded around the active one. + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expect(getMediaInContainer(page, 'video')).toHaveCount(1) + }) + + test('Should make the previous and the next slides hidden for assistive technologies', async () => { + for (const type of ['video', 'img'] as const) { + const media = inactiveMedia(page, type) + await expect(media.locator('xpath=ancestor::*[@aria-hidden="true"]')).not.toHaveCount(0) + await expect(media.locator('xpath=ancestor::*[@inert]')).not.toHaveCount(0) + } + }) + + test('Should make video controls on the next slide not focusable', async () => { + const control = getViewer(page) + .locator('.modal-container .viewer__file:not(.viewer__file--active):has(video) button') + .first() + // Force a focus attempt via the DOM; the [inert] ancestor must reject it. + await control.evaluate((el: HTMLElement) => el.focus()) + await expect(control).not.toBeFocused() + }) +}) diff --git a/playwright/e2e/actions/delete.spec.ts b/playwright/e2e/actions/delete.spec.ts new file mode 100644 index 000000000..1252a8ec2 --- /dev/null +++ b/playwright/e2e/actions/delete.spec.ts @@ -0,0 +1,49 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { expect, setupFilesPage, test } from '../../support/fixtures.ts' +import { getRowForFile, openFile } from '../../support/filesUtils.ts' +import { expectViewerLoaded, getMenuItem, getViewer, openHeaderMenu } from '../../support/viewerUtils.ts' + +test.describe.serial('Delete image.png in viewer', () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [{ fixture: 'image.png', mimeType: 'image/png' }])) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See image.png in the list', async () => { + await expect(getRowForFile(page, 'image.png')).toContainText('image .png') + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'image.png') + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation', async () => { + await expectViewerLoaded(page) + }) + + test('Delete the image and close viewer', async () => { + await openHeaderMenu(page) + await getMenuItem(page, 'Delete').click() + }) + + test('Does not see the viewer anymore', async () => { + await expect(getViewer(page)).toHaveCount(0) + }) + + test('Does not see image.png in the list anymore', async () => { + await page.goto('apps/files') + await expect(getRowForFile(page, 'image.png')).toHaveCount(0) + }) +}) diff --git a/playwright/e2e/actions/download.spec.ts b/playwright/e2e/actions/download.spec.ts new file mode 100644 index 000000000..d2e65924c --- /dev/null +++ b/playwright/e2e/actions/download.spec.ts @@ -0,0 +1,49 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { statSync } from 'node:fs' +import { expect, setupFilesPage, test } from '../../support/fixtures.ts' +import { getRowForFile, openFile } from '../../support/filesUtils.ts' +import { expectViewerLoaded, getMenuItem, getViewer, openHeaderMenu } from '../../support/viewerUtils.ts' +import { fixturePath } from '../../support/webdav.ts' + +const fileName = 'image.png' + +test.describe.serial(`Download ${fileName} in viewer`, () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [{ fixture: fileName, mimeType: 'image/png' }])) + }) + + test.afterAll(async () => { + await page.close() + }) + + test(`See "${fileName}" in the list`, async () => { + await expect(getRowForFile(page, fileName)).toContainText(fileName.replace(/(.*)\./, '$1 .')) + }) + + test('Open the viewer on file click', async () => { + await openFile(page, fileName) + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation', async () => { + await expectViewerLoaded(page) + }) + + test('Download the image and compare it with the fixture by size', async () => { + const downloadPromise = page.waitForEvent('download') + await openHeaderMenu(page) + await getMenuItem(page, 'Download').click() + + const download = await downloadPromise + const downloadedPath = await download.path() + expect(statSync(downloadedPath).size).toBe(statSync(fixturePath(fileName)).size) + }) +}) diff --git a/playwright/e2e/actions/edit.spec.ts b/playwright/e2e/actions/edit.spec.ts new file mode 100644 index 000000000..b35176eee --- /dev/null +++ b/playwright/e2e/actions/edit.spec.ts @@ -0,0 +1,54 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { expect, setupFilesPage, test } from '../../support/fixtures.ts' +import { getRowForFile, openFile } from '../../support/filesUtils.ts' +import { getCloseButton, getEditButton, getViewer, getViewerName } from '../../support/viewerUtils.ts' + +test.describe.serial('Open the new saved as image', () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [{ fixture: 'image1.jpg', mimeType: 'image/jpeg' }])) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See images in the list', async () => { + await expect(getRowForFile(page, 'image1.jpg')).toContainText('image1 .jpg') + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'image1.jpg') + await expect(getViewer(page)).toBeVisible() + }) + + test('Open the image editor', async () => { + await getEditButton(page).click() + }) + + test('Save the image', async () => { + // Filerobot image editor (third-party) selectors. + await page.locator('.FIE_topbar-save-button').click() + const nameInput = page.locator('input[type="text"].SfxInput-Base') + await nameInput.clear() + await nameInput.fill('imageSave') + await page.locator('.SfxModal-Container button[color="primary"].SfxButton-root') + .filter({ hasText: 'Save' }) + .click() + await page.locator('.FIE_topbar-close-button').click() + + await expect(getViewerName(page)).toContainText('imageSave.jpg') + await getCloseButton(page).click() + }) + + test('See the new saved image in the list', async () => { + await expect(getRowForFile(page, 'imageSave.jpg')).toContainText('imageSave .jpg') + }) +}) diff --git a/playwright/e2e/actions/sidebar.spec.ts b/playwright/e2e/actions/sidebar.spec.ts new file mode 100644 index 000000000..bd474d39a --- /dev/null +++ b/playwright/e2e/actions/sidebar.spec.ts @@ -0,0 +1,106 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { expect, setupFilesPage, test } from '../../support/fixtures.ts' +import { getRowForFile, openFile } from '../../support/filesUtils.ts' +import { closeSidebar, getSidebar, getSidebarName, openSidebarFromViewer } from '../../support/sidebarUtils.ts' +import { + expectViewerLoaded, + getCloseButton, + getMenuItem, + getMenuToggle, + getNextButton, + getPrevButton, + getViewer, + getViewerName, + openHeaderMenu, +} from '../../support/viewerUtils.ts' + +test.describe.serial('Open the sidebar from the viewer and open viewer with sidebar already opened', () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [1, 2, 3, 4].map((i) => ({ + fixture: `image${i}.jpg`, + mimeType: 'image/jpeg', + })))) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See images in the list', async () => { + for (const i of [1, 2, 3, 4]) { + await expect(getRowForFile(page, `image${i}.jpg`)).toContainText(`image${i} .jpg`) + } + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'image1.jpg') + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation', async () => { + await expectViewerLoaded(page) + }) + + test('See the menu icon and title on the viewer header', async () => { + await expect(getViewerName(page)).toContainText('image1.jpg') + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Open the sidebar', async () => { + await openSidebarFromViewer(page) + await expect(getSidebar(page)).toBeVisible() + // The sidebar toggle is hidden while the sidebar is open. + await expect(page.locator('.action-button__icon.icon-menu-sidebar')).toHaveCount(0) + await expect(getSidebarName(page)).toContainText('image1.jpg') + }) + + test('Change to next image with sidebar open', async () => { + await expect(getSidebar(page)).toBeVisible() + await expect(getSidebarName(page)).toContainText('image1.jpg') + await getNextButton(page).click() + await expect(getSidebarName(page)).toContainText('image2.jpg') + }) + + test('Change to previous image with sidebar open', async () => { + await expect(getSidebar(page)).toBeVisible() + await expect(getSidebarName(page)).toContainText('image2.jpg') + await getPrevButton(page).click() + await expect(getSidebarName(page)).toContainText('image1.jpg') + }) + + test('Close the sidebar', async () => { + await closeSidebar(page) + await expect(getSidebar(page)).not.toBeVisible() + await expect(getMenuToggle(page)).toBeVisible() + await openHeaderMenu(page) + // The "Open sidebar" entry is back once the sidebar is closed. + await expect(getMenuItem(page, 'Open sidebar')).toBeVisible() + }) + + test('Open the viewer with the sidebar open', async () => { + await getCloseButton(page).click() + await expect(getViewer(page)).toHaveCount(0) + + // Open the sidebar from the files list (without the viewer). + await getRowForFile(page, 'image1.jpg').locator('[data-cy-files-list-row-mtime]').click() + await expect(getSidebar(page)).toBeVisible() + await expect(page).toHaveURL(/opendetails=true/) + + await openFile(page, 'image1.jpg') + await expectViewerLoaded(page) + + // Close the sidebar while the viewer is open. + await closeSidebar(page) + await expect(getSidebar(page)).not.toBeVisible() + await expect(getMenuToggle(page)).toBeVisible() + }) +}) diff --git a/playwright/e2e/audios/audio.mpeg.spec.ts b/playwright/e2e/audios/audio.mpeg.spec.ts new file mode 100644 index 000000000..704785612 --- /dev/null +++ b/playwright/e2e/audios/audio.mpeg.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { audioTest } from '../../mixins/media.ts' + +audioTest('audio.mp3', 'audio/mpeg') diff --git a/playwright/e2e/audios/audio.ogg.spec.ts b/playwright/e2e/audios/audio.ogg.spec.ts new file mode 100644 index 000000000..2fd4c0c75 --- /dev/null +++ b/playwright/e2e/audios/audio.ogg.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { audioTest } from '../../mixins/media.ts' + +audioTest('audio.ogg', 'audio/ogg') diff --git a/playwright/e2e/audios/audios.spec.ts b/playwright/e2e/audios/audios.spec.ts new file mode 100644 index 000000000..63703e814 --- /dev/null +++ b/playwright/e2e/audios/audios.spec.ts @@ -0,0 +1,79 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { expect, setupFilesPage, test, type User } from '../../support/fixtures.ts' +import { getRowForFile, openFile } from '../../support/filesUtils.ts' +import { + expectActiveSource, + expectNavArrows, + expectViewerLoaded, + getMediaInContainer, + getMenuToggle, + getCloseButton, + getViewer, + getViewerName, + goNext, +} from '../../support/viewerUtils.ts' + +test.describe.serial('Open mp3 and ogg audio in viewer', () => { + let page: Page + let user: User + + test.beforeAll(async ({ browser }) => { + ({ page, user } = await setupFilesPage(browser, [ + { fixture: 'audio.mp3', mimeType: 'audio/mpeg' }, + { fixture: 'audio.ogg', mimeType: 'audio/ogg' }, + ])) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See audios in the list', async () => { + await expect(getRowForFile(page, 'audio.mp3')).toContainText('audio .mp3') + await expect(getRowForFile(page, 'audio.ogg')).toContainText('audio .ogg') + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'audio.mp3') + await expect(getViewer(page)).toBeVisible() + }) + + test('See the menu icon and title on the viewer header', async () => { + await expect(getViewerName(page)).toContainText('audio.mp3') + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Does see next navigation arrows', async () => { + await expect(getMediaInContainer(page, 'audio')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('The audio source is the remote url (mp3)', async () => { + await expectActiveSource(page, 'audio', `/remote.php/dav/files/${user.userId}/audio.mp3`) + }) + + test('Does not see a loading animation (mp3)', async () => { + await expectViewerLoaded(page) + }) + + test('Show audio.ogg on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'audio')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('The audio source is the remote url (ogg)', async () => { + await expectActiveSource(page, 'audio', `/remote.php/dav/files/${user.userId}/audio.ogg`) + }) + + test('Does not see a loading animation (ogg)', async () => { + await expectViewerLoaded(page) + }) +}) diff --git a/playwright/e2e/download-forbidden.spec.ts b/playwright/e2e/download-forbidden.spec.ts new file mode 100644 index 000000000..b57ccf3bb --- /dev/null +++ b/playwright/e2e/download-forbidden.spec.ts @@ -0,0 +1,56 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createRandomUser, expect, loginUser, test, type User } from '../support/fixtures.ts' +import { getRowForFile, navigateToFolder, openFile } from '../support/filesUtils.ts' +import { getViewer, getViewerName, openHeaderMenu } from '../support/viewerUtils.ts' +import { createFolder, createShare, ShareType, uploadFile } from '../support/webdav.ts' + +test.describe('Disable download button if forbidden', () => { + let sharee: User + + test.beforeAll(async () => { + sharee = await createRandomUser() + const owner = await createRandomUser() + await createFolder(owner, '/Photos') + await uploadFile(owner, 'image1.jpg', 'image/jpeg', '/Photos/image1.jpg') + await createShare(owner, '/Photos', { + shareWith: sharee.userId, + shareType: ShareType.User, + attributes: [{ scope: 'permissions', key: 'download', value: false }], + }) + }) + + test.beforeEach(async ({ page }) => { + await loginUser(page, sharee) + await page.goto('apps/files') + await navigateToFolder(page, 'Photos') + }) + + test('See the shared folder and images in files list', async ({ page }) => { + await expect(getRowForFile(page, 'image1.jpg')).toContainText('image1 .jpg') + }) + + // TODO: Fix no-download files on the server. + test.fixme('See the image can be shown', async ({ page }) => { + await openFile(page, 'image1.jpg') + await expect(getViewer(page)).toBeVisible() + await expect(getViewer(page)).toHaveClass(/modal-mask/) + await expect(getViewer(page)).not.toHaveClass(/icon-loading/) + }) + + test('See the title on the viewer header but not the Download nor the menu button', async ({ page }) => { + await openFile(page, 'image1.jpg') + await expect(getViewerName(page)).toContainText('image1.jpg') + + await openHeaderMenu(page) + // Only "Full screen" and "Open sidebar" remain — no Download entry. + const menu = page.locator('[role="menu"]:visible') + await expect(menu.getByRole('menuitem')).toHaveCount(2) + await expect(menu.getByRole('menuitem', { name: 'Full screen' })).toBeVisible() + await expect(menu.getByRole('menuitem', { name: 'Open sidebar' })).toBeVisible() + await expect(menu.getByRole('menuitem', { name: 'Download' })).toHaveCount(0) + }) +}) diff --git a/playwright/e2e/files.spec.ts b/playwright/e2e/files.spec.ts new file mode 100644 index 000000000..fe51ba6af --- /dev/null +++ b/playwright/e2e/files.spec.ts @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { ADMIN, expect, loginUser, test } from '../support/fixtures.ts' +import { getRowForFile } from '../support/filesUtils.ts' + +test.describe('Files default view', () => { + test.beforeEach(async ({ page }) => { + await loginUser(page, ADMIN) + }) + + test('See the default files list', async ({ page }) => { + await page.goto('apps/files') + await expect(getRowForFile(page, 'welcome.txt')).toContainText('welcome .txt') + }) +}) diff --git a/playwright/e2e/images/image-apng.spec.ts b/playwright/e2e/images/image-apng.spec.ts new file mode 100644 index 000000000..77b691430 --- /dev/null +++ b/playwright/e2e/images/image-apng.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { imageTest } from '../../mixins/image.ts' + +imageTest('image-apng.png', 'image/png') diff --git a/playwright/e2e/images/image-small.png.spec.ts b/playwright/e2e/images/image-small.png.spec.ts new file mode 100644 index 000000000..3fa3c13fa --- /dev/null +++ b/playwright/e2e/images/image-small.png.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { imageTest } from '../../mixins/image.ts' + +imageTest('image-small.png', 'image/png') diff --git a/playwright/e2e/images/image.gif.spec.ts b/playwright/e2e/images/image.gif.spec.ts new file mode 100644 index 000000000..75ac3dc8c --- /dev/null +++ b/playwright/e2e/images/image.gif.spec.ts @@ -0,0 +1,9 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { imageTest } from '../../mixins/image.ts' + +// GIFs are served raw from DAV instead of through the preview endpoint. +imageTest('image.gif', 'image/gif', '/remote.php/dav/files') diff --git a/playwright/e2e/images/image.ico.spec.ts b/playwright/e2e/images/image.ico.spec.ts new file mode 100644 index 000000000..023d65de8 --- /dev/null +++ b/playwright/e2e/images/image.ico.spec.ts @@ -0,0 +1,9 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { imageTest } from '../../mixins/image.ts' + +// ICOs are served raw from DAV instead of through the preview endpoint. +imageTest('image.ico', 'image/x-icon', '/remote.php/dav/files') diff --git a/playwright/e2e/images/image.png.spec.ts b/playwright/e2e/images/image.png.spec.ts new file mode 100644 index 000000000..ebb4cf639 --- /dev/null +++ b/playwright/e2e/images/image.png.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { imageTest } from '../../mixins/image.ts' + +imageTest('image.png', 'image/png') diff --git a/playwright/e2e/images/image.svg.spec.ts b/playwright/e2e/images/image.svg.spec.ts new file mode 100644 index 000000000..f2c535136 --- /dev/null +++ b/playwright/e2e/images/image.svg.spec.ts @@ -0,0 +1,9 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { imageTest } from '../../mixins/image.ts' + +// SVGs are inlined as a data URI. +imageTest('image.svg', 'image/svg+xml', 'data:image/svg+xml;base64') diff --git a/playwright/e2e/images/image.webp.spec.ts b/playwright/e2e/images/image.webp.spec.ts new file mode 100644 index 000000000..73d54ed31 --- /dev/null +++ b/playwright/e2e/images/image.webp.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { imageTest } from '../../mixins/image.ts' + +imageTest('image.webp', 'image/webp') diff --git a/playwright/e2e/images/images-custom-list-loadmore.spec.ts b/playwright/e2e/images/images-custom-list-loadmore.spec.ts new file mode 100644 index 000000000..53d82c8c4 --- /dev/null +++ b/playwright/e2e/images/images-custom-list-loadmore.spec.ts @@ -0,0 +1,147 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { expect, setupFilesPage, test } from '../../support/fixtures.ts' +import { getFileId, getRowForFile } from '../../support/filesUtils.ts' +import { + expectActiveSource, + expectNavArrows, + expectViewerLoaded, + getMediaInContainer, + getMenuToggle, + getCloseButton, + getViewer, + getViewerName, + goNext, +} from '../../support/viewerUtils.ts' + +const PREVIEW_URL = '/index.php/core/preview' + +test.describe.serial('Open custom list of images in viewer with pagination', () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [1, 2, 3, 4].map((i) => ({ + fixture: `image${i}.jpg`, + mimeType: 'image/jpeg', + })))) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See images in the list', async () => { + for (const i of [1, 2, 3, 4]) { + await expect(getRowForFile(page, `image${i}.jpg`)).toContainText(`image${i} .jpg`) + } + }) + + test('Open the viewer with a specific list', async () => { + const ids = { + 1: Number(await getFileId(page, 'image1.jpg')), + 2: Number(await getFileId(page, 'image2.jpg')), + 3: Number(await getFileId(page, 'image3.jpg')), + 4: Number(await getFileId(page, 'image4.jpg')), + } + + // The closure (list + loadMore) is created inside the browser; the loadMore + // callback returns image3/image4 exactly once when navigation reaches the end. + await page.evaluate((fileIds) => { + const entry = (i: 1 | 2 | 3 | 4, etag: string) => ({ + basename: `image${i}.jpg`, + filename: `/image${i}.jpg`, + hasPreview: true, + fileid: fileIds[i], + mime: 'image/jpeg', + permissions: 'RWD', + etag, + }) + let loaded = false + window.OCA.Viewer.open({ + path: '/image1.jpg', + list: [entry(1, 'etag123'), entry(2, 'etag456')], + loadMore() { + if (loaded) { + return [] + } + loaded = true + return [entry(3, 'etag123'), entry(4, 'etag456')] + }, + }) + }, ids) + + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation (image1)', async () => { + await expectViewerLoaded(page) + }) + + test('See the menu icon and title on the viewer header', async () => { + await expect(getViewerName(page)).toContainText('image1.jpg') + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Does see next navigation arrows', async () => { + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Show image2 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image2)', async () => { + await expectViewerLoaded(page) + }) + + test('The image source is the preview url (image2)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) + + test('Show image3 on next (triggers loadMore)', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(3) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image3)', async () => { + await expectViewerLoaded(page) + }) + + test('Show image4 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image4)', async () => { + await expectViewerLoaded(page) + }) + + test('The image source is the preview url (image4)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) + + test('Show image1 again on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (wrap to image1)', async () => { + await expectViewerLoaded(page) + }) + + test('The image source is the preview url (wrap to image1)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) +}) diff --git a/playwright/e2e/images/images-custom-list.spec.ts b/playwright/e2e/images/images-custom-list.spec.ts new file mode 100644 index 000000000..befb2bb18 --- /dev/null +++ b/playwright/e2e/images/images-custom-list.spec.ts @@ -0,0 +1,129 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' +import type { ViewerFileInfo } from '../../support/globals.d.ts' + +import { expect, setupFilesPage, test } from '../../support/fixtures.ts' +import { getFileId, getRowForFile } from '../../support/filesUtils.ts' +import { + expectActiveSource, + expectNavArrows, + expectViewerLoaded, + getMediaInContainer, + getMenuToggle, + getCloseButton, + getViewer, + getViewerName, + goNext, +} from '../../support/viewerUtils.ts' + +const PREVIEW_URL = '/index.php/core/preview' + +test.describe.serial('Open custom images list in viewer', () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [1, 2, 3, 4].map((i) => ({ + fixture: `image${i}.jpg`, + mimeType: 'image/jpeg', + })))) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See images in the list', async () => { + for (const i of [1, 2, 3, 4]) { + await expect(getRowForFile(page, `image${i}.jpg`)).toContainText(`image${i} .jpg`) + } + }) + + test('Open the viewer with a specific list', async () => { + // Build a 2-item list (image1 + image3 only) with hand-crafted permissions/etags. + const list: ViewerFileInfo[] = [ + { + basename: 'image1.jpg', + filename: '/image1.jpg', + hasPreview: true, + fileid: Number(await getFileId(page, 'image1.jpg')), + permissions: 'RWD', + mime: 'image/jpeg', + etag: '123456789', + }, + { + basename: 'image3.jpg', + filename: '/image3.jpg', + hasPreview: true, + fileid: Number(await getFileId(page, 'image3.jpg')), + permissions: 'R', + mime: 'image/jpeg', + etag: '987654321', + }, + ] + + await page.evaluate((entries) => { + window.OCA.Viewer.open({ path: '/image1.jpg', list: entries }) + }, list) + + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation (image1)', async () => { + await expectViewerLoaded(page) + }) + + test('See the menu icon and title on the viewer header (image1)', async () => { + await expect(getViewerName(page)).toContainText('image1.jpg') + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Does see next navigation arrows', async () => { + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('The image source is the preview url (image1)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) + + test('Show image3 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image3)', async () => { + await expectViewerLoaded(page) + }) + + test('The image source is the preview url (image3)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) + + test('See the menu icon and title on the viewer header (image3)', async () => { + await expect(getViewerName(page)).toContainText('image3.jpg') + }) + + test('Show image1 on next (wraps)', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('The image source is the preview url (wrap to image1)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) + + test('Does not see a loading animation (wrap to image1)', async () => { + await expectViewerLoaded(page) + }) + + test('See the menu icon and title on the viewer header (wrap to image1)', async () => { + await expect(getViewerName(page)).toContainText('image1.jpg') + }) +}) diff --git a/playwright/e2e/images/images.spec.ts b/playwright/e2e/images/images.spec.ts new file mode 100644 index 000000000..bcecb958c --- /dev/null +++ b/playwright/e2e/images/images.spec.ts @@ -0,0 +1,125 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { expect, setupFilesPage, test } from '../../support/fixtures.ts' +import { getRowForFile, openFile } from '../../support/filesUtils.ts' +import { + expectActiveSource, + expectNavArrows, + expectViewerLoaded, + getMediaInContainer, + getMenuToggle, + getCloseButton, + getViewer, + getViewerName, + goNext, +} from '../../support/viewerUtils.ts' + +const PREVIEW_URL = '/index.php/core/preview' + +test.describe.serial('Open images in viewer', () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [1, 2, 3, 4].map((i) => ({ + fixture: `image${i}.jpg`, + mimeType: 'image/jpeg', + })))) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See images in the list', async () => { + for (const i of [1, 2, 3, 4]) { + await expect(getRowForFile(page, `image${i}.jpg`)).toContainText(`image${i} .jpg`) + } + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'image1.jpg') + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation (image1)', async () => { + await expectViewerLoaded(page) + }) + + test('The image source is the preview url (image1)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) + + test('See the menu icon and title on the viewer header', async () => { + await expect(getViewerName(page)).toContainText('image1.jpg') + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Does see next navigation arrows', async () => { + // 2 images: the current one and its single neighbour (start of the list). + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Show image2 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(3) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image2)', async () => { + await expectViewerLoaded(page) + }) + + test('The image source is the preview url (image2)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) + + test('Show image3 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(3) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image3)', async () => { + await expectViewerLoaded(page) + }) + + test('The image source is the preview url (image3)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) + + test('Show image4 on next', async () => { + await goNext(page) + // 2 images: the current one and its single neighbour (end of the list). + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image4)', async () => { + await expectViewerLoaded(page) + }) + + test('The image source is the preview url (image4)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) + + test('Show image1 again on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (wrap to image1)', async () => { + await expectViewerLoaded(page) + }) + + test('The image source is the preview url (wrap to image1)', async () => { + await expectActiveSource(page, 'img', PREVIEW_URL) + }) +}) diff --git a/playwright/e2e/navigation.spec.ts b/playwright/e2e/navigation.spec.ts new file mode 100644 index 000000000..28cef0336 --- /dev/null +++ b/playwright/e2e/navigation.spec.ts @@ -0,0 +1,25 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { expect, setupFilesPage, test } from '../support/fixtures.ts' +import { getRowForFile, openFile } from '../support/filesUtils.ts' +import { getViewer } from '../support/viewerUtils.ts' + +test.describe('Browser navigation', () => { + test('Navigating back to the files overview', async ({ browser }) => { + const { page } = await setupFilesPage(browser, [ + { fixture: 'image.png', mimeType: 'image/png', target: '/image1.png' }, + ]) + + await expect(getRowForFile(page, 'image1.png')).toBeVisible() + await openFile(page, 'image1.png') + await expect(getViewer(page)).toBeVisible() + + await page.goBack() + await expect(getViewer(page)).toHaveCount(0) + + await page.close() + }) +}) diff --git a/playwright/e2e/non-dav-files.spec.ts b/playwright/e2e/non-dav-files.spec.ts new file mode 100644 index 000000000..d8d64ca44 --- /dev/null +++ b/playwright/e2e/non-dav-files.spec.ts @@ -0,0 +1,74 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' +import type { ViewerFileInfo } from '../support/globals.d.ts' + +import { basename as pathBasename } from 'node:path' +import { expect, setupFilesPage, test } from '../support/fixtures.ts' +import { + expectActiveSource, + expectViewerLoaded, + getCloseButton, + getMenuToggle, + getNextButton, + getPrevButton, + getViewer, + getViewerName, +} from '../support/viewerUtils.ts' + +const source = '/apps/theming/img/background/anatoly-mikhaltsov-butterfly-wing-scale.jpg' +const basename = pathBasename(source) + +test.describe.serial('Open non-dav files in viewer', () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [{ fixture: 'test-card.mp4', mimeType: 'video/mp4' }])) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('Open background', async () => { + const fileInfo: ViewerFileInfo = { + filename: source, + basename, + mime: 'image/jpeg', + source, + etag: 'abc', + hasPreview: false, + fileid: 123, + } + + await page.evaluate((info) => { + window.OCA.Viewer.open({ fileInfo: info, list: [info] }) + }, fileInfo) + }) + + test('Does not see a loading animation', async () => { + await expectViewerLoaded(page) + }) + + test('See the title and close button on the viewer header', async () => { + await expect(getViewerName(page)).toContainText(basename) + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Does not see navigation arrows', async () => { + await expect(getPrevButton(page)).not.toBeVisible() + await expect(getNextButton(page)).not.toBeVisible() + }) + + test('See the menu but does not see the sidebar button', async () => { + await expect(getMenuToggle(page)).toBeVisible() + await expect(page.locator('.action-button__icon.icon-menu-sidebar')).toHaveCount(0) + }) + + test('The image source is the remote url', async () => { + await expectActiveSource(page, 'img', source) + }) +}) diff --git a/playwright/e2e/oddname/oddname-audio.spec.ts b/playwright/e2e/oddname/oddname-audio.spec.ts new file mode 100644 index 000000000..c38816519 --- /dev/null +++ b/playwright/e2e/oddname/oddname-audio.spec.ts @@ -0,0 +1,15 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { oddnameTest } from '../../mixins/oddname.ts' + +const files: [string, string][] = [ + ['audio.mp3', 'audio/mpeg'], + ['audio.ogg', 'audio/ogg'], +] + +for (const [file, mimeType] of files) { + oddnameTest(file, mimeType) +} diff --git a/playwright/e2e/oddname/oddname-image.spec.ts b/playwright/e2e/oddname/oddname-image.spec.ts new file mode 100644 index 000000000..e035eb6d2 --- /dev/null +++ b/playwright/e2e/oddname/oddname-image.spec.ts @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { oddnameTest } from '../../mixins/oddname.ts' + +const files: [string, string][] = [ + ['image1.jpg', 'image/jpeg'], + ['image.gif', 'image/gif'], + ['image.png', 'image/png'], + ['image-small.png', 'image/png'], + ['image.svg', 'image/svg'], +] + +for (const [file, mimeType] of files) { + oddnameTest(file, mimeType) +} diff --git a/playwright/e2e/oddname/oddname-sidebar.spec.ts b/playwright/e2e/oddname/oddname-sidebar.spec.ts new file mode 100644 index 000000000..e60b17f1e --- /dev/null +++ b/playwright/e2e/oddname/oddname-sidebar.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { oddnameTest } from '../../mixins/oddname.ts' + +oddnameTest('image.png', 'image/png', true) diff --git a/playwright/e2e/oddname/oddname-video.spec.ts b/playwright/e2e/oddname/oddname-video.spec.ts new file mode 100644 index 000000000..2d02a870d --- /dev/null +++ b/playwright/e2e/oddname/oddname-video.spec.ts @@ -0,0 +1,18 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { OGV_UNSUPPORTED } from '../../support/codecs.ts' +import { oddnameTest } from '../../mixins/oddname.ts' + +const files: [string, string, string?][] = [ + ['video1.mp4', 'video/mp4'], + ['video.mkv', 'video/mkv'], + ['video.ogv', 'video/ogv', OGV_UNSUPPORTED], + ['video.webm', 'video/webm'], +] + +for (const [file, mimeType, skipReason] of files) { + oddnameTest(file, mimeType, false, skipReason) +} diff --git a/playwright/e2e/sharing/download-share-disabled.spec.ts b/playwright/e2e/sharing/download-share-disabled.spec.ts new file mode 100644 index 000000000..7f522b269 --- /dev/null +++ b/playwright/e2e/sharing/download-share-disabled.spec.ts @@ -0,0 +1,112 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { createRandomUser, expect, loginUser, test, type User } from '../../support/fixtures.ts' +import { getRowForFile, navigateToFolder, openFile } from '../../support/filesUtils.ts' +import { getSidebar } from '../../support/sidebarUtils.ts' +import { visitPublicShare } from '../../support/shareUtils.ts' +import { + expectViewerLoaded, + getCloseButton, + getMenuToggle, + getViewer, + getViewerName, +} from '../../support/viewerUtils.ts' +import { createFolder, createLinkShare, uploadFile } from '../../support/webdav.ts' + +const fileName = 'image1.jpg' + +test.describe.serial(`Download ${fileName} in viewer`, () => { + let page: Page + let user: User + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + user = await createRandomUser() + await createFolder(user, '/Photos') + await uploadFile(user, 'image1.jpg', 'image/jpeg', '/Photos/image1.jpg') + await uploadFile(user, 'image2.jpg', 'image/jpeg', '/Photos/image2.jpg') + await loginUser(page, user) + await page.goto('apps/files') + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See the default files list', async () => { + await expect(getRowForFile(page, 'welcome.txt')).toContainText('welcome .txt') + await expect(getRowForFile(page, 'Photos')).toContainText('Photos') + }) + + test('See shared files in the list', async () => { + await navigateToFolder(page, 'Photos') + await expect(getRowForFile(page, 'image1.jpg')).toContainText('image1 .jpg') + await expect(getRowForFile(page, 'image2.jpg')).toContainText('image2 .jpg') + }) + + test('Share the Photos folder with a share link, disable download and access the share link', async () => { + const token = await createLinkShare(user, '/Photos') + + // Open the share sidebar from the breadcrumbs. + const sharingApi = page.waitForResponse(/\/apps\/files_sharing\/api\/v1\/shares/) + await page.getByRole('navigation', { name: 'Current directory path' }) + .getByRole('button', { name: 'Photos' }) + .click() + await page.getByRole('menuitem', { name: 'Share' }).click() + await expect(getSidebar(page)).toBeVisible() + await sharingApi + + // Open the link share settings and switch on "Hide download". + await page.locator('.sharing-link-list > .sharing-entry button[aria-label*="Actions for "]').click() + await page.getByRole('menuitem', { name: 'Customize link' }).click() + await page.getByRole('button', { name: 'Advanced settings' }).click() + + // NcCheckboxRadioSwitch hides the real ; toggle it by clicking the + // visible label text, then read the state from the (hidden) checkbox. + const hideSwitch = page.locator('.checkbox-radio-switch', { hasText: 'Hide download' }) + await hideSwitch.locator('.checkbox-radio-switch__text').click() + await expect(hideSwitch.locator('input[type="checkbox"]')).toBeChecked() + + const updateShare = page.waitForResponse( + (r) => /\/apps\/files_sharing\/api\/v1\/shares\//.test(r.url()) && r.request().method() === 'PUT', + ) + await page.getByRole('button', { name: 'Update share' }).click() + await updateShare + + await visitPublicShare(page, token) + }) + + test('See only view action', async () => { + for (const file of ['image1.jpg', 'image2.jpg']) { + const actions = getRowForFile(page, file).locator('[data-cy-files-list-row-actions] button') + await actions.click() + const menu = page.locator('[role="menu"]:visible') + await expect(menu.getByRole('menuitem')).toHaveCount(1) + await expect(menu.getByRole('menuitem', { name: 'View' })).toBeVisible() + await actions.click() + } + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'image1.jpg') + await expect(getViewer(page)).toBeVisible() + }) + + // TODO: Fix download-disabled shares on the server. + test.fixme('Does not see a loading animation', async () => { + await expectViewerLoaded(page) + }) + + // TODO: Fix download-disabled shares on the server. + test.fixme('See the title on the viewer header but not the Download nor the menu button', async () => { + await expect(getViewerName(page)).toContainText('image1.jpg') + await expect(page.locator('body a[download="image1.jpg"]')).toHaveCount(0) + await expect(getMenuToggle(page)).toHaveCount(0) + await expect(getCloseButton(page)).toBeVisible() + }) +}) diff --git a/playwright/e2e/sharing/download-share.spec.ts b/playwright/e2e/sharing/download-share.spec.ts new file mode 100644 index 000000000..c6e58d5ee --- /dev/null +++ b/playwright/e2e/sharing/download-share.spec.ts @@ -0,0 +1,86 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { statSync } from 'node:fs' +import { createRandomUser, expect, loginUser, test, type User } from '../../support/fixtures.ts' +import { getRowForFile, navigateToFolder, openFile } from '../../support/filesUtils.ts' +import { visitPublicShare } from '../../support/shareUtils.ts' +import { + expectViewerLoaded, + getCloseButton, + getMenuItem, + getViewer, + getViewerName, + openHeaderMenu, +} from '../../support/viewerUtils.ts' +import { createFolder, createLinkShare, fixturePath, uploadFile } from '../../support/webdav.ts' + +const fileName = 'image1.jpg' + +test.describe.serial(`Download ${fileName} from viewer in link share`, () => { + let page: Page + let user: User + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + user = await createRandomUser() + await createFolder(user, '/Photos') + await uploadFile(user, 'image1.jpg', 'image/jpeg', '/Photos/image1.jpg') + await uploadFile(user, 'image2.jpg', 'image/jpeg', '/Photos/image2.jpg') + await loginUser(page, user) + await page.goto('apps/files') + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See the default files list', async () => { + await expect(getRowForFile(page, 'welcome.txt')).toContainText('welcome .txt') + await expect(getRowForFile(page, 'Photos')).toContainText('Photos') + }) + + test('See shared files in the list', async () => { + await navigateToFolder(page, 'Photos') + await expect(getRowForFile(page, 'image1.jpg')).toContainText('image1 .jpg') + await expect(getRowForFile(page, 'image2.jpg')).toContainText('image2 .jpg') + }) + + test('Share the Photos folder with a share link and access the share link', async () => { + const token = await createLinkShare(user, '/Photos') + await visitPublicShare(page, token) + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'image1.jpg') + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation', async () => { + await expectViewerLoaded(page) + }) + + test('See the title and the close icon on the viewer header', async () => { + await expect(getViewerName(page)).toContainText('image1.jpg') + await expect(getCloseButton(page)).toBeVisible() + }) + + test('See the full screen and download entries in the menu', async () => { + await openHeaderMenu(page) + await expect(getMenuItem(page, 'Full screen')).toBeVisible() + await expect(getMenuItem(page, 'Download')).toBeVisible() + }) + + test('Download the image and compare it with the fixture by size', async () => { + const downloadPromise = page.waitForEvent('download') + await getMenuItem(page, 'Download').click() + + const download = await downloadPromise + const downloadedPath = await download.path() + expect(statSync(downloadedPath).size).toBe(statSync(fixturePath(fileName)).size) + }) +}) diff --git a/playwright/e2e/sharing/files-shares.spec.ts b/playwright/e2e/sharing/files-shares.spec.ts new file mode 100644 index 000000000..6eef2c899 --- /dev/null +++ b/playwright/e2e/sharing/files-shares.spec.ts @@ -0,0 +1,130 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { createRandomUser, expect, loginUser, test, type User } from '../../support/fixtures.ts' +import { getRowForFile, navigateToFolder, openFile } from '../../support/filesUtils.ts' +import { visitPublicShare } from '../../support/shareUtils.ts' +import { + expectNavArrows, + expectViewerLoaded, + getCloseButton, + getMediaInContainer, + getViewer, + getViewerName, + goNext, +} from '../../support/viewerUtils.ts' +import { createFolder, createLinkShare, uploadFile } from '../../support/webdav.ts' + +test.describe.serial('See shared folder with link share', () => { + let page: Page + let user: User + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + user = await createRandomUser() + await createFolder(user, '/Photos') + for (const i of [1, 2, 3, 4]) { + await uploadFile(user, `image${i}.jpg`, 'image/jpeg', `/Photos/image${i}.jpg`) + } + await uploadFile(user, 'video1.mp4', 'video/mp4', '/Photos/video1.mp4') + await loginUser(page, user) + await page.goto('apps/files') + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See the default files list', async () => { + await expect(getRowForFile(page, 'welcome.txt')).toContainText('welcome .txt') + await expect(getRowForFile(page, 'Photos')).toContainText('Photos') + }) + + test('See shared files in the list', async () => { + await navigateToFolder(page, 'Photos') + for (const i of [1, 2, 3, 4]) { + await expect(getRowForFile(page, `image${i}.jpg`)).toContainText(`image${i} .jpg`) + } + await expect(getRowForFile(page, 'video1.mp4')).toContainText('video1 .mp4') + }) + + test('Share the Photos folder with a share link and access the share link', async () => { + const token = await createLinkShare(user, '/Photos') + await visitPublicShare(page, token) + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'image1.jpg') + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation (image1)', async () => { + await expectViewerLoaded(page) + }) + + test('See the title on the viewer header', async () => { + await expect(getViewerName(page)).toContainText('image1.jpg') + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Does see next navigation arrows', async () => { + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Show image2 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(3) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image2)', async () => { + await expectViewerLoaded(page) + }) + + test('Show image3 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(3) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image3)', async () => { + await expectViewerLoaded(page) + }) + + test('Show image4 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (image4)', async () => { + await expectViewerLoaded(page) + }) + + test('Show video1 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(1) + await expect(getMediaInContainer(page, 'video')).toHaveCount(1) + await expectNavArrows(page) + await expect(getViewerName(page)).toContainText('video1.mp4') + }) + + test('Does not see a loading animation (video1)', async () => { + await expectViewerLoaded(page) + }) + + test('Show image1 again on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'img')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('Does not see a loading animation (wrap to image1)', async () => { + await expectViewerLoaded(page) + }) +}) diff --git a/playwright/e2e/sharing/single-file-share.spec.ts b/playwright/e2e/sharing/single-file-share.spec.ts new file mode 100644 index 000000000..d68cce8fa --- /dev/null +++ b/playwright/e2e/sharing/single-file-share.spec.ts @@ -0,0 +1,53 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { createRandomUser, expect, test } from '../../support/fixtures.ts' +import { visitPublicShare } from '../../support/shareUtils.ts' +import { + expectActiveSource, + expectViewerLoaded, + getCloseButton, + getMenuToggle, +} from '../../support/viewerUtils.ts' +import { createLinkShare, uploadFile } from '../../support/webdav.ts' + +test.describe('See shared single file with link share', () => { + let imageToken: string + let videoToken: string + + test.beforeAll(async () => { + const user = await createRandomUser() + await uploadFile(user, 'image1.jpg', 'image/jpeg') + await uploadFile(user, 'video1.mp4', 'video/mp4') + imageToken = await createLinkShare(user, '/image1.jpg') + videoToken = await createLinkShare(user, '/video1.mp4') + }) + + test('Opens the shared image in the viewer', async ({ page }) => { + const imageResponse = page.waitForResponse( + (r) => r.url().includes('/apps/files_sharing/publicpreview/') && r.status() === 200, + ) + // A single-file share opens the viewer automatically on load. + await visitPublicShare(page, imageToken) + await imageResponse + + await expectViewerLoaded(page) + await expectActiveSource(page, 'img', '/apps/files_sharing/publicpreview/') + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Opens the shared video in the viewer', async ({ page }) => { + const videoResponse = page.waitForResponse((r) => r.url().includes('/public.php/dav/files/')) + // A single-file share opens the viewer automatically on load. + await visitPublicShare(page, videoToken) + await videoResponse + + await expectViewerLoaded(page) + await expectActiveSource(page, 'video', `/public.php/dav/files/${videoToken}`) + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) +}) diff --git a/playwright/e2e/videos/video.mkv.spec.ts b/playwright/e2e/videos/video.mkv.spec.ts new file mode 100644 index 000000000..8280dc3f5 --- /dev/null +++ b/playwright/e2e/videos/video.mkv.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { videoTest } from '../../mixins/media.ts' + +videoTest('video.mkv', 'image/mkv') diff --git a/playwright/e2e/videos/video.mp4.spec.ts b/playwright/e2e/videos/video.mp4.spec.ts new file mode 100644 index 000000000..048ccf768 --- /dev/null +++ b/playwright/e2e/videos/video.mp4.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { videoTest } from '../../mixins/media.ts' + +videoTest('video1.mp4', 'video/mp4') diff --git a/playwright/e2e/videos/video.ogv.spec.ts b/playwright/e2e/videos/video.ogv.spec.ts new file mode 100644 index 000000000..f9f0d7722 --- /dev/null +++ b/playwright/e2e/videos/video.ogv.spec.ts @@ -0,0 +1,9 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { OGV_UNSUPPORTED } from '../../support/codecs.ts' +import { videoTest } from '../../mixins/media.ts' + +videoTest('video.ogv', 'video/ogv', OGV_UNSUPPORTED) diff --git a/playwright/e2e/videos/video.webm.spec.ts b/playwright/e2e/videos/video.webm.spec.ts new file mode 100644 index 000000000..4cceed8fa --- /dev/null +++ b/playwright/e2e/videos/video.webm.spec.ts @@ -0,0 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { videoTest } from '../../mixins/media.ts' + +videoTest('video.webm', 'video/webm') diff --git a/playwright/e2e/videos/videos.spec.ts b/playwright/e2e/videos/videos.spec.ts new file mode 100644 index 000000000..a365add11 --- /dev/null +++ b/playwright/e2e/videos/videos.spec.ts @@ -0,0 +1,79 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { expect, setupFilesPage, test, type User } from '../../support/fixtures.ts' +import { getRowForFile, openFile } from '../../support/filesUtils.ts' +import { + expectActiveSource, + expectNavArrows, + expectViewerLoaded, + getMediaInContainer, + getMenuToggle, + getCloseButton, + getViewer, + getViewerName, + goNext, +} from '../../support/viewerUtils.ts' + +test.describe.serial('Open mp4 videos in viewer', () => { + let page: Page + let user: User + + test.beforeAll(async ({ browser }) => { + ({ page, user } = await setupFilesPage(browser, [ + { fixture: 'video1.mp4', mimeType: 'video/mp4' }, + { fixture: 'video2.mp4', mimeType: 'video/mp4' }, + ])) + }) + + test.afterAll(async () => { + await page.close() + }) + + test('See videos in the list', async () => { + await expect(getRowForFile(page, 'video1.mp4')).toContainText('video1 .mp4') + await expect(getRowForFile(page, 'video2.mp4')).toContainText('video2 .mp4') + }) + + test('Open the viewer on file click', async () => { + await openFile(page, 'video1.mp4') + await expect(getViewer(page)).toBeVisible() + }) + + test('See the menu icon and title on the viewer header', async () => { + await expect(getViewerName(page)).toContainText('video1.mp4') + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Does see next navigation arrows', async () => { + await expect(getMediaInContainer(page, 'video')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('The video source is the remote url (video1)', async () => { + await expectActiveSource(page, 'video', `/remote.php/dav/files/${user.userId}/video1.mp4`) + }) + + test('Does not see a loading animation (video1)', async () => { + await expectViewerLoaded(page) + }) + + test('Show video 2 on next', async () => { + await goNext(page) + await expect(getMediaInContainer(page, 'video')).toHaveCount(2) + await expectNavArrows(page) + }) + + test('The video source is the remote url (video2)', async () => { + await expectActiveSource(page, 'video', `/remote.php/dav/files/${user.userId}/video2.mp4`) + }) + + test('Does not see a loading animation (video2)', async () => { + await expectViewerLoaded(page) + }) +}) diff --git a/cypress/fixtures/audio.mp3 b/playwright/fixtures/audio.mp3 similarity index 100% rename from cypress/fixtures/audio.mp3 rename to playwright/fixtures/audio.mp3 diff --git a/cypress/fixtures/audio.ogg b/playwright/fixtures/audio.ogg similarity index 100% rename from cypress/fixtures/audio.ogg rename to playwright/fixtures/audio.ogg diff --git a/cypress/fixtures/image-apng.png b/playwright/fixtures/image-apng.png similarity index 100% rename from cypress/fixtures/image-apng.png rename to playwright/fixtures/image-apng.png diff --git a/cypress/fixtures/image-small.png b/playwright/fixtures/image-small.png similarity index 100% rename from cypress/fixtures/image-small.png rename to playwright/fixtures/image-small.png diff --git a/cypress/fixtures/image.bmp b/playwright/fixtures/image.bmp similarity index 100% rename from cypress/fixtures/image.bmp rename to playwright/fixtures/image.bmp diff --git a/cypress/fixtures/image.gif b/playwright/fixtures/image.gif similarity index 100% rename from cypress/fixtures/image.gif rename to playwright/fixtures/image.gif diff --git a/cypress/fixtures/image.heic b/playwright/fixtures/image.heic similarity index 100% rename from cypress/fixtures/image.heic rename to playwright/fixtures/image.heic diff --git a/cypress/fixtures/image.ico b/playwright/fixtures/image.ico similarity index 100% rename from cypress/fixtures/image.ico rename to playwright/fixtures/image.ico diff --git a/cypress/fixtures/image.png b/playwright/fixtures/image.png similarity index 100% rename from cypress/fixtures/image.png rename to playwright/fixtures/image.png diff --git a/cypress/fixtures/image.svg b/playwright/fixtures/image.svg similarity index 100% rename from cypress/fixtures/image.svg rename to playwright/fixtures/image.svg diff --git a/cypress/fixtures/image.webp b/playwright/fixtures/image.webp similarity index 100% rename from cypress/fixtures/image.webp rename to playwright/fixtures/image.webp diff --git a/cypress/fixtures/image1.jpg b/playwright/fixtures/image1.jpg similarity index 100% rename from cypress/fixtures/image1.jpg rename to playwright/fixtures/image1.jpg diff --git a/cypress/fixtures/image2.jpg b/playwright/fixtures/image2.jpg similarity index 100% rename from cypress/fixtures/image2.jpg rename to playwright/fixtures/image2.jpg diff --git a/cypress/fixtures/image3.jpg b/playwright/fixtures/image3.jpg similarity index 100% rename from cypress/fixtures/image3.jpg rename to playwright/fixtures/image3.jpg diff --git a/cypress/fixtures/image4.jpg b/playwright/fixtures/image4.jpg similarity index 100% rename from cypress/fixtures/image4.jpg rename to playwright/fixtures/image4.jpg diff --git a/cypress/fixtures/test-card.mp4 b/playwright/fixtures/test-card.mp4 similarity index 100% rename from cypress/fixtures/test-card.mp4 rename to playwright/fixtures/test-card.mp4 diff --git a/cypress/fixtures/test-card.png b/playwright/fixtures/test-card.png similarity index 100% rename from cypress/fixtures/test-card.png rename to playwright/fixtures/test-card.png diff --git a/cypress/fixtures/video.mkv b/playwright/fixtures/video.mkv similarity index 100% rename from cypress/fixtures/video.mkv rename to playwright/fixtures/video.mkv diff --git a/cypress/fixtures/video.ogv b/playwright/fixtures/video.ogv similarity index 100% rename from cypress/fixtures/video.ogv rename to playwright/fixtures/video.ogv diff --git a/cypress/fixtures/video.webm b/playwright/fixtures/video.webm similarity index 100% rename from cypress/fixtures/video.webm rename to playwright/fixtures/video.webm diff --git a/cypress/fixtures/video1.mp4 b/playwright/fixtures/video1.mp4 similarity index 100% rename from cypress/fixtures/video1.mp4 rename to playwright/fixtures/video1.mp4 diff --git a/cypress/fixtures/video2.mp4 b/playwright/fixtures/video2.mp4 similarity index 100% rename from cypress/fixtures/video2.mp4 rename to playwright/fixtures/video2.mp4 diff --git a/playwright/mixins/image.ts b/playwright/mixins/image.ts new file mode 100644 index 000000000..8fe08cbc1 --- /dev/null +++ b/playwright/mixins/image.ts @@ -0,0 +1,74 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { escapeRegExp, expect, setupFilesPage, test } from '../support/fixtures.ts' +import { getFileId, getRowForFile, openFile } from '../support/filesUtils.ts' +import { + expectViewerLoaded, + getActiveMedia, + getCloseButton, + getMenuToggle, + getNextButton, + getPrevButton, + getViewer, + getViewerName, +} from '../support/viewerUtils.ts' + +/** + * Generate the standard viewer checks for a single image file. + * + * @param fileName the image to upload and test against + * @param mimeType the image mime type + * @param source optional custom source url the active image should be served from + * (null means the preview endpoint is expected) + */ +export function imageTest(fileName = 'image1.jpg', mimeType = 'image/jpeg', source: string | null = null) { + test.describe.serial(`Open ${fileName} in viewer`, () => { + let page: Page + + test.beforeAll(async ({ browser }) => { + ({ page } = await setupFilesPage(browser, [{ fixture: fileName, mimeType }])) + }) + + test.afterAll(async () => { + await page.close() + }) + + test(`See ${fileName} in the list`, async () => { + await expect(getRowForFile(page, fileName)).toContainText(fileName.replace(/(.*)\./, '$1 .')) + }) + + test('Open the viewer on file click and wait for loading to end', async () => { + const fileId = await getFileId(page, fileName) + const urlRegExp = source + ? new RegExp(`/remote\\.php/dav/files/.*/${escapeRegExp(fileName)}`) + : new RegExp(`/index\\.php/core/preview.*fileId=${fileId}`) + const imageResponse = page.waitForResponse((r) => urlRegExp.test(r.url()) && r.status() === 200) + + await openFile(page, fileName) + await expect(getViewer(page)).toBeVisible() + + await imageResponse + await expectViewerLoaded(page) + }) + + test('See the menu icon and title on the viewer header', async () => { + await expect(getViewerName(page)).toContainText(fileName) + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Does not see navigation arrows', async () => { + await expect(getPrevButton(page)).not.toBeVisible() + await expect(getNextButton(page)).not.toBeVisible() + }) + + test(`The image source is the ${source ? 'remote' : 'preview'} url`, async () => { + await expect(getActiveMedia(page, 'img')).toHaveAttribute('src', new RegExp(escapeRegExp(source ?? '/index.php/core/preview'))) + }) + }) +} diff --git a/playwright/mixins/media.ts b/playwright/mixins/media.ts new file mode 100644 index 000000000..39a324ddf --- /dev/null +++ b/playwright/mixins/media.ts @@ -0,0 +1,97 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { escapeRegExp, expect, setupFilesPage, test, type User } from '../support/fixtures.ts' +import { getRowForFile, openFile } from '../support/filesUtils.ts' +import { + expectActiveSource, + expectViewerLoaded, + getCloseButton, + getMenuToggle, + getNextButton, + getPrevButton, + getViewer, + getViewerName, +} from '../support/viewerUtils.ts' + +/** + * Generate the standard viewer checks for a single video/audio file. The media + * is streamed directly from DAV (partial `206` responses), unlike images which + * go through the preview endpoint. + * + * @param type the media element to assert on + * @param fileName the media file to upload and test against + * @param mimeType the media mime type + */ +function mediaTest(type: 'video' | 'audio', fileName: string, mimeType: string, skipReason?: string) { + test.describe.serial(`Open ${fileName} in viewer`, () => { + test.skip(!!skipReason, skipReason) + + let page: Page + let user: User + + test.beforeAll(async ({ browser }) => { + ({ page, user } = await setupFilesPage(browser, [{ fixture: fileName, mimeType }])) + }) + + test.afterAll(async () => { + await page.close() + }) + + test(`See ${fileName} in the list`, async () => { + await expect(getRowForFile(page, fileName)).toContainText(fileName.replace(/(.*)\./, '$1 .')) + }) + + test('Open the viewer on file click and wait for loading to end', async () => { + const sourceRegExp = new RegExp(`/remote\\.php/dav/files/${escapeRegExp(user.userId)}/${escapeRegExp(fileName)}`) + // Media is streamed with HTTP range requests, so the DAV GET answers 206. + const sourceResponse = page.waitForResponse((r) => sourceRegExp.test(r.url()) && r.status() === 206) + + await openFile(page, fileName) + await expect(getViewer(page)).toBeVisible() + + await sourceResponse + await expectViewerLoaded(page) + }) + + test('See the menu icon and title on the viewer header', async () => { + await expect(getViewerName(page)).toContainText(fileName) + await expect(getMenuToggle(page)).toBeVisible() + await expect(getCloseButton(page)).toBeVisible() + }) + + test('Does not see navigation arrows', async () => { + await expect(getPrevButton(page)).not.toBeVisible() + await expect(getNextButton(page)).not.toBeVisible() + }) + + test(`The ${type} source is the remote url`, async () => { + await expectActiveSource(page, type, `/remote.php/dav/files/${user.userId}/${fileName}`) + }) + }) +} + +/** + * Generate the standard viewer checks for a single video file. + * + * @param fileName the video to upload and test against + * @param mimeType the video mime type + * @param skipReason if set, the whole group is skipped with this reason + */ +export function videoTest(fileName: string, mimeType: string, skipReason?: string) { + mediaTest('video', fileName, mimeType, skipReason) +} + +/** + * Generate the standard viewer checks for a single audio file. + * + * @param fileName the audio to upload and test against + * @param mimeType the audio mime type + */ +export function audioTest(fileName = 'audio.ogg', mimeType = 'audio/ogg') { + mediaTest('audio', fileName, mimeType) +} diff --git a/playwright/mixins/oddname.ts b/playwright/mixins/oddname.ts new file mode 100644 index 000000000..bf5a8714f --- /dev/null +++ b/playwright/mixins/oddname.ts @@ -0,0 +1,138 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Page } from '@playwright/test' + +import { createRandomUser, expect, loginUser, test, type User } from '../support/fixtures.ts' +import { getRowForFile, navigateToFolder, openFile } from '../support/filesUtils.ts' +import { getSidebar, getSidebarName, openSidebarFromViewer } from '../support/sidebarUtils.ts' +import { + getCloseButton, + getNextButton, + getPrevButton, + getViewer, + getViewerName, + expectViewerLoaded, +} from '../support/viewerUtils.ts' +import { createLinkShare, createFolder, uploadFile } from '../support/webdav.ts' + +/** + * Build a file name designed to break the viewer if escaping is wrong. + * + * @param realName the original file name (its extension is preserved) + */ +function naughtyFileName(realName: string): string { + const ext = realName.split('.').pop()! + return ( + '~⛰️ shot of a $[big} mountain`, ' + + "realy #1's " + + '" #_+="%2520%27%22%60%25%21%23 was this called ' + + realName + + 'in the' + + '☁️' + + '👩‍💻' + + '? :* .' + + ext.toUpperCase() + ) +} + +/** + * Generate viewer checks for a file with a hostile name, in both the files app + * and a public share. + * + * @param file the fixture file to upload + * @param mimeType the fixture mime type + * @param sidebar whether to also exercise the sidebar + * @param skipReason if set, the whole group is skipped with this reason + */ +export function oddnameTest(file: string, mimeType: string, sidebar = false, skipReason?: string) { + const placedName = naughtyFileName(file) + const folderName = 'Nextcloud "%27%22%60%25%21%23" >`⛰️<' + file + "><` e*'rocks!#?#%~" + + const expectMenuOk = async (page: Page) => { + await expect(getViewer(page).locator('.icon-error')).toHaveCount(0) + await expect(getViewerName(page)).toContainText(placedName) + await expect(getCloseButton(page)).toBeVisible() + } + + const expectNoArrows = async (page: Page) => { + await expect(getPrevButton(page)).not.toBeVisible() + await expect(getNextButton(page)).not.toBeVisible() + } + + test.describe.serial(`Open ${file} in viewer with a naughty name ${sidebar ? 'with sidebar' : ''}`, () => { + test.skip(!!skipReason, skipReason) + + let page: Page + let user: User + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage() + user = await createRandomUser() + await createFolder(user, `/${folderName}`) + await uploadFile(user, file, mimeType, `/${folderName}/${placedName}`) + await loginUser(page, user) + await page.goto('apps/files') + await navigateToFolder(page, folderName) + }) + + test.afterAll(async () => { + await page.close() + }) + + test(`See ${file} as its naughty name in the list`, async () => { + await expect(getRowForFile(page, placedName)).toContainText(placedName.replace(/(.*)\./, '$1 .')) + }) + + test('Open the viewer on file click', async () => { + await openFile(page, placedName) + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation', async () => { + await expectViewerLoaded(page) + }) + + test('See the menu icon and title on the viewer header', async () => { + await expectMenuOk(page) + }) + + test('Does not see navigation arrows', async () => { + await expectNoArrows(page) + }) + + if (sidebar) { + test('Open the sidebar', async () => { + await openSidebarFromViewer(page) + await expect(getSidebar(page)).toBeVisible() + await expect(page.locator('.action-button__icon.icon-menu-sidebar')).toHaveCount(0) + await expect(getSidebarName(page)).toContainText(placedName) + }) + } + + test('Share the folder with a share link and access the share link', async () => { + const token = await createLinkShare(user, folderName) + await page.context().clearCookies() + await page.goto(`s/${token}`) + }) + + test('Open the viewer on file click (public)', async () => { + await openFile(page, placedName) + await expect(getViewer(page)).toBeVisible() + }) + + test('Does not see a loading animation (public)', async () => { + await expectViewerLoaded(page) + }) + + test('See the menu icon and title on the viewer header (public)', async () => { + await expectMenuOk(page) + }) + + test('Does not see navigation arrows (public)', async () => { + await expectNoArrows(page) + }) + }) +} diff --git a/playwright/start-server.mjs b/playwright/start-server.mjs new file mode 100644 index 000000000..e608842f0 --- /dev/null +++ b/playwright/start-server.mjs @@ -0,0 +1,46 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + configureNextcloud, + runExec, + runOcc, + startNextcloud, + stopNextcloud, + waitOnNextcloud, +} from '@nextcloud/e2e-test-server/docker' + +async function stop() { + process.stderr.write('Stopping Nextcloud server…\n') + // Only tear down the container in CI; locally we leave it running so the + // next test run can reuse it without a slow cold start. + if (process.env.CI) { + await stopNextcloud() + } + process.exit(0) +} + +process.on('SIGTERM', stop) +process.on('SIGINT', stop) + +const ip = await startNextcloud(process.env.BRANCH ?? 'master', undefined, { exposePort: 8081 }) +await waitOnNextcloud(ip) +await configureNextcloud(['viewer']) +// Cypress ran on Electron, whose user-agent passes Nextcloud's browserslist check. +// Playwright's Chromium is recent enough too, but disable the redirect explicitly +// so a future browserslist bump can't send tests to the "unsupported browser" page. +await runOcc(['config:system:set', 'no_unsupported_browser_warning', '--value', 'true', '--type', 'boolean']) +// Avoid hitting the app store during tests. +await runOcc(['config:system:set', 'appstoreenabled', '--value', 'false', '--type', 'boolean']) +// SQLite locks up under the parallel DAV/OCS traffic the specs generate; WAL avoids it. +await runExec(['php', '-r', '$db = new SQLite3("data/owncloud.db");$db->busyTimeout(5000);$db->exec("PRAGMA journal_mode = wal;");']) +await runExec(['php', 'cron.php']) + +process.stdout.write('Nextcloud ready at http://localhost:8081\n') + +// Keep the process alive so Playwright's gracefulShutdown can signal us. +while (true) { + await new Promise((resolve) => setTimeout(resolve, 5000)) +} diff --git a/playwright/support/codecs.ts b/playwright/support/codecs.ts new file mode 100644 index 000000000..7214268c3 --- /dev/null +++ b/playwright/support/codecs.ts @@ -0,0 +1,10 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +// Playwright's bundled Chromium ships without the Ogg/Theora decoder (verified +// on CI: canPlayType returns "" and the