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
102 changes: 102 additions & 0 deletions playwright/e2e/propfind.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* 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 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 36 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

1) [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: "" 34 | await user.uploadFile({ name: 'Readme.md', content: '## Hello world\n' }) 35 | const [root2] = await propfindFolder(user, '/', 0, properties) > 36 | expect(root2).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '## Hello world\n') | ^ 37 | 38 | await deleteWebDAVResource(user, '/Readme.md') 39 | const [root3] = await propfindFolder(user, '/', 0, properties) at /home/runner/work/text/text/playwright/e2e/propfind.spec.ts:36:18

Check failure on line 36 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

1) [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: "" 34 | await user.uploadFile({ name: 'Readme.md', content: '## Hello world\n' }) 35 | const [root2] = await propfindFolder(user, '/', 0, properties) > 36 | expect(root2).toHaveProperty(PROPERTY_WORKSPACE_FLAT, '## Hello world\n') | ^ 37 | 38 | await deleteWebDAVResource(user, '/Readme.md') 39 | const [root3] = await propfindFolder(user, '/', 0, properties) at /home/runner/work/text/text/playwright/e2e/propfind.spec.ts:36: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