Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .github/workflows/app-upgrade-mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,6 @@ jobs:
path: apps/text
ref: ${{ env.text_app_ref }}

- name: Checkout viewer app
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: nextcloud/viewer
path: apps/viewer
ref: ${{ matrix.server-versions }}

- name: Get collectives app from app store
id: collectives_app
uses: nextcloud/appstore-action@44639c5dea0995197cb2b2d987b66e2cdc8821ae # v1.0.2
Expand Down
7 changes: 0 additions & 7 deletions .github/workflows/app-upgrade-postgres.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,6 @@ jobs:
path: apps/text
ref: ${{ env.text_app_ref }}

- name: Checkout viewer app
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: nextcloud/viewer
path: apps/viewer
ref: ${{ matrix.server-versions }}

- name: Get collectives app from app store
id: collectives_app
uses: nextcloud/appstore-action@44639c5dea0995197cb2b2d987b66e2cdc8821ae # v1.0.2
Expand Down
17 changes: 4 additions & 13 deletions .github/workflows/cypress-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,9 @@ jobs:
- server-versions: 'stable33'
server-major: '33'
semantic-e2e: '0'
text-app-repository: 'nextcloud/text'
text-app-ref: 'stable33'
- server-versions: 'master'
server-major: ''
semantic-e2e: '1'
text-app-repository: 'nextcloud/text'
text-app-ref: 'b5ff1b0ce8d38b8a8dfdd6acf7b3f36c3fa2e272'

env:
PUPPETEER_SKIP_DOWNLOAD: true
Expand Down Expand Up @@ -122,11 +118,13 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
repository: ${{ matrix.text-app-repository }}
repository: nextcloud/text
path: apps/text
ref: ${{ matrix.text-app-ref }}
ref: ${{ matrix.server-versions == 'master' && 'main' || matrix.server-versions }}

# Can be removed once we only support Nextcloud 36+
- name: Checkout viewer app
if: contains(fromJSON('["stable33", "stable34", "stable35"]'), matrix.server-versions)
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
Expand Down Expand Up @@ -165,13 +163,6 @@ jobs:
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'

- name: Build compatible Text semantic API
if: matrix.semantic-e2e == '1'
working-directory: apps/text
run: |
npm ci
npm run build

- name: Install node dependencies & build app
working-directory: apps/${{ env.APP_NAME }}
run: |
Expand Down
6 changes: 0 additions & 6 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ jobs:
- server-branch: 'stable33'
project-args: '--project=chromium --project=comparison-viewer-chromium'
semantic-e2e: '0'
text-repository: 'nextcloud/text'
text-ref: 'stable33'
- server-branch: 'master'
project-args: '--project=chromium --project=comparison-chromium'
semantic-e2e: '1'
text-repository: 'nextcloud/text'
text-ref: 'b5ff1b0ce8d38b8a8dfdd6acf7b3f36c3fa2e272'
steps:
- name: Checkout app
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -74,8 +70,6 @@ jobs:
env:
COLLECTIVES_SEMANTIC_E2E: ${{ matrix.semantic-e2e }}
PLAYWRIGHT_NC_SERVER_BRANCH: ${{ matrix.server-branch }}
PLAYWRIGHT_TEXT_REPOSITORY: ${{ matrix.text-repository }}
PLAYWRIGHT_TEXT_REF: ${{ matrix.text-ref }}

- name: Upload results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
AGENTS.md
build/
node_modules/
/vendor/
Expand Down
146 changes: 146 additions & 0 deletions AGENTS.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of this seems useful for humans as well. I wonder if we could have all the generic useful stuff in our development docs and then point there from AGENTS.md to avoid duplication.
(As a follow up / separate from this PR)

Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
# AGENTS.md

Nextcloud Collectives. PHP backend (`lib/`) + Vue.js frontend (`src/`).

---

## Generic notes

In frontend, always generate Typescript files when creating new files. Existing `.js` specs are not a template for new code.

For new e2e tests, always generate Playwright tests. Where appropriate, suggest to migrate Cypress tests to Playwright. The goal is to migrate completely from Cypress to Playwright in the future.

## Contributions

