Browse Source

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>
François Freitag 2 years ago
parent
commit
70edda0342
100 changed files with 506 additions and 119 deletions
  1. 1 0
      .npmignore
  2. 2 0
      Makefile
  3. 0 0
      apps/accessibility/js/accessibility.js
  4. 0 0
      apps/accessibility/js/accessibility.js.map
  5. 0 0
      apps/accessibility/js/accessibilityoca.js
  6. 0 0
      apps/accessibility/js/accessibilityoca.js.map
  7. 0 0
      apps/comments/js/comments-app.js
  8. 0 0
      apps/comments/js/comments-app.js.map
  9. 1 1
      apps/comments/js/comments-tab.js
  10. 0 0
      apps/comments/js/comments-tab.js.map
  11. 2 2
      apps/comments/js/comments.js
  12. 0 0
      apps/comments/js/comments.js.map
  13. 0 0
      apps/dashboard/js/dashboard.js
  14. 0 0
      apps/dashboard/js/dashboard.js.map
  15. 0 20
      apps/dav/js/settings-admin-caldav.js
  16. 0 0
      apps/dav/js/settings-admin-caldav.js.map
  17. 20 12
      apps/dav/lib/Settings/CalDAVSettings.php
  18. 24 0
      apps/dav/src/settings.js
  19. 116 0
      apps/dav/src/views/CalDavSettings.spec.js
  20. 127 0
      apps/dav/src/views/CalDavSettings.vue
  21. 146 0
      apps/dav/src/views/__snapshots__/CalDavSettings.spec.js.snap
  22. 3 74
      apps/dav/templates/settings-admin-caldav.php
  23. 21 1
      apps/dav/tests/unit/Settings/CalDAVSettingsTest.php
  24. 34 0
      apps/dav/webpack.js
  25. 0 0
      apps/files/js/dist/files-app-settings.js
  26. 0 0
      apps/files/js/dist/files-app-settings.js.map
  27. 0 0
      apps/files/js/dist/personal-settings.js
  28. 0 0
      apps/files/js/dist/personal-settings.js.map
  29. 0 0
      apps/files/js/dist/sidebar.js
  30. 0 0
      apps/files/js/dist/sidebar.js.map
  31. 0 0
      apps/files/js/dist/templates.js
  32. 0 0
      apps/files/js/dist/templates.js.map
  33. 1 1
      apps/files_sharing/js/dist/additionalScripts.js
  34. 0 0
      apps/files_sharing/js/dist/additionalScripts.js.map
  35. 1 1
      apps/files_sharing/js/dist/collaboration.js
  36. 0 0
      apps/files_sharing/js/dist/collaboration.js.map
  37. 1 1
      apps/files_sharing/js/dist/files_sharing.js
  38. 0 0
      apps/files_sharing/js/dist/files_sharing.js.map
  39. 0 0
      apps/files_sharing/js/dist/files_sharing_tab.js
  40. 0 0
      apps/files_sharing/js/dist/files_sharing_tab.js.map
  41. 1 1
      apps/files_sharing/js/dist/main.js
  42. 0 0
      apps/files_sharing/js/dist/main.js.map
  43. 0 0
      apps/files_sharing/js/dist/personal-settings.js
  44. 0 0
      apps/files_sharing/js/dist/personal-settings.js.map
  45. 0 0
      apps/oauth2/js/oauth2.js
  46. 0 0
      apps/oauth2/js/oauth2.js.map
  47. 0 0
      apps/settings/js/vue-settings-admin-security.js
  48. 0 0
      apps/settings/js/vue-settings-admin-security.js.map
  49. 0 0
      apps/settings/js/vue-settings-apps-71ba1435e61544e666dd.js
  50. 0 0
      apps/settings/js/vue-settings-apps-71ba1435e61544e666dd.js.map
  51. 0 0
      apps/settings/js/vue-settings-apps-users-management.js
  52. 0 0
      apps/settings/js/vue-settings-apps-users-management.js.map
  53. 0 0
      apps/settings/js/vue-settings-nextcloud-pdf.js
  54. 0 0
      apps/settings/js/vue-settings-nextcloud-pdf.js.map
  55. 0 0
      apps/settings/js/vue-settings-personal-security.js
  56. 0 0
      apps/settings/js/vue-settings-personal-security.js.map
  57. 0 0
      apps/settings/js/vue-settings-personal-webauthn.js
  58. 0 0
      apps/settings/js/vue-settings-personal-webauthn.js.map
  59. 0 0
      apps/settings/js/vue-settings-users-e9523b4bd694251f5445.js
  60. 0 0
      apps/settings/js/vue-settings-users-e9523b4bd694251f5445.js.map
  61. 0 0
      apps/settings/js/vue-vendors-settings-apps-746a2c9aa8003db0d7e0.js
  62. 0 0
      apps/settings/js/vue-vendors-settings-apps-746a2c9aa8003db0d7e0.js.map
  63. 0 0
      apps/settings/js/vue-vendors-settings-apps-settings-users-b7eaa4c12d321972abb3.js.map
  64. 0 0
      apps/settings/js/vue-vendors-settings-apps-settings-users-fe9900546ba8b1cb14be.js
  65. 0 0
      apps/settings/js/vue-vendors-settings-apps-settings-users-fe9900546ba8b1cb14be.js.map
  66. 0 0
      apps/settings/js/vue-vendors-settings-users-82a84c02fc05ac2141b8.js.map
  67. 0 0
      apps/settings/js/vue-vendors-settings-users-b43de2c7d332d5bae43a.js
  68. 0 0
      apps/settings/js/vue-vendors-settings-users-b43de2c7d332d5bae43a.js.map
  69. 0 0
      apps/twofactor_backupcodes/js/settings.js
  70. 0 0
      apps/twofactor_backupcodes/js/settings.js.map
  71. 0 0
      apps/updatenotification/js/updatenotification.js
  72. 0 0
      apps/updatenotification/js/updatenotification.js.map
  73. 0 0
      apps/user_status/js/dashboard.js
  74. 0 0
      apps/user_status/js/dashboard.js.map
  75. 0 0
      apps/user_status/js/user-status-menu.js
  76. 0 0
      apps/user_status/js/user-status-menu.js.map
  77. 0 0
      apps/user_status/js/user-status-modal.js
  78. 0 0
      apps/user_status/js/user-status-modal.js.map
  79. 0 0
      apps/user_status/js/vendors-user-status-modal.js
  80. 0 0
      apps/user_status/js/vendors-user-status-modal.js.map
  81. 0 0
      apps/weather_status/js/weather-status.js
  82. 0 0
      apps/weather_status/js/weather-status.js.map
  83. 0 0
      apps/workflowengine/js/workflowengine.js
  84. 0 0
      apps/workflowengine/js/workflowengine.js.map
  85. 0 1
      babel.config.js
  86. 1 0
      build/files-checker.php
  87. 1 1
      core/js/dist/files_client.js
  88. 0 0
      core/js/dist/files_client.js.map
  89. 0 0
      core/js/dist/files_fileinfo.js
  90. 0 0
      core/js/dist/files_fileinfo.js.map
  91. 0 0
      core/js/dist/files_iedavclient.js
  92. 0 0
      core/js/dist/files_iedavclient.js.map
  93. 1 1
      core/js/dist/install.js
  94. 0 0
      core/js/dist/install.js.map
  95. 1 1
      core/js/dist/login.js
  96. 0 0
      core/js/dist/login.js.map
  97. 1 1
      core/js/dist/main.js
  98. 0 0
      core/js/dist/main.js.map
  99. 0 0
      core/js/dist/maintenance.js
  100. 0 0
      core/js/dist/maintenance.js.map

