diff --git a/.github/workflows/cypress-e2e.yml b/.github/workflows/cypress-e2e.yml index 771bae6681c..42c852f6657 100644 --- a/.github/workflows/cypress-e2e.yml +++ b/.github/workflows/cypress-e2e.yml @@ -19,14 +19,29 @@ env: CYPRESS_CACHE_FOLDER: ${{ github.workspace }}/Cypress jobs: - init: + cypress: runs-on: ubuntu-latest - outputs: - nodeVersion: ${{ steps.versions.outputs.nodeVersion }} - npmVersion: ${{ steps.versions.outputs.npmVersion }} + timeout-minutes: 30 + strategy: + fail-fast: false matrix: server-versions: ['stable35'] + containers: [1] + php-versions: ['8.3'] + + name: runner ${{ matrix.containers }} + + services: + postgres: + image: ghcr.io/nextcloud/continuous-integration-postgres-16:latest # zizmor: ignore[unpinned-images] + ports: + - 4444:5432/tcp + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: rootpassword + POSTGRES_DB: nextcloud + options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 steps: - name: Checkout server @@ -45,15 +60,6 @@ jobs: ref: ${{ matrix.server-versions }} path: apps/viewer - - name: Checkout assistant - if: matrix.server-versions == 'main' - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - with: - persist-credentials: false - repository: nextcloud/assistant - ref: main - path: apps/assistant - - name: Checkout app uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: @@ -83,63 +89,6 @@ jobs: npm ci TESTING=true npm run build --if-present - - name: Install node dependencies & build assistant app - if: matrix.server-versions == 'main' - working-directory: apps/assistant - run: | - composer install --no-dev - npm ci - npm run build - - - name: Save context - uses: buildjet/cache/save@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2 - with: - key: cypress-context-${{ github.run_id }} - path: | - ./ - - cypress: - runs-on: ubuntu-latest - timeout-minutes: 30 - needs: init - - strategy: - fail-fast: false - matrix: - containers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] - php-versions: ['8.3'] - - name: runner ${{ matrix.containers }} - - services: - postgres: - image: ghcr.io/nextcloud/continuous-integration-postgres-16:latest # zizmor: ignore[unpinned-images] - ports: - - 4444:5432/tcp - env: - POSTGRES_USER: root - POSTGRES_PASSWORD: rootpassword - POSTGRES_DB: nextcloud - options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 - - steps: - - name: Restore context - uses: buildjet/cache/restore@3e70d19e31d6a8030aeddf6ed8dbe601f94d09f4 # v4.0.2 - 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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 - with: - cache: 'npm' - node-version: ${{ needs.init.outputs.nodeVersion }} - - - name: Set up npm ${{ needs.init.outputs.npmVersion }} - run: npm i -g 'npm@${{ needs.init.outputs.npmVersion }}' - - name: Set up php ${{ matrix.php-versions }} uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.36.0 with: @@ -155,7 +104,11 @@ jobs: PHP_CLI_SERVER_WORKERS: 10 run: | mkdir data - echo '"\OC\Memcache\APCu","hashing_default_password"=>true];' > config/config.php + echo ' config/config.php + echo '"memcache.local"=>"\OC\Memcache\APCu",' >> config/config.php + echo '"hashing_default_password"=>true,' >> config/config.php + echo '"log.condition"=>["apps"=>["text"]]' >> config/config.php + echo '];' >> config/config.php php occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin php -f index.php php -S 0.0.0.0:8081 & @@ -210,7 +163,7 @@ jobs: summary: runs-on: ubuntu-latest-low - needs: [init, cypress] + needs: [cypress] if: always() @@ -218,4 +171,4 @@ jobs: steps: - name: Summary status - run: if ${{ needs.init.result != 'success' || ( needs.cypress.result != 'success' && needs.cypress.result != 'skipped' ) }}; then exit 1; fi + run: if ${{ ( needs.cypress.result != 'success' && needs.cypress.result != 'skipped' ) }}; then exit 1; fi diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml deleted file mode 100644 index 6669c4016d6..00000000000 --- a/.github/workflows/playwright.yml +++ /dev/null @@ -1,85 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors -# SPDX-License-Identifier: MIT - -name: Playwright Tests - -on: - pull_request: - branches: [main, stable3*] - -permissions: - contents: read - -jobs: - playwright: - timeout-minutes: 60 - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - shardIndex: [1, 2, 3] - shardTotal: [3] - steps: - - 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 - uses: nextcloud-libraries/parse-package-engines-action@122ae05d4257008180a514e1ddeb0c1b9d094bdd # v0.1.0 - id: versions - - - name: Set up node - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: ${{ steps.versions.outputs.node-version }} - - - name: Set up npm - run: npm i -g 'npm@${{ steps.versions.outputs.package-manager-version }}' - - - name: Install node dependencies & build app - env: - CYPRESS_INSTALL_BINARY: 0 - run: | - npm ci - npm run build --if-present - - - name: Install Playwright Browsers - run: npx playwright install --with-deps chromium webkit - - - name: Install poppler for PDF snapshots - run: sudo apt-get install -y poppler-utils - - - name: Run Playwright tests - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - env: - TEXT_COMPARISON_E2E: '1' - - - name: Upload results - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: ${{ !cancelled() }} - with: - name: playwright-report_shard${{ matrix.shardIndex }} - path: | - test-results/ - blob-report/ - retention-days: 7 - - 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/cypress/e2e/Assistant.spec.js b/cypress/e2e/Assistant.spec.js deleted file mode 100644 index d2ce59cf617..00000000000 --- a/cypress/e2e/Assistant.spec.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { initUserAndFiles, randUser } from '../utils/index.js' - -const currentUser = randUser() - -const fileName = 'empty.md' - -describe('Assistant', () => { - before(() => { - initUserAndFiles(currentUser, fileName) - }) - - beforeEach(() => { - cy.login(currentUser) - cy.visit('/apps/files') - }) - - it('See assistant button', () => { - cy.isolateTest({ - sourceFile: fileName, - }) - cy.openFile(fileName, { force: true }) - - cy.getContent().click({ force: true }) - - cy.getActionEntry('assistant').should('be.visible') - - cy.getActionEntry('assistant').click() - - cy.get('.action-button').contains('Free text to text prompt') - cy.get('.action-button').contains('Translate') - cy.get('.action-button').contains('Show assistant results') - }) - - it('Send free prompt request', () => { - cy.isolateTest({ - sourceFile: fileName, - }) - cy.openFile(fileName, { force: true }) - - cy.getContent().click({ force: true }) - cy.getActionEntry('assistant').click() - cy.get('.action-button').contains('Free text to text prompt').click() - - cy.get('.assistant-modal--content #input-input').type('Hello World', { - force: true, - }) - cy.get('.assistant-modal--content .submit-button').click() - - cy.wait(2000) - - cy.get('.assistant-modal--content button').contains('Get notified').click() - - cy.get('.assistant-modal--content button .bell-ring-outline-icon').should('be.visible') - - cy.get('.assistant-modal--content .close-button').click() - cy.getActionEntry('assistant').click() - cy.get('.action-button').contains('Show assistant results').click() - - cy.get('.modal-container__content .task-list') - .should('be.visible') - .should('contain', 'Hello World') - }) - - it('Open translate dialog', () => { - cy.isolateTest({ - sourceFile: fileName, - }) - cy.openFile(fileName, { force: true }) - - cy.getActionEntry('assistant').click() - cy.get('.action-button').contains('Translate').click() - - cy.wait(1000) - - cy.get('.assistant-modal--content #input-input').type('Hello World', { - force: true, - }) - - cy.get('.assistant-modal--content button').contains('Translate').click() - }) -}) diff --git a/cypress/e2e/Links.spec.js b/cypress/e2e/Links.spec.js deleted file mode 100644 index b2bda43fcd1..00000000000 --- a/cypress/e2e/Links.spec.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' - -const user = randUser() -const fileName = 'empty.md' - -describe('test link marks', function() { - before(function() { - cy.createUser(user) - }) - - beforeEach(function() { - cy.login(user) - cy.isolateTest({ - sourceFile: fileName, - onBeforeLoad(win) { - cy.stub(win, 'open').as('winOpen') - }, - }) - cy.openFile(fileName, { force: true }) - }) - - describe('autolink', function() { - it('with protocol to files app and fileId', () => { - cy.getFile(fileName).then(($el) => { - const id = $el.data('cyFilesListRowFileid') - - const link = `${Cypress.env('baseUrl')}/apps/files/?dir=/&openfile=${id}#relPath=/${fileName}` - cy.clearContent() - cy.insertLine(link) - - cy.getContent().find(`a[href*="${Cypress.env('baseUrl')}"]`) - }) - }) - - it('without protocol', () => { - cy.clearContent() - cy.insertLine('google.com') - cy.getContent().find('a[href*="google.com"]').should('not.exist') - }) - - it('with protocol but without space', () => { - cy.getContent().type('https://example.org') - - cy.getContent().find('a[href*="example.org"]').should('not.exist') - }) - }) - - describe('link menu', function() { - beforeEach(() => cy.clearContent()) - const text = 'some text' - - describe('link to website', function() { - const url = 'https://example.org/' - // Helper to reduce duplicated code, checking inserting with and without selected text - const checkLinkWebsite = (url, text) => { - cy.getSubmenuEntry('insert-link', 'insert-link-website').click() - cy.getActionSubEntry('insert-link-input') - .click() - .type(`${url}{enter}`) - cy.getContent() - .find(`a[href*="${url}"]`) - .should('have.text', text) // ensure correct text used - .click({ force: true }) - } - - beforeEach(cy.clearContent) - it('Link website without selection', () => { - cy.getFile(fileName).then(() => { - checkLinkWebsite(url, url) - }) - }) - - it('Link website with selection', () => { - cy.getFile(fileName).then(() => { - cy.getContent().type(`${text}{selectAll}`) - checkLinkWebsite(url, text) - }) - }) - }) - - describe('link to local file', function() { - let fileId = null - // Helper to reduce duplicated code, checking inserting with and without selected text - const checkLinkFile = (filename, text, isFolder = false) => { - cy.getSubmenuEntry('insert-link', 'insert-link-file').click() - cy.get('.file-picker').within(() => { - cy.get(`[data-testid="file-list-row"][data-filename="${filename}"]`).click() - cy.get(isFolder ? '.empty-content__name' : '.file-picker__files') - cy.contains( - 'button', - isFolder ? 'Choose' : `Choose ${filename}`, - ).click() - }) - - return cy - .getContent() - .find(`a[href*="/index.php/f/${fileId}"]`) - .should('have.text', text === undefined ? filename : text) - } - - beforeEach(() => cy.clearContent()) - - it('without text', () => { - cy.getFileId(fileName).then((id) => { - fileId = id - }) - cy.getFile(fileName).then(() => { - checkLinkFile(fileName) - cy.get('.modal-header__name').should('include.text', fileName) - }) - }) - it('with selected text', () => { - cy.getFileId(fileName).then((id) => { - fileId = id - }) - cy.getFile(fileName).then(() => { - cy.getContent().type(`${text}{selectAll}`) - checkLinkFile(fileName, text) - cy.get('.modal-header__name').should('include.text', fileName) - }) - }) - it('link to directory', () => { - cy.createFolder(`${window.__currentDirectory}/dummy folder`).then((folderId) => { - fileId = folderId - }) - cy.getFile(fileName).then(() => { - cy.getContent().type(`${text}{selectAll}`) - checkLinkFile('dummy folder', text, true) - }) - }) - }) - }) -}) diff --git a/cypress/e2e/MenuBar.spec.js b/cypress/e2e/MenuBar.spec.js deleted file mode 100644 index 1fec7a62d82..00000000000 --- a/cypress/e2e/MenuBar.spec.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' - -const user = randUser() - -describe('Test the rich text editor menu bar', function() { - before(function() { - cy.createUser(user) - }) - - beforeEach(function() { - cy.login(user) - cy.uploadTestFile() - }) - - describe('word count', function() { - /** - * - */ - function getWordCount() { - return cy - .get('.v-popper__popper .open') - .get('[data-text-action-entry="character-count"]') - } - - beforeEach(function() { - cy.visit('/apps/files') - cy.openTestFile() - }) - - it('empty file', () => { - cy.getContent() - cy.getActionEntry('remain').click() - getWordCount().should('include.text', '0 words, 0 chars') - }) - - it('single word', () => { - cy.getContent().type(' Hello ') - cy.getActionEntry('remain').click() - getWordCount().should('include.text', '1 word, 9 chars') - }) - - it('multiple words', () => { - cy.getContent().type('Hello \nworld') - cy.getActionEntry('remain').click() - getWordCount().should('include.text', '2 words, 11 chars') - }) - }) - - describe('text width toggle', function() { - beforeEach(() => { - cy.configureText('is_full_width_editor', 0) - cy.visit('/apps/files') - cy.window() - .its('document.documentElement.style') - .invoke('getPropertyValue', '--text-editor-max-width') - .as('maxWidth') - }) - - it('applys default', function() { - cy.openTestFile() - cy.get('@maxWidth').should('equal', 'min(80ch, (100% - 2 * 40px))') - }) - - it('toggles value', function() { - cy.openTestFile() - cy.getActionEntry('remain').click() - cy.contains('Full width editor').click() - cy.get('@maxWidth').should('equal', 'calc(100% - 2 * 40px)') - }) - - it('preserves on reopen', function() { - cy.openTestFile() - cy.getActionEntry('remain').click() - cy.contains('Full width editor').click() - cy.closeFile() - cy.openTestFile() - cy.get('@maxWidth').should('equal', 'calc(100% - 2 * 40px)') - }) - - it('preserves on reload', function() { - cy.openTestFile() - cy.getActionEntry('remain').click() - cy.contains('Full width editor').click() - cy.visit('/apps/files') - cy.openTestFile() - cy.get('@maxWidth').should('equal', 'calc(100% - 2 * 40px)') - }) - - it('does not interfere if width is already set', function() { - cy.window() - .its('document.body.style') - .invoke('setProperty', '--text-editor-max-width', '987px') - cy.openTestFile() - cy.getActionEntry('remain').click() - cy.contains('Full width editor').should('not.exist') - cy.get('@maxWidth').should('equal', '') - }) - }) -}) diff --git a/cypress/e2e/api/SessionApi.spec.js b/cypress/e2e/api/SessionApi.spec.js deleted file mode 100644 index 8bcdd132dbb..00000000000 --- a/cypress/e2e/api/SessionApi.spec.js +++ /dev/null @@ -1,344 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../../utils/index.js' - -const user = randUser() -const messages = { - awareness: 'AQoBw9LM0gwAAnt9', - update: 'AAIKAAHYidydCwEEAQ==', - query: 'AAABAA==', - response: 'AAECAAA=', -} - -describe('The session Api', function() { - before(function() { - cy.createUser(user) - }) - - beforeEach(function() { - cy.login(user) - }) - - describe('open the session', function() { - let fileId - let filePath - - beforeEach(function() { - cy.uploadTestFile('test.md').then((id) => { - fileId = id - }) - cy.testName().then((name) => { - filePath = `/${name}.md` - }) - }) - - it('returns connection', function() { - cy.openFileConnection({ fileId }).then(({ connection }) => { - cy.wrap(connection) - .its('documentId') - .then((id) => Number.parseInt(id)) - .should('be.greaterThan', 1_000_000) // snowflake ids have more than 20 bit. - cy.closeConnection(connection) - }) - }) - - it('provides initial content', function() { - cy.openFileConnection({ fileId, filePath }).then(({ connection, data }) => { - cy.wrap(data).its('content').should('eql', '## Hello world\n') - cy.closeConnection(connection) - }) - }) - - it('handles invalid file id', function() { - cy.failToCreateTextSession(123456789).its('status').should('equal', 404) - }) - - it('handles missing file id', function() { - cy.failToCreateTextSession().its('status').should('equal', 404) - }) - }) - - describe('step types', function() { - let connection - - beforeEach(function() { - cy.uploadTestFile() - .then((fileId) => cy.openFileConnection({ fileId })) - .then(({ connection: con }) => { - connection = con - }) - }) - - afterEach(function() { - cy.closeConnection(connection) - }) - - // Echoes updates and responses - ;['update', 'response'].forEach((type) => { - it(`echos ${type} messages`, function() { - const steps = [messages[type]] - const version = 0 - cy.pushSteps({ connection, steps, version }) - .its('version') - .should('eql', 0) - cy.syncSteps(connection).its('steps[0].data').should('eql', steps) - }) - }) - - it('responds to queries with updates and responses', function() { - const version = 0 - Object.entries(messages).forEach(([, sample]) => { - cy.pushSteps({ connection, steps: [sample], version }) - }) - cy.pushSteps({ connection, steps: [messages.query], version }).then((response) => { - cy.wrap(response).its('version').should('eql', 0) - cy.wrap(response).its('steps.length').should('eql', 2) - cy.wrap(response) - .its('steps[0].data') - .should('eql', [messages.update]) - cy.wrap(response) - .its('steps[1].data') - .should('eql', [messages.response]) - }) - }) - }) - - describe('sync', function() { - const version = 0 - let connection - let fileId - let filePath - - beforeEach(function() { - cy.testName().then((name) => { - filePath = `/${name}.md` - }) - cy.uploadTestFile() - .then((id) => { - fileId = id - return cy.openFileConnection({ fileId, filePath }) - }) - .then(({ connection: con }) => { - connection = con - }) - }) - - it('starts empty', function() { - cy.syncSteps(connection).its('steps').should('eql', []) - }) - - it('saves', function() { - cy.pushSteps({ connection, steps: [messages.update], version }) - .its('version') - .should('eql', 0) - cy.save(connection, { - version: 1, - autosaveContent: '# Heading 1', - documentState: 'state', - manualSave: true, - }) - cy.downloadFile(filePath).its('data').should('eql', '# Heading 1') - }) - - it('saves yjs document state', function() { - const documentState = 'Base64 encoded string' - cy.pushSteps({ connection, steps: [messages.update], version }) - .its('version') - .should('eql', 0) - cy.save(connection, { - version: 1, - autosaveContent: '# Heading 1', - documentState, - manualSave: true, - }) - cy.openFileConnection({ fileId, filePath }) - .as('joining') - .its('data.documentState') - .should('eql', documentState) - cy.get('@joining').its('connection').then(cy.closeConnection) - }) - - afterEach(function() { - cy.closeConnection(connection) - }) - }) - - describe('public sync', function() { - const version = 0 - let connection - let filePath - let shareToken - - beforeEach(function() { - cy.testName().then((name) => { - filePath = `/${name}.md` - }) - cy.uploadTestFile() - .then(() => { - return cy.shareFile(filePath, { edit: true }) - }) - .then((token) => { - shareToken = token - }) - .then(() => cy.clearCookies()) - .then(() => { - return cy - .openShareConnection({ filePath: '', token: shareToken }) - .then(({ connection: con }) => { - connection = con - }) - }) - }) - - afterEach(function() { - cy.closeConnection(connection) - }) - - it('starts empty public', function() { - cy.syncSteps(connection).its('steps').should('eql', []) - }) - - it('saves public', function() { - cy.pushSteps({ connection, steps: [messages.update], version }) - .its('version') - .should('eql', 0) - cy.save(connection, { - version: 1, - autosaveContent: '# Heading 1', - documentState: 'state', - manualSave: true, - }) - cy.login(user) - cy.downloadFile('saves.md').its('data').should('eql', '# Heading 1') - }) - - it('saves yjs document state public', function() { - const documentState = 'Base64 encoded string' - cy.pushSteps({ connection, steps: [messages.update], version }) - .its('version') - .should('eql', 0) - cy.save(connection, { - version: 1, - autosaveContent: '# Heading 1', - documentState, - manualSave: true, - }) - cy.openShareConnection({ filePath: '', token: shareToken }) - .as('joining') - .its('data.documentState') - .should('eql', documentState) - cy.get('@joining').its('connection').then(cy.closeConnection) - }) - }) - - describe('race conditions', function() { - const version = 0 - let connection - let shareToken - - beforeEach(function() { - cy.testName() - .then((name) => { - const filePath = `/${name}.md` - cy.uploadTestFile('test.md') - return cy.shareFile(filePath, { edit: true }) - }) - .then((token) => { - cy.log(token) - shareToken = token - cy.clearCookies() - cy.openShareConnection({ filePath: '', token: shareToken }).then(({ connection: con }) => { - connection = con - }) - }) - }) - - it('does not send initial content if other session is alive but did not push any steps', function() { - let joining - cy.openShareConnection({ filePath: '', token: shareToken }) - .then(({ connection: con, data }) => { - joining = con - return data - }) - .its('content') - .should('eql', '## Hello world\n') - .then(() => cy.closeConnection(joining)) - cy.closeConnection(connection) - }) - - it('does not send initial content if session is alive even without saved state', function() { - let joining - cy.pushSteps({ connection, steps: [messages.update], version }) - .its('version') - .should('eql', 0) - cy.openShareConnection({ filePath: '', token: shareToken }) - .then(({ connection: con, data }) => { - joining = con - return data - }) - .its('content') - .should('eql', '## Hello world\n') - .then(() => cy.closeConnection(joining)) - cy.closeConnection(connection) - }) - - it('refuses create,push,sync,save with non-matching baseVersionEtag', function() { - cy.failToCreateTextShareSession(shareToken, 'wrongBaseVersionEtag', { - filePath: '', - }) - .its('status') - .should('eql', 412) - - connection.baseVersionEtag = 'wrongBaseVersionEtag' - - cy.failToPushSteps({ connection, steps: [messages.update], version }) - .its('status') - .should('equal', 412) - - cy.failToSyncSteps(connection, { version: 0 }) - .its('status') - .should('equal', 412) - - cy.failToSave(connection).its('status').should('equal', 412) - - cy.closeConnection(connection) - }) - - it('recovers session even if last person leaves right after create', function() { - let joining - cy.log('Initial user pushes steps') - cy.pushSteps({ connection, steps: [messages.update], version }) - .its('version') - .should('eql', 0) - cy.log('Other user creates session') - cy.openShareConnection({ filePath: '', token: shareToken }).then(({ connection: con }) => { - joining = con - }) - cy.log('Initial user closes session') - cy.closeConnection(connection) - cy.log('Other user still finds the steps').then(() => { - cy.syncSteps(joining, { - version: 0, - }) - .its('steps[0].data') - .should('eql', [messages.update]) - }) - }) - - // Failed with a probability of ~ 50% initially - // Skipped for now since the behaviour chanced by not cleaning up the state on close/create - it.skip('ignores steps stored after close cleaned up', function() { - cy.pushAndClose({ connection, steps: [messages.update], version }) - cy.openShareConnection({ filePath: '', token: shareToken }) - .then(({ connection: con, data }) => { - connection = con - return data - }) - .its('content') - .should('eql', '## Hello world\n') - }) - }) -}) diff --git a/cypress/e2e/api/SyncServiceProvider.spec.js b/cypress/e2e/api/SyncServiceProvider.spec.js deleted file mode 100644 index 68b2707b5be..00000000000 --- a/cypress/e2e/api/SyncServiceProvider.spec.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { Doc } from 'yjs' -import { provideConnection } from '../../../src/composables/useConnection.ts' -import { provideSyncService } from '../../../src/composables/useSyncService.ts' -import createSyncServiceProvider from '../../../src/services/SyncServiceProvider.js' -import { randUser } from '../../utils/index.js' - -const user = randUser() - -describe('Sync service provider', function() { - let fileId - - before(function() { - cy.createUser(user) - }) - - beforeEach(function() { - cy.login(user) - cy.uploadTestFile('test.md').then((id) => { - fileId = id - }) - cy.wrap(new Doc()).as('source') - cy.wrap(new Doc()).as('target') - cy.get('@source') - .then((source) => createProvider(source)) - .as('sourceProvider') - cy.get('@target') - .then((target) => createProvider(target)) - .as('targetProvider') - }) - - afterEach(function() { - cy.get('@sourceProvider').invoke('destroy') - cy.get('@targetProvider').invoke('destroy') - }) - - /** - * @param {object} ydoc Yjs document - */ - function createProvider(ydoc) { - const context = { type: 'file', id: fileId } - const relativePath = '.' - let baseVersionEtag - const setBaseVersionEtag = (val) => { - baseVersionEtag = val - } - const getBaseVersionEtag = () => baseVersionEtag - const { connection, openConnection } = provideConnection( - { - context, - relativePath, - }, - getBaseVersionEtag, - setBaseVersionEtag, - ) - const { syncService } = provideSyncService(connection, openConnection) - syncService.bus.on('opened', () => syncService.startSync()) - return createSyncServiceProvider({ - ydoc, - syncService, - context, - disableBc: true, - }) - } - - it('recovers from a dropped message', function() { - const sourceMap = this.source.getMap() - const targetMap = this.target.getMap() - cy.intercept({ method: 'POST', url: '**/apps/text/session/*/push' }).as('push') - cy.intercept({ method: 'POST', url: '**/apps/text/session/*/sync' }).as('sync') - cy.wait('@push') - cy.then(() => { - sourceMap.set('keyA', 'valueA') - expect(targetMap.get('keyB')).to.be.eq(undefined) - }) - cy.wait('@sync') - cy.wait('@sync') - - cy.wait(1000) - cy.then(() => { - expect(targetMap.get('keyA')).to.be.eq('valueA') - }) - cy.intercept( - { - method: 'POST', - url: '**/apps/text/session/*/push', - }, - (req) => { - if (req.body.steps) { - req.reply({ forceNetworkError: true }) - req.alias = 'dead' - } else { - req.continue() - } - }, - ) - cy.then(() => { - sourceMap.set('keyB', 'valueB') - expect(targetMap.get('keyB')).to.be.eq(undefined) - }) - cy.wait('@dead') - cy.then(() => { - expect(targetMap.get('keyB')).to.be.eq(undefined) - }) - cy.intercept( - { - method: 'POST', - url: '**/apps/text/session/*/push', - }, - (req) => { - if (req.body.steps) { - req.alias = 'alive' - req.continue() - } else { - req.continue() - } - }, - ) - cy.then(() => { - sourceMap.set('keyC', 'valueC') - expect(targetMap.get('keyB')).to.be.eq(undefined) - }) - cy.wait('@alive') - - cy.wait(1000) - cy.then(() => { - expect(targetMap.get('keyC')).to.be.eq('valueC') - expect(targetMap.get('keyB')).to.be.eq('valueB') - }) - }) - - /* - * Counts the amount of push and sync requests in one minute. - * Skipped per default, useful for comparison before/after changes to SyncProvider or PollingBackend. - */ - it.skip('is not too chatty', function() { - const sourceMap = this.source.getMap() - const targetMap = this.target.getMap() - cy.intercept({ method: 'POST', url: '**/apps/text/session/*/push' }).as('push') - cy.intercept({ method: 'POST', url: '**/apps/text/session/*/sync' }).as('sync') - cy.wait('@push') - cy.then(() => { - sourceMap.set('keyA', 'valueA') - expect(targetMap.get('keyB')).to.be.eq(undefined) - }) - - cy.wait(60000) - cy.then(() => { - expect(targetMap.get('keyA')).to.be.eq('valueA') - }) - // 2 clients push awareness updates every 15 seconds -> 2*5 = 10. Actual 15. - cy.get('@push.all').its('length').should('be.lessThan', 30) - // 2 clients sync fast first and then every 5 seconds -> 2*12 = 24. Actual 32. - cy.get('@sync.all').its('length').should('be.lessThan', 60) - }) - - it('syncs even when initial state was present', function() { - const sourceMap = this.source.getMap() - const targetMap = this.target.getMap() - sourceMap.set('unrelated', 'value') - cy.intercept({ method: 'POST', url: '**/apps/text/session/*/push' }).as('push') - cy.intercept({ method: 'POST', url: '**/apps/text/session/*/sync' }).as('sync') - cy.wait('@push') - cy.then(() => { - sourceMap.set('keyA', 'valueA') - expect(targetMap.get('keyB')).to.be.eq(undefined) - }) - cy.wait('@sync') - cy.wait('@sync') - - cy.wait(1000) - cy.then(() => { - expect(targetMap.get('keyA')).to.be.eq('valueA') - }) - }) -}) diff --git a/cypress/e2e/api/UsersApi.spec.js b/cypress/e2e/api/UsersApi.spec.js deleted file mode 100644 index c7482f0f46e..00000000000 --- a/cypress/e2e/api/UsersApi.spec.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../../utils/index.js' - -const user = randUser() - -describe('The user mention API', function() { - before(function() { - cy.createUser(user) - }) - - beforeEach(function() { - cy.login(user) - cy.uploadTestFile('test.md') - .as('fileId') - .then((fileId) => cy.openFileConnection({ fileId })) - .its('connection') - .as('connection') - }) - - it('has a valid connection', function() { - cy.get('@connection') - .its('documentId') - .then((id) => Number.parseInt(id)) - .should('be.greaterThan', 1_000_000) // snowflake ids have more than 20 bit. - cy.closeConnection(this.connection) - }) - - it('fetches users with valid session', function() { - cy.sessionUsers(this.connection).its('status').should('eq', 200) - cy.closeConnection(this.connection) - }) - - it('rejects invalid sessions', function() { - cy.sessionUsers(this.connection, { sessionToken: 'invalid' }) - .its('status') - .should('eq', 403) - cy.sessionUsers(this.connection, { sessionId: 0 }) - .its('status') - .should('eq', 403) - cy.sessionUsers(this.connection, { documentId: 0 }) - .its('status') - .should('eq', 403) - cy.closeConnection(this.connection) - }) - - it('rejects closed sessions', function() { - cy.closeConnection(this.connection) - cy.sessionUsers(this.connection).its('status').should('eq', 403) - }) -}) diff --git a/cypress/e2e/attachments.spec.js b/cypress/e2e/attachments.spec.js deleted file mode 100644 index 2779b76f6a6..00000000000 --- a/cypress/e2e/attachments.spec.js +++ /dev/null @@ -1,491 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { initUserAndFiles, randHash, randUser } from '../utils/index.js' - -const user = randUser() -const recipient = randUser() -const currentUser = user -const attachmentFileNameToId = {} - -const ACTION_UPLOAD_LOCAL_FILE = 'insert-attachment-upload' -const ACTION_INSERT_FROM_FILES = 'insert-attachment-insert' -const ACTION_CREATE_NEW_WHITEBOARD_FILE = 'insert-attachment-add-whiteboard-0' - -/** - * @param {string} name name of file - * @param {string|null} requestAlias alias name - */ -function attachFile(name, requestAlias = null) { - if (requestAlias) { - cy.intercept({ method: 'POST', url: '**/text/attachment/upload?**' }).as(requestAlias) - } - return cy - .getEditor() - .find('input[type="file"][data-text-el="attachment-file-input"]') - .selectFile( - [ - { - contents: 'cypress/fixtures/' + name, - fileName: name, - }, - ], - { force: true }, - ) -} - -/** - * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent - * @param {string} str String to encode - */ -function fixedEncodeURIComponent(str) { - return encodeURIComponent(str).replace(/[!'()*]/g, (c) => { - return '%' + c.charCodeAt(0).toString(16).toUpperCase() - }) -} - -/** - * Open the attachment action menu and click one action - * - * @param {string} actionName position of the action to be clicked - */ -function clickOnAttachmentAction(actionName) { - cy.getActionEntry('insert-attachment').click() - - return cy.get('.v-popper__wrapper .open').getActionEntry(actionName).click() -} - -/** - * Check if an attachment is visible in the document - * - * @param {number} documentId id current document - * @param {string} dirname name of the attachment directory - * @param {string} fileName attachment file name to be checked - * @param {number} fileId attachment file id - * @param {number|undefined} index index of the attachment in the document - * @param {boolean} isImage is the attachment an image or a media file? - */ -function checkAttachment(documentId, dirname, fileName, fileId, index, isImage = true) { - const encodedName = fixedEncodeURIComponent(fileName) - const src = `${dirname}/${encodedName}` - - cy.log( - 'Check the attachment is visible and well formed', - documentId, - dirname, - fileName, - fileId, - index, - encodedName, - ) - return cy - .get(`.text-editor__main [data-component="image-view"][data-src="${src}"]`) - .find('.image__view') // wait for load finish - .within(($el) => { - // keep track that we have created this attachment in the attachment dir - if (!attachmentFileNameToId[dirname]) { - attachmentFileNameToId[dirname] = {} - } - - attachmentFileNameToId[dirname][fileName] = fileId - - if (index > 0) { - expect(fileName).include(`(${index + 1})`) - } - - const srcPathEnd = isImage ? 'image' : 'mediaPreview' - const srcFileNameParam = isImage ? 'imageFileName' : 'mediaFileName' - - // ensure the image is not hidden behind the menu bar. - cy.wrap($el).scrollIntoView({ offset: { top: -50, left: 0 } }) - - cy.wrap($el) - .should('be.visible') - .find('img') - .should('have.attr', 'src') - .should( - 'contain', - 'apps/text/' + srcPathEnd + '?documentId=' + documentId, - ) - .should( - 'contain', - srcFileNameParam + '=' + fixedEncodeURIComponent(fileName), - ) - - return isImage - ? cy - .wrap($el) - .find('.image__caption input') - .should('be.visible') - .should('have.value', fileName) - : cy - .wrap($el) - .find('.metadata .name') - .should('be.visible') - .should('have.text', fileName) - }) -} - -/** - * Wait for the attachment insertion request to finish and check if the attachment is visible - * - * @param {string} requestAlias Alias of the request we are waiting for - * @param {number|undefined} index of the attachment - * @param {boolean} isImage is the attachment an image or a media file? - * @param {Function} check function used to check document for attachment - */ -function waitForRequestAndCheckAttachment( - requestAlias, - index, - isImage = true, - check = checkAttachment, -) { - return cy.wait('@' + requestAlias).then((req) => { - // the name of the created file on NC side is returned in the response - const fileId = req.response.body.id - const fileName = req.response.body.name - const documentId = req.request.query.documentId || req.request.body.documentId - const dirname = req.response.body.dirname - - return check(documentId, dirname, fileName, fileId, index, isImage) - }) -} - -describe('Test all attachment insertion methods', () => { - before(() => { - initUserAndFiles(user, 'test.md', 'empty.md') - - cy.createFolder('.hidden') - - cy.createFolder('sub') - cy.createFolder('sub/a') - cy.createFolder('sub/b') - - cy.uploadFile('github.png', 'image/png') - cy.uploadFile('github.png', 'image/png', 'sub/a/a.png') - cy.uploadFile('github.png', 'image/png', 'sub/b/b.png') - - cy.createUser(recipient) - }) - - beforeEach(() => { - cy.login(currentUser) - cy.showHiddenFiles() - }) - - it('See test files in the list and display hidden files', () => { - cy.visit('/apps/files') - cy.getFile('test.md') - cy.getFile('github.png') - cy.getFile('.hidden') - }) - - it('Insert an image file from Files', () => { - cy.visit('/apps/files') - cy.openFile('test.md') - - clickOnAttachmentAction(ACTION_INSERT_FROM_FILES) - .then(() => { - const requestAlias = 'insertPathRequest-a' - cy.intercept({ method: 'POST', url: '**/filepath' }).as(requestAlias) - - cy.log('Go to sub folder (a)') - cy.get('.file-picker [data-filename="sub"]').click() - cy.get('.file-picker [data-filename="a"]').click() - cy.get('.file-picker [data-filename="a.png"]').click() - - cy.get('.dialog__actions button.button-vue--primary').click() - - return waitForRequestAndCheckAttachment(requestAlias) - }) - .then(() => clickOnAttachmentAction(ACTION_INSERT_FROM_FILES)) - .then(() => { - const requestAlias = 'insertPathRequest-b' - cy.intercept({ method: 'POST', url: '**/filepath' }).as(requestAlias) - - cy.log('Go back from home to sub folder') - cy.get('.file-picker nav [aria-label="All files"]').click() - cy.get('.file-picker [data-filename="sub"]').click() - - cy.log('Go to sub folder (b)') - cy.get('.file-picker [data-filename="b"]').click() - cy.get('.file-picker [data-filename="b.png"]').click() - - cy.get('.dialog__actions button.button-vue--primary').click() - - return waitForRequestAndCheckAttachment(requestAlias) - }) - - .then(() => clickOnAttachmentAction(ACTION_INSERT_FROM_FILES)) - - .then(() => { - const requestAlias = 'insertPathRequest-root' - cy.intercept({ method: 'POST', url: '**/filepath' }).as(requestAlias) - - cy.log('Go back to home') - cy.get('.file-picker nav [aria-label="All files"]').click() - - cy.log('Select the file in the filepicker') - cy.get('.file-picker [data-filename="github.png"]').click() - cy.log('Click OK in the filepicker') - cy.get('.dialog__actions button.button-vue--primary').click() - - return waitForRequestAndCheckAttachment(requestAlias) - }) - cy.closeFile() - }) - - it('Upload a local image file (table.png)', () => { - cy.visit('/apps/files') - cy.openFile('test.md') - // in this case we almost could just attach the file to the input - // BUT we still need to click on the action because otherwise the command - // is not handled correctly when the upload has been done in - clickOnAttachmentAction(ACTION_UPLOAD_LOCAL_FILE).then(() => { - const requestAlias = 'uploadRequest' - cy.log('Upload the file through the input') - - attachFile('table.png', requestAlias) - - return waitForRequestAndCheckAttachment(requestAlias) - }) - cy.closeFile() - }) - - it('Upload a local media file (file.txt.gz)', () => { - cy.visit('/apps/files') - cy.openFile('test.md') - // in this case we almost could just attach the file to the input - // BUT we still need to click on the action because otherwise the command - // is not handled correctly when the upload has been done in - clickOnAttachmentAction(ACTION_UPLOAD_LOCAL_FILE).then(() => { - const requestAlias = 'uploadMediaRequest' - cy.log('Upload the file through the input') - - attachFile('file.txt.gz', requestAlias) - - return waitForRequestAndCheckAttachment(requestAlias, undefined, false) - }) - cy.closeFile() - }) - - it('Upload image files with the same name', () => { - // make sure we start from an emtpy file even on retries - const filename = randHash() + '.md' - - cy.uploadFile('empty.md', 'text/markdown', filename) - cy.visit('/apps/files') - cy.openFile(filename) - - const assertImage = (index) => { - return clickOnAttachmentAction(ACTION_UPLOAD_LOCAL_FILE).then(() => { - const requestAlias = `uploadRequest${index}` - cy.log('Upload the file through the input', { index }) - - attachFile('github.png', requestAlias) - - return waitForRequestAndCheckAttachment(requestAlias, index) - }) - } - - cy.wrap([0, 1, 2]).each((index) => { - return new Cypress.Promise((resolve, reject) => { - assertImage(index).then(resolve, reject) - }) - }) - cy.getEditor().find('[data-component="image-view"]').should('have.length', 3) - cy.closeFile() - }) - - it('Upload a local image file with RTLO character in name (RTLO is stripped)', () => { - const filename = randHash() + '.md' - cy.uploadFile('empty.md', 'text/markdown', filename).as('fileId') - cy.visit('/apps/files') - cy.openFile(filename) - - const requestAlias = 'uploadRTLORequest' - cy.intercept({ method: 'POST', url: '**/text/attachment/upload?**' }).as(requestAlias) - - clickOnAttachmentAction(ACTION_UPLOAD_LOCAL_FILE).then(() => { - cy.getEditor() - .find('input[type="file"][data-text-el="attachment-file-input"]') - .selectFile( - [ - { - contents: 'cypress/fixtures/github.png', - fileName: 'git\u202e.png', - }, - ], - { force: true }, - ) - - return cy.wait('@' + requestAlias).then((req) => { - const fileName = req.response.body.name // server echoes back name with RTLO - - // insertAttachment strips RTLO from the name before building the src URL and the - // alt text. The src URL no longer matches the on-disk filename (which still has - // the RTLO), so the image 404s. The alt text shows the clean name, exposing the - // real file extension instead of the visually spoofed one. - const strippedName = fileName.replaceAll('\u202e', '') - const encodedName = fixedEncodeURIComponent(strippedName) - cy.get('@fileId').then((fileId) => { - cy.get(`.text-editor__main [data-component="image-view"][data-src=".attachments.${fileId}/${encodedName}"]`).should('exist') - }) - }) - }) - cy.closeFile() - }) - - it('Create a new whiteboard file as an attachment', () => { - const check = (documentId, fileName) => { - cy.log( - 'Check the attachment is visible and well formed', - documentId, - fileName, - ) - return cy.get('.preview .widget-file.whiteboard').should('be.visible') - } - - cy.visit('/apps/files') - cy.openFile('test.md') - - cy.log('Create a new text file as an attachment') - const requestAlias = 'create-attachment-request' - cy.intercept({ method: 'POST', url: '**/text/attachment/create' }).as(requestAlias) - clickOnAttachmentAction(ACTION_CREATE_NEW_WHITEBOARD_FILE).then(() => { - return waitForRequestAndCheckAttachment( - requestAlias, - undefined, - false, - check, - ) - }) - }) - - it('test if attachment files are in the attachment folder', () => { - cy.visit('/apps/files') - - cy.getFile('test.md') - .should('have.attr', 'data-cy-files-list-row-fileid') - .then((fileId) => { - const attachmentsFolder = `.attachments.${fileId}` - const files = attachmentFileNameToId[attachmentsFolder] - - cy.expect(Object.keys(files)).to.have.lengthOf(5) - cy.openFolder('.attachments.' + fileId) - for (const name in files) { - cy.getFile(name) - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - .should('eq', String(files[name])) - } - }) - }) - - // retries fail to create the subFolder as it already exists. - it( - 'test if attachment folder is moved with the markdown file', - { retries: 0 }, - () => { - const fileName = 'moveSource.md' - cy.createFolder('subFolder') - cy.createMarkdown(fileName, '![git](.attachments.123/github.png)', false) - .as('createdFileId') - .then((fileId) => { - const attachmentsFolder = `.attachments.${fileId}` - cy.createFolder(attachmentsFolder) - cy.uploadFile( - 'github.png', - 'image/png', - `${attachmentsFolder}/github.png`, - ) - cy.moveFile(fileName, 'subFolder/test.md') - }) - - cy.visit('/apps/files?dir=/subFolder') - cy.getFile('test.md') - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - .then((fileId) => { - const attachmentsFolder = `.attachments.${fileId}` - const files = attachmentFileNameToId[attachmentsFolder] - cy.get('@createdFileId').should('eq', parseInt(fileId)) - cy.openFolder(attachmentsFolder) - for (const name in files) { - cy.getFile(name) - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - .should('eq', String(files[name])) - } - }) - }, - ) - - it('test if attachment folder is copied when copying a markdown file', () => { - const fileName = 'copySource.md' - cy.createMarkdown( - fileName, - '![git](.attachments.123/github.png)', - false, - ).then((fileId) => { - const attachmentsFolder = `.attachments.${fileId}` - cy.createFolder(attachmentsFolder) - cy.uploadFile( - 'github.png', - 'image/png', - `${attachmentsFolder}/github.png`, - ) - }) - - cy.copyFile(fileName, 'testCopied.md') - - cy.visit('/apps/files') - cy.getFile('testCopied.md') - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - .then((fileId) => { - const attachmentsFolder = `.attachments.${fileId}` - const files = attachmentFileNameToId[attachmentsFolder] - - cy.openFolder(attachmentsFolder) - for (const name in files) { - cy.getFile(name) - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - // these are new copied attachment files - // so they should not have the same IDs than the ones created when uploading the files - .should('not.eq', String(files[name])) - } - }) - }) - - it('test if attachment folder is deleted after having deleted a markdown file', () => { - const fileName = 'deleteSource.md' - cy.createMarkdown( - fileName, - '![git](.attachments.123/github.png)', - false, - ).then((fileId) => { - const attachmentsFolder = `.attachments.${fileId}` - cy.createFolder(attachmentsFolder) - cy.uploadFile( - 'github.png', - 'image/png', - `${attachmentsFolder}/github.png`, - ) - }) - - cy.visit('/apps/files') - cy.waitForPreview(fileName) - cy.getFile(fileName) - .should('exist') - .should('have.attr', 'data-cy-files-list-row-fileid') - .then((documentId) => { - cy.deleteFile(fileName) - cy.reloadFileList() - cy.getFile('.attachments.' + documentId).should('not.exist') - }) - }) -}) diff --git a/cypress/e2e/changeMimetype.spec.js b/cypress/e2e/changeMimetype.spec.js deleted file mode 100644 index c1ababfc332..00000000000 --- a/cypress/e2e/changeMimetype.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' - -const user = randUser() - -describe('Changing mimetype from/to markdown resets document session', function() { - before(function() { - // Init user - cy.createUser(user) - cy.login(user) - cy.uploadFile('empty.md', 'text/markdown', 'test1.md') - cy.uploadFile('empty.txt', 'text/plain', 'test2.txt') - }) - beforeEach(function() { - cy.login(user) - cy.visit('/apps/files') - }) - - it('Rename from md to txt', function() { - cy.openFile('test1.md') - cy.getContent().type('## Hello world') - cy.closeFile() - - cy.moveFile('test1.md', 'test1.txt') - cy.visit('/apps/files') - - cy.openFile('test1.txt') - cy.getContent().find('pre').should('contain', '## Hello world') - }) - - it('Rename from txt to md', function() { - cy.openFile('test2.txt') - cy.getContent().type('Hello world') - cy.closeFile() - - cy.moveFile('test2.txt', 'test2.md') - cy.visit('/apps/files') - - cy.openFile('test2.md') - cy.getContent().should('contain', 'Hello world') - }) -}) diff --git a/cypress/e2e/directediting.spec.js b/cypress/e2e/directediting.spec.js deleted file mode 100644 index f7a681de424..00000000000 --- a/cypress/e2e/directediting.spec.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { initUserAndFiles, randUser } from '../utils/index.js' - -const user = randUser() - -/** - * Enter content and close - */ -function enterContentAndClose() { - cy.intercept({ method: 'POST', url: '**/session/*/close' }).as('closeRequest') - cy.intercept({ method: 'POST', url: '**/session/*/push' }).as('push') - cy.intercept({ method: 'POST', url: '**/session/*/sync' }).as('sync') - cy.insertLine('# This is a headline') - cy.insertLine('Some text') - cy.getContent().type('{ctrl+s}') - cy.wait('@push') - cy.wait('@sync') - cy.get('button.icon-close').click() - cy.wait('@closeRequest') -} - -describe('direct editing', function() { - before(function() { - initUserAndFiles(user, 'test.md', 'empty.md', 'empty.txt') - }) - - beforeEach(function() { - // ensure user is enabled if it was disabled before - cy.clearCookies() - cy.enableUser(user, true) - }) - - it('Open an existing file, edit it', () => { - cy.login(user) - cy.createDirectEditingLink('empty.md').then((token) => { - cy.session('direct-editing', () => {}) - cy.openDirectEditingToken(token) - }) - enterContentAndClose() - cy.login(user) - cy.getFileContent('empty.md').should( - 'equal', - '# This is a headline\n\nSome text', - ) - }) - - it('Create a file, edit it', () => { - cy.login(user) - cy.createDirectEditingLinkForNewFile('newfile.md').then((token) => { - cy.session('direct-editing', () => {}) - cy.openDirectEditingToken(token) - }) - enterContentAndClose() - cy.login(user) - cy.getFileContent('newfile.md').should( - 'equal', - '# This is a headline\n\nSome text', - ) - }) - - it('Open an existing plain text file, edit it', () => { - cy.login(user) - cy.createDirectEditingLink('empty.txt').then((token) => { - cy.session('direct-editing', () => {}) - cy.openDirectEditingToken(token) - }) - enterContentAndClose() - cy.login(user) - cy.getFileContent('empty.txt').should( - 'equal', - '# This is a headline\nSome text\n', - ) - }) - - it('Cannot open as disabled user', () => { - cy.login(user) - cy.createDirectEditingLink('empty.md').as('token') - cy.clearCookies() - cy.enableUser(user, false) - cy.get('@token') - .then((token) => { cy.request({ url: token, failOnStatusCode: false }) }) - .its('status') - .should('equal', 404) - }) - - it('Cannot open as deleted user', () => { - cy.login(user) - cy.createDirectEditingLink('empty.md').as('token') - cy.clearCookies() - cy.deleteUser(user) - cy.get('@token') - .then((token) => { cy.request({ url: token, failOnStatusCode: false }) }) - .its('status') - .should('equal', 404) - }) -}) diff --git a/cypress/e2e/files.spec.js b/cypress/e2e/files.spec.js deleted file mode 100644 index 827ea5a5fa8..00000000000 --- a/cypress/e2e/files.spec.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' - -const user = randUser() - -describe('Text and server mimetypes', () => { - before(() => { - cy.createUser(user) - }) - - beforeEach(() => { - cy.login(user) - }) - - it('handle plaintext in a pre tag', () => { - cy.uploadFile('empty.md', 'text/plain', 'textfile.txt') - cy.visit('/apps/files') - cy.getFile('textfile.txt') - cy.openFile('textfile.txt') - cy.getContent().find('pre').should('exist') - }) - - it('handle asciidoc as plaintext for now', () => { - cy.uploadFile('test.adoc', 'text/asciidoc', 'hello.adoc') - cy.visit('/apps/files') - cy.getFile('hello.adoc') - cy.openFile('hello.adoc') - cy.getContent().find('pre').should('contain', 'Hello world') - }) - - it('handle markdown with richtext editor', () => { - cy.uploadFile('test.md', 'text/markdown', 'markdown.md') - cy.visit('/apps/files') - cy.getFile('markdown.md') - cy.openFile('markdown.md') - cy.getContent().find('h2').should('contain', 'Hello world') - }) -}) diff --git a/cypress/e2e/initial.spec.js b/cypress/e2e/initial.spec.js deleted file mode 100644 index 5a6d752de55..00000000000 --- a/cypress/e2e/initial.spec.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' - -const user = randUser() - -describe('Test state loading of documents', function() { - before(function() { - // Init user - cy.createUser(user) - cy.login(user) - cy.uploadFile('test.md', 'text/markdown') - cy.uploadFile('test.md', 'text/markdown', 'test2.md') - cy.uploadFile('test.md', 'text/markdown', 'test3.md') - }) - beforeEach(function() { - cy.login(user) - }) - - it('Initial content can not be undone', function() { - cy.shareFile('/test.md', { edit: true }).then((token) => cy.visit(`/s/${token}`)) - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Hello world') - - cy.getMenu().should('be.visible') - cy.getActionEntry('undo').should('be.disabled') - - cy.getContent().type('New content') - cy.getActionEntry('undo').should('not.be.disabled') - }) - - it('Consecutive sessions work properly', function() { - cy.interceptCreate() - cy.shareFile('/test2.md').as('readToken') - cy.logout() - cy.get('@readToken').then((token) => cy.visit(`/s/${token}`)) - cy.wait('@create') - // Open read only for the first time - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Hello world') - cy.get('@readToken').then(cy.closeInterceptedSession) - - // Open read only for the second time - cy.reload() - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Hello world') - cy.get('@readToken').then(cy.closeInterceptedSession) - - cy.login(user) - cy.shareFile('/test2.md', { edit: true }).as('writeToken') - // Open write link and edit something - cy.get('@writeToken').then((token) => cy.visit(`/s/${token}`)) - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Hello world') - cy.getContent().type('Something new {end}') - cy.intercept({ method: 'POST', url: '**/session/*/sync' }).as('sync') - cy.wait('@sync') - cy.get('@writeToken').then(cy.closeInterceptedSession) - - // Reopen read only link and check if changes are there - cy.get('@readToken').then((token) => cy.visit(`/s/${token}`)) - cy.getEditor().should('be.visible') - cy.getContent().find('h2').should('contain', 'Something new Hello world') - }) - - it('Load after state has been saved', function() { - cy.interceptCreate() - cy.shareFile('/test3.md', { edit: true }).as('writeToken') - cy.logout() - cy.get('@writeToken').then((token) => cy.visit(`/s/${token}`)) - - // Open a file, write and save - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Hello world') - cy.getContent().type('Something new {end}') - cy.intercept({ method: 'POST', url: '**/session/*/save' }).as('save') - cy.get('.save-status button').click() - cy.wait('@save', { timeout: 10000 }) - cy.get('@writeToken').then(cy.closeInterceptedSession) - - // Open writable file again and assert the content - cy.reload() - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Something new Hello world') - - cy.login(user) - cy.shareFile('/test3.md').as('readToken') - cy.logout() - cy.get('@readToken').then((token) => cy.visit(`/s/${token}`)) - - // Open read only file again and assert the content - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Something new Hello world') - }) -}) diff --git a/cypress/e2e/inputRules.spec.js b/cypress/e2e/inputRules.spec.js deleted file mode 100644 index d44efb727ba..00000000000 --- a/cypress/e2e/inputRules.spec.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' -const user = randUser() - -function testPlainInputRule(chars, expected) { - cy.getContent().type(chars) - cy.getContent().should('contain', expected) - return cy.closeFile() -} - -describe('input rules', () => { - before(() => { - cy.createUser(user) - }) - - beforeEach(() => { - cy.login(user) - cy.uploadTestFile() - cy.visit('/apps/files') - cy.openTestFile() - }) - - it('unordered list item', () => { - cy.getContent().type('* item') - cy.getContent().find('ul').find('li').should('contain', 'item') - cy.closeFile() - }) - - it('ordered list item', () => { - cy.getContent().type('1. item') - cy.getContent().find('ol').find('li').should('contain', 'item') - cy.closeFile() - }) - - it('task list item', () => { - cy.getContent().type('* [] item') - cy.getContent().find('ul').find('li').find('input[type="checkbox"]') - cy.getContent().find('ul').find('li').should('contain', 'item') - cy.closeFile() - }) - - it('blockquote', () => { - cy.getContent().type('> quote') - cy.getContent().find('blockquote').should('contain', 'uote') - cy.closeFile() - }) - - it('codeblock', () => { - cy.getContent().type('```{enter}code') - cy.getContent().find('pre').should('contain', 'code') - cy.closeFile() - }) - - it('inline code', () => { - cy.getContent().type('`code`') - cy.getContent().find('code').should('contain', 'code') - cy.closeFile() - }) - - it('link', () => { - cy.getContent().type('[link](https://example.org/)') - cy.getContent().find('a').should('contain', 'link') - cy.closeFile() - }) - - it('emoji', () => testPlainInputRule(':+1{enter}', '👍')) - - it('typography: ellipsis', () => testPlainInputRule('...', '…')) - it('typography: copyright', () => testPlainInputRule('(c)', '©')) - it('typography: oneHalf', () => testPlainInputRule('1/2 ', '½')) - it('typography: superscriptTwo', () => testPlainInputRule('^2', '²')) - it('typography: emDash', () => testPlainInputRule('-- ', '—')) - it('typography: leftArrow', () => testPlainInputRule('<- ', '←')) - it('typography: rightArrow', () => testPlainInputRule('->', '→')) - it('typography: leftRightArrow', () => testPlainInputRule('<->', '↔')) - it('typography: leftRightDoubleArrow', () => testPlainInputRule('<=>', '⇔')) - it('typography: leftRightLongArrow', () => testPlainInputRule('<-->', '⟷')) -}) diff --git a/cypress/e2e/marks/Link.spec.js b/cypress/e2e/marks/Link.spec.js deleted file mode 100644 index 242584868ac..00000000000 --- a/cypress/e2e/marks/Link.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { expectMarkdown, loadMarkdown } from '../nodes/helpers.js' -import Markdown from './../../../src/extensions/Markdown.js' -import { Italic, Link } from './../../../src/marks/index.js' -import { createCustomEditor } from './../../support/components.js' - -describe('Link marks', { retries: 0 }, () => { - const editor = createCustomEditor({ - content: '', - extensions: [Markdown, Link, Italic], - }) - - describe('insertOrSetLink command', { retries: 0 }, () => { - it('is available in commands', () => { - expect(editor.commands).to.have.property('insertOrSetLink') - }) - - it('can run on normal paragraph', () => { - prepareEditor('hello\n', 3) - expect(editor.can().insertOrSetLink()).to.equal(true) - }) - - it('will insert a link in a normal paragraph', () => { - prepareEditor('hello\n', 3) - editor.commands.insertOrSetLink('https://example.org', { - href: 'https://example.org', - }) - expectMarkdown(editor, 'he\n\n\n\nllo') - }) - }) - - /** - * - * @param {*} input markdown content - * @param {*} position cursor pos - */ - function prepareEditor(input, position = 1) { - loadMarkdown(editor, input) - editor.commands.setTextSelection(position) - } -}) diff --git a/cypress/e2e/nodes/CodeBlock.spec.js b/cypress/e2e/nodes/CodeBlock.spec.js deleted file mode 100644 index dabbf097d3c..00000000000 --- a/cypress/e2e/nodes/CodeBlock.spec.js +++ /dev/null @@ -1,244 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { initUserAndFiles, randUser } from '../../utils/index.js' - -const user = randUser() - -describe('Front matter support', function() { - before(function() { - initUserAndFiles(user, 'codeblock.md', 'empty.md') - }) - - beforeEach(function() { - cy.login(user) - }) - - it('See existing code block', function() { - cy.isolateTest({ sourceFile: 'codeblock.md' }) - cy.openFile('codeblock.md').then(() => { - // Plain text block - cy.getContent() - .find('code') - .eq(0) - .find('.hljs-keyword') - .should('not.exist') - - // Javascript block - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-keyword') - .eq(0) - .contains('const') - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-string') - .eq(0) - .contains('"bar"') - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-keyword') - .eq(1) - .contains('function') - - cy.getContent() - .find('.code-block') - .eq(1) - .find('[data-cy="copy-code"]') - .click() - - // Copy code to clipboard - cy.getContent() - .find('code') - .eq(1) - .invoke('text') - .then((code) => cy.window().then((win) => { - win.navigator.clipboard.readText().then((copiedCode) => { - expect(copiedCode).to.equal(code) - }) - })) - - // Remove language - cy.getContent() - .find('.code-block') - .eq(1) - .find('[data-cy="code-action-group"]') - .find('div:first-child') - .click() - // FIXME: Label behaviour changed, should be back once https://github.com/nextcloud-libraries/nextcloud-vue/pull/4484 is merged - // cy.get('.action-input__text-label').contains('Code block language') - cy.get('.input-field__input:visible').clear() - - cy.getContent().find('code').eq(1).click() - - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-keyword') - .should('not.exist') - - // Re-add language - cy.getContent() - .find('.code-block') - .eq(1) - .find('[data-cy="code-action-group"]') - .find('div:first-child') - .click() - cy.get('.input-field__input:visible').type('javascript') - - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-keyword') - .eq(0) - .contains('const') - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-string') - .eq(0) - .contains('"bar"') - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-keyword') - .eq(1) - .contains('function') - }) - }) - - it('Show a code block in a public read only link', function() { - cy.shareFile('/codeblock.md') - .then((token) => { - cy.logout() - cy.visit(`/s/${token}`) - }) - .then(() => { - cy.getEditor().should('be.visible') - // Plain text block - cy.getContent() - .find('code') - .eq(0) - .find('.hljs-keyword') - .should('not.exist') - - // Javascript block - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-keyword') - .eq(0) - .contains('const') - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-string') - .eq(0) - .contains('"bar"') - cy.getContent() - .find('code') - .eq(1) - .find('.hljs-keyword') - .eq(1) - .contains('function') - - // Mermaid diagram - cy.get('#viewer .modal-container__content').scrollTo('bottom') - cy.getContent() - .find('.split-view__preview') - .eq(2) - .should('be.visible') - cy.get('.code-block').eq(2).find('code').should('not.be.visible') - cy.get('.split-view__preview') - .find('svg .erDiagramTitleText') - .contains('Order example') - }) - }) - - it('Add a code block', function() { - cy.isolateTest({ sourceFile: 'codeblock.md' }) - cy.openFile('codeblock.md').then(() => { - cy.clearContent() - cy.insertLine('```javascript') - cy.getContent().type('const foo = "bar"{enter}{enter}{enter}') - cy.getContent().find('.hljs-keyword').first().contains('const') - }) - }) - - it('See a mermaid diagram', function() { - cy.isolateTest({ sourceFile: 'codeblock.md' }) - cy.openFile('codeblock.md').then(() => { - cy.getContent() - .find('.split-view__preview') - .find('svg') - .should('be.visible') - cy.get('.code-block').eq(2).find('code').should('not.be.visible') - cy.get('.split-view__preview') - .find('svg .erDiagramTitleText') - .contains('Order example') - }) - }) - - it('Add an invalid mermaid block', function() { - cy.isolateTest() - cy.openFile('empty.md').then(() => { - cy.insertLine('```mermaid') - cy.getContent().find('code').should('exist') - cy.getContent().get('.split-view__preview').should('be.visible') - - cy.wait(250) - cy.getContent().type('invalid{enter}{enter}') - - cy.get('.split-view__code').find('code').should('be.visible') - cy.get('.split-view__preview').find('svg').should('not.exist') - }) - }) - - it('Add a valid mermaid block', function() { - cy.isolateTest() - cy.openFile('empty.md').then(() => { - cy.insertLine('```mermaid') - cy.getContent().find('code').should('exist') - cy.getContent().get('.split-view__preview').should('be.visible') - - cy.wait(250) - // Tab key does not work in cypress, using spaces instead - cy.getContent().type('flowchart LR{enter} entry{enter}') - - cy.get('.split-view__code').find('code').should('be.visible') - cy.get('.split-view__preview').find('svg').should('be.visible') - - cy.getContent() - .find('.code-block') - .eq(0) - .find('.view-switch div div button') - .click() - cy.get('.action-button').eq(0).contains('Source code').click() - cy.get('.split-view__code').find('code').should('be.visible') - cy.get('.split-view__preview').find('svg').should('not.be.visible') - - cy.getContent() - .find('.code-block') - .eq(0) - .find('.view-switch div div button') - .click() - cy.get('.action-button').eq(1).contains('Diagram').click() - cy.get('.split-view__code').find('code').should('not.be.visible') - cy.get('.split-view__preview').find('svg').should('be.visible') - - cy.getContent() - .find('.code-block') - .eq(0) - .find('.view-switch div div button') - .click() - cy.get('.action-button').eq(2).contains('Both').click() - cy.get('.split-view__code').find('code').should('be.visible') - cy.get('.split-view__preview').find('svg').should('be.visible') - }) - }) -}) diff --git a/cypress/e2e/nodes/FrontMatter.spec.js b/cypress/e2e/nodes/FrontMatter.spec.js deleted file mode 100644 index 50a02208cc6..00000000000 --- a/cypress/e2e/nodes/FrontMatter.spec.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { initUserAndFiles, randUser } from '../../utils/index.js' - -const user = randUser() - -describe('Front matter support', function() { - before(function() { - initUserAndFiles(user, 'frontmatter.md', 'empty.md') - }) - - beforeEach(function() { - cy.login(user) - cy.visit('/apps/files') - }) - - it('Open file with front matter', function() { - cy.openFile('frontmatter.md').then(() => { - cy.getContent() - .find('pre.frontmatter') - .should((pre) => { - expect(pre.length === 1) - expect(pre[0].text === 'some: value\nother: 1.2') - }) - }) - }) - - it('Add front matter', function() { - cy.openFile('empty.md').clearContent() - cy.getContent().type('---') - cy.getContent().type('test') - cy.getContent() - .find('pre.frontmatter') - .should((pre) => { - expect(pre.length === 1) - expect(pre[0].text === 'test') - }) - }) - - it('Do not add multiple front matter', function() { - cy.openFile('empty.md').clearContent() - cy.getContent().type('---test') - cy.getContent().type('{downArrow}') - cy.getContent().type('---test') - cy.getContent() - .find('pre.frontmatter') - .should((pre) => expect(pre.length === 1)) - cy.getContent() - .find('hr') - .should((hr) => expect(hr.length === 1)) - }) - - it('Reopen front matter', function() { - cy.openFile('frontmatter.md') - cy.getContent().type('{moveToEnd}New line{enter}') - cy.getContent() - .find('pre.frontmatter') - .should((pre) => { - expect(pre.length === 1) - }) - cy.closeFile() - cy.intercept({ method: 'POST', url: '**/apps/text/session/*/sync' }).as('sync') - cy.openFile('frontmatter.md') - cy.wait('@sync') - cy.wait('@sync') - cy.getContent().find('pre.frontmatter').should('have.length', 1) - }) -}) diff --git a/cypress/e2e/nodes/HardBreak.spec.js b/cypress/e2e/nodes/HardBreak.spec.js deleted file mode 100644 index bea49ac85a2..00000000000 --- a/cypress/e2e/nodes/HardBreak.spec.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../../utils/index.js' - -const fileName = 'empty.md' -const user = randUser() - -describe('Hard break support', function() { - before(function() { - cy.createUser(user) - }) - - beforeEach(function() { - cy.login(user) - cy.isolateTest({ - sourceFile: fileName, - }) - - return cy.openFile(fileName, { force: true }) - }) - - it('Can create hard breaks with shift+enter', () => { - cy.getContent().type('Hello') - cy.getContent().type('{shift+enter}world') - cy.getContent().find('p br').should('exist') - }) - - it('Convert paragraph break into hard break', () => { - cy.getContent().type('Hello') - cy.getContent().type('{enter}') - cy.getContent().type('world') - cy.getContent() - .find('p') - .should('have.length', 2) - .and(($p) => { - expect($p[0]).to.contain('Hello') - expect($p[1]).to.contain('world') - }) - cy.getContent().type('{home}{backspace}') - cy.getContent().find('p br').should('exist') - }) - - it('Do not create hard breaks within headings', () => { - cy.getContent().type('# Hello') - cy.getContent().type('{shift+enter}world') - cy.getContent().find('h1 br').should('not.exist') - }) -}) diff --git a/cypress/e2e/nodes/ImageView.spec.js b/cypress/e2e/nodes/ImageView.spec.js deleted file mode 100644 index 98abff18137..00000000000 --- a/cypress/e2e/nodes/ImageView.spec.js +++ /dev/null @@ -1,168 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../../utils/index.js' - -const user = randUser() - -describe('Image View', () => { - before(() => { - cy.createUser(user) - cy.login(user) - - // Upload test files to user's storage - cy.createFolder('child-folder') - cy.uploadFile('github.png', 'image/png') - cy.uploadFile('github.png', 'image/png', 'child-folder/github.png') - }) - - beforeEach(() => { - cy.login(user) - cy.visit('/apps/files') - }) - - describe('direct access', () => { - it('from root', () => { - const fileName = `${Cypress.currentTest.title}.md` - - cy.createMarkdown(fileName, '# from root\n\n ![git](/github.png)') - - cy.openFile(fileName) - - cy.getContent() - .find('[data-component="image-view"]') - .should('have.attr', 'data-src') - .should('eq', '/github.png') - - cy.getContent() - .find('[data-component="image-view"] img') - .should('have.attr', 'src') - .should('contains', `/dav/files/${user.userId}/github.png`) - }) - - it('from child folder', () => { - const fileName = `${Cypress.currentTest.title}.md` - - cy.createMarkdown( - fileName, - '# from child\n\n ![git](child-folder/github.png)', - ) - - cy.openFile(fileName) - - cy.getContent() - .find('[data-component="image-view"]') - .should('have.attr', 'data-src') - .should('eq', 'child-folder/github.png') - - cy.getContent() - .find('[data-component="image-view"] img') - .should('have.attr', 'src') - .should( - 'contains', - `/dav/files/${user.userId}/child-folder/github.png`, - ) - }) - - it('from parent folder', () => { - cy.visit('apps/files?dir=/child-folder') - - const fileName = `${Cypress.currentTest.title}.md` - - cy.createMarkdown( - `/child-folder/${fileName}`, - '# from parent\n\n ![git](../github.png)', - ) - - cy.openFile(fileName, { force: true }) - - cy.getContent() - .find('[data-component="image-view"]') - .should('have.attr', 'data-src') - .should('eq', '../github.png') - - cy.getContent() - .find('[data-component="image-view"] img') - .should('have.attr', 'src') - .should('contain', `/dav/files/${user.userId}/github.png`) - }) - }) - - describe('fail to load', () => { - it('direct access', () => { - const fileName = `${Cypress.currentTest.title}.md` - - cy.createMarkdown(fileName, '# from root\n\n ![yaha](/yaha.png)') - - cy.openFile(fileName) - - cy.getContent() - .find('[data-component="image-view"]') - .should('have.class', 'image-view--failed') - - cy.getContent() - .find('[data-component="image-view"] svg') - .should('be.visible') - - cy.getContent() - .find('[data-component="image-view"] .image__error-message') - .should('be.visible') - - cy.getContent() - .find('[data-component="image-view"] .image__caption input') - .should('have.value', 'yaha') - }) - }) - - describe('native attachments', () => { - before(() => { - cy.login(user) - const fileName = 'native attachments.md' - cy.createFile( - fileName, - '# open image in modal\n\n ![git](.attachments.123/github.png)\n\n ![file.txt.gz](.attachments.123/file.txt.gz)', - ).then((fileId) => { - const attachmentsFolder = `.attachments.${fileId}` - cy.createFolder(attachmentsFolder) - cy.uploadFile( - 'github.png', - 'image/png', - `${attachmentsFolder}/github.png`, - ) - cy.uploadFile( - 'file.txt.gz', - 'application/gzip', - `${attachmentsFolder}/file.txt.gz`, - ) - }) - }) - - it('open image in modal', () => { - const fileName = 'native attachments.md' - cy.openFile(fileName) - - cy.getContent() - .find('[data-component="image-view"][data-src=".attachments.123/github.png"] img') - .click() - - cy.get('.modal__content img') - .should('have.attr', 'src') - .should('contain', 'imageFileName=github.png') - }) - - it('download non-image gzip attachment', () => { - const fileName = 'native attachments.md' - cy.openFile(fileName) - - cy.getContent() - .find('[data-component="image-view"][data-src=".attachments.123/file.txt.gz"] img') - .click() - - const downloadsFolder = Cypress.config('downloadsFolder') - cy.log(`downloadsFolder: ${downloadsFolder}`) - cy.readFile(`${downloadsFolder}/file.txt.gz`) - }) - }) -}) diff --git a/cypress/e2e/nodes/ListItem.spec.js b/cypress/e2e/nodes/ListItem.spec.js deleted file mode 100644 index 128c788eec7..00000000000 --- a/cypress/e2e/nodes/ListItem.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { ListItem, OrderedList } from '@tiptap/extension-list' -// https://github.com/import-js/eslint-plugin-import/issues/1739 -import testData from '../../fixtures/ListItem.md?raw' -import Markdown from './../../../src/extensions/Markdown.js' -import BulletList from './../../../src/nodes/BulletList.ts' -import TaskItem from './../../../src/nodes/TaskItem.ts' -import TaskList from './../../../src/nodes/TaskList.ts' -import { createCustomEditor } from './../../support/components.js' -import { expectMarkdown, loadMarkdown, runCommands } from './helpers.js' - -describe('ListItem extension integrated in the editor', () => { - const editor = createCustomEditor({ - content: '', - extensions: [ - Markdown, - BulletList, - OrderedList, - ListItem, - TaskList, - TaskItem, - ], - }) - - for (const spec of testData.split(/#+\s+/)) { - const [description, ...rest] = spec.split(/\n/) - const [input, output] = rest.join('\n').split(/\n\n---\n\n/) - if (!description) { - continue - } - it(description, () => { - expect(spec).to.include('\n') - - expect(input).to.be.ok - expect(output).to.be.ok - - loadMarkdown(editor, input) - runCommands(editor) - expectMarkdown(editor, output.replace(/\n*$/, '')) - }) - } -}) diff --git a/cypress/e2e/nodes/Mentions.spec.js b/cypress/e2e/nodes/Mentions.spec.js deleted file mode 100644 index da8931c18ca..00000000000 --- a/cypress/e2e/nodes/Mentions.spec.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { initUserAndFiles, randUser } from '../../utils/index.js' - -const user = randUser() -const mentionMe = randUser() -const mention = mentionMe.userId -const currentUser = user - -const fileName = 'empty.md' - -function createFileWithMention(target, userToMention) { - const content = `Hello @[${userToMention}](mention://user/${userToMention})` - cy.createFile(target, content).then(() => cy.reloadFileList()) -} - -describe('Test mentioning users', () => { - before(() => { - initUserAndFiles(user, 'test.md') - cy.createUser(mentionMe) - }) - - beforeEach(() => { - cy.login(currentUser) - cy.visit('/apps/files') - }) - - it('Type @ and see the user list', () => { - const requestAlias = 'fetchUsersList' - cy.intercept({ method: 'POST', url: '**/users' }).as(requestAlias) - - cy.isolateTest({ - sourceFile: fileName, - onBeforeLoad(win) { - cy.stub(win, 'open').as('winOpen') - }, - }) - - cy.openFile(fileName, { force: true }) - - cy.getContent().type(`@${mention.substring(0, 3)}`) - - return cy.wait(`@${requestAlias}`).then(() => { - cy.get('.tippy-box .suggestion-list') - .children() - .should(($children) => { - const users = $children.map((i, el) => el.innerText).get() - expect(users.length).to.be.greaterThan(0) - expect(mention).to.be.oneOf(users) - }) - }) - }) - - it('Select a user will insert the mention', () => { - const autocompleteReauestAlias = 'fetchUsersList' - cy.intercept({ method: 'POST', url: '**/users' }).as(autocompleteReauestAlias) - - cy.isolateTest({ - sourceFile: fileName, - onBeforeLoad(win) { - cy.stub(win, 'open').as('winOpen') - }, - }) - - cy.openFile(fileName, { force: true }) - - cy.get('.text-editor__content div[contenteditable="true"]').clear() - cy.get('.text-editor__content div[contenteditable="true"]').type(`@${mention.substring(0, 3)}`) - - return cy.wait(`@${autocompleteReauestAlias}`).then(() => { - cy.intercept({ method: 'PUT', url: '**/mention' }).as('putMention') - cy.get('.tippy-box .suggestion-list').contains(mention).click() - cy.get('span.mention').contains(mention).should('be.visible') - cy.wait('@putMention').its('response.statusCode').should('eq', 200) - }) - }) - - it('Open a document with an existing mention and properly see the user bubble rendered', () => { - const mentionFilename = 'mention.md' - createFileWithMention(mentionFilename, mention) - cy.openFile(mentionFilename, { force: true }) - cy.get('.text-editor__content div[contenteditable="true"] span.mention') - .contains(mention) - .should('be.visible') - }) -}) diff --git a/cypress/e2e/nodes/Preview.spec.js b/cypress/e2e/nodes/Preview.spec.js deleted file mode 100644 index f5e2c04402d..00000000000 --- a/cypress/e2e/nodes/Preview.spec.js +++ /dev/null @@ -1,187 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -// https://github.com/import-js/eslint-plugin-import/issues/1739 -import testData from '../../fixtures/Preview.md?raw' -import Markdown from './../../../src/extensions/Markdown.js' -import { Italic, Link } from './../../../src/marks/index.js' -import Preview from './../../../src/nodes/Preview.js' -import { createCustomEditor } from './../../support/components.js' -import { expectMarkdown, loadMarkdown, runCommands } from './helpers.js' - -describe('Preview extension', { retries: 0 }, () => { - const editor = createCustomEditor({ - content: '', - extensions: [Markdown, Preview, Link, Italic], - }) - - describe('setPreview command', { retries: 0 }, () => { - it('is available in commands', () => { - expect(editor.commands).to.have.property('setPreview') - }) - - it('cannot run on normal paragraph', () => { - prepareEditor('hello\n') - expect(editor.can().setPreview()).to.be.false - }) - - it('cannot run on a paragraph with a different mark', () => { - prepareEditor('*link text*\n') - expect(editor.can().setPreview()).to.be.false - }) - - it('cannot run on a paragraph with a link without a href', () => { - prepareEditor('[link text]()\n') - expect(editor.can().setPreview()).to.be.false - }) - - it('cannot run on a paragraph with an anchor link', () => { - prepareEditor('[link text](#top)\n') - expect(editor.can().setPreview()).to.be.false - }) - - it('convert a paragraph with a link', () => { - prepareEditor('[link text](https://example.org)\n') - editor.commands.setPreview() - expectPreview() - }) - - it('convert the second a paragraph with a link', () => { - prepareEditor('hello\n\n[link text](https://example.org)\n') - editor.commands.setTextSelection(10) - editor.commands.setPreview() - expectPreview() - }) - - it('convert a paragraph with a link and a space', () => { - prepareEditor('[link text](https://example.org)\n') - editor.commands.insertContentAt(editor.state.doc.content.size - 1, ' ', { - updateSelection: false, - }) - editor.commands.setPreview() - expectPreview() - }) - - it('results in a preview node with the href and text with link mark', () => { - prepareEditor('[link text](https://example.org)\n') - editor.commands.setPreview() - expectPreview() - }) - - it('cannot run twice', () => { - prepareEditor('[link text](https://example.org)\n') - editor.commands.setPreview() - expect(editor.can().setPreview()).to.be.false - }) - }) - - describe('unsetPreview command', { retries: 0 }, () => { - it('is available in commands', () => { - expect(editor.commands).to.have.property('unsetPreview') - }) - - it('cannot run on normal paragraph', () => { - prepareEditor('hello\n') - expect(editor.can().unsetPreview()).to.be.false - }) - - it('can run on the output of setPreview', () => { - prepareEditor('[link text](https://example.org)\n') - editor.commands.setPreview() - expect(editor.can().unsetPreview()).to.be.true - }) - - it('creates a paragraph', () => { - prepareEditor('[link text](https://example.org)\n') - editor.commands.setPreview() - editor.commands.unsetPreview() - expect(getParentNode().type.name).to.equal('paragraph') - }) - - it('includes a link', () => { - prepareEditor('[link text](https://example.org)\n') - editor.commands.setPreview() - editor.commands.unsetPreview() - expect(getMark().attrs.href).to.equal('https://example.org') - }) - }) - - describe('insertPreview command', { retries: 0 }, () => { - it('is available in commands', () => { - expect(editor.commands).to.have.property('insertPreview') - }) - - it('inserts a preview', () => { - editor.commands.clearContent() - editor.commands.insertPreview('https://example.org') - editor.commands.setTextSelection(1) - expectPreview() - }) - }) - - /** - * Expect a preview in the editor. - */ - function expectPreview() { - expect(getParentNode().type.name).to.equal('preview') - expect(getParentNode().attrs.href).to.equal('https://example.org') - expect(getMark().attrs.href).to.equal('https://example.org') - } - - /** - * - */ - function getParentNode() { - const { - state: { selection }, - } = editor - return selection.$head.parent - } - - /** - * - */ - function getMark() { - const { - state: { selection }, - } = editor - console.info(selection.$head) - return selection.$head.nodeAfter.marks[0] - } - - /** - * - * @param {string} input - markdown content - */ - function prepareEditor(input) { - loadMarkdown(editor, input) - editor.commands.setTextSelection(1) - } -}) - -describe('Markdown tests for Previews in the editor', { retries: 0 }, () => { - const editor = createCustomEditor({ - content: '', - extensions: [Markdown, Preview, Link], - }) - - for (const spec of testData.split(/#+\s+/)) { - const [description, ...rest] = spec.split(/\n/) - const [input, output] = rest.join('\n').split(/\n\n---\n\n/) - if (!description) { - continue - } - it(description, () => { - expect(spec).to.include('\n') - - expect(input).to.be.ok - expect(output).to.be.ok - - loadMarkdown(editor, input) - runCommands(editor) - expectMarkdown(editor, output.replace(/\n*$/, '')) - }) - } -}) diff --git a/cypress/e2e/nodes/PreviewOptions.spec.js b/cypress/e2e/nodes/PreviewOptions.spec.js deleted file mode 100644 index 19b4ec81973..00000000000 --- a/cypress/e2e/nodes/PreviewOptions.spec.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { initUserAndFiles, randUser } from '../../utils/index.js' - -const user = randUser() - -describe('Preview Options', function() { - before(function() { - initUserAndFiles(user, 'codeblock.md', 'empty.md') - }) - beforeEach(function() { - cy.login(user) - cy.isolateTest({ sourceFile: 'empty.md' }) - cy.openFile('empty.md') - cy.get('.entry-action__insert-link').click() - cy.get('li').get('[data-text-action-entry="insert-link-website"]').click() - cy.get('[data-text-action-entry="insert-link-input"]') - .click() - .type('example.org{enter}') - - cy.getContent().find('a[href*="https://example.org"]').click() - cy.get('.link-options').click() - }) - - it('should render previewOptions correctly', function() { - cy.get('.action-button__text') - .contains('Open in new tab') - .should('be.visible') - cy.get('.action-button__text').contains('Remove link').should('be.visible') - cy.get('.action-radio__text').each((el) => { - cy.wrap(el) - .invoke('text') - .should('match', /Text only|Show link preview/) - }) - }) - - it('should toggle to Link Preview', function() { - cy.get('.preview').should('not.exist') - cy.get('.action-radio__text').each((el) => { - cy.wrap(el) - .invoke('text') - .then((text) => { - if (text === 'Show link preview') { - cy.wrap(el).click() - } - }) - }) - cy.get('.preview').should('be.visible') - }) - - it('should Remove link', function() { - cy.get('p > a').should('have.text', 'example.org') - cy.get('.action-button__text').contains('Remove link').click() - cy.get('p > a').should('not.exist') - }) -}) diff --git a/cypress/e2e/nodes/Table.spec.js b/cypress/e2e/nodes/Table.spec.js deleted file mode 100644 index c2cf7b33892..00000000000 --- a/cypress/e2e/nodes/Table.spec.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -// https://github.com/import-js/eslint-plugin-import/issues/1739 -import testData from '../../fixtures/Table.md?raw' -import { initUserAndFiles, randUser } from '../../utils/index.js' -import Markdown from './../../../src/extensions/Markdown.js' -import markdownit from './../../../src/markdownit/index.js' -import EditableTable from './../../../src/nodes/EditableTable.js' -import { createCustomEditor } from './../../support/components.js' -import { expectMarkdown, runCommands } from './helpers.js' - -const user = randUser() -const fileName = 'empty.md' - -describe('table plugin', () => { - before(() => { - initUserAndFiles(user) - }) - - beforeEach(() => { - cy.login(user) - - cy.isolateTest({ - sourceFile: fileName, - }) - - return cy.openFile(fileName, { force: true }) - }) - - it('inserts and removes a table', () => { - cy.getContent().type("Let's insert a Table") - - cy.getActionEntry('table').click() - - cy.getContent().type('content') - - cy.getContent() - .find('table tr:first-child th:first-child') - .should('contain', 'content') - - cy.getContent() - .find('[data-text-table-actions="settings"] [aria-label="Actions"]') - .click() - - cy.get('[data-text-table-action="delete"] [role="menuitem"]').click() - cy.getContent().should('not.contain', 'content') - }) - - it('Table column alignment', () => { - cy.getActionEntry('table').click() - - cy.getContent().type('center') - ;['center', 'left', 'right'].forEach((align) => { - cy.getContent() - .find('table tr:first-child th:first-child button') - .click() - cy.get(`[data-text-table-action="align-column-${align}"] [role="menuitemradio"]`).click() - // Check header has correct text align and text is preserved - cy.getContent() - .find('table tr:first-child th:first-child') - .should('contain', 'center') - .should('have.css', 'text-align', align) - // Check every normal cell in this column to have the text align set - cy.getContent() - .find('table tr td:first-child') - .should('have.length', 2) - .each((td) => cy.wrap(td).should('have.css', 'text-align', align)) - }) - }) - - it('Keep alignment on new row', () => { - cy.getActionEntry('table').click() - - cy.getContent().find('table tr:first-child th:first-child button').click() - cy.get('[data-text-table-action="align-column-center"] [role="menuitemradio"]').click() - - // Test before adding a row - cy.getContent() - .find('table tr td:first-child') - .should('have.length', 2) - .each((td) => cy.wrap(td).should('have.css', 'text-align', 'center')) - - // Fill first cell in existing rows - cy.getContent().type('1{downArrow}2{downArrow}3{downArrow}') - - // Add row and fill first cell - cy.getContent().find('.table-wrapper .table-add-row').click() - cy.getContent().find('table tr:last-child td:first-child').type('4') - - // Test after the row was added - cy.getContent() - .find('table tr td:first-child') - .should('have.length', 3) - .each((td) => cy.wrap(td).should('have.css', 'text-align', 'center')) - }) - - it('Creates table and add multilines', function() { - cy.getActionEntry('table').click() - cy.getContent() - .find('table:nth-of-type(1) tr:nth-child(2) td:nth-child(1)') - .click() - - cy.getContent().type('Line 1\nLine 2\nLine 3') - - cy.getContent() - .find('table:nth-of-type(1) tr:nth-child(2) td:nth-child(1)') - .then(($el) => { - expect($el.get(0).innerHTML).to.equal('

