Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>pull/57129/head
parent
d9b0346a84
commit
b770bbdd54
@ -1,123 +0,0 @@ |
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import RemoteShareDialog from './RemoteShareDialog.vue' |
||||
|
||||
describe('RemoteShareDialog', () => { |
||||
it('can be mounted', () => { |
||||
cy.mount(RemoteShareDialog, { |
||||
propsData: { |
||||
owner: 'user123', |
||||
name: 'my-photos', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: false, |
||||
}, |
||||
}) |
||||
|
||||
cy.findByRole('dialog') |
||||
.should('be.visible') |
||||
.and('contain.text', 'user123@nextcloud.local') |
||||
.and('contain.text', 'my-photos') |
||||
cy.findByRole('button', { name: 'Cancel' }) |
||||
.should('be.visible') |
||||
cy.findByRole('button', { name: /add remote share/i }) |
||||
.should('be.visible') |
||||
}) |
||||
|
||||
it('does not show password input if not enabled', () => { |
||||
cy.mount(RemoteShareDialog, { |
||||
propsData: { |
||||
owner: 'user123', |
||||
name: 'my-photos', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: false, |
||||
}, |
||||
}) |
||||
|
||||
cy.findByRole('dialog') |
||||
.should('be.visible') |
||||
.find('input[type="password"]') |
||||
.should('not.exist') |
||||
}) |
||||
|
||||
it('emits true when accepted', () => { |
||||
const onClose = cy.spy().as('onClose') |
||||
|
||||
cy.mount(RemoteShareDialog, { |
||||
listeners: { |
||||
close: onClose, |
||||
}, |
||||
propsData: { |
||||
owner: 'user123', |
||||
name: 'my-photos', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: false, |
||||
}, |
||||
}) |
||||
|
||||
cy.findByRole('button', { name: 'Cancel' }).click() |
||||
cy.get('@onClose') |
||||
.should('have.been.calledWith', false) |
||||
}) |
||||
|
||||
it('show password input if needed', () => { |
||||
cy.mount(RemoteShareDialog, { |
||||
propsData: { |
||||
owner: 'admin', |
||||
name: 'secret-data', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: true, |
||||
}, |
||||
}) |
||||
|
||||
cy.findByRole('dialog') |
||||
.should('be.visible') |
||||
.find('input[type="password"]') |
||||
.should('be.visible') |
||||
}) |
||||
|
||||
it('emits the submitted password', () => { |
||||
const onClose = cy.spy().as('onClose') |
||||
|
||||
cy.mount(RemoteShareDialog, { |
||||
listeners: { |
||||
close: onClose, |
||||
}, |
||||
propsData: { |
||||
owner: 'admin', |
||||
name: 'secret-data', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: true, |
||||
}, |
||||
}) |
||||
|
||||
cy.get('input[type="password"]') |
||||
.type('my password{enter}') |
||||
cy.get('@onClose') |
||||
.should('have.been.calledWith', true, 'my password') |
||||
}) |
||||
|
||||
it('emits no password if cancelled', () => { |
||||
const onClose = cy.spy().as('onClose') |
||||
|
||||
cy.mount(RemoteShareDialog, { |
||||
listeners: { |
||||
close: onClose, |
||||
}, |
||||
propsData: { |
||||
owner: 'admin', |
||||
name: 'secret-data', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: true, |
||||
}, |
||||
}) |
||||
|
||||
cy.get('input[type="password"]') |
||||
.type('my password') |
||||
cy.findByRole('button', { name: 'Cancel' }).click() |
||||
cy.get('@onClose') |
||||
.should('have.been.calledWith', false) |
||||
}) |
||||
}) |
||||
@ -0,0 +1,115 @@ |
||||
/** |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import { cleanup, fireEvent, render } from '@testing-library/vue' |
||||
import { beforeEach, describe, expect, it, vi } from 'vitest' |
||||
import RemoteShareDialog from './RemoteShareDialog.vue' |
||||
|
||||
describe('RemoteShareDialog', () => { |
||||
beforeEach(cleanup) |
||||
|
||||
it('can be mounted', async () => { |
||||
const component = render(RemoteShareDialog, { |
||||
props: { |
||||
owner: 'user123', |
||||
name: 'my-photos', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: false, |
||||
}, |
||||
}) |
||||
|
||||
await expect(component.findByRole('dialog', { name: 'Remote share' })).resolves.not.toThrow() |
||||
expect(component.getByRole('dialog').innerText).toContain(/my-photos from user123@nextcloud.local/) |
||||
await expect(component.findByRole('button', { name: 'Cancel' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('button', { name: /Add remote share/ })).resolves.not.toThrow() |
||||
}) |
||||
|
||||
it('does not show password input if not enabled', async () => { |
||||
const component = render(RemoteShareDialog, { |
||||
props: { |
||||
owner: 'user123', |
||||
name: 'my-photos', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: false, |
||||
}, |
||||
}) |
||||
|
||||
await expect(component.findByLabelText('Remote share password')).rejects.toThrow() |
||||
}) |
||||
|
||||
it('emits true when accepted', () => { |
||||
const onClose = vi.fn() |
||||
|
||||
const component = render(RemoteShareDialog, { |
||||
listeners: { |
||||
close: onClose, |
||||
}, |
||||
props: { |
||||
owner: 'user123', |
||||
name: 'my-photos', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: false, |
||||
}, |
||||
}) |
||||
|
||||
component.getByRole('button', { name: 'Cancel' }).click() |
||||
expect(onClose).toHaveBeenCalledWith(false) |
||||
}) |
||||
|
||||
it('show password input if needed', async () => { |
||||
const component = render(RemoteShareDialog, { |
||||
props: { |
||||
owner: 'admin', |
||||
name: 'secret-data', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: true, |
||||
}, |
||||
}) |
||||
|
||||
await expect(component.findByLabelText('Remote share password')).resolves.not.toThrow() |
||||
}) |
||||
|
||||
it('emits the submitted password', async () => { |
||||
const onClose = vi.fn() |
||||
|
||||
const component = render(RemoteShareDialog, { |
||||
listeners: { |
||||
close: onClose, |
||||
}, |
||||
props: { |
||||
owner: 'admin', |
||||
name: 'secret-data', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: true, |
||||
}, |
||||
}) |
||||
|
||||
const input = component.getByLabelText('Remote share password') |
||||
await fireEvent.update(input, 'my password') |
||||
component.getByRole('button', { name: 'Add remote share' }).click() |
||||
expect(onClose).toHaveBeenCalledWith(true, 'my password') |
||||
}) |
||||
|
||||
it('emits no password if cancelled', async () => { |
||||
const onClose = vi.fn() |
||||
|
||||
const component = render(RemoteShareDialog, { |
||||
listeners: { |
||||
close: onClose, |
||||
}, |
||||
props: { |
||||
owner: 'admin', |
||||
name: 'secret-data', |
||||
remote: 'nextcloud.local', |
||||
passwordRequired: true, |
||||
}, |
||||
}) |
||||
|
||||
const input = component.getByLabelText('Remote share password') |
||||
await fireEvent.update(input, 'my password') |
||||
component.getByRole('button', { name: 'Cancel' }).click() |
||||
expect(onClose).toHaveBeenCalledWith(false) |
||||
}) |
||||
}) |
||||
@ -1,56 +0,0 @@ |
||||
/*! |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import { defineComponent } from 'vue' |
||||
import { useFileListWidth } from './useFileListWidth.ts' |
||||
|
||||
const ComponentMock = defineComponent({ |
||||
template: '<div id="test-component" style="width: 100%;background: white;">{{ fileListWidth }}</div>', |
||||
setup() { |
||||
return { |
||||
fileListWidth: useFileListWidth(), |
||||
} |
||||
}, |
||||
}) |
||||
const FileListMock = defineComponent({ |
||||
template: '<main id="app-content-vue" style="width: 100%;"><component-mock /></main>', |
||||
components: { |
||||
ComponentMock, |
||||
}, |
||||
}) |
||||
|
||||
describe('composable: fileListWidth', () => { |
||||
|
||||
it('Has initial value', () => { |
||||
cy.viewport(600, 400) |
||||
|
||||
cy.mount(FileListMock, {}) |
||||
cy.get('#app-content-vue') |
||||
.should('be.visible') |
||||
.and('contain.text', '600') |
||||
}) |
||||
|
||||
it('Is reactive to size change', () => { |
||||
cy.viewport(600, 400) |
||||
cy.mount(FileListMock) |
||||
cy.get('#app-content-vue').should('contain.text', '600') |
||||
|
||||
cy.viewport(800, 400) |
||||
cy.screenshot() |
||||
cy.get('#app-content-vue').should('contain.text', '800') |
||||
}) |
||||
|
||||
it('Is reactive to style changes', () => { |
||||
cy.viewport(600, 400) |
||||
cy.mount(FileListMock) |
||||
cy.get('#app-content-vue') |
||||
.should('be.visible') |
||||
.and('contain.text', '600') |
||||
.invoke('attr', 'style', 'width: 100px') |
||||
|
||||
cy.get('#app-content-vue') |
||||
.should('contain.text', '100') |
||||
}) |
||||
}) |
||||
@ -0,0 +1,79 @@ |
||||
/*! |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import { cleanup, render } from '@testing-library/vue' |
||||
import { configMocks, mockResizeObserver } from 'jsdom-testing-mocks' |
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest' |
||||
import { defineComponent, nextTick } from 'vue' |
||||
|
||||
let resizeObserver: ReturnType<typeof mockResizeObserver> |
||||
|
||||
describe('composable: fileListWidth', () => { |
||||
configMocks({ beforeAll, afterAll, beforeEach, afterEach }) |
||||
|
||||
beforeAll(() => { |
||||
resizeObserver = mockResizeObserver() |
||||
}) |
||||
|
||||
beforeEach(cleanup) |
||||
|
||||
it('Has initial value', async () => { |
||||
const { component } = await getFileList() |
||||
expect(component.textContent).toBe('600') |
||||
}) |
||||
|
||||
it('observes the file list element', async () => { |
||||
const { fileList } = await getFileList() |
||||
expect(resizeObserver.getObservedElements()).toContain(fileList) |
||||
}) |
||||
|
||||
it('Is reactive to size change', async () => { |
||||
const { component, fileList } = await getFileList() |
||||
expect(component.textContent).toBe('600') |
||||
expect(resizeObserver.getObservedElements()).toHaveLength(1) |
||||
|
||||
resizeObserver.mockElementSize(fileList, { contentBoxSize: { inlineSize: 800, blockSize: 300 } }) |
||||
resizeObserver.resize(fileList) |
||||
|
||||
// await rending
|
||||
await nextTick() |
||||
expect(component.textContent).toBe('800') |
||||
}) |
||||
}) |
||||
|
||||
async function getFileList() { |
||||
const { useFileListWidth } = await import('./useFileListWidth.ts') |
||||
|
||||
const ComponentMock = defineComponent({ |
||||
template: '<div data-testid="component" style="width: 100%;background: white;">{{ fileListWidth }}</div>', |
||||
setup() { |
||||
return { |
||||
fileListWidth: useFileListWidth(), |
||||
} |
||||
}, |
||||
}) |
||||
|
||||
const FileListMock = defineComponent({ |
||||
template: '<main id="app-content-vue" style="width: 100%;"><component-mock /></main>', |
||||
components: { |
||||
ComponentMock, |
||||
}, |
||||
}) |
||||
|
||||
const root = render(FileListMock) |
||||
const fileList = root.baseElement.querySelector('#app-content-vue') as HTMLElement |
||||
|
||||
// mock initial size
|
||||
resizeObserver.mockElementSize(fileList, { contentBoxSize: { inlineSize: 600, blockSize: 200 } }) |
||||
resizeObserver.resize() |
||||
// await rending
|
||||
await nextTick() |
||||
|
||||
return { |
||||
root, |
||||
component: root.getByTestId('component'), |
||||
fileList, |
||||
} |
||||
} |
||||
@ -1,161 +0,0 @@ |
||||
/*! |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
import { createTestingPinia } from '@pinia/testing' |
||||
import DialogConfirmFileExtension from './DialogConfirmFileExtension.vue' |
||||
import { useUserConfigStore } from '../store/userconfig' |
||||
|
||||
describe('DialogConfirmFileExtension', () => { |
||||
it('renders with both extensions', () => { |
||||
cy.mount(DialogConfirmFileExtension, { |
||||
propsData: { |
||||
oldExtension: '.old', |
||||
newExtension: '.new', |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.findByRole('dialog') |
||||
.as('dialog') |
||||
.should('be.visible') |
||||
cy.get('@dialog') |
||||
.findByRole('heading') |
||||
.should('contain.text', 'Change file extension') |
||||
cy.get('@dialog') |
||||
.findByRole('checkbox', { name: /Do not show this dialog again/i }) |
||||
.should('exist') |
||||
.and('not.be.checked') |
||||
cy.get('@dialog') |
||||
.findByRole('button', { name: 'Keep .old' }) |
||||
.should('be.visible') |
||||
cy.get('@dialog') |
||||
.findByRole('button', { name: 'Use .new' }) |
||||
.should('be.visible') |
||||
}) |
||||
|
||||
it('renders without old extension', () => { |
||||
cy.mount(DialogConfirmFileExtension, { |
||||
propsData: { |
||||
newExtension: '.new', |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.findByRole('dialog') |
||||
.as('dialog') |
||||
.should('be.visible') |
||||
cy.get('@dialog') |
||||
.findByRole('button', { name: 'Keep without extension' }) |
||||
.should('be.visible') |
||||
cy.get('@dialog') |
||||
.findByRole('button', { name: 'Use .new' }) |
||||
.should('be.visible') |
||||
}) |
||||
|
||||
it('renders without new extension', () => { |
||||
cy.mount(DialogConfirmFileExtension, { |
||||
propsData: { |
||||
oldExtension: '.old', |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.findByRole('dialog') |
||||
.as('dialog') |
||||
.should('be.visible') |
||||
cy.get('@dialog') |
||||
.findByRole('button', { name: 'Keep .old' }) |
||||
.should('be.visible') |
||||
cy.get('@dialog') |
||||
.findByRole('button', { name: 'Remove extension' }) |
||||
.should('be.visible') |
||||
}) |
||||
|
||||
it('emits correct value on keep old', () => { |
||||
cy.mount(DialogConfirmFileExtension, { |
||||
propsData: { |
||||
oldExtension: '.old', |
||||
newExtension: '.new', |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}).as('component') |
||||
|
||||
cy.findByRole('dialog') |
||||
.as('dialog') |
||||
.should('be.visible') |
||||
cy.get('@dialog') |
||||
.findByRole('button', { name: 'Keep .old' }) |
||||
.click() |
||||
cy.get('@component') |
||||
.its('wrapper') |
||||
.should((wrapper) => expect(wrapper.emitted('close')).to.eql([[false]])) |
||||
}) |
||||
|
||||
it('emits correct value on use new', () => { |
||||
cy.mount(DialogConfirmFileExtension, { |
||||
propsData: { |
||||
oldExtension: '.old', |
||||
newExtension: '.new', |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}).as('component') |
||||
|
||||
cy.findByRole('dialog') |
||||
.as('dialog') |
||||
.should('be.visible') |
||||
cy.get('@dialog') |
||||
.findByRole('button', { name: 'Use .new' }) |
||||
.click() |
||||
cy.get('@component') |
||||
.its('wrapper') |
||||
.should((wrapper) => expect(wrapper.emitted('close')).to.eql([[true]])) |
||||
}) |
||||
|
||||
it('updates user config when checking the checkbox', () => { |
||||
const pinia = createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
}) |
||||
|
||||
cy.mount(DialogConfirmFileExtension, { |
||||
propsData: { |
||||
oldExtension: '.old', |
||||
newExtension: '.new', |
||||
}, |
||||
global: { |
||||
plugins: [pinia], |
||||
}, |
||||
}).as('component') |
||||
|
||||
cy.findByRole('dialog') |
||||
.as('dialog') |
||||
.should('be.visible') |
||||
cy.get('@dialog') |
||||
.findByRole('checkbox', { name: /Do not show this dialog again/i }) |
||||
.check({ force: true }) |
||||
|
||||
cy.wrap(useUserConfigStore()) |
||||
.its('update') |
||||
.should('have.been.calledWith', 'show_dialog_file_extension', false) |
||||
}) |
||||
}) |
||||
@ -0,0 +1,132 @@ |
||||
/*! |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import { createTestingPinia } from '@pinia/testing' |
||||
import { cleanup, fireEvent, render } from '@testing-library/vue' |
||||
import { beforeEach, describe, expect, it, vi } from 'vitest' |
||||
import DialogConfirmFileExtension from './DialogConfirmFileExtension.vue' |
||||
import { useUserConfigStore } from '../store/userconfig.ts' |
||||
|
||||
describe('DialogConfirmFileExtension', () => { |
||||
beforeEach(cleanup) |
||||
|
||||
it('renders with both extensions', async () => { |
||||
const component = render(DialogConfirmFileExtension, { |
||||
props: { |
||||
oldExtension: '.old', |
||||
newExtension: '.new', |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
await expect(component.findByRole('dialog', { name: 'Change file extension' })).resolves.not.toThrow() |
||||
expect((component.getByRole('checkbox', { name: /Do not show this dialog again/i }) as HTMLInputElement).checked).toBe(false) |
||||
await expect(component.findByRole('button', { name: 'Keep .old' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('button', { name: 'Use .new' })).resolves.not.toThrow() |
||||
}) |
||||
|
||||
it('renders without old extension', async () => { |
||||
const component = render(DialogConfirmFileExtension, { |
||||
props: { |
||||
newExtension: '.new', |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
await expect(component.findByRole('dialog', { name: 'Change file extension' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('button', { name: 'Keep without extension' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('button', { name: 'Use .new' })).resolves.not.toThrow() |
||||
}) |
||||
|
||||
it('renders without new extension', async () => { |
||||
const component = render(DialogConfirmFileExtension, { |
||||
props: { |
||||
oldExtension: '.old', |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
await expect(component.findByRole('dialog', { name: 'Change file extension' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('button', { name: 'Keep .old' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('button', { name: 'Remove extension' })).resolves.not.toThrow() |
||||
}) |
||||
|
||||
it('emits correct value on keep old', async () => { |
||||
const onclose = vi.fn() |
||||
const component = render(DialogConfirmFileExtension, { |
||||
props: { |
||||
oldExtension: '.old', |
||||
newExtension: '.new', |
||||
}, |
||||
listeners: { |
||||
close: onclose, |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
await fireEvent.click(component.getByRole('button', { name: 'Keep .old' })) |
||||
expect(onclose).toHaveBeenCalledOnce() |
||||
expect(onclose).toHaveBeenCalledWith(false) |
||||
}) |
||||
|
||||
it('emits correct value on use new', async () => { |
||||
const onclose = vi.fn() |
||||
const component = render(DialogConfirmFileExtension, { |
||||
props: { |
||||
oldExtension: '.old', |
||||
newExtension: '.new', |
||||
}, |
||||
listeners: { |
||||
close: onclose, |
||||
}, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
await fireEvent.click(component.getByRole('button', { name: 'Use .new' })) |
||||
expect(onclose).toHaveBeenCalledOnce() |
||||
expect(onclose).toHaveBeenCalledWith(true) |
||||
}) |
||||
|
||||
it('updates user config when checking the checkbox', async () => { |
||||
const pinia = createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
}) |
||||
|
||||
const component = render(DialogConfirmFileExtension, { |
||||
props: { |
||||
oldExtension: '.old', |
||||
newExtension: '.new', |
||||
}, |
||||
global: { |
||||
plugins: [pinia], |
||||
}, |
||||
}) |
||||
|
||||
await fireEvent.click(component.getByRole('checkbox', { name: /Do not show this dialog again/i })) |
||||
const store = useUserConfigStore() |
||||
expect(store.update).toHaveBeenCalledOnce() |
||||
expect(store.update).toHaveBeenCalledWith('show_dialog_file_extension', false) |
||||
}) |
||||
}) |
||||
@ -1,260 +0,0 @@ |
||||
/** |
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
import type { Navigation } from '@nextcloud/files' |
||||
import FolderSvg from '@mdi/svg/svg/folder.svg?raw' |
||||
import { createTestingPinia } from '@pinia/testing' |
||||
|
||||
import NavigationView from './Navigation.vue' |
||||
import { useViewConfigStore } from '../store/viewConfig' |
||||
import { Folder, View, getNavigation } from '@nextcloud/files' |
||||
|
||||
import router from '../router/router.ts' |
||||
import RouterService from '../services/RouterService' |
||||
|
||||
const resetNavigation = () => { |
||||
const nav = getNavigation() |
||||
;[...nav.views].forEach(({ id }) => nav.remove(id)) |
||||
nav.setActive(null) |
||||
} |
||||
|
||||
const createView = (id: string, name: string, parent?: string) => new View({ |
||||
id, |
||||
name, |
||||
getContents: async () => ({ folder: {} as Folder, contents: [] }), |
||||
icon: FolderSvg, |
||||
order: 1, |
||||
parent, |
||||
}) |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
function mockWindow() { |
||||
window.OCP ??= {} |
||||
window.OCP.Files ??= {} |
||||
window.OCP.Files.Router = new RouterService(router) |
||||
} |
||||
|
||||
describe('Navigation renders', () => { |
||||
before(async () => { |
||||
delete window._nc_navigation |
||||
mockWindow() |
||||
getNavigation().register(createView('files', 'Files')) |
||||
await router.replace({ name: 'filelist', params: { view: 'files' } }) |
||||
|
||||
cy.mockInitialState('files', 'storageStats', { |
||||
used: 1000 * 1000 * 1000, |
||||
quota: -1, |
||||
}) |
||||
}) |
||||
|
||||
after(() => cy.unmockInitialState()) |
||||
|
||||
it('renders', () => { |
||||
cy.mount(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.get('[data-cy-files-navigation]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-settings-quota]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-settings-button]').should('be.visible') |
||||
}) |
||||
}) |
||||
|
||||
describe('Navigation API', () => { |
||||
let Navigation: Navigation |
||||
|
||||
before(async () => { |
||||
delete window._nc_navigation |
||||
Navigation = getNavigation() |
||||
mockWindow() |
||||
|
||||
await router.replace({ name: 'filelist', params: { view: 'files' } }) |
||||
}) |
||||
|
||||
beforeEach(() => resetNavigation()) |
||||
|
||||
it('Check API entries rendering', () => { |
||||
Navigation.register(createView('files', 'Files')) |
||||
console.warn(Navigation.views) |
||||
|
||||
cy.mount(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [ |
||||
createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
}), |
||||
], |
||||
}, |
||||
}) |
||||
|
||||
cy.get('[data-cy-files-navigation]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-item]').should('have.length', 1) |
||||
cy.get('[data-cy-files-navigation-item="files"]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-item="files"]').should('contain.text', 'Files') |
||||
}) |
||||
|
||||
it('Adds a new entry and render', () => { |
||||
Navigation.register(createView('files', 'Files')) |
||||
Navigation.register(createView('sharing', 'Sharing')) |
||||
|
||||
cy.mount(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.get('[data-cy-files-navigation]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-item]').should('have.length', 2) |
||||
cy.get('[data-cy-files-navigation-item="sharing"]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-item="sharing"]').should('contain.text', 'Sharing') |
||||
}) |
||||
|
||||
it('Adds a new children, render and open menu', () => { |
||||
Navigation.register(createView('files', 'Files')) |
||||
Navigation.register(createView('sharing', 'Sharing')) |
||||
Navigation.register(createView('sharingin', 'Shared with me', 'sharing')) |
||||
|
||||
cy.mount(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.wrap(useViewConfigStore()).as('viewConfigStore') |
||||
|
||||
cy.get('[data-cy-files-navigation]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-item]').should('have.length', 3) |
||||
|
||||
// Toggle the sharing entry children
|
||||
cy.get('[data-cy-files-navigation-item="sharing"] button.icon-collapse').should('exist') |
||||
cy.get('[data-cy-files-navigation-item="sharing"] button.icon-collapse').click({ force: true }) |
||||
|
||||
// Expect store update to be called
|
||||
cy.get('@viewConfigStore').its('update').should('have.been.calledWith', 'sharing', 'expanded', true) |
||||
|
||||
// Validate children
|
||||
cy.get('[data-cy-files-navigation-item="sharingin"]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-item="sharingin"]').should('contain.text', 'Shared with me') |
||||
|
||||
// Toggle the sharing entry children 🇦again
|
||||
cy.get('[data-cy-files-navigation-item="sharing"] button.icon-collapse').click({ force: true }) |
||||
cy.get('[data-cy-files-navigation-item="sharingin"]').should('not.be.visible') |
||||
|
||||
// Expect store update to be called
|
||||
cy.get('@viewConfigStore').its('update').should('have.been.calledWith', 'sharing', 'expanded', false) |
||||
}) |
||||
|
||||
it('Throws when adding a duplicate entry', () => { |
||||
Navigation.register(createView('files', 'Files')) |
||||
expect(() => Navigation.register(createView('files', 'Files'))) |
||||
.to.throw('View id files is already registered') |
||||
}) |
||||
}) |
||||
|
||||
describe('Quota rendering', () => { |
||||
before(async () => { |
||||
delete window._nc_navigation |
||||
mockWindow() |
||||
getNavigation().register(createView('files', 'Files')) |
||||
await router.replace({ name: 'filelist', params: { view: 'files' } }) |
||||
}) |
||||
|
||||
afterEach(() => cy.unmockInitialState()) |
||||
|
||||
it('Unknown quota', () => { |
||||
cy.mount(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.get('[data-cy-files-navigation-settings-quota]').should('not.exist') |
||||
}) |
||||
|
||||
it('Unlimited quota', () => { |
||||
cy.mockInitialState('files', 'storageStats', { |
||||
used: 1024 * 1024 * 1024, |
||||
quota: -1, |
||||
total: 50 * 1024 * 1024 * 1024, |
||||
}) |
||||
|
||||
cy.mount(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.get('[data-cy-files-navigation-settings-quota]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-settings-quota]').should('contain.text', '1 GB used') |
||||
cy.get('[data-cy-files-navigation-settings-quota] progress').should('not.exist') |
||||
}) |
||||
|
||||
it('Non-reached quota', () => { |
||||
cy.mockInitialState('files', 'storageStats', { |
||||
used: 1024 * 1024 * 1024, |
||||
quota: 5 * 1024 * 1024 * 1024, |
||||
total: 5 * 1024 * 1024 * 1024, |
||||
relative: 20, // percent
|
||||
}) |
||||
|
||||
cy.mount(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.get('[data-cy-files-navigation-settings-quota]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-settings-quota]').should('contain.text', '1 GB of 5 GB used') |
||||
cy.get('[data-cy-files-navigation-settings-quota] progress') |
||||
.should('exist') |
||||
.and('have.attr', 'value', '20') |
||||
}) |
||||
|
||||
it('Reached quota', () => { |
||||
cy.mockInitialState('files', 'storageStats', { |
||||
used: 5 * 1024 * 1024 * 1024, |
||||
quota: 1024 * 1024 * 1024, |
||||
total: 1024 * 1024 * 1024, |
||||
relative: 500, // percent
|
||||
}) |
||||
|
||||
cy.mount(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: cy.spy, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
cy.get('[data-cy-files-navigation-settings-quota]').should('be.visible') |
||||
cy.get('[data-cy-files-navigation-settings-quota]').should('contain.text', '5 GB of 1 GB used') |
||||
cy.get('[data-cy-files-navigation-settings-quota] progress') |
||||
.should('exist') |
||||
.and('have.attr', 'value', '100') // progress max is 100
|
||||
}) |
||||
}) |
||||
@ -0,0 +1,286 @@ |
||||
/** |
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import type { Folder, Navigation } from '@nextcloud/files' |
||||
|
||||
import FolderSvg from '@mdi/svg/svg/folder.svg?raw' |
||||
import { getNavigation, View } from '@nextcloud/files' |
||||
import { createTestingPinia } from '@pinia/testing' |
||||
import { cleanup, fireEvent, getAllByRole, render } from '@testing-library/vue' |
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' |
||||
import NavigationView from './Navigation.vue' |
||||
import router from '../router/router.ts' |
||||
import RouterService from '../services/RouterService.ts' |
||||
import { useViewConfigStore } from '../store/viewConfig.ts' |
||||
|
||||
afterEach(() => removeInitialState()) |
||||
beforeAll(async () => { |
||||
Object.defineProperty(document.documentElement, 'clientWidth', { value: 1920 }) |
||||
await fireEvent.resize(window) |
||||
}) |
||||
|
||||
describe('Navigation', () => { |
||||
beforeEach(cleanup) |
||||
|
||||
beforeEach(async () => { |
||||
delete window._nc_navigation |
||||
mockWindow() |
||||
getNavigation().register(createView('files', 'Files')) |
||||
await router.replace({ name: 'filelist', params: { view: 'files' } }) |
||||
}) |
||||
|
||||
it('renders navigation with settings button and search', async () => { |
||||
const component = render(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
// see the navigation
|
||||
await expect(component.findByRole('navigation', { name: 'Files' })).resolves.not.toThrow() |
||||
// see the search box
|
||||
await expect(component.findByRole('searchbox', { name: /Search here/ })).resolves.not.toThrow() |
||||
// see the settings entry
|
||||
await expect(component.findByRole('link', { name: /Files settings/ })).resolves.not.toThrow() |
||||
}) |
||||
|
||||
it('renders no quota without storage stats', () => { |
||||
const component = render(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
expect(component.baseElement.querySelector('[data-cy-files-navigation-settings-quota]')).toBeNull() |
||||
}) |
||||
|
||||
it('Unlimited quota shows used storage but no progressbar', async () => { |
||||
mockInitialState('files', 'storageStats', { |
||||
used: 1024 * 1024 * 1024, |
||||
quota: -1, |
||||
total: 50 * 1024 * 1024 * 1024, |
||||
}) |
||||
|
||||
const component = render(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
expect(component.baseElement.querySelector('[data-cy-files-navigation-settings-quota]')).not.toBeNull() |
||||
|
||||
await expect(component.findByText('1 GB used')).resolves.not.toThrow() |
||||
await expect(component.findByRole('progressbar')).rejects.toThrow() |
||||
}) |
||||
|
||||
it('Non-reached quota shows stats and progress', async () => { |
||||
mockInitialState('files', 'storageStats', { |
||||
used: 1024 * 1024 * 1024, |
||||
quota: 5 * 1024 * 1024 * 1024, |
||||
total: 5 * 1024 * 1024 * 1024, |
||||
relative: 20, // percent
|
||||
}) |
||||
|
||||
const component = render(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
await expect(component.findByText('1 GB of 5 GB used')).resolves.not.toThrow() |
||||
await expect(component.findByRole('progressbar')).resolves.not.toThrow() |
||||
expect((component.getByRole('progressbar') as HTMLProgressElement).value).toBe(20) |
||||
}) |
||||
|
||||
it('Reached quota', async () => { |
||||
mockInitialState('files', 'storageStats', { |
||||
used: 5 * 1024 * 1024 * 1024, |
||||
quota: 1024 * 1024 * 1024, |
||||
total: 1024 * 1024 * 1024, |
||||
relative: 500, // percent
|
||||
}) |
||||
|
||||
const component = render(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
})], |
||||
}, |
||||
}) |
||||
|
||||
await expect(component.findByText('5 GB of 1 GB used')).resolves.not.toThrow() |
||||
await expect(component.findByRole('progressbar')).resolves.not.toThrow() |
||||
expect((component.getByRole('progressbar') as HTMLProgressElement).value).toBe(100) |
||||
}) |
||||
}) |
||||
|
||||
describe('Navigation API', () => { |
||||
let Navigation: Navigation |
||||
|
||||
beforeEach(async () => { |
||||
delete window._nc_navigation |
||||
Navigation = getNavigation() |
||||
mockWindow() |
||||
|
||||
await router.replace({ name: 'filelist', params: { view: 'files' } }) |
||||
}) |
||||
|
||||
beforeEach(resetNavigation) |
||||
beforeEach(cleanup) |
||||
|
||||
it('Check API entries rendering', async () => { |
||||
Navigation.register(createView('files', 'Files')) |
||||
|
||||
const component = render(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [ |
||||
createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
}), |
||||
], |
||||
}, |
||||
}) |
||||
|
||||
// see the navigation
|
||||
await expect(component.findByRole('navigation', { name: 'Files' })).resolves.not.toThrow() |
||||
// see the views
|
||||
await expect(component.findByRole('list', { name: 'Views' })).resolves.not.toThrow() |
||||
// see the entry
|
||||
await expect(component.findByRole('link', { name: 'Files' })).resolves.not.toThrow() |
||||
// see that the entry has all props
|
||||
const entry = component.getByRole('link', { name: 'Files' }) |
||||
expect(entry.getAttribute('href')).toMatch(/\/apps\/files\/files$/) |
||||
expect(entry.getAttribute('aria-current')).toBe('page') |
||||
expect(entry.getAttribute('title')).toBe('Files') |
||||
}) |
||||
|
||||
it('Adds a new entry and render', async () => { |
||||
Navigation.register(createView('files', 'Files')) |
||||
Navigation.register(createView('sharing', 'Sharing')) |
||||
|
||||
const component = render(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [ |
||||
createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
}), |
||||
], |
||||
}, |
||||
}) |
||||
|
||||
const list = component.getByRole('list', { name: 'Views' }) |
||||
expect(getAllByRole(list, 'listitem')).toHaveLength(2) |
||||
|
||||
await expect(component.findByRole('link', { name: 'Files' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('link', { name: 'Sharing' })).resolves.not.toThrow() |
||||
// see that the entry has all props
|
||||
const entry = component.getByRole('link', { name: 'Sharing' }) |
||||
expect(entry.getAttribute('href')).toMatch(/\/apps\/files\/sharing$/) |
||||
expect(entry.getAttribute('aria-current')).toBeNull() |
||||
expect(entry.getAttribute('title')).toBe('Sharing') |
||||
}) |
||||
|
||||
it('Adds a new children, render and open menu', async () => { |
||||
Navigation.register(createView('files', 'Files')) |
||||
Navigation.register(createView('sharing', 'Sharing')) |
||||
Navigation.register(createView('sharingin', 'Shared with me', 'sharing')) |
||||
|
||||
const component = render(NavigationView, { |
||||
router, |
||||
global: { |
||||
plugins: [ |
||||
createTestingPinia({ |
||||
createSpy: vi.fn, |
||||
}), |
||||
], |
||||
}, |
||||
}) |
||||
const viewConfigStore = useViewConfigStore() |
||||
|
||||
const list = component.getByRole('list', { name: 'Views' }) |
||||
expect(getAllByRole(list, 'listitem')).toHaveLength(3) |
||||
|
||||
// Toggle the sharing entry children
|
||||
const entry = component.getByRole('link', { name: 'Sharing' }) |
||||
expect(entry.getAttribute('aria-expanded')).toBe('false') |
||||
await fireEvent.click(component.getByRole('button', { name: 'Open menu' })) |
||||
expect(entry.getAttribute('aria-expanded')).toBe('true') |
||||
|
||||
// Expect store update to be called
|
||||
expect(viewConfigStore.update).toHaveBeenCalled() |
||||
expect(viewConfigStore.update).toHaveBeenCalledWith('sharing', 'expanded', true) |
||||
|
||||
// Validate children
|
||||
await expect(component.findByRole('link', { name: 'Shared with me' })).resolves.not.toThrow() |
||||
|
||||
await fireEvent.click(component.getByRole('button', { name: 'Collapse menu' })) |
||||
// Expect store update to be called
|
||||
expect(viewConfigStore.update).toHaveBeenCalledWith('sharing', 'expanded', false) |
||||
}) |
||||
}) |
||||
|
||||
/** |
||||
* Remove the mocked initial state |
||||
*/ |
||||
function removeInitialState(): void { |
||||
document.querySelectorAll('input[type="hidden"]').forEach((el) => { |
||||
el.remove() |
||||
}) |
||||
// clear the cache
|
||||
delete globalThis._nc_initial_state |
||||
} |
||||
|
||||
/** |
||||
* Helper to mock an initial state value |
||||
* @param app - The app |
||||
* @param key - The key |
||||
* @param value - The value |
||||
*/ |
||||
function mockInitialState(app: string, key: string, value: unknown): void { |
||||
const el = document.createElement('input') |
||||
el.value = btoa(JSON.stringify(value)) |
||||
el.id = `initial-state-${app}-${key}` |
||||
el.type = 'hidden' |
||||
|
||||
document.head.appendChild(el) |
||||
} |
||||
|
||||
function resetNavigation() { |
||||
const nav = getNavigation() |
||||
;[...nav.views].forEach(({ id }) => nav.remove(id)) |
||||
nav.setActive(null) |
||||
} |
||||
|
||||
function createView(id: string, name: string, parent?: string) { |
||||
return new View({ |
||||
id, |
||||
name, |
||||
getContents: async () => ({ folder: {} as Folder, contents: [] }), |
||||
icon: FolderSvg, |
||||
order: 1, |
||||
parent, |
||||
}) |
||||
} |
||||
|
||||
function mockWindow() { |
||||
window.OCP ??= {} |
||||
window.OCP.Files ??= {} |
||||
window.OCP.Files.Router = new RouterService(router) |
||||
} |
||||
@ -1,58 +0,0 @@ |
||||
/*! |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import Markdown from './Markdown.vue' |
||||
|
||||
describe('Markdown component', () => { |
||||
it('renders links', () => { |
||||
cy.mount(Markdown, { |
||||
propsData: { |
||||
text: 'This is [a link](http://example.com)!', |
||||
}, |
||||
}) |
||||
|
||||
cy.contains('This is') |
||||
.find('a') |
||||
.should('exist') |
||||
.and('have.attr', 'href', 'http://example.com') |
||||
.and('contain.text', 'a link') |
||||
}) |
||||
|
||||
it('renders headings', () => { |
||||
cy.mount(Markdown, { |
||||
propsData: { |
||||
text: '# level 1\nText\n## level 2\nText\n### level 3\nText\n#### level 4\nText\n##### level 5\nText\n###### level 6\nText\n', |
||||
}, |
||||
}) |
||||
|
||||
for (let level = 1; level <= 6; level++) { |
||||
cy.contains(`h${level}`, `level ${level}`) |
||||
.should('be.visible') |
||||
} |
||||
}) |
||||
|
||||
it('can limit headings', () => { |
||||
cy.mount(Markdown, { |
||||
propsData: { |
||||
text: '# level 1\nText\n## level 2\nText\n### level 3\nText\n#### level 4\nText\n##### level 5\nText\n###### level 6\nText\n', |
||||
minHeading: 4, |
||||
}, |
||||
}) |
||||
|
||||
cy.get('h1').should('not.exist') |
||||
cy.get('h2').should('not.exist') |
||||
cy.get('h3').should('not.exist') |
||||
cy.get('h4') |
||||
.should('exist') |
||||
.and('contain.text', 'level 1') |
||||
cy.get('h5') |
||||
.should('exist') |
||||
.and('contain.text', 'level 2') |
||||
cy.contains('h6', 'level 3').should('exist') |
||||
cy.contains('h6', 'level 4').should('exist') |
||||
cy.contains('h6', 'level 5').should('exist') |
||||
cy.contains('h6', 'level 6').should('exist') |
||||
}) |
||||
}) |
||||
@ -0,0 +1,58 @@ |
||||
/*! |
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import { cleanup, render } from '@testing-library/vue' |
||||
import { beforeEach, describe, expect, it } from 'vitest' |
||||
import Markdown from './Markdown.vue' |
||||
|
||||
describe('Markdown component', () => { |
||||
beforeEach(cleanup) |
||||
|
||||
it('renders links', () => { |
||||
const component = render(Markdown, { |
||||
props: { |
||||
text: 'This is [a link](http://example.com)!', |
||||
}, |
||||
}) |
||||
|
||||
const link = component.getByRole('link') |
||||
expect(link).toBeInstanceOf(HTMLAnchorElement) |
||||
expect(link.getAttribute('href')).toBe('http://example.com') |
||||
expect(link.textContent).toBe('a link') |
||||
}) |
||||
|
||||
it('renders headings', () => { |
||||
const component = render(Markdown, { |
||||
props: { |
||||
text: '# level 1\nText\n## level 2\nText\n### level 3\nText\n#### level 4\nText\n##### level 5\nText\n###### level 6\nText\n', |
||||
}, |
||||
}) |
||||
|
||||
for (let level = 1; level <= 6; level++) { |
||||
const heading = component.getByRole('heading', { level }) |
||||
expect(heading.textContent).toBe(`level ${level}`) |
||||
} |
||||
}) |
||||
|
||||
it('can limit headings', async () => { |
||||
const component = render(Markdown, { |
||||
props: { |
||||
text: '# level 1\nText\n## level 2\nText\n### level 3\nText\n#### level 4\nText\n##### level 5\nText\n###### level 6\nText\n', |
||||
minHeading: 4, |
||||
}, |
||||
}) |
||||
|
||||
await expect(component.findByRole('heading', { level: 1 })).rejects.toThrow() |
||||
await expect(component.findByRole('heading', { level: 2 })).rejects.toThrow() |
||||
await expect(component.findByRole('heading', { level: 3 })).rejects.toThrow() |
||||
|
||||
expect(component.getByRole('heading', { level: 4 }).textContent).toBe('level 1') |
||||
expect(component.getByRole('heading', { level: 5 }).textContent).toBe('level 2') |
||||
await expect(component.findByRole('heading', { level: 6, name: 'level 3' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('heading', { level: 6, name: 'level 4' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('heading', { level: 6, name: 'level 5' })).resolves.not.toThrow() |
||||
await expect(component.findByRole('heading', { level: 6, name: 'level 6' })).resolves.not.toThrow() |
||||
}) |
||||
}) |
||||
Loading…
Reference in new issue