Skip to content
Open
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
114 changes: 0 additions & 114 deletions cypress/e2e/propfind.spec.js

This file was deleted.

68 changes: 0 additions & 68 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,74 +237,6 @@ Cypress.Commands.add('getFileContent', (path) => {
.then((response) => response.data)
})

Cypress.Commands.add('propfindFolder', (path, depth = 0, properties = null) => {
const defaultProperties = `
<nc:rich-workspace />
<nc:rich-workspace-file />`

const propsXml = properties
? properties.map((p) => `<${p} />`).join('\n')
: defaultProperties

const rootPath = `${url}/remote.php/webdav/`
const requestPath = path === '/' ? rootPath : `${rootPath}${path}`

return axios
.request({
method: 'PROPFIND',
url: requestPath,
headers: {
Depth: depth,
'Content-Type': 'application/xml',
},
data: `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:"
xmlns:oc="http://owncloud.org/ns"
xmlns:nc="http://nextcloud.org/ns">
<d:prop>
${propsXml}
</d:prop>
</d:propfind>`,
})
.then((response) => {
const parser = new DOMParser()
const xmlDoc = parser.parseFromString(response.data, 'text/xml')
const responses = xmlDoc.querySelectorAll('d\\:response, response')
const results = Array.from(responses).map((resp) => {
const props = {}
const propStats = resp.querySelectorAll('d\\:propstat, propstat')
propStats.forEach((propStat) => {
const status
= propStat.querySelector('d\\:status, status')?.textContent

// Skip properties with 404 status ( not found)
if (status?.includes('404')) {
return
}

const propElements = resp.querySelectorAll('d\\:prop > *, prop > * ')

propElements.forEach((prop) => {
const tagName = prop.localName
const namespace = prop.namespaceURI

let key = tagName
if (namespace === 'http://nextcloud.org/ns') {
key = `nc:${tagName}`
} else if (namespace === 'http://owncloud.org/ns') {
key = `oc:${tagName}`
}

props[key] = prop.textContent || ''
})
})
return props
})

return depth > 0 ? results : results[0] || {}
})
})

