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
67 changes: 56 additions & 11 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@
"markdown-it": "^14.3.0",
"markdown-it-bidi": "^0.2.0",
"markdown-it-task-checkbox": "^1.0.6",
"pinia": "^2.3.1",
"vue": "^2.7.16",
"vue-frag": "^1.4.3",
"vue-material-design-icons": "^5.3.1",
"vue-observe-visibility": "^1.0.0",
"vue-router": "^3.5.3",
"vuex": "^3.6.2"
"vue-router": "^3.5.3"
},
"devDependencies": {
"@nextcloud/babel-config": "^1.3.0",
Expand Down
70 changes: 48 additions & 22 deletions playwright/e2e/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Page } from '@playwright/test'

import { expect, test } from '@playwright/test'
import { login } from '../support/login.ts'
import { currentNoteId, newNoteButton, waitForNoteRoute } from '../support/note.ts'
import { NoteEditor } from '../support/sections/NoteEditor.ts'

function currentNoteId(page: Page): number | null {
const match = page.url().match(/\/note\/(\d+)(?:\?.*)?$/)
return match ? Number(match[1]) : null
}

async function waitForNoteRoute(page: Page, previousNoteId: number | null): Promise<number> {
await expect.poll(() => currentNoteId(page)).not.toBe(previousNoteId)

const noteId = currentNoteId(page)
if (noteId === null || noteId === previousNoteId) {
throw new Error('Expected to navigate to a note route')
}

return noteId
}

