diff --git a/.github/workflows/cypress-component.yml b/.github/workflows/cypress-component.yml new file mode 100644 index 0000000000..e16ee12916 --- /dev/null +++ b/.github/workflows/cypress-component.yml @@ -0,0 +1,78 @@ +# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + +name: Cypress Component Tests + +on: + pull_request: + push: + branches: + - main + - master + - stable* + +concurrency: + group: cypress-component-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + APP_NAME: tables + +jobs: + component-tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + node-version: [20] + + steps: + - name: Checkout app + uses: actions/checkout@v4 + + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Build app + run: npm run build --if-present + + - name: Run component tests + run: npm run tests:component + + - name: Upload test failure screenshots + uses: actions/upload-artifact@v4 + if: failure() + with: + name: cypress-component-screenshots-node${{ matrix.node-version }} + path: cypress/screenshots/ + retention-days: 5 + + - name: Upload test videos + uses: actions/upload-artifact@v4 + if: failure() + with: + name: cypress-component-videos-node${{ matrix.node-version }} + path: cypress/videos/ + retention-days: 5 + + summary: + permissions: + contents: none + runs-on: ubuntu-latest-low + needs: component-tests + + if: always() + + name: cypress-component-summary + + steps: + - name: Summary status + run: if ${{ needs.component-tests.result != 'success' && needs.component-tests.result != 'skipped' }}; then exit 1; fi diff --git a/cypress.config.mjs b/cypress.config.mjs index 1065fc6bb5..889089b86d 100644 --- a/cypress.config.mjs +++ b/cypress.config.mjs @@ -45,6 +45,38 @@ export default defineConfig({ devServer: { framework: 'vue', bundler: 'vite', + viteConfig: { + plugins: [vue(), nodePolyfills()], + optimizeDeps: { + exclude: [ + 'vite-plugin-node-polyfills/shims/buffer', + 'vite-plugin-node-polyfills/shims/global', + 'vite-plugin-node-polyfills/shims/process' + ], + force: true + }, + define: { + global: 'globalThis', + }, + }, + }, + setupNodeEvents(on, config) { + on('file:preprocessor', vitePreprocessor({ + plugins: [vue(), nodePolyfills()], + configFile: false, + optimizeDeps: { + exclude: [ + 'vite-plugin-node-polyfills/shims/buffer', + 'vite-plugin-node-polyfills/shims/global', + 'vite-plugin-node-polyfills/shims/process' + ], + force: true + }, + define: { + global: 'globalThis', + }, + })) + return config }, viewportWidth: 800, viewportHeight: 600, diff --git a/cypress/component/ContentReferenceWidget.cy.js b/cypress/component/ContentReferenceWidget.cy.js index 27862b07e1..acd16c04d5 100644 --- a/cypress/component/ContentReferenceWidget.cy.js +++ b/cypress/component/ContentReferenceWidget.cy.js @@ -22,7 +22,7 @@ describe('ContentReferenceWidget', () => { // Verify the table loaded the richObject // by checking the title - cy.get('.tables-content-widget h2').as('heading') + cy.get('[data-cy="contentReferenceWidget"] h2').as('heading') cy.get('@heading').contains(title) }) @@ -32,7 +32,7 @@ describe('ContentReferenceWidget', () => { const searchTerm = 'cat' // Search for the row including the above search term - cy.get('@options').find('input').type(searchTerm) + cy.get('@options').find('input').first().type(searchTerm) // Ensure there is only one resultant row and // verify the row correctly includes the search term @@ -47,11 +47,15 @@ describe('ContentReferenceWidget', () => { // Load a fixture used to reply to the create row request cy.fixture('widgets/createRow.json') .then((rowData) => { - cy.reply('**/index.php/apps/tables/row', rowData) + cy.reply('**/ocs/v2.php/apps/tables/api/2/tables/*/rows', rowData) + + // Also mock the reload rows endpoint to return updated rows including the new one + const updatedRows = [...richObject.rows, rowData] + cy.reply('**/apps/tables/row/table/*', updatedRows) }) // Click the Create Row button - cy.get('@options').find('button').click() + cy.get('@options').find('button').first().click() // Input row data cy.get('[data-cy="Name"] input').type('Hello') @@ -99,7 +103,7 @@ describe('ContentReferenceWidget', () => { }) function mountContentWidget(richObject) { - cy.reply('**/index.php/apps/tables/row/table/*', richObject.rows) + cy.reply('**/apps/tables/row/table/*', richObject.rows) cy.mount(ContentReferenceWidget, { propsData: { @@ -108,7 +112,7 @@ function mountContentWidget(richObject) { }) // Get some often used elements - cy.get('.tables-content-widget > .options').as('options') - cy.get('.tables-content-widget .NcTable table').as('table') - cy.get('@table').find('tbody tr[data-cy="customTableRow"]').as('rows') + cy.get('[data-cy="contentReferenceWidget"] .options').as('options') + cy.get('[data-cy="contentReferenceWidget"] .NcTable table').as('table') + cy.get('@table').find('tr[data-cy="customTableRow"]').as('rows') } diff --git a/cypress/styleguide/global.requires.js b/cypress/styleguide/global.requires.js index 553f45cba3..3ce16d67b5 100644 --- a/cypress/styleguide/global.requires.js +++ b/cypress/styleguide/global.requires.js @@ -5,7 +5,6 @@ /* eslint-disable-next-line */ import 'core-js/stable' import Vue from 'vue' -import VTooltip from './../src/directives/Tooltip/index.js' import axios from '@nextcloud/axios' @@ -141,7 +140,7 @@ window.OC = { webroot: '', } window.OCA = {} -window.appName = 'nextcloud-vue' +window.appName = 'tables' Vue.prototype.OC = window.OC Vue.prototype.OCA = window.OCA @@ -149,5 +148,3 @@ Vue.prototype.OCA = window.OCA window.NextcloudVueDocs = { tags: '/remote.php/dav/systemtags/HTTP/1.1 404 Not Found/remote.php/dav/systemtags/77tag1truetruetrueHTTP/1.1 200 OK/remote.php/dav/systemtags/22tag2falsetruetrueHTTP/1.1 200 OK/remote.php/dav/systemtags/33tag3truetruetrueHTTP/1.1 200 OK/remote.php/dav/systemtags/44importanttruetruetrueHTTP/1.1 200 OK/remote.php/dav/systemtags/11secrettruefalsetrueHTTP/1.1 200 OK/remote.php/dav/systemtags/55testtruefalsetrueHTTP/1.1 200 OK/remote.php/dav/systemtags/66test2falsefalsetrueHTTP/1.1 200 OK', } - -Vue.directive('tooltip', VTooltip) diff --git a/cypress/support/component.js b/cypress/support/component.js index 55cb0dc519..4f179fd968 100644 --- a/cypress/support/component.js +++ b/cypress/support/component.js @@ -4,8 +4,12 @@ */ import { mount } from 'cypress/vue2' import { translate as t, translatePlural as n } from '@nextcloud/l10n' -import store from '../../src/store/store.js' -import data from '../../src/store/data.js' +import { createPinia, PiniaVuePlugin } from 'pinia' +import Vue from 'vue' +Vue.use(PiniaVuePlugin) +const pinia = createPinia() + +import '../styleguide/global.requires.js' // Styles necessary for rendering the component import '../styleguide/assets/default.css' @@ -13,13 +17,11 @@ import '../styleguide/assets/additional.css' import '../styleguide/assets/icons.css' const prepareOptions = (options = {}) => { - store.data = data - const defaultOptions = { + pinia, extensions: { mixins: [ { methods: { t, n } }, - { store, }, ], plugins: [], components: {}, diff --git a/package.json b/package.json index d46d52890b..01e3bdd60d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "lint": "eslint --ext .js,.vue src", "lint:fix": "eslint --ext .js,.vue src --fix", "stylelint": "stylelint 'css/*.css' 'css/*.scss' 'src/**/*.scss' 'src/**/*.vue'", - "stylelint:fix": "stylelint 'css/*.css' 'css/*.scss' 'src/**/*.scss' 'src/**/*.vue' --fix" + "stylelint:fix": "stylelint 'css/*.css' 'css/*.scss' 'src/**/*.scss' 'src/**/*.vue' --fix", + "tests": "npx cypress run", + "tests:component": "npx cypress run --component" }, "dependencies": { "@mdi/svg": "^7.4.47", diff --git a/src/views/ContentReferenceWidget.vue b/src/views/ContentReferenceWidget.vue index 64c20b9b5d..30e9e5ee69 100644 --- a/src/views/ContentReferenceWidget.vue +++ b/src/views/ContentReferenceWidget.vue @@ -3,7 +3,7 @@ - SPDX-License-Identifier: AGPL-3.0-or-later -->