Cypress.Commands.add('reloadFileList', () => {
cy.get('[title="Reload current directory"] button').click()
return cy.get('button').contains('Reload content').click()
Expand Down
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"@nextcloud/eslint-config": "^9.0.1",
"@nextcloud/vite-config": "^2.5.4",
"@playwright/test": "^1.62.1",
"@types/jsdom": "^30.0.0",
"@types/markdown-it": "^14.1.2",
"@types/markdown-it-footnote": "^3.0.4",
"@vitejs/plugin-vue": "^6.0.8",
Expand Down
1 change: 1 addition & 0 deletions playwright/e2e/conflict.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
await user.uploadFile({ name: file.name, content: 'Good bye' })

// Verify both verisons are shown
await expect(editor.content).toHaveText('Good bye', { timeout: 10_000 })

Check failure on line 152 in playwright/e2e/conflict.spec.ts

View workflow job for this annotation

GitHub Actions / playwright (1, 3)

[chromium] › playwright/e2e/conflict.spec.ts:140:3 › Plaintext conflict resolution › [conflict

1) [chromium] › playwright/e2e/conflict.spec.ts:140:3 › Plaintext conflict resolution › [conflict, plaintext] manual resolution after typing while online - resolve with server version Error: expect(locator).toHaveText(expected) failed Locator: locator('.editor').first().getByRole('textbox') Expected: "Good bye" Received: "Good byeHello world" Timeout: 10000ms Call log: - Expect "toHaveText" with timeout 10000ms - waiting for locator('.editor').first().getByRole('textbox') 6 × locator resolved to <div tabindex="0" role="textbox" translate="no" contenteditable="true" class="tiptap ProseMirror ProseMirror-focused">…</div> - unexpected value "Hello world" 17 × locator resolved to <div tabindex="0" role="textbox" translate="no" contenteditable="true" class="tiptap ProseMirror ProseMirror-focused">…</div> - unexpected value "Good byeHello world" 150 | 151 | // Verify both verisons are shown > 152 | await expect(editor.content).toHaveText('Good bye', { timeout: 10_000 }) | ^ 153 | await expect(reader.content).toHaveText('Hello world') 154 | 155 | // Resolve conflict at /home/runner/work/text/text/playwright/e2e/conflict.spec.ts:152:33
await expect(reader.content).toHaveText('Hello world')

// Resolve conflict
Expand Down Expand Up @@ -325,6 +325,7 @@
setOnline,
user,
}) => {
test.slow()
await expect(editor.el).toBeVisible()
const pushPromise = page.waitForRequest(/push/)
await editor.typeHeading('Long content\n')
Expand Down
103 changes: 103 additions & 0 deletions playwright/e2e/propfind.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { expect } from '@playwright/test'
import { createFolder, uploadFile } from '../support/fixtures/Node.ts'
import { test } from '../support/fixtures/random-user.ts'
import { setTextSetting } from '../support/fixtures/settings.ts'
import {
deleteWebDAVResource,
PROPERTY_WORKSPACE,
PROPERTY_WORKSPACE_FILE,
PROPERTY_WORKSPACE_FILE_FLAT,
PROPERTY_WORKSPACE_FLAT,
propfindFolder,
} from '../support/fixtures/webdav.ts'

test.describe('Text PROPFIND extension', () => {
test.describe('with workspaces enabled', () => {
test.beforeEach(async ({ user }) => {
await setTextSetting(user, 'workspace_enabled', 1)
})

test('always adds rich workspace property', async ({ page, user }) => {
const properties = [PROPERTY_WORKSPACE_FLAT, PROPERTY_WORKSPACE_FILE_FLAT]

await page.goto('/apps/dashboard')
await user.uploadFile({ name: 'Readme.md', content: '' })

const [root1] = await propfindFolder(user, '/', 0, properties)
expect(root1).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '')

// await deleteWebDAVResource(user, '/Readme.md')
await user.uploadFile({ name: 'Readme.md', content: '## Hello world\n' })
const [root2] = await propfindFolder(user, '/', 0, properties)
expect(root2).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '## Hello world\n')

Check failure on line 37 in playwright/e2e/propfind.spec.ts

View workflow job for this annotation

GitHub Actions / playwright (3, 3)

[chromium] › playwright/e2e/propfind.spec.ts:25:3 › Text PROPFIND extension › with workspaces enabled › always adds rich workspace property

2) [chromium] › playwright/e2e/propfind.spec.ts:25:3 › Text PROPFIND extension › with workspaces enabled › always adds rich workspace property Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toHaveProperty(path, value) Expected path: "nc:rich-workspace-flat" Expected value: "## Hello world " Received value: "" 35 | await user.uploadFile({ name: 'Readme.md', content: '## Hello world\n' }) 36 | const [root2] = await propfindFolder(user, '/', 0, properties) > 37 | expect(root2).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '## Hello world\n') | ^ 38 | 39 | await deleteWebDAVResource(user, '/Readme.md') 40 | const [root3] = await propfindFolder(user, '/', 0, properties) at /home/runner/work/text/text/playwright/e2e/propfind.spec.ts:37:18

Check failure on line 37 in playwright/e2e/propfind.spec.ts

View workflow job for this annotation

GitHub Actions / playwright (3, 3)

[chromium] › playwright/e2e/propfind.spec.ts:25:3 › Text PROPFIND extension › with workspaces enabled › always adds rich workspace property

2) [chromium] › playwright/e2e/propfind.spec.ts:25:3 › Text PROPFIND extension › with workspaces enabled › always adds rich workspace property Error: expect(received).toHaveProperty(path, value) Expected path: "nc:rich-workspace-flat" Expected value: "## Hello world " Received value: "" 35 | await user.uploadFile({ name: 'Readme.md', content: '## Hello world\n' }) 36 | const [root2] = await propfindFolder(user, '/', 0, properties) > 37 | expect(root2).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '## Hello world\n') | ^ 38 | 39 | await deleteWebDAVResource(user, '/Readme.md') 40 | const [root3] = await propfindFolder(user, '/', 0, properties) at /home/runner/work/text/text/playwright/e2e/propfind.spec.ts:37:18

await deleteWebDAVResource(user, '/Readme.md')
const [root3] = await propfindFolder(user, '/', 0, properties)
expect(root3).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '')
})