Line 1

Line 2

Line 3

') - }) - }) - - it('Button add row below', () => { - cy.getActionEntry('table').click() - - cy.getContent().find('table tr').should('have.length', 3) - - cy.getContent().find('.table-wrapper .table-add-row').should('be.visible') - - cy.getContent().find('.table-wrapper .table-add-row').click() - - // Added a row - cy.getContent().find('table tr').should('have.length', 4) - }) - - it('Button add column after', () => { - cy.getActionEntry('table').click() - - cy.getContent().find('table tr').should('have.length', 3) - - cy.getContent().find('.table-wrapper .table-add-column').should('be.visible') - - cy.getContent().find('.table-wrapper .table-add-column').click() - - // Added a column - cy.getContent().find('table tr th').should('have.length', 4) - }) -}) - -describe('Table extension integrated in the editor', () => { - const editor = createCustomEditor({ - content: '', - extensions: [Markdown, EditableTable], - }) - - for (const spec of testData.split(/#+\s+/)) { - const [description, ...rest] = spec.split(/\n/) - const [input, output] = rest.join('\n').split(/\n\n---\n\n/) - if (!description) { - continue - } - it(description, () => { - expect(spec).to.include('\n') - - expect(input).to.be.ok - expect(output).to.be.ok - - editor.commands.setContent(markdownit.render(input)) - runCommands(editor) - expectMarkdown(editor, output.replace(/\n*$/, '')) - }) - } -}) diff --git a/cypress/e2e/nodes/helpers.js b/cypress/e2e/nodes/helpers.js deleted file mode 100644 index 8cbc514045a..00000000000 --- a/cypress/e2e/nodes/helpers.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { createMarkdownSerializer } from './../../../src/extensions/Markdown.js' -import { findChildren } from './../../../src/helpers/prosemirrorUtils.js' -import markdownit from './../../../src/markdownit/index.js' - -/** - * - * @param {object} editor - the editor object - * @param {string} markdown - markdown content - */ -export function loadMarkdown(editor, markdown) { - const stripped = markdown.replace(/\t*/g, '') - editor.commands.setContent(markdownit.render(stripped)) -} - -/** - * - * @param {object} editor - the editor object - */ -export function runCommands(editor) { - let found - while ((found = findCommand(editor))) { - const { node, pos } = found - const name = node.text - editor.commands.setTextSelection(pos) - editor.commands[name]() - const updated = findCommand(editor) - if (updated) { - editor.commands.setTextSelection(updated.pos) - editor.commands.insertContent('did ') - } - } -} - -/** - * - * @param {object} editor - the editor object - */ -function findCommand(editor) { - const doc = editor.state.doc - return findChildren(doc, (child) => { - return child.isText && Object.hasOwn(editor.commands, child.text) - })[0] -} - -/** - * - * @param {object} editor - the editor object - * @param {string} markdown - markdown content - */ -export function expectMarkdown(editor, markdown) { - const stripped = markdown.replace(/\t*/g, '') - expect(getMarkdown(editor).replace(/\n*$/, '')).to.equal(stripped) -} - -/** - * - * @param {object} editor - the editor object - */ -function getMarkdown(editor) { - const serializer = createMarkdownSerializer(editor.schema) - return serializer.serialize(editor.state.doc) -} diff --git a/cypress/e2e/openreadonly.spec.js b/cypress/e2e/openreadonly.spec.js deleted file mode 100644 index 59a3826bb7a..00000000000 --- a/cypress/e2e/openreadonly.spec.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { User } from '@nextcloud/e2e-test-server/cypress' -import { randUser } from '../utils/index.js' - -const admin = new User('admin', 'admin') -const user = randUser() - -describe('Open read-only mode', function() { - before(function() { - cy.createUser(user) - cy.login(user) - cy.uploadFile('test.md', 'text/markdown') - cy.uploadFile('test.md', 'text/plain', 'test.txt') - }) - - const setReadOnlyMode = function(mode) { - cy.login(admin) - cy.setAppConfig('open_read_only_enabled', mode) - } - - describe('Disabled', function() { - const checkMenubar = function() { - cy.get('[data-text-el="readonly-bar"]').should('not.exist') - cy.get('.text-menubar', { timeout: 10000 }) - .getActionEntry('done') - .should('not.exist') - } - - before(function() { - setReadOnlyMode(0) - }) - - beforeEach(function() { - cy.login(user) - cy.visit('/apps/files') - }) - - it('Test writable markdown file', function() { - cy.openFile('test.md') - checkMenubar() - }) - - it('Test writable text file', function() { - cy.openFile('test.txt') - checkMenubar() - }) - }) - - describe('Enabled', function() { - const requireReadOnlyBar = function() { - cy.get('[data-text-el="readonly-bar"]').should('exist') - cy.get('[data-text-el="readonly-bar"]') - .getActionEntry('edit') - .should('exist') - } - - const requireMenubar = function() { - cy.get('[data-text-el="readonly-bar"]').should('not.exist') - cy.get('.text-menubar').getActionEntry('done').should('exist') - } - - before(function() { - setReadOnlyMode(1) - }) - - beforeEach(function() { - cy.login(user) - cy.visit('/apps/files') - }) - - after(function() { - setReadOnlyMode(0) - }) - - it('Test read-only markdown file', function() { - cy.openFile('test.md') - - requireReadOnlyBar() - - // Switch to edit-mode - cy.get('[data-text-el="readonly-bar"]').getActionEntry('edit').click() - - requireMenubar() - - // Switch to read-only mode - cy.get('.text-menubar').getActionEntry('done').click() - - requireReadOnlyBar() - }) - - it('Test read-only text file', function() { - cy.openFile('test.txt') - - requireReadOnlyBar() - - // Switch to edit-mode - cy.get('[data-text-el="readonly-bar"]').getActionEntry('edit').click() - - // Check that read-only bar does not exist - cy.get('[data-text-el="readonly-bar"]').should('not.exist') - }) - }) -}) diff --git a/cypress/e2e/sections.spec.js b/cypress/e2e/sections.spec.js deleted file mode 100644 index a86f8757f1a..00000000000 --- a/cypress/e2e/sections.spec.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' - -const user = randUser() -const fileName = 'empty.md' - -function clickMenubarTableOfContents() { - cy.getActionEntry('headings').click() - - cy.get('.v-popper__wrapper .open').getActionEntry('outline').click() -} - -let currentFolder - -describe('Content Sections', () => { - before(function() { - cy.createUser(user) - }) - - beforeEach(function() { - cy.clearAllSessionStorage() - cy.login(user) - cy.createTestFolder().then((folderName) => { - currentFolder = folderName - cy.uploadFile(fileName, 'text/markdown', `${currentFolder}/${fileName}`) - }) - }) - - describe('Heading anchors', () => { - it('Anchor exists', () => { - cy.visitTestFolder() - cy.openFile(fileName, { force: true }) - cy.getContent().type('# Heading\nText\n## Heading 2\nText\n## Heading 2') - cy.getContent() - .find('a.heading-anchor') - .should(($anchor) => { - expect($anchor).to.have.length(3) - expect($anchor.eq(0)) - .to.have.attr('href') - .and.equal('#h-heading') - expect($anchor.eq(1)) - .to.have.attr('href') - .and.equal('#h-heading-2') - expect($anchor.eq(2)) - .to.have.attr('href') - .and.equal('#h-heading-2--1') - }) - }) - - it('Anchor ID is updated', () => { - cy.visitTestFolder() - cy.openFile(fileName, { force: true }) - cy.insertLine('# Heading 1') - cy.getContent() - .find('h1 > a') - .should('have.attr', 'id') - .and('equal', 'h-heading-1') - cy.getContent() - .find('a.heading-anchor') - .should('have.attr', 'href') - .and('equal', '#h-heading-1') - cy.getContent().type('{backspace}{backspace}2{enter}') - cy.getContent() - .find('h1 > a') - .should('have.attr', 'id') - .and('equal', 'h-heading-2') - cy.getContent() - .find('a.heading-anchor') - .should('have.attr', 'href') - .and('equal', '#h-heading-2') - }) - - it('scrolls anchor into view', () => { - cy.uploadFile( - 'anchors.md', - 'text/markdown', - `${currentFolder}/anchors.md`, - ) - cy.visitTestFolder() - cy.openFile('anchors.md') - cy.getContent().get('h2 > a[id="h-bottom"]').should('not.be.inViewport') - cy.getContent().find('a[href="#h-bottom"]:not(.heading-anchor)').click() - cy.getContent().get('h2 > a[id="h-bottom"]').should('be.inViewport') - }) - - it('Can change heading level', () => { - cy.visitTestFolder() - cy.openFile(fileName, { force: true }) - // Issue #2868 - cy.insertLine('# Heading 1') - cy.getContent() - .find('h1 > a') - .should('have.attr', 'id') - .and('equal', 'h-heading-1') - cy.getContent().find('h1').click({ force: true, position: 'center' }) - cy.getActionEntry('headings').click() - cy.get('.v-popper__wrapper .open').getActionEntry('headings-h3').click() - cy.getContent() - .find('h3 > a') - .should('have.attr', 'id') - .and('equal', 'h-heading-1') - }) - }) - - describe('Table of Contents', () => { - it('toc via sidebar', () => { - cy.visitTestFolder() - cy.openFile(fileName, { force: true }) - cy.getContent().type('# T1 \n## T2 \n### T3 \n#### T4 \n##### T5 \n###### T6\n') - cy.closeFile() - .then(() => cy.openFile(fileName, { force: true })) - .then(clickMenubarTableOfContents) - - cy.getTOC().find('.editor__toc-header').should('exist') - - cy.getTOC().find('ul li').should('have.length', 6) - cy.getTOC() - .find('ul li') - .each((el, index) => { - cy.wrap(el) - .should('have.attr', 'data-toc-level') - .and('equal', String(index + 1)) - - cy.wrap(el) - .find('a') - .should('have.attr', 'href') - .and('equal', `#h-t${index + 1}`) - }) - }) - - it('outline', () => { - cy.visitTestFolder() - cy.openFile(fileName, { force: true }) - cy.getContent().type('# T1 \n') - - // No outline with one heading - cy.getOutline().should('not.exist') - - cy.getContent().type('## T2 \n### T3 \n#### T4 \n##### T5 \n###### T6\n') - - // Open table of contents via outline - cy.getOutline().should('exist').click() - - cy.getTOC().find('.editor__toc-header').should('exist') - cy.getTOC().find('ul li').should('have.length', 6) - }) - - it('empty toc', () => { - cy.visitTestFolder() - cy.openFile(fileName, { force: true }) - // No outline without headings - cy.getOutline().should('not.exist').then(clickMenubarTableOfContents) - - cy.getTOC().find('ul').should('be.empty') - }) - }) -}) diff --git a/cypress/e2e/share.spec.js b/cypress/e2e/share.spec.js deleted file mode 100644 index e72c32e223c..00000000000 --- a/cypress/e2e/share.spec.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' - -const user = randUser() -const recipient = randUser() - -describe('Open test.md in viewer', function() { - before(function() { - // Init user - cy.createUser(user) - cy.login(user) - - // Upload test files - cy.createFolder('folder') - cy.uploadFile('test.md', 'text/markdown', 'folder/test.md') - cy.uploadFile('test.md', 'text/markdown', 'folder/Readme.md') - cy.uploadFile('test.md', 'text/markdown', 'test3.md') - cy.uploadFile('test.md', 'text/markdown', 'test2.md') - cy.uploadFile('test.md', 'text/markdown') - - cy.createUser(recipient) - }) - beforeEach(function() { - cy.login(user) - }) - - it('Shares the file as a public read only link', function() { - cy.shareFile('/test.md') - .then((token) => { - cy.clearCookies() - cy.visit(`/s/${token}`) - }) - .then(() => { - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Hello world') - - cy.get('[data-text-el="readonly-bar"]') - .getActionEntry('outline') - .click() - - cy.getTOC().find('.editor__toc-header').should('exist') - }) - }) - - it('Shares the file as a public link with write permissions', function() { - cy.shareFile('/test2.md', { edit: true }) - .then((token) => { - cy.visit(`/s/${token}`) - }) - .then(() => { - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Hello world') - - cy.getMenu().should('be.visible') - cy.getActionEntry('undo').should('be.visible') - cy.getActionEntry('redo').should('be.visible') - cy.getActionEntry('bold').should('be.visible') - }) - }) - - it('Opens the editor as guest', function() { - cy.shareFile('/test2.md', { edit: true }) - .then((token) => { - cy.clearCookies() - cy.visit(`/s/${token}`) - }) - .then(() => { - cy.getEditor().should('be.visible') - cy.getContent() - .should('contain', 'Hello world') - .find('h2') - .should('contain', 'Hello world') - - cy.getMenu().should('be.visible') - cy.getActionEntry('undo').should('be.visible') - cy.getActionEntry('redo').should('be.visible') - cy.getActionEntry('bold').should('be.visible') - }) - }) - - it('Shares a folder as a public read only link', function() { - cy.shareFile('/folder') - .then((token) => { - cy.clearCookies() - cy.visit(`/s/${token}`) - }) - .then(() => { - cy.openFile('test.md') - cy.getModal().getContent().should('be.visible') - cy.getModal().getContent().should('contain', 'Hello world') - cy.getModal() - .getContent() - .find('h2') - .should('contain', 'Hello world') - cy.getModal().find('.modal-header button.header-close').click() - cy.get('.modal-mask').should('not.be.visible') - // cy.get('#rich-workspace').getContent().should('contain', 'Hello world') - }) - }) - - it('Opens the editor as guest and set a session username', function() { - cy.shareFile('/test3.md', { edit: true }) - .then((token) => { - cy.clearCookies() - cy.visit(`/s/${token}`) - }) - .then(() => { - cy.getEditor().should('be.visible') - cy.getContent().find('h2').should('contain', 'Hello world') - cy.get('button.avatar-list').click() - cy.get('.session-menu button').click() - cy.get('.guest-name-dialog input[type="text"]').type('someone{enter}') - cy.get('.session-menu .session-label.guest').should( - 'contain', - 'someone', - ) - }) - }) - - it('Share a file with download disabled shows an error', function() { - cy.shareFileToUser('test.md', recipient, { - attributes: '[{"scope":"permissions","key":"download","value":false}]', - }).then(() => { - cy.login(recipient) - cy.visit('/apps/files') - cy.openFile('test.md') - cy.getModal() - .find('.document-status') - .should( - 'contain', - 'This file cannot be displayed as download is disabled by the share', - ) - cy.getModal().getContent().should('not.exist') - }) - }) - - it('makes use of the file id', function() { - cy.intercept({ - method: 'PUT', - url: '**/apps/text/public/session/*/create', - }).as('create') - cy.shareFile('/test2.md', { edit: true }).then((token) => { - cy.clearCookies() - cy.visit(`/s/${token}`) - }) - cy.wait('@create', { timeout: 10000 }) - .its('request.body.fileId') - .should('be.a', 'Number') - }) -}) diff --git a/cypress/e2e/shareWithAttachments.spec.js b/cypress/e2e/shareWithAttachments.spec.js deleted file mode 100644 index fc85807a7ee..00000000000 --- a/cypress/e2e/shareWithAttachments.spec.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' - -const user = randUser() -const recipient = randUser() - -describe('Share with attachments', () => { - before(() => { - cy.createUser(user) - cy.createUser(recipient) - }) - - it('handle file operations by recipient user', () => { - cy.login(user) - const fileName = 'testShared.md' - cy.createFile(fileName, '![git](.attachments.123/github.png)') - .as('textFileId') - .then((fileId) => { - const attachmentsFolder = `.attachments.${fileId}` - cy.createFolder(attachmentsFolder) - cy.uploadFile( - 'github.png', - 'image/png', - `${attachmentsFolder}/github.png`, - ).as('attachmentId') - cy.shareFileToUser(fileName, recipient) - }) - - cy.login(recipient) - cy.showHiddenFiles() - - cy.visit('/apps/files') - // check the file list - cy.getFile('testShared.md').should('exist') - cy.getFile('github.png').should('not.exist') - - // check the attachment folder is not there - cy.getFileId('testShared.md').then((documentId) => { - cy.getFile('.attachments.' + documentId).should('not.exist') - }) - - // move the file and check the attachment folder is still not there - cy.moveFile('testShared.md', 'testMoved.md') - cy.reloadFileList() - cy.getFileId('testMoved.md').then((documentId) => { - cy.getFile('.attachments.' + documentId).should('not.exist') - }) - - // copy the file and check the attachment folder was copied - cy.copyFile('testMoved.md', 'testCopied.md') - cy.reloadFileList() - cy.getFileId('testCopied.md').then((documentId) => { - cy.openFolder('.attachments.' + documentId) - }) - cy.get('@attachmentId').then((attachmentId) => { - // these are new copied attachment files - // so they should not have the same IDs than the ones created when uploading the files - cy.getFileId('github.png').should('not.eq', String(attachmentId)) - }) - }) -}) - -describe('Public Share with attachments', () => { - before(function() { - cy.createUser(user) - }) - - beforeEach(function() { - cy.login(user) - cy.createTestFolder().as('folder').then(cy.shareFile).as('token') - cy.then(function() { - cy.createFile( - `${this.folder}/Readme.md`, - '![Attached text](.attachments.123/lines.txt)', - ).as('fileId') - }) - cy.then(function() { - const attachmentsFolder = `${this.folder}/.attachments.${this.fileId}` - cy.createFolder(attachmentsFolder) - cy.uploadFile( - 'lines.txt', - 'text/plain', - `${attachmentsFolder}/lines.txt`, - ) - }) - cy.clearCookies() - }) - - it('open attached files in folder description', function() { - cy.visit(`/s/${this.token}`) - cy.get('.editor__content-wrapper').should('exist') - cy.get('.editor__content-wrapper .name', { timeout: 10_000 }).click() - cy.get('.viewer').should('exist') - cy.get('.language-plaintext').should('contain', 'multiple lines') - }) -}) diff --git a/cypress/e2e/shortcuts.spec.js b/cypress/e2e/shortcuts.spec.js deleted file mode 100644 index 1eccea7d6b7..00000000000 --- a/cypress/e2e/shortcuts.spec.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' -const user = randUser() - -const modKey = Cypress.platform === 'darwin' ? '{cmd}' : '{ctrl}' -function testShortcut(shortcut, tag) { - cy.getContent().type(shortcut) - - cy.getContent().find(tag).should('contain', Cypress.currentTest.title) - - return cy.closeFile() -} - -function testHeading(num) { - testShortcut(`${modKey}{shift}${num}`, `h${num}`) -} - -describe('keyboard shortcuts', () => { - before(() => { - cy.createUser(user) - }) - - beforeEach(() => { - cy.login(user) - cy.uploadTestFile() - cy.visit('/apps/files') - cy.openTestFile() - cy.getContent().type(Cypress.currentTest.title) - cy.getContent().type('{selectall}') - }) - - it('bold', () => testShortcut(`${modKey}b`, 'strong')) - it('italic', () => testShortcut(`${modKey}i`, 'em')) - it('underline', () => testShortcut(`${modKey}u`, 'u')) - it('strikethrough', () => testShortcut(`${modKey}{shift}s`, 's')) - it('highlight', () => testShortcut(`${modKey}{shift}h`, 'mark')) - it('blockquote', () => testShortcut(`${modKey}{shift}b`, 'blockquote')) - it('codeblock', () => testShortcut(`${modKey}{alt}c`, 'pre')) - it('ordered-list', () => testShortcut(`${modKey}{shift}7`, 'ol')) - it('unordered-list', () => testShortcut(`${modKey}{shift}8`, 'ul')) - it('task-list', () => testShortcut(`${modKey}{shift}9`, 'ul.contains-task-list')) - - // Headings - const levels = [1, 2, 3, 4, 5, 6] - levels.forEach((level) => { - it(`heading-${level}`, () => testHeading(level)) - }) -}) diff --git a/cypress/e2e/sync.spec.js b/cypress/e2e/sync.spec.js index 6d4def6fee8..64a7f980778 100644 --- a/cypress/e2e/sync.spec.js +++ b/cypress/e2e/sync.spec.js @@ -168,9 +168,10 @@ describe('Sync', () => { .should('include', 'after the lost connection') }) - it('asks to reload page when document session got cleaned up', () => { + it.only('asks to reload page when document session got cleaned up', () => { cy.get('.save-status button').click() cy.wait('@save') + cy.wait(1_000) // ensure that the etag changes cy.uploadTestFile('test.md') cy.getContent().should('not.exist') diff --git a/cypress/e2e/viewer.spec.js b/cypress/e2e/viewer.spec.js deleted file mode 100644 index 3d7fe575054..00000000000 --- a/cypress/e2e/viewer.spec.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { initUserAndFiles, randUser } from '../utils/index.js' -const user = randUser() - -describe('Open test.md in viewer', function() { - const getViewer = () => cy.get('#viewer') - - before(function() { - initUserAndFiles(user, 'test.md', 'empty.md') - }) - - beforeEach(function() { - cy.login(user) - cy.visit('/apps/files') - }) - - it('See test.md in the list', function() { - cy.getFile('test.md').should('be.visible') - }) - - it('Open the viewer on file click', function() { - cy.openFile('test.md') - - cy.log('Inspect viewer') - getViewer() - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - getViewer().find('.modal-header__name').should('contain', 'test.md') - getViewer() - .find('.modal-header button.action-item__menutoggle') - .should('be.visible') - - cy.log('Inspect editor') - cy.getContent().should('contain', 'Hello world') - cy.getContent().get('h2').should('contain', 'Hello world') - - cy.log('Inspect menubar') - cy.getActionEntry('undo').should('be.visible') - cy.getActionEntry('bold').should('be.visible') - }) - - it('Open an empty file', function() { - cy.openFile('empty.md') - - cy.log('Inspect viewer') - getViewer() - .should('be.visible') - .and('have.class', 'modal-mask') - .and('not.have.class', 'icon-loading') - getViewer().find('.modal-header__name').should('contain', 'empty.md') - getViewer() - .find('.modal-header button.action-item__menutoggle') - .should('be.visible') - - cy.log('Inspect editor') - cy.getContent().should('contain', '') - - cy.log('Inspect menubar') - cy.getActionEntry('undo').should('be.visible') - cy.getActionEntry('bold').should('be.visible') - }) - - it('Closes the editor', function() { - cy.openFile('test.md') - cy.get('#viewer .modal-header button.header-close').click() - cy.get('#viewer').should('not.exist') - }) - - it('Can use tab keys for list in the viewer', function() { - // This used to break with the focus trap that the viewer modal has - cy.openFile('empty.md') - - cy.insertLine('- test') - - // Ignore TypeError thrown by cypress keydown + keyup - cy.on('uncaught:exception', (err) => { - return !err.message.includes("Cannot read properties of undefined (reading 'toLowerCase')") - }) - - // Cypress does not have native tab key support, though this seems to work - // for triggering the key handler of tiptap - // https://github.com/cypress-io/cypress/issues/311 - cy.getContent().trigger('keydown', { keyCode: 9 }) - cy.getContent().trigger('keyup', { keyCode: 9 }) - cy.getContent().type('Nested list') - - cy.getContent().find('ul li').should('contain', 'test') - cy.getContent().find('ul li ul li').should('contain', 'Nested list') - - cy.getContent().trigger('keydown', { keyCode: 9, shiftKey: true }) - cy.getContent().trigger('keyup', { keyCode: 9, shiftKey: true }) - - cy.getContent().find('ul li').eq(0).should('contain', 'test') - cy.getContent().find('ul li').eq(1).should('contain', 'Nested list') - }) -}) diff --git a/cypress/e2e/workspace.spec.js b/cypress/e2e/workspace.spec.js deleted file mode 100644 index 7f9b228c5e9..00000000000 --- a/cypress/e2e/workspace.spec.js +++ /dev/null @@ -1,243 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -import { randUser } from '../utils/index.js' -const user = randUser() - -describe('Workspace', function() { - before(function() { - cy.createUser(user) - }) - - beforeEach(function() { - cy.login(user) - // Some tests modify the language. - // Make sure it's the default otherwise. - cy.modifyUser(user, 'language', 'en') - // isolate tests - each happens in its own folder - cy.createTestFolder().as('testFolder') - }) - - it('Initializes the workspace without errors', function() { - cy.visitTestFolder({ - onBeforeLoad: (win) => cy.spy(win.console, 'error').as('consoleError'), - }) - cy.then(() => { - expect(this.consoleError).to.not.have.been.calledWithMatch('workspace') - }) - }) - - it('creates headings via submenu', function() { - cy.visitTestFolder() - cy.openWorkspace().type('Heading') - cy.getContent().type('{selectall}') - ;['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach((heading) => { - const actionName = `headings-${heading}` - - cy.getSubmenuEntry('headings', actionName).click() - - cy.getContent().find(`${heading}`).should('contain', 'Heading') - - cy.getSubmenuEntry('headings', actionName) - .should('have.class', 'is-active') - .click() - - cy.getMenuEntry('headings').should('not.have.class', 'is-active') - }) - }) - - it('creates lists', function() { - cy.visitTestFolder() - cy.openWorkspace().type('List me') - cy.getContent().type('{selectall}') - ;[ - ['unordered-list', 'ul'], - ['ordered-list', 'ol'], - ['task-list', 'ul.contains-task-list'], - ].forEach(([button, tag]) => testListButton(button, tag, 'List me')) - }) - - it('takes README.md into account', function() { - cy.uploadFile('test.md', 'text/markdown', `${this.testFolder}/README.md`) - cy.visitTestFolder() - cy.getFile('README.md') - cy.get('#rich-workspace .ProseMirror').should('contain', 'Hello world') - }) - - it('emoji picker', function() { - cy.visitTestFolder() - cy.openWorkspace().type("# Let's smile together{enter}## ") - - cy.getMenuEntry('emoji-picker').click() - - cy.get('#emoji-mart-list button[aria-label="😀, grinning"]').first().click() - - cy.getEditor().find('h2').contains('😀') - }) - - it('file links', function() { - cy.createFolder(`${this.testFolder}/sub-folder`) - cy.createFolder(`${this.testFolder}/sub-folder/alpha`) - - cy.uploadFile( - 'test.md', - 'text/markdown', - `${this.testFolder}/sub-folder/alpha/test.md`, - ) - cy.visitTestFolder() - - cy.openWorkspace().type('link me') - cy.getContent().type('{selectall}') - - cy.getSubmenuEntry('insert-link', 'insert-link-file').click() - - cy.get('.file-picker [data-filename="sub-folder"]').click() - cy.get('.file-picker [data-filename="alpha"]').click() - cy.get('.file-picker [data-filename="test.md"]').click() - cy.contains('button', 'Choose test.md').click() - - cy.getContent().type('{leftArrow}') - - cy.get('.link-view-bubble .widget-file', { timeout: 10000 }) - .find('.widget-file__title') - .contains('test.md') - .click({ force: true }) - - cy.getModal().find('.modal-header').contains('test.md') - - cy.getModal().getEditor().contains('Hello world') - - cy.getModal().find('button.header-close').click() - }) - - describe('callouts', function() { - const types = ['info', 'warn', 'error', 'success'] - - beforeEach(function() { - cy.visitTestFolder() - cy.openWorkspace().type('Callout') - }) - - it('create callout', function() { - cy.wrap(types).each((type) => { - cy.log(`creating ${type} callout`) - - const actionName = `callout-${type}` - - // enable callout - cy.getSubmenuEntry('blocks', actionName).click() - - // check content - cy.getContent() - .find(`.callout.callout--${type}`) - .should('contain', 'Callout') - - // disable - cy.getSubmenuEntry('blocks', actionName) - .should('have.class', 'is-active') - .click() - }) - }) - - it('toggle callouts', function() { - const [first, ...rest] = types - - // enable callout - cy.getSubmenuEntry('blocks', `callout-${first}`).click() - - cy.wrap(rest).each((type) => { - const actionName = `callout-${type}` - cy.getSubmenuEntry('blocks', actionName).click() - cy.getContent() - .find(`.callout.callout--${type}`) - .should('contain', 'Callout') - }) - - cy.getSubmenuEntry('blocks', `callout-${rest.at(-1)}`).click() - cy.getMenuEntry('blocks').should('not.have.class', 'is-active') - }) - }) - - describe('localize', function() { - it('takes localized file name into account', function() { - cy.modifyUser(user, 'language', 'de_DE') - cy.uploadFile( - 'test.md', - 'text/markdown', - `${this.testFolder}/Anleitung.md`, - ) - cy.visitTestFolder() - cy.getFile('Anleitung.md') - cy.get('#rich-workspace .ProseMirror').should('contain', 'Hello world') - }) - - it('creates description with default name', function() { - cy.modifyUser(user, 'language', 'es') - cy.visitTestFolder() - cy.createDescription('Añadir descripción a carpeta') - cy.getFile('Readme.md') - cy.get('#rich-workspace .editor__content').should('be.visible') - }) - - it('ignores localized file name in other language', function() { - cy.modifyUser(user, 'language', 'fr') - cy.uploadFile( - 'test.md', - 'text/markdown', - `${this.testFolder}/Anleitung.md`, - ) - cy.visitTestFolder() - cy.getFile('Anleitung.md') - cy.get('#rich-workspace .ProseMirror').should('not.exist') - }) - }) - - describe('create Readme.md', function() { - const checkContent = function() { - const txt = Cypress.currentTest.title - - cy.getEditor().find('[data-text-el="editor-content-wrapper"]').click() - - cy.getContent().type(txt) - cy.getContent().should('contain', txt) - } - - beforeEach(function() { - cy.visitTestFolder() - }) - - it('click', function() { - cy.openWorkspace().click() - checkContent() - }) - - it('enter', function() { - cy.openWorkspace().type('{enter}') - checkContent() - }) - - it('spacebar', function() { - cy.openWorkspace().trigger('keyup', { - keyCode: 32, - which: 32, - shiftKey: false, - ctrlKey: false, - force: true, - }) - checkContent() - }) - }) -}) - -/** - * @param {string} button Name of the button to click. - * @param {string} tag Html tag expected to be toggled. - * @param {string} content Content expected in the element. - */ -function testListButton(button, tag, content) { - cy.getSubmenuEntry('lists', button).should('not.have.class', 'is-active').click() - cy.getContent().find(`${tag}`).should('contain', content) - cy.getSubmenuEntry('lists', button).should('have.class', 'is-active').click() -}