+ 1 - 0
.npmignore

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

+ 2 - 0
Makefile

@@ -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 it is too large
+ 0 - 0
apps/accessibility/js/accessibility.js


File diff suppressed because it is too large
+ 0 - 0
apps/accessibility/js/accessibility.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/accessibility/js/accessibilityoca.js


File diff suppressed because it is too large
+ 0 - 0
apps/accessibility/js/accessibilityoca.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/comments/js/comments-app.js


File diff suppressed because it is too large
+ 0 - 0
apps/comments/js/comments-app.js.map


+ 1 - 1
apps/comments/js/comments-tab.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>
  *

File diff suppressed because it is too large
+ 0 - 0
apps/comments/js/comments-tab.js.map


File diff suppressed because it is too large
+ 2 - 2
apps/comments/js/comments.js


File diff suppressed because it is too large
+ 0 - 0
apps/comments/js/comments.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/dashboard/js/dashboard.js


File diff suppressed because it is too large
+ 0 - 0
apps/dashboard/js/dashboard.js.map


File diff suppressed because it is too large
+ 0 - 20
apps/dav/js/settings-admin-caldav.js


File diff suppressed because it is too large
+ 0 - 0
apps/dav/js/settings-admin-caldav.js.map


+ 20 - 12
apps/dav/lib/Settings/CalDAVSettings.php

@@ -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 - 0
apps/dav/src/settings.js

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

+ 116 - 0
apps/dav/src/views/CalDavSettings.spec.js

@@ -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()
+	})
+})

+ 127 - 0
apps/dav/src/views/CalDavSettings.vue

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