test('never adds rich workspace property to nested folders for flat properties', async ({ page, user }) => {
const properties = [PROPERTY_WORKSPACE_FLAT, PROPERTY_WORKSPACE_FILE_FLAT]

await page.goto('/apps/dashboard')
await createFolder({ name: 'workspace-flat', owner: user })

const results1 = await propfindFolder(user, '/', 1, properties)
const folder1 = results1.find((r) => r['d:href']?.endsWith('/workspace-flat/'))
expect(folder1).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '')

await uploadFile({ name: 'workspace-flat/Readme.md', content: '## Hello world\n', owner: user })
const results2 = await propfindFolder(user, '/', 1, properties)
const folder2 = results2.find((r) => r['d:href']?.endsWith('/workspace-flat/'))
expect(folder2).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '')
})

// Android app relies on this to detect rich workspace availability in subfolders properly
test('adds rich workspace property to nested folders for the default properties', async ({ page, user }) => {
const properties = [PROPERTY_WORKSPACE, PROPERTY_WORKSPACE_FILE]

await page.goto('/apps/dashboard')
await createFolder({ name: 'workspace', owner: user })

const results1 = await propfindFolder(user, '/', 1, properties)
const folder1 = results1.find((r) => r['d:href']?.endsWith('/workspace/'))
expect(folder1).toHaveProperty(PROPERTY_WORKSPACE, '')

await uploadFile({ name: 'workspace/Readme.md', content: '## Hello world\n', owner: user })
const results2 = await propfindFolder(user, '/', 1, properties)
const folder2 = results2.find((r) => r['d:href']?.endsWith('/workspace/'))
expect(folder2).toHaveProperty(PROPERTY_WORKSPACE, '## Hello world\n')
})
})

test.describe('with workspaces disabled', () => {
test.beforeEach(async ({ user }) => {
await setTextSetting(user, 'workspace_enabled', 0)
})

test('does not return a rich workspace property', async ({ page, user }) => {
await page.goto('/apps/dashboard')

const results1 = await propfindFolder(user, '/', 1, [PROPERTY_WORKSPACE_FLAT, PROPERTY_WORKSPACE_FILE_FLAT])
for (const result of results1) {
expect(result).not.toHaveProperty(PROPERTY_WORKSPACE_FLAT)
}

await user.uploadFile({ name: 'Readme.md', content: '## Hello world\n' })
const results2 = await propfindFolder(user, '/', 1, [PROPERTY_WORKSPACE_FLAT, PROPERTY_WORKSPACE_FILE_FLAT])
for (const result of results2) {
expect(result).not.toHaveProperty(PROPERTY_WORKSPACE_FLAT)
}

await createFolder({ name: 'without-workspace', owner: user })
const results3 = await propfindFolder(user, '/', 1)
const folder = results3.find((r) => r['d:href']?.endsWith('/without-workspace/'))
expect(folder).not.toHaveProperty(PROPERTY_WORKSPACE)
})
})
})
20 changes: 20 additions & 0 deletions playwright/support/fixtures/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { User } from './User.ts'

/**
* Set a user-level configuration value for the Text app.
*
* @param user The user to do the request
* @param key The setting key to set
* @param value The value
*/
export async function setTextSetting(user: User, key: string, value: number | string): Promise<void> {
await user.request.post('/index.php/apps/text/settings', {
data: { key, value },
failOnStatusCode: true,
})
}
Loading
Loading