- Conventional Commits with a scope (e.g. `fix(versions): ...`).
- AI-assisted commits get an `Assisted-by: <Tool>:<model>` trailer.
- Changed or added code needs unit tests: Vitest in `src/tests/`, PHPUnit in `tests/Unit/`.
- New files need SPDX headers (REUSE CI enforces this).
- Comments describe the code, not the change that produced it.
- Keep PRs to one concern. Disclose AI use in the PR description (the template has a section for it).

## Commands

### Install

```bash
npm ci # JS deps
composer install # PHP deps (uses platform php=8.2 regardless of local PHP)
```

### Build

```bash
npm run build # Production (Vite, NODE_ENV=production)
npm run dev # Dev build
```

### Frontend tests (Vitest, jsdom, no server needed)

```bash
npm run test # Run all unit tests once
npx vitest run src/tests/util/sortOrders..spec.js # Single file
npx vitest run -t "pattern" # Filter by name
```

Unit specs live in `src/tests/**/*.spec.{js,ts}`; E2E specs live in `playwright/e2e/`.

### PHP unit tests (requires full Nextcloud server install β€” see below)

```bash
composer run test:unit # phpunit -c tests/phpunit.xml
vendor/bin/phpunit --filter testMethodName tests/Unit/Service/EmojiHelperTest.php
```

PHPUnit should only be run with the user's consent (see Testing gotchas).

### Lint / typecheck / format

```bash
npm run lint # tsc typecheck + ESLint
npm run lint:fix # ESLint auto-fix
npm run stylelint # Stylelint for CSS/SCSS/Vue styles
composer run lint # PHP syntax check (php -l)
composer run cs:check # PHP code style check (php-cs-fixer --dry-run)
composer run cs:fix # php-cs-fixer auto-fix
composer run psalm # Psalm static analysis (level 4, baseline: tests/psalm-baseline.xml)
composer run rector # Rector refactoring (followed by cs:fix)
```

### E2E β€” Playwright (newer suite)

```bash
npx playwright install chromium --only-shell # One-time browser install
npx playwright test
npx playwright test playwright/e2e/settings.spec.ts
npx playwright test --grep "pattern"
```

- Default URL: `http://localhost:8089/index.php/`. Unless a server already runs there, `playwright.config.ts` starts Nextcloud in Docker (`playwright/start-nextcloud-server.js`).
- CI runs 4 shards.
- Setting the `baseURL` env var skips the Docker `webServer`, but most tests still need the container: the fixtures `random-user.ts` and `Collective.ts` create test users with `createRandomUser()` from `@nextcloud/e2e-test-server/playwright`, which runs `occ` inside it.

#### Optional: running against an existing local Nextcloud instance

1. Build first (`npm run dev`) if not already done, since the instance serves `js/` and `css/` from this checkout.
2. Run the tests from a scratch copy whose `random-user.ts` and `Collective.ts` create users via OCS (`POST /ocs/v2.php/cloud/users` with `OCS-APIRequest: true` and admin credentials) instead of `createRandomUser()`.
3. Set `baseURL`. If Node doesn't trust the instance's certificate, also set `NODE_TLS_REJECT_UNAUTHORIZED=0`:
```bash
baseURL=https://nextcloud.local/index.php/ \
npx playwright test playwright/e2e/page-attachments.spec.ts --project=chromium
```

Tests that run `occ` directly still fail without the container: `page-content.spec.s` (whiteboard), `page-share.spec.ts`, `unified-search.spec.ts`, `version-comparison.spec.ts`, and collectives created with `markdownImportPath`.

### E2E β€” Cypress (deprecated, requires Nextcloud server)

```bash
npx cypress open # Interactive Cypress UI (run from repo root)
npx cypress run --spec "cypress/e2e/files.spec.js" # Single spec
```

- Default server URL: `http://localhost:8081/index.php`. Override with `CYPRESS_baseUrl`.
- CI uses 4 parallel runners via `cypress-split`.

## Testing gotchas

**Behat integration tests** require a live Nextcloud reachable at `https://nextcloud.local`
(the default Behat profile).

**PHPUnit** should not be run without manual consent as it might brick the development instance.

---

## Architecture notes

