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:
Ferdinand Thiessen 2024-07-16 21:30:59 +02:00
Родитель 17c3098658
Коммит 803f317f45
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 45FAE7268762B400
39 изменённых файлов: 1750 добавлений и 3215 удалений

Просмотреть файл

@ -11,9 +11,6 @@
"extends": [
"@nextcloud/eslint-config/vue3"
],
"plugins": [
"cypress"
],
"parserOptions": {
"babelOptions": {
"plugins": [

104
.github/workflows/command-playwright-update.yml поставляемый Normal file
Просмотреть файл

@ -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"

71
.github/workflows/cypress.yml поставляемый
Просмотреть файл

@ -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

109
.github/workflows/playwright.yml поставляемый Normal file
Просмотреть файл

@ -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

11
.gitignore поставляемый
Просмотреть файл

@ -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&lt;/span&gt;',
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&lt;/span&gt;`',
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&lt;/span&gt;\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 (&gt;) syntax - escaped)', () => {
mount(NcRichText, {
props: {
text: '&gt; 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"
]
}
}

2483
package-lock.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",

85
playwright.config.ts Normal file
Просмотреть файл

@ -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&lt;/span&gt;',
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&lt;/span&gt;`',
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&lt;/span&gt;\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 (&gt;) syntax - escaped)', async ({ mount }) => {
const component = await mount(NcRichText, {
props: {
text: '&gt; 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'),
],
},
},