+ 146 - 0
apps/dav/src/views/__snapshots__/CalDavSettings.spec.js.snap

@@ -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 - 74
apps/dav/templates/settings-admin-caldav.php

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

+ 21 - 1
apps/dav/tests/unit/Settings/CalDAVSettingsTest.php

@@ -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 - 0
apps/dav/webpack.js

@@ -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 it is too large
+ 0 - 0
apps/files/js/dist/files-app-settings.js


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/files-app-settings.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/personal-settings.js


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/personal-settings.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/sidebar.js


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/sidebar.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/templates.js


File diff suppressed because it is too large
+ 0 - 0
apps/files/js/dist/templates.js.map


File diff suppressed because it is too large
+ 1 - 1
apps/files_sharing/js/dist/additionalScripts.js


File diff suppressed because it is too large
+ 0 - 0
apps/files_sharing/js/dist/additionalScripts.js.map


+ 1 - 1
apps/files_sharing/js/dist/collaboration.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>
  *

File diff suppressed because it is too large
+ 0 - 0
apps/files_sharing/js/dist/collaboration.js.map


File diff suppressed because it is too large
+ 1 - 1
apps/files_sharing/js/dist/files_sharing.js


File diff suppressed because it is too large
+ 0 - 0
apps/files_sharing/js/dist/files_sharing.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/files_sharing/js/dist/files_sharing_tab.js


File diff suppressed because it is too large
+ 0 - 0
apps/files_sharing/js/dist/files_sharing_tab.js.map


+ 1 - 1
apps/files_sharing/js/dist/main.js

@@ -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 it is too large
+ 0 - 0
apps/files_sharing/js/dist/main.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/files_sharing/js/dist/personal-settings.js


File diff suppressed because it is too large
+ 0 - 0
apps/files_sharing/js/dist/personal-settings.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/oauth2/js/oauth2.js


File diff suppressed because it is too large
+ 0 - 0
apps/oauth2/js/oauth2.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-admin-security.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-admin-security.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-apps-71ba1435e61544e666dd.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-apps-71ba1435e61544e666dd.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-apps-users-management.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-apps-users-management.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-nextcloud-pdf.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-nextcloud-pdf.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-personal-security.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-personal-security.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-personal-webauthn.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-personal-webauthn.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-users-e9523b4bd694251f5445.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-users-e9523b4bd694251f5445.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-vendors-settings-apps-746a2c9aa8003db0d7e0.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-vendors-settings-apps-746a2c9aa8003db0d7e0.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-vendors-settings-apps-settings-users-b7eaa4c12d321972abb3.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-vendors-settings-apps-settings-users-fe9900546ba8b1cb14be.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-vendors-settings-apps-settings-users-fe9900546ba8b1cb14be.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-vendors-settings-users-82a84c02fc05ac2141b8.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-vendors-settings-users-b43de2c7d332d5bae43a.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-vendors-settings-users-b43de2c7d332d5bae43a.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/twofactor_backupcodes/js/settings.js


File diff suppressed because it is too large
+ 0 - 0
apps/twofactor_backupcodes/js/settings.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/updatenotification/js/updatenotification.js


File diff suppressed because it is too large
+ 0 - 0
apps/updatenotification/js/updatenotification.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/user_status/js/dashboard.js


File diff suppressed because it is too large
+ 0 - 0
apps/user_status/js/dashboard.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/user_status/js/user-status-menu.js


File diff suppressed because it is too large
+ 0 - 0
apps/user_status/js/user-status-menu.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/user_status/js/user-status-modal.js


File diff suppressed because it is too large
+ 0 - 0
apps/user_status/js/user-status-modal.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/user_status/js/vendors-user-status-modal.js


File diff suppressed because it is too large
+ 0 - 0
apps/user_status/js/vendors-user-status-modal.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/weather_status/js/weather-status.js


File diff suppressed because it is too large
+ 0 - 0
apps/weather_status/js/weather-status.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/workflowengine/js/workflowengine.js


File diff suppressed because it is too large
+ 0 - 0
apps/workflowengine/js/workflowengine.js.map


+ 0 - 1
babel.config.js

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

+ 1 - 0
build/files-checker.php

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

File diff suppressed because it is too large
+ 1 - 1
core/js/dist/files_client.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_client.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_fileinfo.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_fileinfo.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_iedavclient.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/files_iedavclient.js.map


File diff suppressed because it is too large
+ 1 - 1
core/js/dist/install.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/install.js.map


File diff suppressed because it is too large
+ 1 - 1
core/js/dist/login.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/login.js.map


File diff suppressed because it is too large
+ 1 - 1
core/js/dist/main.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/main.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/maintenance.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/maintenance.js.map


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