- **Multiple frontend entry points** (`vite.config.ts`): `icons`, `init`, `main`, `reference`, `settings-admin`; the PWA service worker is also compiled from `src/`.
- **Built assets are not committed.** `js/` and `css/` contain compiled output but it's not tracked in Git.
- **Version lives only in `appinfo/info.xml`** β€” not in `package.json` or `composer.json`.
- **vendor-bin isolation.** PHP dev tools (phpunit, psalm, php-cs-fixer, behat, openapi-extractor) each have their own scoped Composer install under `vendor-bin/`. They are accessible via `./vendor/bin/` symlinks.

## OpenAPI

Run `composer run openapi` after any OCS API change. The `openapi.yml` CI workflow fails if
`openapi.json` has uncommitted changes.

## Psalm

- Error level 4, PHP 8.2 target; baseline at `tests/psalm-baseline.xml`
- Run `composer run psalm:update-baseline` to update the baseline (requires review β€” don't use
it to silently suppress new issues)

## License compliance (REUSE)

Every new file needs SPDX headers (as PHP/JS/HTML comment):

```
SPDX-FileCopyrightText: <year> Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: AGPL-3.0-or-later
```

Bulk entries for files that can't carry inline headers go in `REUSE.toml`. The `reuse.yml`
CI workflow enforces compliance on every PR.
3 changes: 1 addition & 2 deletions cypress/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ RUN set -ex; \
RUN set -ex; \
git clone --depth 1 https://github.com/nextcloud/server.git; \
git clone --depth 1 https://github.com/nextcloud/circles.git server/apps/circles; \
git clone --depth 1 https://github.com/nextcloud/text.git server/apps/text; \
git clone --depth 1 https://github.com/nextcloud/viewer.git server/apps/viewer;
git clone --depth 1 https://github.com/nextcloud/text.git server/apps/text;

RUN set -ex; \
cd server; \
Expand Down
7 changes: 2 additions & 5 deletions cypress/e2e/collective-name-conflicts.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('Collective name conflicts', function() {
cy.get('.dialog__actions button').contains('Create').click()

cy.get('[data-cy-collectives="page-title-container"] input').invoke('val').should('contain', 'History Club')
cy.get('.toast-info').should('contain', 'Created collective "History Club" for existing team.')
cy.contains('[role="status"]', 'Created collective "History Club" for existing team.')
})
it('collectives of visible teams only show for members', function() {
cy.loginAs('jane')
Expand All @@ -54,10 +54,7 @@ describe('Collective name conflicts', function() {
cy.visit('apps/collectives')
cy.createCollective('Preexisting Team')
cy.get('[data-cy-collectives="page-title-container"] input').invoke('val').should('contain', 'Preexisting Team')
cy.get('.toast-info').should(
'contain',
'Created collective "Preexisting Team" for existing team.',
)
cy.contains('[role="status"]', 'Created collective "Preexisting Team" for existing team.')
})

after(function() {
Expand Down
6 changes: 3 additions & 3 deletions cypress/e2e/collective-settings.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,23 @@ describe('Collective settings', function() {
cy.clickMenuButton('Settings')
cy.get('.edit-permissions').contains('Admins only')
.click()
cy.get('div.toast-success').should('contain', 'Editing permissions updated')
cy.contains('[role="status"]', 'Editing permissions updated')
})

it('Allows to change sharing permissions', function() {
cy.openCollectiveMenu('Change me')
cy.clickMenuButton('Settings')
cy.get('.share-permissions').contains('Admins only')
.click()
cy.get('div.toast-success').should('contain', 'Sharing permissions updated')
cy.contains('[role="status"]', 'Sharing permissions updated')
})

it('Allows to change page mode', function() {
cy.openCollectiveMenu('Change me')
cy.clickMenuButton('Settings')
cy.get('.page-mode').contains('Edit')
.click()
cy.get('div.toast-success').should('contain', 'Default page mode updated')
cy.contains('[role="status"]', 'Default page mode updated')
})

it('Allows to open settings from landing page actions', function() {
Expand Down
6 changes: 2 additions & 4 deletions cypress/e2e/collective.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,8 @@ describe('Collective', function() {
.should('not.be.visible')

// Undo leave collective
cy.get('.toast-undo')
.should('contain', 'You left collective Preexisting Collective')
cy.get('.toast-undo button')
.contains('Undo')
cy.contains('[role="alert"]', 'You left collective Preexisting Collective')
.contains('button', 'Undo')
.click()

cy.openCollectiveSelector()
Expand Down
Loading
Loading