- 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>tags/v22.0.0beta4
@@ -0,0 +1 @@ | |||
**/*.spec.js |
@@ -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/ |
@@ -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> | |||
* |
@@ -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 |
@@ -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'); | |||
} | |||
/** |
@@ -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') |
@@ -0,0 +1,116 @@ | |||
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) | |||
expect(generateBirthdayCalendar).toBeEnabled() | |||
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() | |||
}) | |||
}) |
@@ -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> |
@@ -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> | |||
`; |
@@ -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> |
@@ -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); |
@@ -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', | |||
}, | |||
} |
@@ -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> | |||
* |
@@ -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> | |||
* |
@@ -7,7 +7,6 @@ module.exports = { | |||
[ | |||
'@babel/preset-env', | |||
{ | |||
modules: false, | |||
useBuiltIns: false, | |||
}, | |||
], |
@@ -37,6 +37,7 @@ $expectedFiles = [ | |||
'.idea', | |||
'.jshintrc', | |||
'.mailmap', | |||
'.npmignore', | |||
'.php_cs.dist', | |||
'.scrutinizer.yml', | |||
'.tag', |