test.describe('Basic checks', () => {
test.beforeEach(async ({ page }) => {
await login(page)
Expand All @@ -38,9 +21,8 @@ test.describe('Basic checks', () => {
test('Create note and type', async ({ page }) => {
await page.goto('/index.php/apps/notes/')
const previousNoteId = currentNoteId(page)
const newNoteButton = page.getByRole('button', { name: 'New note', exact: true })
await expect(newNoteButton).toBeVisible()
await newNoteButton.click()
await expect(newNoteButton(page)).toBeVisible()
await newNoteButton(page).click()
await waitForNoteRoute(page, previousNoteId)

const editor = new NoteEditor(page)
Expand All @@ -51,7 +33,7 @@ test.describe('Basic checks', () => {
test('Open share sidebar from note actions', async ({ page }) => {
await page.goto('/index.php/apps/notes/')
const previousNoteId = currentNoteId(page)
await page.getByRole('button', { name: 'New note', exact: true }).click()
await newNoteButton(page).click()
const noteId = await waitForNoteRoute(page, previousNoteId)

const editor = new NoteEditor(page)
Expand All @@ -68,4 +50,48 @@ test.describe('Basic checks', () => {
await expect(page.locator('[data-cy-notes-share-sidebar]')).toBeVisible({ timeout: 15000 })
await expect(page.getByText('Internal shares')).toBeVisible({ timeout: 15000 })
})

test('persists note content after a reload', async ({ page }) => {
await page.goto('/index.php/apps/notes/')
const previousNoteId = currentNoteId(page)
await newNoteButton(page).click()
const noteId = await waitForNoteRoute(page, previousNoteId)

const editor = new NoteEditor(page)
const content = `Persistence check ${Date.now()}`

const savedResponse = page.waitForResponse((response) => response.url().includes(`/notes/${noteId}`)
&& response.request().method() === 'PUT')
await editor.type(content)
await savedResponse

await page.reload()
await expect(page).toHaveURL(new RegExp(`/note/${noteId}(\\?.*)?$`))
await editor.expectText(content)
})

test('filters notes with the search field', async ({ page }) => {
await page.goto('/index.php/apps/notes/')

const uniqueWord = `Findme${Date.now()}`
const previousNoteId = currentNoteId(page)
await newNoteButton(page).click()
const noteId = await waitForNoteRoute(page, previousNoteId)

const editor = new NoteEditor(page)
await editor.type(uniqueWord)
await editor.expectText(uniqueWord)

const noteLink = page.locator(`a[href$="/note/${noteId}"]`).first()

const searchField = page.getByRole('textbox', { name: 'Search for notes', exact: true })
await searchField.fill('this text matches no note at all')
await expect(noteLink).toHaveCount(0)

await searchField.fill(uniqueWord)
await expect(noteLink).toBeVisible()

await searchField.fill('')
await expect(noteLink).toBeVisible()
})
})
61 changes: 30 additions & 31 deletions playwright/e2e/category-actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Locator, Page, TestInfo } from '@playwright/test'

import { expect, test } from '@playwright/test'
import { login } from '../support/login.ts'
import { createNote, currentNoteId, newNoteButton, noteRow, uniqueTitle, waitForNoteRoute } from '../support/note.ts'

function appNavigation(page: Page): Locator {
return page.getByRole('navigation').filter({
Expand All @@ -18,10 +19,6 @@ function newCategoryButton(page: Page): Locator {
return page.getByRole('button', { name: 'New category', exact: true })
}

function contentNewNoteButton(page: Page): Locator {
return page.getByRole('button', { name: 'New note', exact: true })
}

function notesSearchField(page: Page): Locator {
return page.getByRole('textbox', { name: 'Search for notes', exact: true })
}
Expand All @@ -46,20 +43,11 @@ async function expectNavigationItemActive(page: Page, name: string): Promise<voi
await expect(navigationLink(page, name)).toHaveAttribute('aria-current', 'page')
}

function uniqueCategoryName(prefix: string, testInfo: TestInfo): string {
return `Playwright ${prefix} ${testInfo.parallelIndex}-${Date.now()}`
}

function currentNoteId(page: Page): number | null {
const match = page.url().match(/\/note\/(\d+)(?:\?.*)?$/)
return match ? Number(match[1]) : null
}

async function openNotesApp(page: Page): Promise<void> {
await page.goto('/index.php/apps/notes/')
await expect(newCategoryButton(page)).toBeVisible()
await expect(contentNewNoteButton(page)).toHaveCount(1)
await expect(contentNewNoteButton(page)).toBeVisible()
await expect(newNoteButton(page)).toHaveCount(1)
await expect(newNoteButton(page)).toBeVisible()
}

async function createCategory(page: Page, name: string): Promise<void> {
Expand All @@ -76,25 +64,14 @@ async function createCategory(page: Page, name: string): Promise<void> {
await expectNavigationItemActive(page, name)
}

async function waitForNewNoteRoute(page: Page, previousNoteId: number | null): Promise<number> {
await expect.poll(() => currentNoteId(page)).not.toBe(previousNoteId)

const noteId = currentNoteId(page)
if (noteId === null || noteId === previousNoteId) {
throw new Error('Expected a new note route after creating a note')
}

return noteId
}

async function ensureNotesView(page: Page): Promise<void> {
if (await notesSearchField(page).isVisible()) {
return
}

const previousNoteId = currentNoteId(page)
await contentNewNoteButton(page).click()
await waitForNewNoteRoute(page, previousNoteId)
await newNoteButton(page).click()
await waitForNoteRoute(page, previousNoteId)
await expect(notesSearchField(page)).toBeVisible()
}

Expand All @@ -104,7 +81,7 @@ async function createNoteInSelectedCategory(page: Page, category: string): Promi
const previousNoteId = currentNoteId(page)
await notesViewNewNoteButton(page).click()

const noteId = await waitForNewNoteRoute(page, previousNoteId)
const noteId = await waitForNoteRoute(page, previousNoteId)
await expect(navigationRow(page, category).locator('.app-navigation-entry__counter-wrapper')).toContainText('1')

return noteId
Expand All @@ -128,7 +105,7 @@ test.describe('Category actions', () => {
})

test('renames a category from the actions menu', async ({ page }, testInfo: TestInfo) => {
const category = uniqueCategoryName('rename', testInfo)
const category = uniqueTitle('rename', testInfo)
const renamedCategory = `${category} renamed`
const noteId = await (async () => {
await createCategory(page, category)
Expand All @@ -151,7 +128,7 @@ test.describe('Category actions', () => {
})

test('deletes a category from the actions menu', async ({ page }, testInfo: TestInfo) => {
const category = uniqueCategoryName('delete', testInfo)
const category = uniqueTitle('delete', testInfo)
await createCategory(page, category)
const noteId = await createNoteInSelectedCategory(page, category)
const deletedNoteUrl = new RegExp(`/note/${noteId}(\\?.*)?$`)
Expand All @@ -169,3 +146,25 @@ test.describe('Category actions', () => {
await expect(page).not.toHaveURL(deletedNoteUrl)
})
})

test.describe('Drag and drop', () => {
test.beforeEach(async ({ page }) => {
await login(page)
await page.goto('/index.php/apps/notes/')
await expect(newCategoryButton(page)).toBeVisible()
})

test('moves a note into a category by dragging it', async ({ page }, testInfo: TestInfo) => {
const category = uniqueTitle('drag-target', testInfo)
const title = uniqueTitle('drag-note', testInfo)

await createCategory(page, category)
const noteId = await createNote(page, title)

await noteRow(page, noteId).dragTo(navigationRow(page, category))

await expect(navigationRow(page, category).locator('.app-navigation-entry__counter-wrapper')).toContainText('1')
await navigationRow(page, category).getByRole('link').click()
await expect(noteRow(page, noteId)).toBeVisible()
})
})
Loading
Loading