Add component testing

Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
This commit is contained in:
John Molakvoæ 2022-12-28 15:29:54 +01:00
parent 8f1bf13ae3
commit 5b9a8f0407
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
110 changed files with 3337 additions and 322 deletions

View File

@ -136,7 +136,6 @@
this._setupEvents(); this._setupEvents();
// trigger URL change event handlers // trigger URL change event handlers
console.debug('F2V init', { ...OC.Util.History.parseUrlQuery(), view: this.navigation?.active?.id })
this._onPopState({ ...OC.Util.History.parseUrlQuery(), view: this.navigation?.active?.id }); this._onPopState({ ...OC.Util.History.parseUrlQuery(), view: this.navigation?.active?.id });
this._debouncedPersistShowHiddenFilesState = _.debounce(this._persistShowHiddenFilesState, 1200); this._debouncedPersistShowHiddenFilesState = _.debounce(this._persistShowHiddenFilesState, 1200);
@ -310,7 +309,6 @@
* Event handler for when the URL changed * Event handler for when the URL changed
*/ */
_onPopState: function(params) { _onPopState: function(params) {
console.debug('F2V onPopState', params);
params = _.extend({ params = _.extend({
dir: '/', dir: '/',
view: 'files' view: 'files'
@ -348,7 +346,6 @@
* Change the URL to point to the given dir and view * Change the URL to point to the given dir and view
*/ */
_changeUrl: function(view, dir, fileId) { _changeUrl: function(view, dir, fileId) {
console.debug('F2V changeUrl', { arguments });
var params = { dir: dir }; var params = { dir: dir };
if (view !== 'files') { if (view !== 'files') {
params.view = view; params.view = view;
@ -359,16 +356,13 @@
if (currentParams.dir === params.dir && currentParams.view === params.view) { if (currentParams.dir === params.dir && currentParams.view === params.view) {
if (currentParams.fileid !== params.fileid) { if (currentParams.fileid !== params.fileid) {
// if only fileid changed or was added, replace instead of push // if only fileid changed or was added, replace instead of push
console.debug('F2V 1', currentParams.fileid, params.fileid, params);
OC.Util.History.replaceState(this._makeUrlParams(params)); OC.Util.History.replaceState(this._makeUrlParams(params));
return return
} }
} else { } else {
console.debug('F2V 2', params);
OC.Util.History.pushState(this._makeUrlParams(params)); OC.Util.History.pushState(this._makeUrlParams(params));
return return
} }
console.debug('F2V 3', params, currentParams);
}, },
/** /**

View File

@ -756,7 +756,6 @@
* Event handler when leaving previously hidden state * Event handler when leaving previously hidden state
*/ */
_onShow: function(e) { _onShow: function(e) {
console.debug('F2V onShow', [e.dir, e.itemId], e);
OCA.Files.App && OCA.Files.App.updateCurrentFileList(this); OCA.Files.App && OCA.Files.App.updateCurrentFileList(this);
if (e.itemId === this.id) { if (e.itemId === this.id) {
this._setCurrentDir('/', false); this._setCurrentDir('/', false);
@ -771,7 +770,6 @@
* Event handler for when the URL changed * Event handler for when the URL changed
*/ */
_onUrlChanged: function(e) { _onUrlChanged: function(e) {
console.debug('F2V onUrlChanged', [e.dir], e);
if (e && _.isString(e.dir)) { if (e && _.isString(e.dir)) {
var currentDir = this.getCurrentDirectory(); var currentDir = this.getCurrentDirectory();
// this._currentDirectory is NULL when fileList is first initialised // this._currentDirectory is NULL when fileList is first initialised

View File

@ -285,13 +285,13 @@ class ApiController extends Controller {
* *
* @NoAdminRequired * @NoAdminRequired
* *
* @param bool $key * @param string $key
* @param string|bool $value * @param string|bool $value
* @return JSONResponse * @return JSONResponse
*/ */
public function setConfig(string $key, string|bool $value): JSONResponse { public function setConfig(string $key, string|bool $value): JSONResponse {
try { try {
$this->userConfig->setConfig($key, $value); $this->userConfig->setConfig($key, (string)$value);
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST); return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
} }

View File

@ -41,8 +41,9 @@ class UserConfig {
], ],
]; ];
private IConfig $config; protected IConfig $config;
private IUser|null $user; /** @var \OCP\IUser|null */
protected mixed $user = null;
public function __construct(IConfig $config, IUserSession $userSession) { public function __construct(IConfig $config, IUserSession $userSession) {
$this->config = $config; $this->config = $config;
@ -98,7 +99,7 @@ class UserConfig {
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public function setConfig($key, $value) { public function setConfig($key, $value) {
if (!$this->user) { if ($this->user === null) {
throw new \Exception('No user logged in'); throw new \Exception('No user logged in');
} }
@ -123,7 +124,7 @@ class UserConfig {
* @return array * @return array
*/ */
public function getConfigs(): array { public function getConfigs(): array {
if (!$this->user) { if ($this->user === null) {
throw new \Exception('No user logged in'); throw new \Exception('No user logged in');
} }

View File

@ -0,0 +1,118 @@
/* eslint-disable import/first */
import FolderSvg from '@mdi/svg/svg/folder.svg'
import ShareSvg from '@mdi/svg/svg/share-variant.svg'
import NavigationService from '../services/Navigation'
import NavigationView from './Navigation.vue'
import router from '../router/router.js'
const Navigation = new NavigationService()
console.log(FolderSvg)
describe('Navigation renders', () => {
it('renders', () => {
cy.mount(NavigationView, {
propsData: {
Navigation,
},
})
cy.get('[data-cy-files-navigation]').should('be.visible')
cy.get('[data-cy-files-navigation-settings-button]').should('be.visible')
})
})
describe('Navigation API', () => {
it('Check API entries rendering', () => {
Navigation.register({
id: 'files',
name: 'Files',
getFiles: () => [],
icon: FolderSvg,
order: 1,
})
cy.mount(NavigationView, {
propsData: {
Navigation,
},
router,
})
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({
id: 'sharing',
name: 'Sharing',
getFiles: () => [],
icon: ShareSvg,
order: 2,
})
cy.mount(NavigationView, {
propsData: {
Navigation,
},
router,
})
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({
id: 'sharingin',
name: 'Shared with me',
getFiles: () => [],
parent: 'sharing',
icon: ShareSvg,
order: 1,
})
cy.mount(NavigationView, {
propsData: {
Navigation,
},
router,
})
cy.get('[data-cy-files-navigation]').should('be.visible')
cy.get('[data-cy-files-navigation-item]').should('have.length', 3)
// Intercept collapse preference request
cy.intercept('POST', '*/apps/files/api/v1/toggleShowFolder/*', {
statusCode: 200,
}).as('toggleShowFolder')
// 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 })
cy.wait('@toggleShowFolder')
// 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')
})
it('Throws when adding a duplicate entry', () => {
expect(() => {
Navigation.register({
id: 'files',
name: 'Files',
getFiles: () => [],
icon: FolderSvg,
order: 1,
})
}).to.throw('Navigation id files is already registered')
})
})

View File

@ -20,22 +20,24 @@
- -
--> -->
<template> <template>
<NcAppNavigation> <NcAppNavigation data-cy-files-navigation>
<template #list> <template #list>
<NcAppNavigationItem v-for="view in parentViews" <NcAppNavigationItem v-for="view in parentViews"
:key="view.id" :key="view.id"
:allow-collapse="true" :allow-collapse="true"
:to="{name: 'filelist', params: { view: view.id }}" :data-cy-files-navigation-item="view.id"
:icon="view.iconClass" :icon="view.iconClass"
:open="view.expanded" :open="view.expanded"
:pinned="view.sticky" :pinned="view.sticky"
:title="view.name" :title="view.name"
:to="{name: 'filelist', params: { view: view.id }}"
@update:open="onToggleExpand(view)"> @update:open="onToggleExpand(view)">
<NcAppNavigationItem v-for="child in childViews[view.id]" <NcAppNavigationItem v-for="child in childViews[view.id]"
:key="child.id" :key="child.id"
:to="{name: 'filelist', params: { view: child.id }}" :data-cy-files-navigation-item="child.id"
:icon="child.iconClass" :icon="child.iconClass"
:title="child.name" /> :title="child.name"
:to="{name: 'filelist', params: { view: child.id }}" />
</NcAppNavigationItem> </NcAppNavigationItem>
</template> </template>
@ -44,6 +46,7 @@
<ul class="app-navigation-entry__settings"> <ul class="app-navigation-entry__settings">
<NcAppNavigationItem :aria-label="t('files', 'Open the files app settings')" <NcAppNavigationItem :aria-label="t('files', 'Open the files app settings')"
:title="t('files', 'Files settings')" :title="t('files', 'Files settings')"
data-cy-files-navigation-settings-button
@click.prevent.stop="openSettings"> @click.prevent.stop="openSettings">
<Cog slot="icon" :size="20" /> <Cog slot="icon" :size="20" />
</NcAppNavigationItem> </NcAppNavigationItem>
@ -52,6 +55,7 @@
<!-- Settings modal--> <!-- Settings modal-->
<SettingsModal :open="settingsOpened" <SettingsModal :open="settingsOpened"
data-cy-files-navigation-settings
@close="onSettingsClose" /> @close="onSettingsClose" />
</NcAppNavigation> </NcAppNavigation>
</template> </template>
@ -60,13 +64,15 @@
import { emit, subscribe } from '@nextcloud/event-bus' import { emit, subscribe } from '@nextcloud/event-bus'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import Cog from 'vue-material-design-icons/Cog.vue'
import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js' import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js' import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
import Cog from 'vue-material-design-icons/Cog.vue'
import SettingsModal from './Settings.vue'
import Navigation from '../services/Navigation.ts'
import logger from '../logger.js' import logger from '../logger.js'
import Navigation from '../services/Navigation.ts'
import SettingsModal from './Settings.vue'
import { translate } from '@nextcloud/l10n'
export default { export default {
name: 'Navigation', name: 'Navigation',
@ -152,7 +158,7 @@ export default {
*/ */
showView(view, oldView) { showView(view, oldView) {
// Closing any opened sidebar // Closing any opened sidebar
OCA.Files?.Sidebar?.close?.() window?.OCA?.Files?.Sidebar?.close?.()
if (view.legacy) { if (view.legacy) {
const newAppContent = document.querySelector('#app-content #app-content-' + this.currentView.id + '.viewcontainer') const newAppContent = document.querySelector('#app-content #app-content-' + this.currentView.id + '.viewcontainer')
@ -161,9 +167,6 @@ export default {
}) })
newAppContent.classList.remove('hidden') newAppContent.classList.remove('hidden')
// Legacy event
console.debug('F2V', $(newAppContent))
// Trigger init if not already done // Trigger init if not already done
window.jQuery(newAppContent).trigger(new window.jQuery.Event('show')) window.jQuery(newAppContent).trigger(new window.jQuery.Event('show'))
@ -171,7 +174,6 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
const { dir = '/' } = OC.Util.History.parseUrlQuery() const { dir = '/' } = OC.Util.History.parseUrlQuery()
const params = { itemId: view.id, dir } const params = { itemId: view.id, dir }
console.debug('F2V showView events', params, newAppContent);
window.jQuery(newAppContent).trigger(new window.jQuery.Event('show', params)) window.jQuery(newAppContent).trigger(new window.jQuery.Event('show', params))
window.jQuery(newAppContent).trigger(new window.jQuery.Event('urlChanged', params)) window.jQuery(newAppContent).trigger(new window.jQuery.Event('urlChanged', params))
}) })
@ -212,20 +214,20 @@ export default {
}, },
/** /**
* Open the settings modal and update the settings API entries * Open the settings modal
*/ */
openSettings() { openSettings() {
this.settingsOpened = true this.settingsOpened = true
OCA.Files.Settings.settings.forEach(setting => setting.open())
}, },
/** /**
* Close the settings modal and update the settings API entries * Close the settings modal
*/ */
onSettingsClose() { onSettingsClose() {
this.settingsOpened = false this.settingsOpened = false
OCA.Files.Settings.settings.forEach(setting => setting.close())
}, },
t: translate,
}, },
} }
</script> </script>

View File

@ -67,8 +67,12 @@ import { getCurrentUser } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state' import { loadState } from '@nextcloud/initial-state'
import { emit } from '@nextcloud/event-bus' import { emit } from '@nextcloud/event-bus'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { translate } from '@nextcloud/l10n'
const userConfig = loadState('files', 'config') const userConfig = loadState('files', 'config', {
show_hidden: false,
crop_image_previews: true,
})
export default { export default {
name: 'Settings', name: 'Settings',
@ -93,7 +97,7 @@ export default {
...userConfig, ...userConfig,
// Settings API // Settings API
settings: OCA.Files.Settings.settings, settings: window.OCA?.Files?.Settings?.settings || [],
// Webdav infos // Webdav infos
webdavUrl: generateRemoteUrl('dav/files/' + encodeURIComponent(getCurrentUser()?.uid)), webdavUrl: generateRemoteUrl('dav/files/' + encodeURIComponent(getCurrentUser()?.uid)),
@ -101,6 +105,16 @@ export default {
} }
}, },
beforeMount() {
// Update the settings API entries state
this.settings.forEach(setting => setting.open())
},
beforeDestroy() {
// Update the settings API entries state
this.settings.forEach(setting => setting.close())
},
methods: { methods: {
onClose() { onClose() {
this.$emit('close') this.$emit('close')
@ -112,6 +126,8 @@ export default {
value, value,
}) })
}, },
t: translate,
}, },
} }
</script> </script>

View File

@ -93,10 +93,8 @@ $expectedFiles = [
'tsconfig.json', 'tsconfig.json',
'vendor-bin', 'vendor-bin',
'version.php', 'version.php',
'webpack.common.js', 'webpack.config.js',
'webpack.dev.js',
'webpack.modules.js', 'webpack.modules.js',
'webpack.prod.js',
]; ];
$actualFiles = []; $actualFiles = [];

View File

@ -1,5 +1,12 @@
/* eslint-disable node/no-unpublished-import */ /* eslint-disable node/no-unpublished-import */
import { applyChangesToNextcloud, configureNextcloud, preppingNextcloud, startNextcloud, stopNextcloud, waitOnNextcloud } from './cypress/dockerNode'
import {
applyChangesToNextcloud,
configureNextcloud,
startNextcloud,
stopNextcloud,
waitOnNextcloud,
} from './cypress/dockerNode'
import { defineConfig } from 'cypress' import { defineConfig } from 'cypress'
import browserify from '@cypress/browserify-preprocessor' import browserify from '@cypress/browserify-preprocessor'
@ -29,6 +36,7 @@ export default defineConfig({
failSilently: false, failSilently: false,
type: 'actual', type: 'actual',
}, },
screenshotsFolder: 'cypress/snapshots/actual', screenshotsFolder: 'cypress/snapshots/actual',
trashAssetsBeforeRuns: true, trashAssetsBeforeRuns: true,
@ -82,4 +90,24 @@ export default defineConfig({
}) })
}, },
}, },
component: {
devServer: {
framework: 'vue',
bundler: 'webpack',
webpackConfig: async () => {
process.env.npm_package_name = 'NcCypress'
process.env.npm_package_version = '1.0.0'
process.env.NODE_ENV = 'development'
const config = require('@nextcloud/webpack-vue-config')
config.module.rules.push({
test: /\.svg$/,
type: 'asset/source',
})
return config
},
},
},
}) })

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<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>

View File

@ -0,0 +1,35 @@
/**
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { mount } from 'cypress/vue2'
type MountParams = Parameters<typeof mount>;
type OptionsParam = MountParams[1];
declare global {
namespace Cypress {
interface Chainable<Subject = any> {
mount: typeof mount;
}
}
}
Cypress.Commands.add('mount', mount);

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/core-login.js vendored

Binary file not shown.

BIN
dist/core-login.js.map vendored

Binary file not shown.

BIN
dist/core-main.js vendored

Binary file not shown.

BIN
dist/core-main.js.map vendored

Binary file not shown.

Binary file not shown.

BIN
dist/core-profile.js vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/dashboard-main.js vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/files-main.js vendored

Binary file not shown.

Binary file not shown.

BIN
dist/files-main.js.map vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/oauth2-oauth2.js vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More