Port dav calendar settings page to Vue.js

- Drop reliance on deprecated global jQuery object.
- Allow testing user interactions.
- Use newer technology stack.

---

Test user interactions with the groupware dav settings

Add infrastructure to test Vue components:

- Use recommended libraries:

    - https://vuejs.org/v2/guide/testing.html#Recommendations
    - Use jest-dom for robust assertions on the DOM state
    - Use user-event to be more representative of user actions

- Code is transpiled by Jest, with the help of vue-jest.

Ignore test files for no-unpublished-import. Prevent ESLint from
flagging:

```
/home/runner/work/server/server/apps/dav/src/views/CalDavSettings.spec.js
Error:   1:24  error  "@testing-library/vue" is not published         node/no-unpublished-import
Error:   2:23  error  "@testing-library/user-event" is not published  node/no-unpublished-import
```

Signed-off-by: François Freitag <mail@franek.fr>
This commit is contained in:
François Freitag 2021-05-16 17:56:50 +02:00
parent fd63132f47
commit 4cbd965840
108 changed files with 1945 additions and 988 deletions

1
.npmignore Normal file
View File

@ -0,0 +1 @@
**/*.spec.js

View File

@ -30,6 +30,7 @@ lint-fix-watch:
clean:
rm -rf apps/accessibility/js/
rm -rf apps/comments/js/
rm -rf apps/dav/js/
rm -rf apps/files/js/dist/
rm -rf apps/files_sharing/js/dist/
rm -rf apps/files_trashbin/js/
@ -46,6 +47,7 @@ clean:
clean-git: clean
git checkout -- apps/accessibility/js/
git checkout -- apps/comments/js/
git checkout -- apps/dav/js/
git checkout -- apps/files/js/dist/
git checkout -- apps/files_sharing/js/dist/
git checkout -- apps/files_trashbin/js/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/js/",n(n.s=706)}({706:function(e,n){
!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/js/",n(n.s=721)}({721:function(e,n){
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
!function(e){var n={};function t(o){if(n[o])return n[o].exports;var i=n[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,t),i.l=!0,i.exports}t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:o})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(t.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var i in e)t.d(o,i,function(n){return e[n]}.bind(null,i));return o},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/js/",t(t.s=429)}({429:function(e,n,t){"use strict";t.r(n);t(430),t(431),t(432),t(433);
!function(e){var n={};function t(o){if(n[o])return n[o].exports;var i=n[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,t),i.l=!0,i.exports}t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:o})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(t.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var i in e)t.d(o,i,function(n){return e[n]}.bind(null,i));return o},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/js/",t(t.s=437)}({437:function(e,n,t){"use strict";t.r(n);t(438),t(439),t(440),t(441);
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
@ -21,7 +21,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
window.OCA.Comments=OCA.Comments},430:function(e,n){
window.OCA.Comments=OCA.Comments},438:function(e,n){
/**
* Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com>
*
@ -44,5 +44,5 @@ window.OCA.Comments=OCA.Comments},430:function(e,n){
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
OCA.Comments||(OCA.Comments={})},431:function(e,n){var t;t=Handlebars.template,(OCA.Comments.Templates=OCA.Comments.Templates||{}).filesplugin=t({compiler:[8,">= 4.3.0"],main:function(e,n,t,o,i){var s,r=null!=n?n:e.nullContext||{},a=e.hooks.helperMissing,l=e.escapeExpression,m=e.lookupProperty||function(e,n){if(Object.prototype.hasOwnProperty.call(e,n))return e[n]};return'<a class="action action-comment permanent" title="'+l("function"==typeof(s=null!=(s=m(t,"countMessage")||(null!=n?m(n,"countMessage"):n))?s:a)?s.call(r,{name:"countMessage",hash:{},data:i,loc:{start:{line:1,column:50},end:{line:1,column:66}}}):s)+'" href="#">\n\t<img class="svg" src="'+l("function"==typeof(s=null!=(s=m(t,"iconUrl")||(null!=n?m(n,"iconUrl"):n))?s:a)?s.call(r,{name:"iconUrl",hash:{},data:i,loc:{start:{line:2,column:23},end:{line:2,column:34}}}):s)+'"/>\n</a>\n'},useData:!0})},432:function(e,o){_.extend(OC.Files.Client,{PROPERTY_COMMENTS_UNREAD:"{"+OC.Files.Client.NS_OWNCLOUD+"}comments-unread"}),OCA.Comments=_.extend({},OCA.Comments),OCA.Comments||(OCA.Comments={}),OCA.Comments.FilesPlugin={ignoreLists:["trashbin","files.public"],_formatCommentCount:e=>OCA.Comments.Templates.filesplugin({count:e,countMessage:n("comments","%n unread comment","%n unread comments",e),iconUrl:OC.imagePath("core","actions/comment")}),attach(e){const o=this;if(this.ignoreLists.indexOf(e.id)>=0)return;const i=e._getWebdavProperties;e._getWebdavProperties=function(){const e=i.apply(this,arguments);return e.push(OC.Files.Client.PROPERTY_COMMENTS_UNREAD),e},e.filesClient.addFileInfoParser((function(e){const n={},t=e.propStat[0].properties[OC.Files.Client.PROPERTY_COMMENTS_UNREAD];return _.isUndefined(t)||""===t||(n.commentsUnread=parseInt(t,10)),n})),e.$el.addClass("has-comments");const s=e._createRow;e._createRow=function(e){const n=s.apply(this,arguments);return e.commentsUnread&&n.attr("data-comments-unread",e.commentsUnread),n},e.fileActions.registerAction({name:"Comment",displayName(e){if(e&&e.$file){const t=parseInt(e.$file.data("comments-unread"),10);if(t>=0)return n("comments","1 new comment","{unread} new comments",t,{unread:t})}return t("comments","Comment")},mime:"all",order:-140,iconClass:"icon-comment",permissions:OC.PERMISSION_READ,type:OCA.Files.FileActions.TYPE_INLINE,render(e,n,t){const i=t.$file.data("comments-unread");if(i){const e=$(o._formatCommentCount(i));return t.$file.find("a.name>span.fileactions").append(e),e}return""},actionHandler(e,n){n.$file.find(".action-comment").tooltip("hide"),OCA.Files.Sidebar.setActiveTab("comments"),OCA.Files.Sidebar.open("/"+e)}});const r=e.elementToFile;e.elementToFile=function(e){const n=r.apply(this,arguments),t=e.data("comments-unread");return t&&(n.commentsUnread=t),n}}},OC.Plugins.register("OCA.Files.FileList",OCA.Comments.FilesPlugin)},433:function(e,n){OCA.Comments.ActivityTabViewPlugin={prepareModelForDisplay(e,n,t){if("comments"===e.get("app")&&"comments"===e.get("type")&&"ActivityTabView"===t&&(n.addClass("comment"),e.get("message")&&this._isLong(e.get("message")))){n.addClass("collapsed");const e=$("<div>").addClass("message-overlay");n.find(".activitymessage").after(e),n.on("click",this._onClickCollapsedComment)}},_onClickCollapsedComment(e){let n=$(e.target);n.is(".comment")||(n=n.closest(".comment")),n.removeClass("collapsed")},_isLong:e=>e.length>250||(e.match(/\n/g)||[]).length>1},OC.Plugins.register("OCA.Activity.RenderingPlugins",OCA.Comments.ActivityTabViewPlugin)}});
OCA.Comments||(OCA.Comments={})},439:function(e,n){var t;t=Handlebars.template,(OCA.Comments.Templates=OCA.Comments.Templates||{}).filesplugin=t({compiler:[8,">= 4.3.0"],main:function(e,n,t,o,i){var s,r=null!=n?n:e.nullContext||{},a=e.hooks.helperMissing,l=e.escapeExpression,m=e.lookupProperty||function(e,n){if(Object.prototype.hasOwnProperty.call(e,n))return e[n]};return'<a class="action action-comment permanent" title="'+l("function"==typeof(s=null!=(s=m(t,"countMessage")||(null!=n?m(n,"countMessage"):n))?s:a)?s.call(r,{name:"countMessage",hash:{},data:i,loc:{start:{line:1,column:50},end:{line:1,column:66}}}):s)+'" href="#">\n\t<img class="svg" src="'+l("function"==typeof(s=null!=(s=m(t,"iconUrl")||(null!=n?m(n,"iconUrl"):n))?s:a)?s.call(r,{name:"iconUrl",hash:{},data:i,loc:{start:{line:2,column:23},end:{line:2,column:34}}}):s)+'"/>\n</a>\n'},useData:!0})},440:function(e,o){_.extend(OC.Files.Client,{PROPERTY_COMMENTS_UNREAD:"{"+OC.Files.Client.NS_OWNCLOUD+"}comments-unread"}),OCA.Comments=_.extend({},OCA.Comments),OCA.Comments||(OCA.Comments={}),OCA.Comments.FilesPlugin={ignoreLists:["trashbin","files.public"],_formatCommentCount:e=>OCA.Comments.Templates.filesplugin({count:e,countMessage:n("comments","%n unread comment","%n unread comments",e),iconUrl:OC.imagePath("core","actions/comment")}),attach(e){const o=this;if(this.ignoreLists.indexOf(e.id)>=0)return;const i=e._getWebdavProperties;e._getWebdavProperties=function(){const e=i.apply(this,arguments);return e.push(OC.Files.Client.PROPERTY_COMMENTS_UNREAD),e},e.filesClient.addFileInfoParser((function(e){const n={},t=e.propStat[0].properties[OC.Files.Client.PROPERTY_COMMENTS_UNREAD];return _.isUndefined(t)||""===t||(n.commentsUnread=parseInt(t,10)),n})),e.$el.addClass("has-comments");const s=e._createRow;e._createRow=function(e){const n=s.apply(this,arguments);return e.commentsUnread&&n.attr("data-comments-unread",e.commentsUnread),n},e.fileActions.registerAction({name:"Comment",displayName(e){if(e&&e.$file){const t=parseInt(e.$file.data("comments-unread"),10);if(t>=0)return n("comments","1 new comment","{unread} new comments",t,{unread:t})}return t("comments","Comment")},mime:"all",order:-140,iconClass:"icon-comment",permissions:OC.PERMISSION_READ,type:OCA.Files.FileActions.TYPE_INLINE,render(e,n,t){const i=t.$file.data("comments-unread");if(i){const e=$(o._formatCommentCount(i));return t.$file.find("a.name>span.fileactions").append(e),e}return""},actionHandler(e,n){n.$file.find(".action-comment").tooltip("hide"),OCA.Files.Sidebar.setActiveTab("comments"),OCA.Files.Sidebar.open("/"+e)}});const r=e.elementToFile;e.elementToFile=function(e){const n=r.apply(this,arguments),t=e.data("comments-unread");return t&&(n.commentsUnread=t),n}}},OC.Plugins.register("OCA.Files.FileList",OCA.Comments.FilesPlugin)},441:function(e,n){OCA.Comments.ActivityTabViewPlugin={prepareModelForDisplay(e,n,t){if("comments"===e.get("app")&&"comments"===e.get("type")&&"ActivityTabView"===t&&(n.addClass("comment"),e.get("message")&&this._isLong(e.get("message")))){n.addClass("collapsed");const e=$("<div>").addClass("message-overlay");n.find(".activitymessage").after(e),n.on("click",this._onClickCollapsedComment)}},_onClickCollapsedComment(e){let n=$(e.target);n.is(".comment")||(n=n.closest(".comment")),n.removeClass("collapsed")},_isLong:e=>e.length>250||(e.match(/\n/g)||[]).length>1},OC.Plugins.register("OCA.Activity.RenderingPlugins",OCA.Comments.ActivityTabViewPlugin)}});
//# sourceMappingURL=comments.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,7 @@
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Thomas Citharel <nextcloud@tcit.fr>
* @author François Freitag <mail@franek.fr>
*
* @license GNU AGPL version 3 or any later version
*
@ -25,8 +26,10 @@
namespace OCA\DAV\Settings;
use OCA\DAV\AppInfo\Application;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\AppFramework\Services\IInitialState;
use OCP\Settings\ISettings;
class CalDAVSettings implements ISettings {
@ -34,27 +37,32 @@ class CalDAVSettings implements ISettings {
/** @var IConfig */
private $config;
/** @var IInitialState */
private $initialState;
/**
* CalDAVSettings constructor.
*
* @param IConfig $config
* @param IInitialState $initialState
*/
public function __construct(IConfig $config) {
public function __construct(IConfig $config, IInitialState $initialState) {
$this->config = $config;
$this->initialState = $initialState;
}
/**
* @return TemplateResponse
*/
public function getForm() {
$parameters = [
'send_invitations' => $this->config->getAppValue('dav', 'sendInvitations', 'yes'),
'generate_birthday_calendar' => $this->config->getAppValue('dav', 'generateBirthdayCalendar', 'yes'),
'send_reminders_notifications' => $this->config->getAppValue('dav', 'sendEventReminders', 'yes'),
'send_reminders_notifications_push' => $this->config->getAppValue('dav', 'sendEventRemindersPush', 'no'),
public function getForm(): TemplateResponse {
$defaults = [
'sendInvitations' => 'yes',
'generateBirthdayCalendar' => 'yes',
'sendEventReminders' => 'yes',
'sendEventRemindersPush' => 'no',
];
return new TemplateResponse('dav', 'settings-admin-caldav', $parameters);
foreach ($defaults as $key => $default) {
$value = $this->config->getAppValue(Application::APP_ID, $key, $default);
$this->initialState->provideInitialState($key, $value === 'yes');
}
return new TemplateResponse(Application::APP_ID, 'settings-admin-caldav');
}
/**

24
apps/dav/src/settings.js Normal file
View File

@ -0,0 +1,24 @@
import Vue from 'vue'
import { loadState } from '@nextcloud/initial-state'
import { translate } from '@nextcloud/l10n'
import CalDavSettings from './views/CalDavSettings'
Vue.prototype.$t = translate
const View = Vue.extend(CalDavSettings)
const CalDavSettingsView = new View({
name: 'CalDavSettingsView',
data() {
return {
sendInvitations: loadState('dav', 'sendInvitations'),
generateBirthdayCalendar: loadState(
'dav',
'generateBirthdayCalendar'
),
sendEventReminders: loadState('dav', 'sendEventReminders'),
sendEventRemindersPush: loadState('dav', 'sendEventRemindersPush'),
}
},
})
CalDavSettingsView.$mount('#settings-admin-caldav')

View File

@ -0,0 +1,115 @@
import axios from '@nextcloud/axios'
import { render } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import CalDavSettings from './CalDavSettings'
// eslint-disable-next-line no-unused-vars
import { generateUrl } from '@nextcloud/router'
jest.mock('@nextcloud/axios')
jest.mock('@nextcloud/router', () => {
return {
generateUrl(url) {
return url
},
}
})
describe('CalDavSettings', () => {
const originalOC = global.OC
const originalOCP = global.OCP
beforeEach(() => {
global.OC = { requestToken: 'secret' }
global.OCP = {
AppConfig: {
setValue: jest.fn(),
},
}
})
afterAll(() => {
global.OC = originalOC
global.OCP = originalOCP
})
test('interactions', async() => {
const TLUtils = render(
CalDavSettings,
{
data() {
return {
sendInvitations: true,
generateBirthdayCalendar: true,
sendEventReminders: true,
sendEventRemindersPush: true,
}
},
},
Vue => {
Vue.prototype.$t = jest.fn((app, text) => text)
}
)
expect(TLUtils.container).toMatchSnapshot()
const sendInvitations = TLUtils.getByLabelText(
'Send invitations to attendees'
)
expect(sendInvitations).toBeChecked()
const generateBirthdayCalendar = TLUtils.getByLabelText(
'Automatically generate a birthday calendar'
)
expect(generateBirthdayCalendar).toBeChecked()
const sendEventReminders = TLUtils.getByLabelText(
'Send notifications for events'
)
expect(sendEventReminders).toBeChecked()
const sendEventRemindersPush = TLUtils.getByLabelText(
'Enable notifications for events via push'
)
expect(sendEventRemindersPush).toBeChecked()
await userEvent.click(sendInvitations)
expect(sendInvitations).not.toBeChecked()
expect(OCP.AppConfig.setValue).toHaveBeenCalledWith(
'dav',
'sendInvitations',
'no'
)
OCP.AppConfig.setValue.mockClear()
await userEvent.click(sendInvitations)
expect(sendInvitations).toBeChecked()
expect(OCP.AppConfig.setValue).toHaveBeenCalledWith(
'dav',
'sendInvitations',
'yes'
)
axios.post.mockImplementationOnce((uri) => {
expect(uri).toBe('/apps/dav/disableBirthdayCalendar')
return Promise.resolve()
})
await userEvent.click(generateBirthdayCalendar)
axios.post.mockImplementationOnce((uri) => {
expect(uri).toBe('/apps/dav/enableBirthdayCalendar')
return Promise.resolve()
})
await userEvent.click(generateBirthdayCalendar)
OCP.AppConfig.setValue.mockClear()
await userEvent.click(sendEventReminders)
expect(sendEventReminders).not.toBeChecked()
expect(OCP.AppConfig.setValue).toHaveBeenCalledWith(
'dav',
'sendEventReminders',
'no'
)
expect(sendEventRemindersPush).toBeDisabled()
OCP.AppConfig.setValue.mockClear()
await userEvent.click(sendEventReminders)
expect(sendEventReminders).toBeChecked()
expect(OCP.AppConfig.setValue).toHaveBeenCalledWith(
'dav',
'sendEventReminders',
'yes'
)
expect(sendEventRemindersPush).toBeEnabled()
})
})

View File

@ -0,0 +1,127 @@
<template>
<div class="section">
<h2>{{ $t('dav', 'Calendar server') }}</h2>
<!-- Can use v-html as:
- $t passes the translated string through DOMPurify.sanitize,
- replacement strings are not user-controlled. -->
<!-- eslint-disable-next-line vue/no-v-html -->
<p class="settings-hint" v-html="hint" />
<p>
<input
id="caldavSendInvitations"
v-model="sendInvitations"
type="checkbox"
class="checkbox">
<label for="caldavSendInvitations">
{{ $t('dav', 'Send invitations to attendees') }}
</label>
<br>
<!-- Can use v-html as:
- $t passes the translated string through DOMPurify.sanitize,
- replacement strings are not user-controlled. -->
<!-- eslint-disable-next-line vue/no-v-html -->
<em v-html="sendInvitationsHelpText" />
</p>
<p>
<input
id="caldavGenerateBirthdayCalendar"
v-model="generateBirthdayCalendar"
type="checkbox"
class="checkbox">
<label for="caldavGenerateBirthdayCalendar">
{{ $t('dav', 'Automatically generate a birthday calendar') }}
</label>
<br>
<em>
{{ $t('dav', 'Birthday calendars will be generated by a background job.') }}
</em>
<br>
<em>
{{ $t('dav', 'Hence they will not be available immediately after enabling but will show up after some time.') }}
</em>
</p>
<p>
<input
id="caldavSendEventReminders"
v-model="sendEventReminders"
type="checkbox"
class="checkbox">
<label for="caldavSendEventReminders">
{{ $t('dav', 'Send notifications for events') }}
</label>
<br>
<!-- Can use v-html as:
- $t passes the translated string through DOMPurify.sanitize,
- replacement strings are not user-controlled. -->
<!-- eslint-disable-next-line vue/no-v-html -->
<em v-html="sendEventRemindersHelpText" />
<br>
<em>
{{ $t('dav', 'Notifications are sent via background jobs, so these must occur often enough.') }}
</em>
</p>
<p>
<input
id="caldavSendEventRemindersPush"
v-model="sendEventRemindersPush"
type="checkbox"
class="checkbox"
:disabled="!sendEventReminders">
<label for="caldavSendEventRemindersPush">
{{ $t('dav', 'Enable notifications for events via push') }}
</label>
</p>
</div>
</template>
<script>
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
export default {
name: 'CalDavSettings',
computed: {
hint() {
const translated = this.$t(
'dav',
'Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}.',
)
return translated
.replace('{calendarappstoreopen}', '<a target="_blank" href="../apps/office/calendar">')
.replace('{calendardocopen}', '<a target="_blank" :href="userSyncCalendarsUrl" rel="noreferrer noopener">')
.replace(/\{linkclose\}/g, '</a>')
},
sendInvitationsHelpText() {
const translated = this.$t('dav', 'Please make sure to properly set up {emailopen}the email server{linkclose}.')
return translated
.replace('{emailopen}', '<a href="../admin#mail_general_settings">')
.replace('{linkclose}', '</a>')
},
sendEventRemindersHelpText() {
const translated = this.$t('dav', 'Please make sure to properly set up {emailopen}the email server{linkclose}.')
return translated
.replace('{emailopen}', '<a href="../admin#mail_general_settings">')
.replace('{linkclose}', '</a>')
},
},
watch: {
generateBirthdayCalendar(value) {
const baseUrl = value ? '/apps/dav/enableBirthdayCalendar' : '/apps/dav/disableBirthdayCalendar'
axios.post(generateUrl(baseUrl))
},
sendInvitations(value) {
OCP.AppConfig.setValue(
'dav',
'sendInvitations',
value ? 'yes' : 'no'
)
},
sendEventReminders(value) {
OCP.AppConfig.setValue('dav', 'sendEventReminders', value ? 'yes' : 'no')
},
sendEventRemindersPush(value) {
OCP.AppConfig.setValue('dav', 'sendEventRemindersPush', value ? 'yes' : 'no')
},
},
}
</script>

View File

@ -0,0 +1,146 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CalDavSettings interactions 1`] = `
<div>
<div
class="section"
>
<h2>
Calendar server
</h2>
<p
class="settings-hint"
>
Also install the
<a
href="../apps/office/calendar"
target="_blank"
>
Calendar app
</a>
, or
<a
:href="userSyncCalendarsUrl"
rel="noreferrer noopener"
target="_blank"
>
connect your desktop & mobile for syncing ↗
</a>
.
</p>
<p>
<input
class="checkbox"
id="caldavSendInvitations"
type="checkbox"
/>
<label
for="caldavSendInvitations"
>
Send invitations to attendees
</label>
<br />
<em>
Please make sure to properly set up
<a
href="../admin#mail_general_settings"
>
the email server
</a>
.
</em>
</p>
<p>
<input
class="checkbox"
id="caldavGenerateBirthdayCalendar"
type="checkbox"
/>
<label
for="caldavGenerateBirthdayCalendar"
>
Automatically generate a birthday calendar
</label>
<br />
<em>
Birthday calendars will be generated by a background job.
</em>
<br />
<em>
Hence they will not be available immediately after enabling but will show up after some time.
</em>
</p>
<p>
<input
class="checkbox"
id="caldavSendEventReminders"
type="checkbox"
/>
<label
for="caldavSendEventReminders"
>
Send notifications for events
</label>
<br />
<em>
Please make sure to properly set up
<a
href="../admin#mail_general_settings"
>
the email server
</a>
.
</em>
<br />
<em>
Notifications are sent via background jobs, so these must occur often enough.
</em>
</p>
<p>
<input
class="checkbox"
id="caldavSendEventRemindersPush"
type="checkbox"
/>
<label
for="caldavSendEventRemindersPush"
>
Enable notifications for events via push
</label>
</p>
</div>
</div>
`;

View File

@ -3,6 +3,7 @@
* @copyright 2017, Georg Ehrke <oc.list@georgehrke.com>
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author François Freitag <mail@franek.fr>
*
* @license GNU AGPL version 3 or any later version
*
@ -23,78 +24,6 @@
script('dav', 'settings-admin-caldav');
/** @var \OCP\IL10N $l */
/** @var array $_ */
?>
<form id="CalDAV" class="section">
<h2><?php p($l->t('Calendar server')); ?></h2>
<p class="settings-hint">
<?php print_unescaped(str_replace(
[
'{calendarappstoreopen}',
'{calendardocopen}',
'{linkclose}',
],
[
'<a target="_blank" href="../apps/office/calendar">',
'<a target="_blank" href="' . link_to_docs('user-sync-calendars') . '" rel="noreferrer noopener">',
'</a>',
],
$l->t('Also install the {calendarappstoreopen}Calendar app{linkclose}, or {calendardocopen}connect your desktop & mobile for syncing ↗{linkclose}.')
)); ?>
</p>
<p>
<input type="checkbox" name="caldav_send_invitations" id="caldavSendInvitations" class="checkbox"
<?php ($_['send_invitations'] === 'yes') ? print_unescaped('checked="checked"') : null ?>/>
<label for="caldavSendInvitations"><?php p($l->t('Send invitations to attendees')); ?></label>
<br>
<em>
<?php print_unescaped(str_replace(
[
'{emailopen}',
'{linkclose}',
],
[
'<a href="../admin#mail_general_settings">',
'</a>',
],
$l->t('Please make sure to properly set up {emailopen}the email server{linkclose}.')
)); ?>
</em>
</p>
<p>
<input type="checkbox" name="caldav_generate_birthday_calendar" id="caldavGenerateBirthdayCalendar" class="checkbox"
<?php ($_['generate_birthday_calendar'] === 'yes') ? print_unescaped('checked="checked"') : null ?>/>
<label for="caldavGenerateBirthdayCalendar"><?php p($l->t('Automatically generate a birthday calendar')); ?></label>
<br>
<em><?php p($l->t('Birthday calendars will be generated by a background job.')); ?></em><br>
<em><?php p($l->t('Hence they will not be available immediately after enabling but will show up after some time.')); ?></em>
</p>
<p>
<input type="checkbox" name="caldav_send_reminders_notifications" id="caldavSendRemindersNotifications" class="checkbox"
<?php ($_['send_reminders_notifications'] === 'yes') ? print_unescaped('checked="checked"') : null ?>/>
<label for="caldavSendRemindersNotifications"><?php p($l->t('Send notifications for events')); ?></label>
<br>
<em>
<?php print_unescaped(str_replace(
[
'{emailopen}',
'{linkclose}',
],
[
'<a href="../admin#mail_general_settings">',
'</a>',
],
$l->t('Please make sure to properly set up {emailopen}the email server{linkclose}.')
)); ?>
</em>
<br>
<em><?php p($l->t('Notifications are sent via background jobs, so these must occur often enough.')); ?></em>
</p>
<p>
<input type="checkbox" name="caldav_send_reminders_notifications_push" id="caldavSendRemindersNotificationsPush" class="checkbox"
<?php ($_['send_reminders_notifications_push'] === 'yes') ? print_unescaped('checked="checked"') : null ?>
<?php ($_['send_reminders_notifications'] === 'yes') ? null : print_unescaped('disabled="disabled"') ?> />
<label for="caldavSendRemindersNotificationsPush"><?php p($l->t('Enable notifications for events via push')); ?></label>
</p>
</form>
<div id="settings-admin-caldav"></div>

View File

@ -28,6 +28,7 @@ namespace OCA\DAV\Tests\Unit\DAV\Settings;
use OCA\DAV\Settings\CalDAVSettings;
use OCP\IConfig;
use OCP\AppFramework\Services\IInitialState;
use Test\TestCase;
class CalDAVSettingsTest extends TestCase {
@ -35,6 +36,9 @@ class CalDAVSettingsTest extends TestCase {
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
private $config;
/** @var OCP\AppFramework\Services\IInitialState|\PHPUnit\Framework\MockObject\MockObject */
private $initialState;
/** @var CalDAVSettings */
private $settings;
@ -42,10 +46,26 @@ class CalDAVSettingsTest extends TestCase {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->settings = new CalDAVSettings($this->config);
$this->initialState = $this->createMock(IInitialState::class);
$this->settings = new CalDAVSettings($this->config, $this->initialState);
}
public function testGetForm() {
$this->config->method('getAppValue')
->withConsecutive(
['dav', 'sendInvitations', 'yes'],
['dav', 'generateBirthdayCalendar', 'yes'],
['dav', 'sendEventReminders', 'yes'],
['dav', 'sendEventRemindersPush', 'no'],
)
->will($this->onConsecutiveCalls('yes', 'no', 'yes', 'yes'));
$this->initialState->method('provideInitialState')
->withConsecutive(
['sendInvitations', true],
['generateBirthdayCalendar', false],
['sendEventReminders', true],
['sendEventRemindersPush', true],
);
$result = $this->settings->getForm();
$this->assertInstanceOf('OCP\AppFramework\Http\TemplateResponse', $result);

34
apps/dav/webpack.js Normal file
View File

@ -0,0 +1,34 @@
/**
* @copyright Copyright (c) 2021 François Freitag <mail@franek.fr>
*
* @author François Freitag <mail@franek.fr>
*
* @license GNU AGPL version 3 or any later version
*
* 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/>.
*
*/
const path = require('path')
module.exports = {
entry: {
'settings-admin-caldav': path.join(__dirname, 'src', 'settings.js'),
},
output: {
path: path.resolve(__dirname, './js'),
publicPath: '/js/',
filename: '[name].js',
},
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/js/",t(t.s=155)}({155:function(e,n,r){
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/js/",t(t.s=179)}({179:function(e,n,r){
/**
* @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>
*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="/js/",r(r.s=467)}({467:function(e,t){
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="/js/",r(r.s=482)}({482:function(e,t){
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,6 @@ module.exports = {
[
'@babel/preset-env',
{
modules: false,
useBuiltIns: false,
},
],

View File

@ -37,6 +37,7 @@ $expectedFiles = [
'.idea',
'.jshintrc',
'.mailmap',
'.npmignore',
'.php_cs.dist',
'.scrutinizer.yml',
'.tag',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

119
core/js/dist/login.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

141
core/js/dist/main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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