refactor: Migrate component tests to Playwright
This contains all normal component tests Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Родитель
17c3098658
Коммит
803f317f45
|
@ -11,9 +11,6 @@
|
|||
"extends": [
|
||||
"@nextcloud/eslint-config/vue3"
|
||||
],
|
||||
"plugins": [
|
||||
"cypress"
|
||||
],
|
||||
"parserOptions": {
|
||||
"babelOptions": {
|
||||
"plugins": [
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: Command update Playwright snapshots
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
init:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# On pull requests and if the comment starts with `/update-snapshots`
|
||||
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/update-snapshots')
|
||||
|
||||
outputs:
|
||||
arg1: ${{ steps.command.outputs.arg1 }}
|
||||
head_ref: ${{ steps.comment-branch.outputs.head_ref }}
|
||||
|
||||
steps:
|
||||
- name: Add reaction on start
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reaction-type: "+1"
|
||||
|
||||
- name: Parse command
|
||||
uses: skjnldsv/parse-command-comment@master
|
||||
id: command
|
||||
|
||||
- name: Init branch
|
||||
uses: xt0rted/pull-request-comment-branch@v2
|
||||
id: comment-branch
|
||||
|
||||
process:
|
||||
runs-on: ubuntu-latest
|
||||
needs: init
|
||||
|
||||
steps:
|
||||
- name: Checkout ${{ needs.init.outputs.head_ref }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
fetch-depth: 0
|
||||
ref: ${{ needs.init.outputs.head_ref }}
|
||||
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --local user.email "nextcloud-command@users.noreply.github.com"
|
||||
git config --local user.name "nextcloud-command"
|
||||
|
||||
- name: Read package.json node and npm engines version
|
||||
uses: skjnldsv/read-package-engines-version-actions@v3
|
||||
id: package-engines-versions
|
||||
with:
|
||||
fallbackNode: '^20'
|
||||
fallbackNpm: '^10'
|
||||
|
||||
- name: Set up node ${{ steps.package-engines-versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ steps.package-engines-versions.outputs.nodeVersion }}
|
||||
cache: npm
|
||||
|
||||
- name: Set up npm ${{ steps.package-engines-versions.outputs.npmVersion }}
|
||||
run: npm i -g npm@"${{ steps.package-engines-versions.outputs.npmVersion }}"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Update snapshots
|
||||
run: npm run update:snapshots
|
||||
|
||||
- name: Commit and push default
|
||||
if: ${{ needs.init.outputs.arg1 != 'fixup' && needs.init.outputs.arg1 != 'amend' }}
|
||||
run: |
|
||||
git add .
|
||||
git commit --signoff -m 'Updating l10n asset'
|
||||
git push origin ${{ needs.init.outputs.head_ref }}
|
||||
|
||||
- name: Commit and push fixup
|
||||
if: ${{ needs.init.outputs.arg1 == 'fixup' }}
|
||||
run: |
|
||||
git add .
|
||||
git commit --fixup=HEAD --signoff
|
||||
git push origin ${{ needs.init.outputs.head_ref }}
|
||||
|
||||
- name: Commit and push amend
|
||||
if: ${{ needs.init.outputs.arg1 == 'amend' }}
|
||||
run: |
|
||||
git add .
|
||||
git commit --amend --no-edit --signoff
|
||||
git push --force origin ${{ needs.init.outputs.head_ref }}
|
||||
|
||||
- name: Add reaction on failure
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
if: failure()
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reaction-type: "-1"
|
|
@ -1,71 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: Cypress
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
env:
|
||||
APP_NAME: nextcloud-vue
|
||||
|
||||
jobs:
|
||||
cypress:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# run x copies of the current job in parallel
|
||||
containers: [1, 2]
|
||||
|
||||
name: Runner ${{ matrix.containers }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Read package.json node and npm engines version
|
||||
uses: skjnldsv/read-package-engines-version-actions@v3
|
||||
id: versions
|
||||
with:
|
||||
fallbackNode: '^20'
|
||||
fallbackNpm: '^9'
|
||||
|
||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
||||
|
||||
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
|
||||
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v6
|
||||
with:
|
||||
component: true
|
||||
install: false # we have already installed all dependencies above
|
||||
record: true
|
||||
parallel: true
|
||||
# cypress dashboard env
|
||||
tag: ${{ github.event_name }}
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
# https://github.com/cypress-io/github-action/issues/124
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||
# https://github.com/cypress-io/github-action/issues/524
|
||||
npm_package_name: ${{ env.APP_NAME }}
|
||||
|
||||
- name: Upload snapshots
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: snapshots-${{ matrix.containers }}
|
||||
path: cypress/snapshots
|
|
@ -0,0 +1,109 @@
|
|||
# SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: Playwright Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, next ]
|
||||
pull_request:
|
||||
branches: [ main, next ]
|
||||
jobs:
|
||||
playwright-tests:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2]
|
||||
shardTotal: [2]
|
||||
|
||||
outputs:
|
||||
nodeVersion: ${{ steps.versions.outputs.nodeVersion }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Read package.json node and npm engines version
|
||||
uses: skjnldsv/read-package-engines-version-actions@v3
|
||||
id: versions
|
||||
with:
|
||||
fallbackNode: '^20'
|
||||
fallbackNpm: '^10'
|
||||
|
||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
||||
|
||||
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
|
||||
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: npm run test:component -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: blob-report-${{ matrix.shardIndex }}
|
||||
path: blob-report
|
||||
retention-days: 1
|
||||
|
||||
merge-reports:
|
||||
# Merge reports after playwright-tests, even if some shards have failed
|
||||
if: ${{ !cancelled() }}
|
||||
needs: [playwright-tests]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ needs.playwright-tests.outputs.nodeVersion }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: all-blob-reports
|
||||
pattern: blob-report-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Merge into HTML Report
|
||||
run: npx playwright merge-reports --reporter html,github ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: html-report--attempt-${{ github.run_attempt }}
|
||||
path: playwright-report
|
||||
retention-days: 7
|
||||
|
||||
- name: Show the logs
|
||||
run: |
|
||||
echo 'To view the report:'
|
||||
echo ' 1. Extract the folder from the zip file'
|
||||
echo ' 2. run "npx playwright show-report name-of-my-extracted-playwright-report"'
|
||||
|
||||
summary:
|
||||
permissions:
|
||||
contents: none
|
||||
runs-on: ubuntu-latest
|
||||
needs: [playwright-tests]
|
||||
|
||||
if: always()
|
||||
|
||||
name: playwright-test-summary
|
||||
|
||||
steps:
|
||||
- name: Summary status
|
||||
run: if ${{ needs.playwright-tests.result != 'success' }}; then exit 1; fi
|
|
@ -27,9 +27,8 @@ coverage
|
|||
styleguide/build/
|
||||
styleguide/index.html
|
||||
|
||||
# Cypress files
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
cypress/snapshots/actual
|
||||
cypress/snapshots/diff
|
||||
cypress/snapshots/base/**/*-base-*
|
||||
# Playwright files
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/tests/component/setup/.cache/
|
||||
|
|
|
@ -12,7 +12,11 @@ SPDX-FileCopyrightText = "Nextcloud GmbH and Nextcloud contributors"
|
|||
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
||||
|
||||
[[annotations]]
|
||||
path = ["styleguide/assets/dark.css", "styleguide/assets/icons.css", "styleguide/assets/server.css", "cypress/snapshots/**", "tests/unit/functions/usernameToColor/__snapshots__/usernameToColor.spec.js.snap"]
|
||||
path = [
|
||||
"styleguide/assets/apps.css", "styleguide/assets/dark.css", "styleguide/assets/icons.css", "styleguide/assets/server.css",
|
||||
"tests/unit/functions/usernameToColor/__snapshots__/usernameToColor.spec.js.snap",
|
||||
"tests/component/snapshots/**"
|
||||
]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "Nextcloud GmbH and Nextcloud contributors"
|
||||
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"cypress/globals": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:cypress/recommended"
|
||||
]
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { h } from 'vue'
|
||||
import NcAppNavigation from '../../src/components/NcAppNavigation/NcAppNavigation.vue'
|
||||
import NcAppNavigationItem from '../../src/components/NcAppNavigationItem/NcAppNavigationItem.vue'
|
||||
import NcAppNavigationSpacer from '../../src/components/NcAppNavigationSpacer/NcAppNavigationSpacer.vue'
|
||||
|
||||
describe('NcAppNavigationSpacer', () => {
|
||||
it('works', () => {
|
||||
cy.mount({
|
||||
render: () => h(NcAppNavigation, null, {
|
||||
list: () => [
|
||||
h(NcAppNavigationItem, { name: 'First' }),
|
||||
h(NcAppNavigationSpacer),
|
||||
h(NcAppNavigationItem, { name: 'Second' }),
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
cy.contains('li', 'First').should('exist').then(($first) => {
|
||||
cy.contains('li', 'Second').should('exist').then(($second) => {
|
||||
// Check that the second element is at least 22px below the first one (thats our spacer)
|
||||
expect($second.offset()!.top - 22).gte($first.offset()!.top + $first.height()!)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { defineComponent, h } from 'vue'
|
||||
|
||||
import NcAppSettingsDialog from '../../src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue'
|
||||
import NcAppSettingsSection from '../../src/components/NcAppSettingsSection/NcAppSettingsSection.vue'
|
||||
|
||||
describe('NcAppSettingsDialog', () => {
|
||||
it('Dialog is correctly labelled', () => {
|
||||
cy.mount(NcAppSettingsDialog, {
|
||||
propsData: {
|
||||
open: true,
|
||||
name: 'My settings dialog',
|
||||
},
|
||||
slots: {
|
||||
default: defineComponent({
|
||||
render: () => h(NcAppSettingsSection, { props: { name: 'First section', id: 'first' } }),
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByRole('dialog', { name: 'My settings dialog' }).should('exist')
|
||||
})
|
||||
|
||||
it('Dialog sections are correctly labelled', () => {
|
||||
cy.mount(NcAppSettingsDialog, {
|
||||
propsData: {
|
||||
open: true,
|
||||
name: 'My settings dialog',
|
||||
showNavigation: true,
|
||||
},
|
||||
slots: {
|
||||
default: defineComponent({
|
||||
render: () => h(NcAppSettingsSection, { props: { name: 'First section', id: 'first' } }, ['The section content']),
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByRole('dialog', { name: 'My settings dialog' }).should('exist')
|
||||
cy.findByRole('dialog', { name: 'My settings dialog' })
|
||||
.findByRole('region', { name: 'First section' })
|
||||
.should('exist')
|
||||
.and('contain.text', 'The section content')
|
||||
})
|
||||
})
|
|
@ -1,18 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { mount } from 'cypress/vue'
|
||||
import NcButton from '../../src/components/NcButton/NcButton.vue'
|
||||
|
||||
describe('NcButton', () => {
|
||||
it('Text is rendered when provided', () => {
|
||||
mount(NcButton, {
|
||||
slots: {
|
||||
default: 'Nextcloud',
|
||||
},
|
||||
})
|
||||
cy.get('span.button-vue__text').should('have.text', 'Nextcloud')
|
||||
})
|
||||
})
|
|
@ -1,22 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import NcDialog from '../../src/components/NcDialog/NcDialog.vue'
|
||||
|
||||
describe('NcDialog', () => {
|
||||
it('Dialog is correctly labelled', () => {
|
||||
cy.mount(NcDialog, {
|
||||
props: {
|
||||
show: true,
|
||||
name: 'My dialog',
|
||||
},
|
||||
slots: {
|
||||
default: 'Text',
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByRole('dialog', { name: 'My dialog' }).should('exist')
|
||||
})
|
||||
})
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Component } from 'vue'
|
||||
|
||||
import { h } from 'vue'
|
||||
import NcModal from '../../src/components/NcModal/NcModal.vue'
|
||||
|
||||
|
||||
describe('NcModal', () => {
|
||||
it('Modal is labelled correctly if name is set', () => {
|
||||
cy.mount(NcModal, {
|
||||
props: {
|
||||
show: true,
|
||||
name: 'My modal',
|
||||
size: 'small',
|
||||
},
|
||||
slots: {
|
||||
default: 'Text',
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByRole('dialog', { name: 'My modal' }).should('exist')
|
||||
})
|
||||
|
||||
it('Modal is labelled correctly if `labelId` is set', () => {
|
||||
cy.mount(NcModal, {
|
||||
props: {
|
||||
show: true,
|
||||
size: 'small',
|
||||
labelId: 'my-id',
|
||||
},
|
||||
slots: {
|
||||
default: '<h2 id="my-id">Labelled dialog</h2>',
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByRole('dialog', { name: 'Labelled dialog' }).should('exist')
|
||||
})
|
||||
|
||||
it('Modal is labelled correctly if `labelId` and `name` are set', () => {
|
||||
cy.mount(NcModal, {
|
||||
props: {
|
||||
show: true,
|
||||
size: 'small',
|
||||
name: 'My modal',
|
||||
labelId: 'my-id',
|
||||
},
|
||||
slots: {
|
||||
default: '<h2 id="my-id">Real name</h2>',
|
||||
},
|
||||
})
|
||||
|
||||
cy.findByRole('dialog', { name: 'Real name' }).should('exist')
|
||||
})
|
||||
|
||||
it('close button is visible when content is scrolled', () => {
|
||||
cy.mount(NcModal, {
|
||||
props: {
|
||||
show: true,
|
||||
size: 'small',
|
||||
name: 'Name',
|
||||
},
|
||||
slots: {
|
||||
// Create two div as children, first is 100vh = overflows the content, second just gets some data attribute so we can scroll into view
|
||||
default: {
|
||||
render: () =>
|
||||
h('div', [
|
||||
h('div', { style: 'height: 100vh;' }),
|
||||
h('div', { 'data-cy': 'bottom' }),
|
||||
]),
|
||||
} as Component,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('[data-cy="bottom"]').scrollIntoView()
|
||||
cy.get('button.modal-container__close').should('be.visible')
|
||||
})
|
||||
})
|
|
@ -1,79 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { mount } from 'cypress/vue'
|
||||
import NcSelect from '../../src/components/NcSelect/NcSelect.vue'
|
||||
|
||||
describe('NcSelect', () => {
|
||||
const mountSelect = () => mount(NcSelect, {
|
||||
props: {
|
||||
userSelect: true,
|
||||
inputClass: 'cypress-search-input',
|
||||
options: [
|
||||
{
|
||||
id: '0-john',
|
||||
displayName: 'John',
|
||||
isNoUser: false,
|
||||
subname: 'john@example.org',
|
||||
icon: '',
|
||||
},
|
||||
{
|
||||
id: '0-emma',
|
||||
displayName: 'Emma',
|
||||
isNoUser: false,
|
||||
subname: 'emma@example.org',
|
||||
icon: '',
|
||||
},
|
||||
{
|
||||
id: '0-olivia',
|
||||
displayName: 'Olivia',
|
||||
isNoUser: false,
|
||||
subname: 'olivia@example.org',
|
||||
icon: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
it('has options', () => {
|
||||
mountSelect()
|
||||
|
||||
cy.get('.select').click()
|
||||
cy.contains('.option', 'Olivia').should('exist')
|
||||
cy.contains('.option', 'John').should('exist')
|
||||
cy.contains('.option', 'Emma').should('exist')
|
||||
})
|
||||
|
||||
it('can filter by name', () => {
|
||||
mountSelect()
|
||||
|
||||
cy.get('.cypress-search-input').scrollIntoView().type('Em')
|
||||
cy.contains('.option', 'Emma').should('exist')
|
||||
cy.document().find('.option').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('can filter by mail', () => {
|
||||
mountSelect()
|
||||
|
||||
cy.get('.cypress-search-input').scrollIntoView().type('olivia@example')
|
||||
cy.contains('.option', 'Olivia').should('exist')
|
||||
cy.document().find('.option').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('can filter by mail in ticks', () => {
|
||||
mountSelect()
|
||||
|
||||
// Until this it should not be visible
|
||||
cy.get('.cypress-search-input').clear().type('O. <')
|
||||
cy.contains('.option', 'Olivia').should('not.exist')
|
||||
// now it should match
|
||||
cy.get('.cypress-search-input').type('olivia')
|
||||
cy.contains('.option', 'Olivia').should('exist')
|
||||
// and with full search query it should also exist
|
||||
cy.get('.cypress-search-input').type('@example.org>')
|
||||
cy.contains('.option', 'Olivia').should('exist')
|
||||
cy.document().find('.option').should('have.length', 1)
|
||||
})
|
||||
})
|
|
@ -1,632 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Markdown guide: https://www.markdownguide.org/basic-syntax/
|
||||
// Reference tests: https://github.com/nextcloud-deps/CDMarkdownKit/tree/master/CDMarkdownKitTests
|
||||
|
||||
import { mount } from 'cypress/vue'
|
||||
import NcRichText from '../../src/components/NcRichText/NcRichText.vue'
|
||||
|
||||
describe('NcRichText', () => {
|
||||
describe('renders with markdown', () => {
|
||||
describe('normal text', () => {
|
||||
it('XML-like text (escaped and unescaped)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '<span>text</span>',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('p').should('have.text', '<span>text</span>')
|
||||
})
|
||||
})
|
||||
|
||||
describe('headings', () => {
|
||||
it('heading (with hash (#) syntax divided with space from text)', () => {
|
||||
const testCases = [
|
||||
{ tag: 'h1', input: '# heading 1', output: 'heading 1' },
|
||||
{ tag: 'h2', input: '## heading 2', output: 'heading 2' },
|
||||
{ tag: 'h3', input: '### heading 3', output: 'heading 3' },
|
||||
{ tag: 'h4', input: '#### heading 4', output: 'heading 4' },
|
||||
{ tag: 'h5', input: '##### heading 5', output: 'heading 5' },
|
||||
{ tag: 'h6', input: '###### heading 6', output: 'heading 6' },
|
||||
]
|
||||
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
testCases.forEach((item) => {
|
||||
cy.get(item.tag).should('have.text', item.output)
|
||||
})
|
||||
})
|
||||
|
||||
it('ignored heading (with hash (#) syntax padded to the text)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '#heading',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('h1').should('not.exist')
|
||||
})
|
||||
|
||||
it('heading 1 (with equal (=) syntax on the next line)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'heading 1\n==',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('h1').should('have.text', 'heading 1')
|
||||
})
|
||||
|
||||
it('heading 2 (with dash (-) syntax on the next line)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'heading 2\n--',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('h2').should('have.text', 'heading 2')
|
||||
})
|
||||
})
|
||||
|
||||
describe('bold text', () => {
|
||||
it('bold text (single with asterisk syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '**bold asterisk**',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('strong').should('have.text', 'bold asterisk')
|
||||
})
|
||||
|
||||
it('bold text (single with underscore syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '__bold underscore__',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('strong').should('have.text', 'bold underscore')
|
||||
})
|
||||
|
||||
it('bold text (several in line with different syntax)', () => {
|
||||
const outputs = ['bold underscore', 'bold asterisk']
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text __bold underscore__ normal text **bold asterisk** normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('strong').each((item, index, list) => {
|
||||
expect(list).have.length(2)
|
||||
expect(item).have.text(outputs[index])
|
||||
})
|
||||
})
|
||||
|
||||
it('bold text (between normal texts with asterisk syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text**bold**text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('strong').should('have.text', 'bold')
|
||||
})
|
||||
|
||||
it('ignored bold text (between normal texts with underscore syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text__bold__text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('strong').should('not.exist')
|
||||
})
|
||||
|
||||
it('normal text (between bold texts with asterisk syntax)', () => {
|
||||
const outputs = ['bold asterisk', 'bold asterisk']
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '**bold asterisk**normal text**bold asterisk**',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('strong').each((item, index, list) => {
|
||||
expect(list).have.length(2)
|
||||
expect(item).have.text(outputs[index])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('italic text', () => {
|
||||
it('italic text (single with asterisk syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '*italic asterisk*',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('em').should('have.text', 'italic asterisk')
|
||||
})
|
||||
|
||||
it('italic text (single with underscore syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '_italic underscore_',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('em').should('have.text', 'italic underscore')
|
||||
})
|
||||
|
||||
it('italic text (several in line with different syntax)', () => {
|
||||
const outputs = ['italic underscore', 'italic asterisk']
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text _italic underscore_ normal text *italic asterisk* normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('em').each((item, index, list) => {
|
||||
expect(list).have.length(2)
|
||||
expect(item).have.text(outputs[index])
|
||||
})
|
||||
})
|
||||
|
||||
it('italic text (between normal texts with asterisk syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text*italic*text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('em').should('have.text', 'italic')
|
||||
})
|
||||
|
||||
it('ignored italic text (between normal texts with underscore syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text_italic_text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('em').should('not.exist')
|
||||
})
|
||||
|
||||
it('normal text (between italic texts with asterisk syntax)', () => {
|
||||
const outputs = ['italic asterisk', 'italic asterisk']
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '*italic asterisk*normal text*italic asterisk*',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('em').each((item, index, list) => {
|
||||
expect(list).have.length(2)
|
||||
expect(item).have.text(outputs[index])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('strikethrough text', () => {
|
||||
it('strikethrough text (with single tilda syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '~strikethrough single~',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('del').should('have.text', 'strikethrough single')
|
||||
})
|
||||
|
||||
it('strikethrough text (with double tilda syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '~~strikethrough double~~',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('del').should('have.text', 'strikethrough double')
|
||||
})
|
||||
|
||||
it('strikethrough text (several in line with different syntax)', () => {
|
||||
const outputs = ['strikethrough single', 'strikethrough double']
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text ~strikethrough single~ normal text ~~strikethrough double~~ normal text',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('del').should('have.length', 2)
|
||||
cy.get('del').each((item, index) => {
|
||||
expect(item).have.text(outputs[index])
|
||||
})
|
||||
})
|
||||
|
||||
it('strikethrough text (between normal texts with different syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text~strikethrough~text~~strikethrough~~text',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('del').should('have.length', 2)
|
||||
cy.get('del').each((item) => {
|
||||
expect(item).have.text('strikethrough')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('inline code', () => {
|
||||
it('inline code (single with backticks syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text `inline code` normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('code').should('have.text', 'inline code')
|
||||
})
|
||||
|
||||
it('inline code (single with double backticks syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text ``inline code`` normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('code').should('have.text', 'inline code')
|
||||
})
|
||||
|
||||
it('inline code (single with triple backticks syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text ```inline code``` normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('code').should('have.text', 'inline code')
|
||||
})
|
||||
|
||||
it('inline code (several in line )', () => {
|
||||
const outputs = ['inline code 1', 'inline code 2']
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text `inline code 1`normal text ``inline code 2`` normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('code').each((item, index, list) => {
|
||||
expect(list).have.length(2)
|
||||
expect(item).have.text(outputs[index])
|
||||
})
|
||||
})
|
||||
|
||||
it('inline code (between normal texts)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text`inline code`text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('code').should('have.text', 'inline code')
|
||||
})
|
||||
|
||||
it('inline code (with ignored bold, italic, XML-like syntax))', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '`inline code **bold text** _italic text_ <span>text</span>`',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('code').should('have.text', 'inline code **bold text** _italic text_ <span>text</span>')
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiline code', () => {
|
||||
it('multiline code (with triple backticks syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '```\nmultiline code\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('pre').should('have.text', 'multiline code\n')
|
||||
})
|
||||
|
||||
it('multiline code (ignored info)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '```vue\nmultiline code\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('pre').should('have.text', 'multiline code\n')
|
||||
})
|
||||
|
||||
it('empty multiline code', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '``````',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('pre').should('have.text', '')
|
||||
})
|
||||
|
||||
it('empty multiline code (with new line)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '```\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('pre').should('have.text', '')
|
||||
})
|
||||
|
||||
it('multiline code (with several lines)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '```\nline 1\nline 2\nline 3\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('pre').should('have.text', 'line 1\nline 2\nline 3\n')
|
||||
cy.get('code').should('have.text', 'line 1\nline 2\nline 3\n')
|
||||
})
|
||||
|
||||
it('multiline code (with ignored bold, italic, inline code, XML-like syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '```\n**bold text**\n_italic text_\n`inline code`\n<span>text</span>\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('pre').should('have.text', '**bold text**\n_italic text_\n`inline code`\n<span>text</span>\n')
|
||||
})
|
||||
})
|
||||
|
||||
describe('blockquote', () => {
|
||||
it('blockquote (with greater then (>) syntax - normal)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '> blockquote',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('blockquote').should('have.text', '\nblockquote\n')
|
||||
})
|
||||
|
||||
it('blockquote (with greater then (>) syntax - escaped)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '> blockquote',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('blockquote').should('have.text', '\nblockquote\n')
|
||||
})
|
||||
|
||||
it('blockquote (with bold, italic text, inline code)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '> blockquote **bold text** _italic text_ `inline code`',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('blockquote').should('have.text', '\nblockquote bold text italic text inline code\n')
|
||||
cy.get('strong').should('have.text', 'bold text')
|
||||
cy.get('em').should('have.text', 'italic text')
|
||||
cy.get('code').should('have.text', 'inline code')
|
||||
})
|
||||
|
||||
it('blockquote (with several lines)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '> line 1\nline 2\n line 3',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('blockquote').should('have.text', '\nline 1\nline 2\nline 3\n')
|
||||
})
|
||||
|
||||
it('blockquote (divided from normal text)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text\n> line 1\nline 2\n\nnormal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('blockquote').should('have.text', '\nline 1\nline 2\n')
|
||||
})
|
||||
|
||||
it('blockquote (with several paragraphs)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '> line 1\n>\n> line 3',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('blockquote').should('have.text', '\nline 1\nline 3\n')
|
||||
})
|
||||
|
||||
it('blockquote (with nested blockquote)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '> blockquote\n>\n>> nested blockquote',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('blockquote').children('blockquote').should('have.text', '\nnested blockquote\n')
|
||||
})
|
||||
})
|
||||
|
||||
describe('lists', () => {
|
||||
it('ordered list (with number + `.` syntax divided with space from text)', () => {
|
||||
const testCases = [
|
||||
{ input: '1. item 1', output: 'item 1' },
|
||||
{ input: '2. item 2', output: 'item 2' },
|
||||
{ input: '3. item 3', output: 'item 3' },
|
||||
]
|
||||
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('ol').should('exist')
|
||||
cy.get('li').each((item, index, list) => {
|
||||
expect(list).have.length(testCases.length)
|
||||
expect(item).have.text(testCases[index].output)
|
||||
})
|
||||
})
|
||||
|
||||
it('unordered list (with unite syntax divided with space from text)', () => {
|
||||
const testCases = [
|
||||
{ input: '* item 1', output: 'item 1' },
|
||||
{ input: '* item 2', output: 'item 2' },
|
||||
{ input: '* item 3', output: 'item 3' },
|
||||
]
|
||||
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('ul').should('exist')
|
||||
cy.get('li').each((item, index, list) => {
|
||||
expect(list).have.length(testCases.length)
|
||||
expect(item).have.text(testCases[index].output)
|
||||
})
|
||||
})
|
||||
|
||||
it('unordered lists (with different syntax divided with space from text)', () => {
|
||||
const testCases = [
|
||||
{ input: '* item 1', output: 'item 1' },
|
||||
{ input: '+ item 2', output: 'item 2' },
|
||||
{ input: '- item 3', output: 'item 3' },
|
||||
]
|
||||
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('ul').each((item, index, list) => {
|
||||
expect(list).have.length(testCases.length)
|
||||
expect(item).have.text('\n' + testCases[index].output + '\n')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('task lists', () => {
|
||||
it('task list (with `- [ ]` and `- [x]` syntax divided with space from text)', () => {
|
||||
const testCases = [
|
||||
{ input: '- [ ] item 1', output: 'item 1', checked: false },
|
||||
{ input: '- [x] item 2', output: 'item 2', checked: true },
|
||||
{ input: '- [ ] item 3', output: 'item 3', checked: false },
|
||||
]
|
||||
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('ul').should('exist')
|
||||
cy.get('li').should('have.length', testCases.length)
|
||||
cy.get('li').each((item, index) => {
|
||||
// Vue 2.7 renders three non-breaking spaces here for some reason
|
||||
expect(item).contain(testCases[index].output)
|
||||
})
|
||||
cy.get('input:checked').should('have.length', testCases.filter(test => test.checked).length)
|
||||
})
|
||||
})
|
||||
|
||||
describe('tables', () => {
|
||||
it('table (with `-- | --` syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: 'Table | Column A | Column B\n-- | -- | --\nRow 1 | Value A1 | Value B1\nRow 2 | Value A2 | Value B2',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('table').should('exist')
|
||||
cy.get('thead').should('exist')
|
||||
cy.get('tbody').should('exist')
|
||||
cy.get('tr').should('have.length', 3)
|
||||
cy.get('th').should('have.length', 3)
|
||||
cy.get('td').should('have.length', 6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dividers', () => {
|
||||
it('dividers (with different syntax)', () => {
|
||||
mount(NcRichText, {
|
||||
props: {
|
||||
text: '***\n---\n___',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('hr').should('have.length', 3)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addCompareSnapshotCommand } from 'cypress-visual-regression/dist/command'
|
||||
import { mount } from 'cypress/vue'
|
||||
import '@testing-library/cypress/add-commands'
|
||||
|
||||
addCompareSnapshotCommand()
|
||||
|
||||
// Augment the Cypress namespace to include type definitions for
|
||||
// your custom command.
|
||||
// Alternatively, can be defined in cypress/support/component.d.ts
|
||||
// with a <reference path="./component" /> at the top of your spec.
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
mount: typeof mount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example use:
|
||||
// cy.mount(MyComponent)
|
||||
Cypress.Commands.add('mount', mount)
|
|
@ -1,16 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
- SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Components App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div data-cy-root></div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// register commands
|
||||
import './commands.ts'
|
||||
|
||||
// setup styles
|
||||
import '../../styleguide/assets/default.css'
|
||||
import '../../styleguide/assets/additional.css'
|
||||
import '../../styleguide/assets/icons.css'
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"exclude": [],
|
||||
"include": ["./**/*.ts"],
|
||||
"compilerOptions": {
|
||||
"rootDir": "..",
|
||||
"types": [
|
||||
"cypress",
|
||||
"cypress-visual-regression",
|
||||
"@testing-library/cypress"
|
||||
]
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
22
package.json
22
package.json
|
@ -23,13 +23,13 @@
|
|||
"lint:fix": "eslint src --fix",
|
||||
"test": "TZ=UTC vitest run",
|
||||
"test:coverage": "TZ=UTC vitest run --coverage",
|
||||
"test:component": "playwright test -c playwright.config.ts",
|
||||
"test:component:gui": "playwright test --ui -c playwright.config.ts",
|
||||
"stylelint": "stylelint src/**/*.vue src/**/*.scss src/**/*.css",
|
||||
"stylelint:fix": "stylelint src/**/*.vue src/**/*.scss src/**/*.css --fix",
|
||||
"styleguide": "vue-styleguidist --config styleguide.config.cjs server",
|
||||
"styleguide:build": "vue-styleguidist --config styleguide.config.cjs build",
|
||||
"cypress": "TZ=UTC cypress run --component",
|
||||
"cypress:gui": "TZ=UTC cypress open --component",
|
||||
"cypress:update-snapshots": "TZ=UTC cypress run --component --spec \"cypress/visual/**/*.cy.{t,j}s\" --env type=base --config screenshotsFolder=cypress/snapshots/base"
|
||||
"update:snapshots": "npm run test:component -- --update-snapshots"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
|
@ -106,14 +106,13 @@
|
|||
"vue-select": "^4.0.0-beta.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.0.0",
|
||||
"node": "^20.11.0",
|
||||
"npm": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-import-assertions": "^7.24.7",
|
||||
"@babel/plugin-transform-typescript": "^7.24.7",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@cypress/vue": "^6.0.1",
|
||||
"@fontsource/roboto": "^5.0.13",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@mdi/svg": "^7.4.47",
|
||||
|
@ -123,15 +122,14 @@
|
|||
"@nextcloud/stylelint-config": "^3.0.1",
|
||||
"@nextcloud/vite-config": "^2.1.0",
|
||||
"@nextcloud/webpack-vue-config": "github:nextcloud/webpack-vue-config#vue3",
|
||||
"@testing-library/cypress": "^10.0.2",
|
||||
"@playwright/experimental-ct-vue": "^1.45.2",
|
||||
"@playwright/test": "^1.45.2",
|
||||
"@types/gettext-parser": "^4.0.4",
|
||||
"@vitest/coverage-v8": "^1.6.0",
|
||||
"@types/node": "^20.14.10",
|
||||
"@vitest/coverage-v8": "^2.0.3",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"babel-loader-exclude-node-modules-except": "^1.2.1",
|
||||
"cypress": "^13.12.0",
|
||||
"cypress-visual-regression": "5.0.0",
|
||||
"eslint-plugin-cypress": "^3.3.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"gettext-extractor": "^3.8.0",
|
||||
"gettext-parser": "^8.0.0",
|
||||
|
@ -143,8 +141,8 @@
|
|||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.5.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"vite": "^5.3.1",
|
||||
"vitest": "^1.6.0",
|
||||
"vite": "^5.3.4",
|
||||
"vitest": "^2.0.3",
|
||||
"vue-eslint-parser": "^9.4.3",
|
||||
"vue-material-design-icons": "^5.3.0",
|
||||
"vue-styleguidist": "^4.72.4",
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
import { defineConfig, devices } from '@playwright/experimental-ct-vue';
|
||||
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
const projects = [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
]
|
||||
// This requires Debian or Ubuntu system so do not run by default locally
|
||||
if (process.env.CI) {
|
||||
projects.push({
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: 'tests/component',
|
||||
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
|
||||
snapshotDir: 'tests/component/snapshots',
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: 10 * 1000,
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
|
||||
// On CI we are using the github annotations + blob which will be merged by workflow to a downloadable HTML report (like the one we receive locally)
|
||||
reporter: process.env.CI ? 'blob' : 'html',
|
||||
|
||||
use: {
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
|
||||
/* Port to use for Playwright component endpoint. */
|
||||
ctPort: 3100,
|
||||
|
||||
ctTemplateDir: 'tests/component/setup',
|
||||
|
||||
ctViteConfig: async () => ({
|
||||
plugins: [
|
||||
// normally added by default but we overwrite the plugins so we need to add it back manually
|
||||
(await import('@vitejs/plugin-vue')).default(),
|
||||
// We do have some dependencies that use node modules -> we need to polyfill
|
||||
(await import('vite-plugin-node-polyfills')).nodePolyfills(),
|
||||
// We need to strip off the docs sections for the vue plugin
|
||||
(await import('./vite.config')).vueDocsPlugin,
|
||||
],
|
||||
define: {
|
||||
PRODUCTION: 'false',
|
||||
SCOPE_VERSION: '"testing"',
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@use 'sass:math'; $scope_version:'"testing"'; @import 'variables'; @import 'material-icons';`,
|
||||
sourceMapContents: false,
|
||||
includePaths: [
|
||||
`${import.meta.dirname}/src/assets`,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
projects,
|
||||
});
|
|
@ -316,7 +316,7 @@ Just set the `pinned` prop.
|
|||
<!-- @slot Slot for the optional leading icon -->
|
||||
<slot v-else name="icon" />
|
||||
</div>
|
||||
<span v-if="!editingActive" class="app-navigation-entry__name">
|
||||
<span class="app-navigation-entry__name" :class="{ 'hidden-visually': editingActive }">
|
||||
{{ name }}
|
||||
</span>
|
||||
<div v-if="editingActive" class="editingContainer">
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
@import 'default.css';
|
||||
@import 'server.css';
|
||||
@import 'apps.css';
|
||||
@import 'default.css';
|
||||
@import 'dark.css';
|
||||
|
||||
div[data-preview] * {
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { expect, test } from '@playwright/experimental-ct-vue'
|
||||
import SpacedAppNaviation from './SpacedAppNavigation.story.vue'
|
||||
|
||||
test('it correctly spaces two elements', async ({ mount, page }) => {
|
||||
await mount(SpacedAppNaviation)
|
||||
|
||||
const first = page.getByRole('link', { name: 'First' })
|
||||
const second = page.getByRole('link', { name: 'Second' })
|
||||
|
||||
await expect(first).toBeVisible()
|
||||
await expect(second).toBeVisible()
|
||||
|
||||
const firstRect = await first.boundingBox()
|
||||
const secondRect = await second.boundingBox()
|
||||
|
||||
// Check that the second element is at least 17px below the first one (thats our spacer)
|
||||
expect(secondRect!.y - 17).toBeGreaterThanOrEqual(firstRect!.y + firstRect!.height)
|
||||
})
|
|
@ -0,0 +1,18 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcAppNavigation>
|
||||
<NcAppNavigationItem name="First" />
|
||||
<NcAppNavigationSpacer />
|
||||
<NcAppNavigationItem name="Second" />
|
||||
</NcAppNavigation>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NcAppNavigation from '../../../../src/components/NcAppNavigation/NcAppNavigation.vue'
|
||||
import NcAppNavigationItem from '../../../../src/components/NcAppNavigationItem/NcAppNavigationItem.vue'
|
||||
import NcAppNavigationSpacer from '../../../../src/components/NcAppNavigationSpacer/NcAppNavigationSpacer.vue'
|
||||
</script>
|
|
@ -0,0 +1,20 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcAppSettingsDialog open name="Settings dialog" show-navigation>
|
||||
<NcAppSettingsSection id="first" name="First section">
|
||||
First content
|
||||
</NcAppSettingsSection>
|
||||
<NcAppSettingsSection id="second" name="Second section">
|
||||
Second content
|
||||
</NcAppSettingsSection>
|
||||
</NcAppSettingsDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NcAppSettingsDialog from '../../../../src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue'
|
||||
import NcAppSettingsSection from '../../../../src/components/NcAppSettingsSection/NcAppSettingsSection.vue'
|
||||
</script>
|
|
@ -0,0 +1,54 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { expect, test } from '@playwright/experimental-ct-vue'
|
||||
|
||||
import AppSettings from './AppSettings.story.vue'
|
||||
|
||||
test('Dialog has visible headline', async ({ mount, page }) => {
|
||||
await mount(AppSettings)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
await expect(dialog.getByRole('heading', { level: 2 })).toBeVisible()
|
||||
await expect(dialog.getByRole('heading', { level: 2 })).toHaveText('Settings dialog')
|
||||
})
|
||||
|
||||
test('Dialog is correctly labelled', async ({ mount, page }) => {
|
||||
await mount(AppSettings)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
await expect(dialog).toHaveAccessibleName('Settings dialog')
|
||||
})
|
||||
|
||||
test('Dialog sections have navigation entries', async ({ mount, page }) => {
|
||||
await mount(AppSettings)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
const navigation = dialog.getByRole('navigation')
|
||||
await expect(navigation).toBeVisible()
|
||||
await expect(navigation.getByRole('link', { name: 'First section' })).toBeVisible()
|
||||
await expect(navigation.getByRole('link', { name: 'Second section' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('Dialog sections are correctly labelled', async ({ mount, page }) => {
|
||||
await mount(AppSettings)
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
const firstSection = dialog.getByRole('region', { name: 'First section' })
|
||||
await expect(firstSection).toHaveCount(1)
|
||||
await expect(firstSection).toContainText('First content')
|
||||
await expect(firstSection.getByRole('heading')).toHaveText('First section')
|
||||
|
||||
const secondSection = dialog.getByRole('region', { name: 'Second section' })
|
||||
await expect(secondSection).toHaveCount(1)
|
||||
await expect(secondSection).toContainText('Second content')
|
||||
await expect(secondSection.getByRole('heading')).toHaveText('Second section')
|
||||
})
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { expect, test } from '@playwright/experimental-ct-vue'
|
||||
import NcButton from '../../../src/components/NcButton/NcButton.vue'
|
||||
|
||||
test('Text is rendered when provided', async ({ mount, page }) => {
|
||||
await mount(NcButton, {
|
||||
slots: {
|
||||
default: 'Nextcloud',
|
||||
},
|
||||
})
|
||||
await expect(page.getByRole('button')).toHaveText('Nextcloud')
|
||||
await expect(page.getByRole('button')).toHaveAccessibleName('Nextcloud')
|
||||
})
|
||||
|
||||
test('Can overwrite accessible name', async ({ mount, page }) => {
|
||||
await mount(NcButton, {
|
||||
props: {
|
||||
ariaLabel: 'Nextcloud'
|
||||
},
|
||||
slots: {
|
||||
default: 'Your favorite cloud',
|
||||
},
|
||||
})
|
||||
await expect(page.getByRole('button')).toHaveText('Your favorite cloud')
|
||||
await expect(page.getByRole('button')).toHaveAccessibleName('Nextcloud')
|
||||
})
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { expect, test } from '@playwright/experimental-ct-vue'
|
||||
import NcDialog from '../../../src/components/NcDialog/NcDialog.vue'
|
||||
|
||||
test('Dialog is correctly labelled', async ({ mount, page }) => {
|
||||
const component = await mount(NcDialog, {
|
||||
props: {
|
||||
open: true,
|
||||
name: 'My dialog',
|
||||
},
|
||||
slots: {
|
||||
default: 'Text',
|
||||
},
|
||||
})
|
||||
|
||||
await expect(page.getByRole('dialog', { name: 'My dialog' })).toBeVisible()
|
||||
})
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Component } from 'vue'
|
||||
|
||||
import { expect, test } from '@playwright/experimental-ct-vue'
|
||||
import NcModal from '../../../src/components/NcModal/NcModal.vue'
|
||||
|
||||
test('Modal is labelled correctly if name is set', async ({ mount, page }) => {
|
||||
await mount(NcModal, {
|
||||
props: {
|
||||
show: true,
|
||||
name: 'My modal',
|
||||
size: 'small',
|
||||
},
|
||||
slots: {
|
||||
default: 'Text',
|
||||
},
|
||||
})
|
||||
|
||||
await expect(page.getByRole('dialog', { name: 'My modal' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('Modal is labelled correctly if `labelId` is set', async ({ mount, page }) => {
|
||||
await mount(NcModal, {
|
||||
props: {
|
||||
show: true,
|
||||
size: 'small',
|
||||
labelId: 'my-id',
|
||||
},
|
||||
slots: {
|
||||
default: '<h2 id="my-id">Labelled modal</h2>',
|
||||
},
|
||||
})
|
||||
// There should be the dialog spawned
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
// With the heading inside
|
||||
await expect(dialog.getByRole('heading')).toHaveText('Labelled modal')
|
||||
// And the heading is used to label the dialog
|
||||
await expect(dialog).toHaveAccessibleName('Labelled modal')
|
||||
})
|
||||
|
||||
test('Modal is labelled correctly if `labelId` and `name` are set', async ({ mount, page }) => {
|
||||
await mount(NcModal, {
|
||||
props: {
|
||||
show: true,
|
||||
size: 'small',
|
||||
name: 'My modal',
|
||||
labelId: 'my-id',
|
||||
},
|
||||
slots: {
|
||||
default: '<h2 id="my-id">Real name</h2>',
|
||||
},
|
||||
})
|
||||
await expect(page.getByRole('dialog', { name: 'Real name' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('Close button is visible when content is scrolled', async ({ mount, page }) => {
|
||||
await mount(NcModal, {
|
||||
props: {
|
||||
show: true,
|
||||
size: 'small',
|
||||
name: 'My modal',
|
||||
labelId: 'my-id',
|
||||
},
|
||||
slots: {
|
||||
default: '<div><div style="height: 200vh;"></div><div data-testid="bottom">Hello</div></div>',
|
||||
},
|
||||
})
|
||||
|
||||
const dialog = page.getByRole('dialog')
|
||||
await expect(dialog).toBeVisible()
|
||||
|
||||
await dialog.getByTestId('bottom').scrollIntoViewIfNeeded()
|
||||
await expect(dialog.getByTestId('bottom')).toBeVisible()
|
||||
|
||||
await expect(dialog.getByRole('button', { name: 'Close' })).toBeVisible()
|
||||
await expect(dialog.getByRole('button', { name: 'Close' })).toBeInViewport()
|
||||
})
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { expect, test } from '@playwright/experimental-ct-vue';
|
||||
import NcRichText from '../../../../src/components/NcRichText/NcRichText.vue'
|
||||
|
||||
test('XML-like text (escaped and unescaped)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '<span>text</span>',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component).toHaveText('<span>text</span>')
|
||||
})
|
|
@ -0,0 +1,634 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Markdown guide: https://www.markdownguide.org/basic-syntax/
|
||||
// Reference tests: https://github.com/nextcloud-deps/CDMarkdownKit/tree/master/CDMarkdownKitTests
|
||||
|
||||
import { expect, test } from '@playwright/experimental-ct-vue';
|
||||
import NcRichText from '../../../../src/components/NcRichText/NcRichText.vue'
|
||||
|
||||
test.describe('dividers', () => {
|
||||
test('dividers with asterisks', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'First\n\n***\n\nsecond',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(await component.locator('hr').all()).toHaveLength(1)
|
||||
})
|
||||
|
||||
test('dividers with dashes', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'First\n\n---\n\nsecond',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(await component.locator('hr').all()).toHaveLength(1)
|
||||
})
|
||||
|
||||
test('dividers with underlines', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'First\n\n___\n\nsecond',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(await component.locator('hr').all()).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('headings', () => {
|
||||
test('heading (with hash (#) syntax divided with space from text)', async ({ mount }) => {
|
||||
const testCases = [
|
||||
{ tag: 'h1', input: '# heading 1', output: 'heading 1' },
|
||||
{ tag: 'h2', input: '## heading 2', output: 'heading 2' },
|
||||
{ tag: 'h3', input: '### heading 3', output: 'heading 3' },
|
||||
{ tag: 'h4', input: '#### heading 4', output: 'heading 4' },
|
||||
{ tag: 'h5', input: '##### heading 5', output: 'heading 5' },
|
||||
{ tag: 'h6', input: '###### heading 6', output: 'heading 6' },
|
||||
]
|
||||
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('heading', { level: 1 })).toHaveText('heading 1')
|
||||
await expect(component.getByRole('heading', { level: 2 })).toHaveText('heading 2')
|
||||
await expect(component.getByRole('heading', { level: 3 })).toHaveText('heading 3')
|
||||
await expect(component.getByRole('heading', { level: 4 })).toHaveText('heading 4')
|
||||
await expect(component.getByRole('heading', { level: 5 })).toHaveText('heading 5')
|
||||
await expect(component.getByRole('heading', { level: 6 })).toHaveText('heading 6')
|
||||
})
|
||||
|
||||
test('ignore heading (with hash (#) syntax padded to the text)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '#heading',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('heading', { level: 1 })).toHaveCount(0)
|
||||
await expect(component.getByText('#heading')).toBeVisible()
|
||||
})
|
||||
|
||||
test('render heading 1 (with equal (=) syntax on the next line)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'heading 1\n==',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('heading', { level: 1 })).toHaveText('heading 1')
|
||||
})
|
||||
|
||||
test('render heading 2 (with dash (-) syntax on the next line)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'heading 2\n--',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('heading', { level: 2 })).toHaveText('heading 2')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('bold text', () => {
|
||||
test('bold text (single with asterisk syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '**bold asterisk**',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('strong')).toHaveText('bold asterisk')
|
||||
})
|
||||
|
||||
test('bold text (single with underscore syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '__bold underscore__',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('strong')).toHaveText('bold underscore')
|
||||
})
|
||||
|
||||
test('bold text (several in line with different syntax)', async ({ mount }) => {
|
||||
const outputs = ['bold underscore', 'bold asterisk']
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text __bold underscore__ normal text **bold asterisk** normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('strong')).toHaveCount(2)
|
||||
expect(await component.getByRole('strong').allInnerTexts()).toEqual(outputs)
|
||||
})
|
||||
|
||||
test('bold text (between normal texts with asterisk syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text**bold**text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component).toHaveText('textboldtext')
|
||||
await expect(component.getByRole('strong')).toHaveText('bold')
|
||||
})
|
||||
|
||||
test('ignored bold text (between normal texts with underscore syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text__bold__text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component).toHaveText('text__bold__text')
|
||||
await expect(component.getByRole('strong')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('normal text (between bold texts with asterisk syntax)', async ({ mount }) => {
|
||||
const outputs = ['bold asterisk', 'bold asterisk']
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '**bold asterisk**normal text**bold asterisk**',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('strong')).toHaveCount(2)
|
||||
expect(await component.getByRole('strong').allInnerTexts()).toEqual(outputs)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test.describe('italic text', () => {
|
||||
test('italic text (single with asterisk syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '*italic asterisk*',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('em')).toHaveText('italic asterisk')
|
||||
})
|
||||
|
||||
test('italic text (single with underscore syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '_italic underscore_',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('em')).toHaveText('italic underscore')
|
||||
})
|
||||
|
||||
test('italic text (several in line with different syntax)', async ({ mount }) => {
|
||||
const outputs = ['italic underscore', 'italic asterisk']
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text _italic underscore_ normal text *italic asterisk* normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(await component.locator('em').count()).toBe(2)
|
||||
expect(await component.locator('em').allInnerTexts()).toEqual(outputs)
|
||||
})
|
||||
|
||||
test('italic text (between normal texts with asterisk syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text*italic*text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('em')).toHaveText('italic')
|
||||
})
|
||||
|
||||
test('ignored italic text (between normal texts with underscore syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text_italic_text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByText('text_italic_text')).toBeVisible()
|
||||
await expect(component.locator('em')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('normal text (between italic texts with asterisk syntax)', async ({ mount }) => {
|
||||
const outputs = ['italic asterisk', 'italic asterisk']
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '*italic asterisk*normal text*italic asterisk*',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(await component.locator('em').count()).toBe(2)
|
||||
expect(await component.locator('em').allInnerTexts()).toEqual(outputs)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test.describe('strikethrough text', () => {
|
||||
test('strikethrough text (with single tilda syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '~strikethrough single~',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('del')).toHaveText('strikethrough single')
|
||||
})
|
||||
|
||||
test('strikethrough text (with double tilda syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '~~strikethrough double~~',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('del')).toHaveText('strikethrough double')
|
||||
})
|
||||
|
||||
test('strikethrough text (several in line with different syntax)', async ({ mount }) => {
|
||||
const outputs = ['strikethrough single', 'strikethrough double']
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text ~strikethrough single~ normal text ~~strikethrough double~~ normal text',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(await component.locator('del').count()).toBe(2)
|
||||
expect(await component.locator('del').allInnerTexts()).toEqual(outputs)
|
||||
})
|
||||
|
||||
test('strikethrough text (between normal texts with different syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text~strikethrough~text~~strikethrough~~text',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(await component.locator('del').count()).toBe(2)
|
||||
expect(await component.locator('del').allInnerTexts()).toEqual(['strikethrough', 'strikethrough'])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test.describe('inline code', () => {
|
||||
test('inline code (single with backticks syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text `inline code` normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('code')).toHaveText('inline code')
|
||||
})
|
||||
|
||||
test('inline code (single with double backticks syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text ``inline code`` normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('code')).toHaveText('inline code')
|
||||
})
|
||||
|
||||
test('inline code (single with triple backticks syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text ```inline code``` normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('code')).toHaveText('inline code')
|
||||
})
|
||||
|
||||
test('inline code (several in line )', async ({ mount }) => {
|
||||
const outputs = ['inline code 1', 'inline code 2']
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text `inline code 1`normal text ``inline code 2`` normal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
expect(await component.locator('code').count()).toBe(2)
|
||||
expect(await component.locator('code').allInnerTexts()).toEqual(outputs)
|
||||
})
|
||||
|
||||
test('inline code (between normal texts)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'text`inline code`text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('code')).toHaveText('inline code')
|
||||
})
|
||||
|
||||
test('inline code (with ignored bold, italic, XML-like syntax))', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '`inline code **bold text** _italic text_ <span>text</span>`',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('code')).toHaveText('inline code **bold text** _italic text_ <span>text</span>')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test.describe('multiline code', () => {
|
||||
test('multiline code (with triple backticks syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '```\nmultiline code\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('pre')).toHaveText('multiline code\n')
|
||||
})
|
||||
|
||||
test('multiline code (ignored info)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '```vue\nmultiline code\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('pre')).toHaveText('multiline code\n')
|
||||
})
|
||||
|
||||
test('empty multiline code', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '``````',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('pre')).toBeEmpty()
|
||||
})
|
||||
|
||||
test('empty multiline code (with new line)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '```\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('pre')).toBeEmpty()
|
||||
})
|
||||
|
||||
test('multiline code (with several lines)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '```\nline 1\nline 2\nline 3\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('pre')).toHaveText('line 1\nline 2\nline 3\n')
|
||||
await expect(component.locator('code')).toHaveText('line 1\nline 2\nline 3\n')
|
||||
})
|
||||
|
||||
test('multiline code (with ignored bold, italic, inline code, XML-like syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '```\n**bold text**\n_italic text_\n`inline code`\n<span>text</span>\n```',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('pre')).toHaveText('**bold text**\n_italic text_\n`inline code`\n<span>text</span>\n')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test.describe('blockquote', () => {
|
||||
test('blockquote (with greater then (>) syntax - normal)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '> blockquote',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('blockquote')).toHaveText('\nblockquote\n')
|
||||
})
|
||||
|
||||
test('blockquote (with greater then (>) syntax - escaped)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '> blockquote',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('blockquote')).toHaveText('\nblockquote\n')
|
||||
})
|
||||
|
||||
test('blockquote (with bold, italic text, inline code)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '> blockquote **bold text** _italic text_ `inline code`',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('blockquote')).toHaveText('\nblockquote bold text italic text inline code\n')
|
||||
await expect(component.locator('strong')).toHaveText('bold text')
|
||||
await expect(component.locator('em')).toHaveText('italic text')
|
||||
await expect(component.locator('code')).toHaveText('inline code')
|
||||
})
|
||||
|
||||
test('blockquote (with several lines)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '> line 1\nline 2\n line 3',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('blockquote')).toHaveText('\nline 1\nline 2\nline 3\n')
|
||||
})
|
||||
|
||||
test('blockquote (divided from normal text)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'normal text\n> line 1\nline 2\n\nnormal text',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('blockquote')).toHaveText('\nline 1\nline 2\n')
|
||||
})
|
||||
|
||||
test('blockquote (with several paragraphs)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '> line 1\n>\n> line 3',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('blockquote')).toHaveText('\nline 1\nline 3\n')
|
||||
})
|
||||
|
||||
test('blockquote (with nested blockquote)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: '> blockquote\n>\n>> nested blockquote',
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.locator('blockquote blockquote')).toHaveText('nested blockquote')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test.describe('lists', () => {
|
||||
test('ordered list (with number + `.` syntax divided with space from text)', async ({ mount }) => {
|
||||
const testCases = [
|
||||
{ input: '1. item 1', output: 'item 1' },
|
||||
{ input: '2. item 2', output: 'item 2' },
|
||||
{ input: '3. item 3', output: 'item 3' },
|
||||
]
|
||||
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('list')).toBeVisible()
|
||||
await expect(component.locator('ol')).toHaveCount(1)
|
||||
await expect(component.getByRole('listitem')).toHaveText(testCases.map(({ output }) => output))
|
||||
})
|
||||
|
||||
test('unordered list (with unite syntax divided with space from text)', async ({ mount }) => {
|
||||
const testCases = [
|
||||
{ input: '* item 1', output: 'item 1' },
|
||||
{ input: '* item 2', output: 'item 2' },
|
||||
{ input: '* item 3', output: 'item 3' },
|
||||
]
|
||||
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('list')).toBeVisible()
|
||||
await expect(component.locator('ul')).toHaveCount(1)
|
||||
await expect(component.getByRole('listitem')).toHaveText(testCases.map(({ output }) => output))
|
||||
})
|
||||
|
||||
test('unordered lists (with different syntax divided with space from text)', async ({ mount }) => {
|
||||
const testCases = [
|
||||
{ input: '* item 1', output: 'item 1' },
|
||||
{ input: '+ item 2', output: 'item 2' },
|
||||
{ input: '- item 3', output: 'item 3' },
|
||||
]
|
||||
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('list')).toHaveCount(testCases.length)
|
||||
await expect(component.getByRole('listitem')).toHaveText(testCases.map(({ output }) => output))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test.describe('task lists', () => {
|
||||
test('task list (with `- [ ]` and `- [x]` syntax divided with space from text)', async ({ mount }) => {
|
||||
const testCases = [
|
||||
{ input: '- [ ] item 1', output: 'item 1', checked: false },
|
||||
{ input: '- [x] item 2', output: 'item 2', checked: true },
|
||||
{ input: '- [ ] item 3', output: 'item 3', checked: false },
|
||||
]
|
||||
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: testCases.map(i => i.input).join('\n'),
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('list')).toBeVisible()
|
||||
await expect(component.locator('ul')).toHaveCount(1)
|
||||
await expect(component.getByRole('listitem')).toHaveCount(testCases.length)
|
||||
|
||||
for(const [index, testcase] of testCases.entries()) {
|
||||
await expect(component.getByRole('listitem').nth(index)).toHaveText(testcase.output)
|
||||
await expect(component.getByRole('listitem').nth(index).getByRole('checkbox')).toBeChecked({ checked: testcase.checked})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test.describe('tables', () => {
|
||||
test('table (with `-- | --` syntax)', async ({ mount }) => {
|
||||
const component = await mount(NcRichText, {
|
||||
props: {
|
||||
text: 'Table | Column A | Column B\n-- | -- | --\nRow 1 | Value A1 | Value B1\nRow 2 | Value A2 | Value B2',
|
||||
useExtendedMarkdown: true,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(component.getByRole('table')).toBeVisible()
|
||||
await expect(component.locator('thead')).toHaveCount(1)
|
||||
await expect(component.locator('tbody')).toHaveCount(1)
|
||||
|
||||
await expect(component.locator('th')).toHaveCount(3)
|
||||
await expect(component.locator('tr')).toHaveCount(3)
|
||||
await expect(component.locator('td')).toHaveCount(6)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { expect, test } from '@playwright/experimental-ct-vue'
|
||||
import UserSelect from './UserSelect.story.vue'
|
||||
|
||||
test('has options', async ({ mount, page }) => {
|
||||
const component = await mount(UserSelect)
|
||||
|
||||
await expect(component.getByRole('searchbox')).toBeVisible()
|
||||
await component.getByRole('searchbox').click()
|
||||
|
||||
expect(await page.getByRole('option').all()).toHaveLength(3)
|
||||
await expect(page.getByRole('option', { name: 'Olivia' })).toBeVisible()
|
||||
await expect(page.getByRole('option', { name: 'John' })).toBeVisible()
|
||||
await expect(page.getByRole('option', { name: 'Emma' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('can filter by name', async ({ mount, page }) => {
|
||||
const component = await mount(UserSelect)
|
||||
|
||||
await expect(component.getByRole('searchbox')).toBeVisible()
|
||||
await component.getByRole('searchbox').fill('Em')
|
||||
|
||||
await expect(page.getByRole('option', { name: 'Emma' })).toBeVisible()
|
||||
expect(await page.getByRole('option').all()).toHaveLength(1)
|
||||
})
|
||||
|
||||
test('can filter by mail', async ({ mount, page }) => {
|
||||
const component = await mount(UserSelect)
|
||||
|
||||
await expect(component.getByRole('searchbox')).toBeVisible()
|
||||
await component.getByRole('searchbox').fill('olivia@example')
|
||||
|
||||
await expect(page.getByRole('option', { name: 'Olivia' })).toBeVisible()
|
||||
expect(await page.getByRole('option').all()).toHaveLength(1)
|
||||
})
|
||||
|
||||
test(
|
||||
'can filter by mail in brackets',
|
||||
{
|
||||
annotation: {
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nextcloud-libraries/nextcloud-vue/issues/4491',
|
||||
},
|
||||
},
|
||||
async ({ mount, page }) => {
|
||||
const component = await mount(UserSelect)
|
||||
|
||||
await expect(component.getByRole('searchbox')).toBeVisible()
|
||||
await component.getByRole('searchbox').fill('O. <')
|
||||
|
||||
// should not exist right now as neither Name no email provided
|
||||
await expect(page.getByText('No results')).toBeVisible()
|
||||
expect(await page.getByRole('option', { name: 'Olivia' }).all()).toHaveLength(0)
|
||||
|
||||
await component.getByRole('searchbox').fill('O. <olivia')
|
||||
// now it should match the name
|
||||
await expect(page.getByRole('option', { name: 'Olivia' })).toBeVisible()
|
||||
|
||||
await component.getByRole('searchbox').fill('O. <olivia@example.org>')
|
||||
// now it should match the email
|
||||
await expect(page.getByRole('option', { name: 'Olivia' })).toBeVisible()
|
||||
expect(await page.getByRole('option').all()).toHaveLength(1)
|
||||
},
|
||||
)
|
|
@ -0,0 +1,37 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcSelect user-select
|
||||
input-label="My account"
|
||||
input-class="cypress-search-input"
|
||||
:options="[
|
||||
{
|
||||
id: '0-john',
|
||||
displayName: 'John',
|
||||
isNoUser: false,
|
||||
subname: 'john@example.org',
|
||||
icon: '',
|
||||
},
|
||||
{
|
||||
id: '0-emma',
|
||||
displayName: 'Emma',
|
||||
isNoUser: false,
|
||||
subname: 'emma@example.org',
|
||||
icon: '',
|
||||
},
|
||||
{
|
||||
id: '0-olivia',
|
||||
displayName: 'Olivia',
|
||||
isNoUser: false,
|
||||
subname: 'olivia@example.org',
|
||||
icon: '',
|
||||
},
|
||||
]" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NcSelect from '../../../../src/components/NcSelect/NcSelect.vue'
|
||||
</script>
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Testing Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<header id="header" style="height: var(--header-height); width: 100%; position: absolute;"></header>
|
||||
<div id="content">
|
||||
<main id="app-content">
|
||||
<div id="root"></div>
|
||||
</main>
|
||||
</div>
|
||||
<script type="module" src="./index.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,24 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Visual setup
|
||||
import '../../../styleguide/assets/additional.css'
|
||||
import '@fontsource/roboto'
|
||||
|
||||
// Mount hooks
|
||||
import { beforeMount } from '@playwright/experimental-ct-vue/hooks';
|
||||
import { createMemoryHistory, createRouter, type RouteRecordRaw } from 'vue-router';
|
||||
|
||||
export type HooksConfig = {
|
||||
routes?: RouteRecordRaw[];
|
||||
}
|
||||
|
||||
beforeMount<HooksConfig>(async ({ hooksConfig, app }) => {
|
||||
const router = createRouter({
|
||||
routes: hooksConfig?.routes ?? [],
|
||||
history: createMemoryHistory(),
|
||||
})
|
||||
app.use(router)
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.json",
|
||||
"include": ["./src/**/*.js","./src/**/*.ts", "./src/**/*.vue", "**/*.ts"],
|
||||
"exclude": ["./src/**/*.cy.ts", "cypress", "tests"],
|
||||
"exclude": ["tests"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
@ -18,12 +18,4 @@
|
|||
"vueCompilerOptions": {
|
||||
"target": 3.3,
|
||||
},
|
||||
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "ES2015",
|
||||
"target": "ES2015",
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ const entryPoints = {
|
|||
const name = item
|
||||
.replace(/\/index\.(j|t)s/, '')
|
||||
.replace('src/directives/', 'Directives/')
|
||||
acc[name] = join(__dirname, item)
|
||||
acc[name] = join(import.meta.dirname, item)
|
||||
return acc
|
||||
}, {}),
|
||||
|
||||
|
@ -31,7 +31,7 @@ const entryPoints = {
|
|||
const name = item
|
||||
.replace(/\/index\.(j|t)s/, '')
|
||||
.replace('src/components/', 'Components/')
|
||||
acc[name] = join(__dirname, item)
|
||||
acc[name] = join(import.meta.dirname, item)
|
||||
return acc
|
||||
}, {}),
|
||||
|
||||
|
@ -39,7 +39,7 @@ const entryPoints = {
|
|||
const name = item
|
||||
.replace(/\/index\.(j|t)s/, '')
|
||||
.replace('src/functions/', 'Functions/')
|
||||
acc[name] = join(__dirname, item)
|
||||
acc[name] = join(import.meta.dirname, item)
|
||||
return acc
|
||||
}, {}),
|
||||
|
||||
|
@ -47,7 +47,7 @@ const entryPoints = {
|
|||
const name = item
|
||||
.replace(/\/index\.(j|t)s/, '')
|
||||
.replace('src/mixins/', 'Mixins/')
|
||||
acc[name] = join(__dirname, item)
|
||||
acc[name] = join(import.meta.dirname, item)
|
||||
return acc
|
||||
}, {}),
|
||||
|
||||
|
@ -55,15 +55,15 @@ const entryPoints = {
|
|||
const name = item
|
||||
.replace(/\/index\.(j|t)s/, '')
|
||||
.replace('src/composables/', 'Composables/')
|
||||
acc[name] = join(__dirname, item)
|
||||
acc[name] = join(import.meta.dirname, item)
|
||||
return acc
|
||||
}, {}),
|
||||
|
||||
index: resolve(__dirname, 'src/index.ts'),
|
||||
index: resolve(import.meta.dirname, 'src/index.ts'),
|
||||
}
|
||||
|
||||
// Plugin for stripping out <docs> sections from vue files
|
||||
const vueDocsPlugin: Plugin = {
|
||||
export const vueDocsPlugin: Plugin = {
|
||||
name: 'vue-docs-plugin',
|
||||
transform(code, id) {
|
||||
if (!/vue&type=doc/.test(id)) {
|
||||
|
@ -77,7 +77,7 @@ const vueDocsPlugin: Plugin = {
|
|||
const overrides = defineConfig({
|
||||
plugins: [
|
||||
vueDocsPlugin,
|
||||
l10nPlugin(resolve(__dirname, 'l10n')),
|
||||
l10nPlugin(resolve(import.meta.dirname, 'l10n')),
|
||||
],
|
||||
css: {
|
||||
devSourcemap: true,
|
||||
|
@ -86,7 +86,7 @@ const overrides = defineConfig({
|
|||
additionalData: `@use 'sass:math'; $scope_version:${SCOPE_VERSION}; @import 'variables'; @import 'material-icons';`,
|
||||
sourceMapContents: false,
|
||||
includePaths: [
|
||||
resolve(__dirname, 'src/assets'),
|
||||
resolve(import.meta.dirname, 'src/assets'),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче