Cleanup and tests for some Stores

Signed-off-by: Georg Ehrke <developer@georgehrke.com>
This commit is contained in:
Georg Ehrke 2020-05-22 15:14:00 +02:00
parent 0931c6cdff
commit e2237b6ba0
No known key found for this signature in database
GPG Key ID: 9D98FD9380A1CB43
27 changed files with 1478 additions and 237 deletions

View File

@ -45,7 +45,7 @@
<script>
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import client from '../../../services/caldavService.js'
import { findPrincipalsByDisplayName } from '../../../services/caldavService.js'
import HttpClient from '@nextcloud/axios'
import debounce from 'debounce'
import { generateOcsUrl } from '@nextcloud/router'
@ -140,7 +140,7 @@ export default {
async findShareesFromDav(query, hiddenPrincipals, hiddenUrls) {
let results
try {
results = await client.principalPropertySearchByDisplayname(query)
results = await findPrincipalsByDisplayName(query)
} catch (error) {
return []
}

View File

@ -116,8 +116,13 @@ import {
import SettingsImportSection from './Settings/SettingsImportSection.vue'
import SettingsTimezoneSelect from './Settings/SettingsTimezoneSelect.vue'
import client from '../../services/caldavService.js'
import { getCurrentUserPrincipal } from '../../services/caldavService.js'
import ShortcutOverview from './Settings/ShortcutOverview.vue'
import {
IMPORT_STAGE_DEFAULT,
IMPORT_STAGE_IMPORTING,
IMPORT_STAGE_PROCESSING,
} from '../../models/consts.js'
export default {
name: 'Settings',
@ -169,13 +174,13 @@ export default {
return this.$store.state.importFiles.importFiles
},
showUploadButton() {
return this.$store.state.importState.importState.stage === 'default'
return this.$store.state.importState.importState.stage === IMPORT_STAGE_DEFAULT
},
showImportModal() {
return this.$store.state.importState.importState.stage === 'processing'
return this.$store.state.importState.importState.stage === IMPORT_STAGE_PROCESSING
},
showProgressBar() {
return this.$store.state.importState.importState.stage === 'importing'
return this.$store.state.importState.importState.stage === IMPORT_STAGE_IMPORTING
},
settingsTitle() {
return this.$t('calendar', 'Settings & import').replace(/&amp;/g, '&')
@ -324,7 +329,7 @@ export default {
*/
async copyAppleCalDAV() {
const rootURL = generateRemoteUrl('dav')
const url = new URL(client.currentUserPrincipal.principalUrl, rootURL)
const url = new URL(getCurrentUserPrincipal().principalUrl, rootURL)
try {
await this.$copyText(url)

View File

@ -60,6 +60,12 @@ import {
showWarning,
showError,
} from '@nextcloud/dialogs'
import {
IMPORT_STAGE_AWAITING_USER_SELECT,
IMPORT_STAGE_DEFAULT,
IMPORT_STAGE_IMPORTING,
IMPORT_STAGE_PROCESSING,
} from '../../../models/consts.js'
export default {
name: 'SettingsImportSection',
@ -94,7 +100,7 @@ export default {
* @returns {Boolean}
*/
allowUploadOfFiles() {
return this.stage === 'default'
return this.stage === IMPORT_STAGE_DEFAULT
},
/**
* Whether or not to display the import modal
@ -102,7 +108,7 @@ export default {
* @returns {Boolean}
*/
showImportModal() {
return this.stage === 'awaitingUserSelect'
return this.stage === IMPORT_STAGE_AWAITING_USER_SELECT
},
/**
* Whether or not to display progress bar
@ -110,7 +116,7 @@ export default {
* @returns {Boolean}
*/
showProgressBar() {
return this.stage === 'importing'
return this.stage === IMPORT_STAGE_IMPORTING
},
/**
* Unique identifier for the input field.
@ -149,7 +155,7 @@ export default {
* @param {Event} event The change-event of the input-field
*/
async processFiles(event) {
this.$store.commit('changeStage', 'processing')
this.$store.commit('changeStage', IMPORT_STAGE_PROCESSING)
let addedFiles = false
for (const file of event.target.files) {
@ -225,7 +231,7 @@ export default {
return
}
this.$store.commit('changeStage', 'awaitingUserSelect')
this.$store.commit('changeStage', IMPORT_STAGE_AWAITING_USER_SELECT)
},
/**
* Import all events into the calendars

View File

@ -34,7 +34,7 @@ import {
} from 'vuex'
import TimezoneSelect from '../../Shared/TimezoneSelect.vue'
import detectTimezone from '../../../services/timezoneDetectionService.js'
import { detectTimezone } from '../../../services/timezoneDetectionService.js'
import {
showInfo,
} from '@nextcloud/dialogs'

View File

@ -81,7 +81,7 @@
<script>
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import client from '../../../services/caldavService.js'
import { findPrincipalsByDisplayName } from '../../../services/caldavService.js'
import HttpClient from '@nextcloud/axios'
import debounce from 'debounce'
import { linkTo } from '@nextcloud/router'
@ -209,7 +209,7 @@ export default {
async findAttendeesFromDAV(query) {
let results
try {
results = await client.principalPropertySearchByDisplayname(query)
results = await findPrincipalsByDisplayName(query)
} catch (error) {
console.debug(error)
return []

View File

@ -22,7 +22,7 @@
import getTimezoneManager from '../services/timezoneDataProviderService.js'
import { createFreeBusyRequest } from 'calendar-js'
import DateTimeValue from 'calendar-js/src/values/dateTimeValue.js'
import client from '../services/caldavService.js'
import { findSchedulingOutbox } from '../services/caldavService.js'
import freeBusyEventSourceFunction from './freeBusyEventSourceFunction.js'
import logger from '../utils/logger.js'
// import AttendeeProperty from 'calendar-js/src/properties/attendeeProperty.js'
@ -60,8 +60,7 @@ export default function(id, organizer, attendees) {
let outbox
try {
const outboxes = await client.calendarHomes[0].findAllScheduleOutboxes()
outbox = outboxes[0]
outbox = await findSchedulingOutbox()
} catch (error) {
failureCallback(error)
return

View File

@ -38,6 +38,13 @@ const PRINCIPAL_PREFIX_CIRCLE = 'principal:principals/circles/'
const PRINCIPAL_PREFIX_CALENDAR_RESOURCE = 'principal:principals/calendar-resources/'
const PRINCIPAL_PREFIX_CALENDAR_ROOM = 'principal:principals/calendar-rooms/'
const CALDAV_BIRTHDAY_CALENDAR = 'contact_birthdays'
const IMPORT_STAGE_DEFAULT = 'default'
const IMPORT_STAGE_IMPORTING = 'importing'
const IMPORT_STAGE_AWAITING_USER_SELECT = 'awaitingUserSelect'
const IMPORT_STAGE_PROCESSING = 'processing'
export {
COMPONENT_NAME_EVENT,
COMPONENT_NAME_JOURNAL,
@ -55,4 +62,9 @@ export {
PRINCIPAL_PREFIX_CIRCLE,
PRINCIPAL_PREFIX_CALENDAR_RESOURCE,
PRINCIPAL_PREFIX_CALENDAR_ROOM,
CALDAV_BIRTHDAY_CALENDAR,
IMPORT_STAGE_DEFAULT,
IMPORT_STAGE_IMPORTING,
IMPORT_STAGE_AWAITING_USER_SELECT,
IMPORT_STAGE_PROCESSING,
}

View File

@ -1,5 +1,5 @@
/**
* @copyright Copyright (c) 2019 Georg Ehrke
* @copyright Copyright (c) 2020 Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
@ -22,30 +22,211 @@
import DavClient from 'cdav-library'
import { generateRemoteUrl } from '@nextcloud/router'
import { getRequestToken } from '@nextcloud/auth'
import { CALDAV_BIRTHDAY_CALENDAR } from '../models/consts.js'
function xhrProvider() {
const headers = {
'X-Requested-With': 'XMLHttpRequest',
'requesttoken': getRequestToken(),
'X-NC-CalDAV-Webcal-Caching': 'On',
let client = null
const getClient = () => {
if (client) {
return client
}
const xhr = new XMLHttpRequest()
const oldOpen = xhr.open
// override open() method to add headers
xhr.open = function() {
const result = oldOpen.apply(this, arguments)
for (const name in headers) {
xhr.setRequestHeader(name, headers[name])
client = new DavClient({
rootUrl: generateRemoteUrl('dav'),
}, () => {
const headers = {
'X-Requested-With': 'XMLHttpRequest',
'requesttoken': getRequestToken(),
'X-NC-CalDAV-Webcal-Caching': 'On',
}
const xhr = new XMLHttpRequest()
const oldOpen = xhr.open
// override open() method to add headers
xhr.open = function() {
const result = oldOpen.apply(this, arguments)
for (const name in headers) {
xhr.setRequestHeader(name, headers[name])
}
return result
}
return result
}
OC.registerXHRForErrorProcessing(xhr) // eslint-disable-line no-undef
return xhr
})
OC.registerXHRForErrorProcessing(xhr) // eslint-disable-line no-undef
return xhr
return getClient()
}
export default new DavClient({
rootUrl: generateRemoteUrl('dav'),
}, xhrProvider)
/**
* Initializes the client for use in the user-view
*/
const initializeClientForUserView = async() => {
await getClient().connect({ enableCalDAV: true })
}
/**
* Initializes the client for use in the public/embed-view
*/
const initializeClientForPublicView = async() => {
await getClient()._createPublicCalendarHome()
}
/**
* Fetch all calendars from the server
*
* @returns {Promise<Calendar[]>}
*/
const findAllCalendars = () => {
return getClient().calendarHomes[0].findAllCalendars()
}
/**
* Fetch public calendars by their token
*
* @param {String[]} tokens List of tokens
* @returns {Promise<Calendar[]>}
*/
const findPublicCalendarsByTokens = async(tokens) => {
const findPromises = []
for (const token of tokens) {
const promise = getClient().publicCalendarHome
.find(token)
.catch(() => null) // Catch outdated tokens
findPromises.push(promise)
}
const calendars = await Promise.all(findPromises)
return calendars.filter((calendar) => calendar !== null)
}
/**
* Fetches all scheduling inboxes
*
* Nitpick detail: Technically, we shouldn't be querying all scheduling inboxes
* in the calendar-home and just take the first one, but rather query the
* "CALDAV:schedule-inbox-URL" property on the principal URL and take that one.
* However, it doesn't make any difference for the Nextcloud CalDAV server
* and saves us extraneous requests here.
*
* https://tools.ietf.org/html/rfc6638#section-2.2.1
*
* @returns {Promise<ScheduleInbox[]>}
*/
const findSchedulingInbox = async() => {
const inboxes = await getClient().calendarHomes[0].findAllScheduleInboxes()
return inboxes[0]
}
/**
* Fetches all scheduling outboxes
*
* Nitpick detail: Technically, we shouldn't be querying all scheduling outboxes
* in the calendar-home and just take the first one, but rather query the
* "CALDAV:schedule-outbox-URL" property on the principal URL and take that one.
* However, it doesn't make any difference for the Nextcloud CalDAV server
* and saves us extraneous requests here.
*
* https://tools.ietf.org/html/rfc6638#section-2.1.1
*
* @returns {Promise<ScheduleOutbox>}
*/
const findSchedulingOutbox = async() => {
const outboxes = await getClient().calendarHomes[0].findAllScheduleOutboxes()
return outboxes[0]
}
/**
* Creates a calendar
*
* @param {String} displayName Visible name
* @param {String} color Color
* @param {String[]} components Supported component set
* @param {Number} order Order of calendar in list
* @param {String} timezoneIcs ICS representation of timezone
* @returns {Promise<Calendar>}
*/
const createCalendar = async(displayName, color, components, order, timezoneIcs) => {
return getClient().calendarHomes[0].createCalendarCollection(displayName, color, components, order, timezoneIcs)
}
/**
* Creates a subscription
*
* This function does not return a subscription, but a cached calendar
*
* @param {String} displayName Visible name
* @param {String} color Color
* @param {String} source Link to WebCAL Source
* @param {Number} order Order of calendar in list
* @returns {Promise<Calendar>}
*/
const createSubscription = async(displayName, color, source, order) => {
return getClient().calendarHomes[0].createSubscribedCollection(displayName, color, source, order)
}
/**
* Enables the birthday calendar
*
* @returns {Promise<Calendar>}
*/
const enableBirthdayCalendar = async() => {
await getClient().calendarHomes[0].enableBirthdayCalendar()
return getBirthdayCalendar()
}
/**
* Gets the birthday calendar
*
* @returns {Promise<Calendar>}
*/
const getBirthdayCalendar = async() => {
return getClient().calendarHomes[0].find(CALDAV_BIRTHDAY_CALENDAR)
}
/**
* Returns the Current User Principal
*
* @returns {Principal}
*/
const getCurrentUserPrincipal = () => {
return getClient().currentUserPrincipal
}
/**
* Finds calendar principals by displayname
*
* @param {String} query The search-term
* @returns {Promise<void>}
*/
const findPrincipalsByDisplayName = async(query) => {
return getClient().principalPropertySearchByDisplayname(query)
}
/**
* Finds one principal by it's URL
*
* @param {String} url The principal-url
* @returns {Promise<Principal>}
*/
const findPrincipalByUrl = async(url) => {
return getClient().findPrincipal(url)
}
export {
initializeClientForUserView,
initializeClientForPublicView,
findAllCalendars,
findPublicCalendarsByTokens,
findSchedulingInbox,
findSchedulingOutbox,
createCalendar,
createSubscription,
enableBirthdayCalendar,
getBirthdayCalendar,
getCurrentUserPrincipal,
findPrincipalsByDisplayName,
findPrincipalByUrl,
}

37
src/services/settings.js Normal file
View File

@ -0,0 +1,37 @@
/**
* @copyright Copyright (c) 2020 Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @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/>.
*
*/
import HttpClient from '@nextcloud/axios'
import { getLinkToConfig } from '../utils/settings.js'
/**
*
* @param {String} key Config-key to set
* @param {String|Number|Boolean} value Config-value to set
* @returns {Promise<void>}
*/
const setConfig = async(key, value) => {
await HttpClient.post(getLinkToConfig(key), { value })
}
export {
setConfig,
}

View File

@ -26,7 +26,7 @@ import jstz from 'jstz'
*
* @returns {String} Current timezone of user
*/
export default () => {
const detectTimezone = () => {
const determinedTimezone = jstz.determine()
if (!determinedTimezone) {
return 'UTC'
@ -39,3 +39,8 @@ export default () => {
return timezoneName
}
export default detectTimezone
export {
detectTimezone,
}

View File

@ -77,7 +77,7 @@ export default function(router, store) {
const date = getDateFromFirstdayParam(router.currentRoute.params.firstDay)
const view = router.currentRoute.params.view
const locale = mutation.payload
const { locale } = mutation.payload
updateTitle(date, view, locale)
})

View File

@ -24,7 +24,12 @@
*
*/
import Vue from 'vue'
import client from '../services/caldavService.js'
import {
createCalendar,
createSubscription,
findAllCalendars,
findPublicCalendarsByTokens,
} from '../services/caldavService.js'
import { mapCDavObjectToCalendarObject } from '../models/calendarObject'
import { dateFactory, getUnixTimestampFromDate } from '../utils/date.js'
import { getDefaultCalendarObject, mapDavCollectionToCalendar } from '../models/calendar'
@ -34,6 +39,11 @@ import { translate as t } from '@nextcloud/l10n'
import getTimezoneManager from '../services/timezoneDataProviderService.js'
import Timezone from 'calendar-js/src/timezones/timezone.js'
import CalendarComponent from 'calendar-js/src/components/calendarComponent.js'
import {
CALDAV_BIRTHDAY_CALENDAR,
IMPORT_STAGE_IMPORTING,
IMPORT_STAGE_PROCESSING,
} from '../models/consts.js'
const state = {
calendars: [],
@ -375,7 +385,7 @@ const getters = {
const lastSlash = url.lastIndexOf('/')
const uri = url.substr(lastSlash + 1)
if (uri === 'contact_birthdays') {
if (uri === CALDAV_BIRTHDAY_CALENDAR) {
return calendar
}
}
@ -383,6 +393,17 @@ const getters = {
return null
},
/**
* Whether or not a birthday calendar exists
*
* @param {Object} state The Vuex state
* @param {Object} getters the vuex getters
* @returns {boolean}
*/
hasBirthdayCalendar: (state, getters) => {
return !!getters.getBirthdayCalendar
},
/**
*
* @param {Object} state the store data
@ -417,7 +438,7 @@ const actions = {
* @returns {Promise<Array>} the calendars
*/
async getCalendars({ commit, state, getters }) {
const calendars = await client.calendarHomes[0].findAllCalendars()
const calendars = await findAllCalendars()
calendars.map((calendar) => mapDavCollectionToCalendar(calendar, getters.getCurrentUserPrincipal)).forEach(calendar => {
commit('addCalendar', { calendar })
})
@ -436,26 +457,9 @@ const actions = {
* @returns {Promise<Object[]>}
*/
async getPublicCalendars({ commit, state, getters }, { tokens }) {
const findPromises = []
for (const token of tokens) {
const promise = client.publicCalendarHome
.find(token)
.catch(() => null) // Catch outdated tokens
findPromises.push(promise)
}
const calendars = await Promise.all(findPromises)
const existingCalendars = []
for (const calendar of calendars) {
if (calendar !== null) {
existingCalendars.push(calendar)
}
}
const calendars = findPublicCalendarsByTokens(tokens)
const calendarObjects = []
for (const davCalendar of existingCalendars) {
for (const davCalendar of calendars) {
const calendar = mapDavCollectionToCalendar(davCalendar)
commit('addCalendar', { calendar })
calendarObjects.push(calendar)
@ -490,7 +494,7 @@ const actions = {
timezoneIcs = calendar.toICS(false)
}
const response = await client.calendarHomes[0].createCalendarCollection(displayName, color, components, order, timezoneIcs)
const response = await createCalendar(displayName, color, components, order, timezoneIcs)
const calendar = mapDavCollectionToCalendar(response, context.getters.getCurrentUserPrincipal)
context.commit('addCalendar', { calendar })
},
@ -507,7 +511,7 @@ const actions = {
* @returns {Promise}
*/
async appendSubscription(context, { displayName, color, order, source }) {
const response = await client.calendarHomes[0].createSubscribedCollection(displayName, color, source, order)
const response = await createSubscription(displayName, color, source, order)
const calendar = mapDavCollectionToCalendar(response, context.getters.getCurrentUserPrincipal)
context.commit('addCalendar', { calendar })
},
@ -776,7 +780,7 @@ const actions = {
* @param {Object} context the store mutations
*/
async importEventsIntoCalendar(context) {
context.commit('changeStage', 'importing')
context.commit('changeStage', IMPORT_STAGE_IMPORTING)
// Create a copy
const files = context.rootState.importFiles.importFiles.slice()
@ -803,7 +807,7 @@ const actions = {
}
try {
const response = await client.calendarHomes[0].createCalendarCollection(displayName, color, components, 0)
const response = await createCalendar(displayName, color, components, 0)
const calendar = mapDavCollectionToCalendar(response, context.getters.getCurrentUserPrincipal)
context.commit('addCalendar', { calendar })
context.commit('setCalendarForFileId', {
@ -854,7 +858,7 @@ const actions = {
}
await Promise.all(requests)
context.commit('changeStage', 'default')
context.commit('changeStage', IMPORT_STAGE_PROCESSING)
},
}

View File

@ -28,25 +28,6 @@ const state = {
const mutations = {
/**
* Append multiple contacts to the store
*
* @param {Object} state The store data
* @param {Object} data The destructuring object
* @param {Object[]} data.contacts List of contacts to add
*/
appendContacts(state, { contacts = [] }) {
for (const contact of contacts) {
if (state.contacts.indexOf(contact) === -1) {
state.contacts.push(contact)
}
for (const email of contact.emails) {
Vue.set(state.contactByEMail, email, contact)
}
}
},
/**
* Append a single contact to the store
*
@ -60,7 +41,12 @@ const mutations = {
}
for (const email of contact.emails) {
Vue.set(state.contactByEMail, email, contact)
// In the unlikely case that multiple contacts
// share the same email address, we will just follow
// first come, first served.
if (state.contactByEMail[email] === undefined) {
Vue.set(state.contactByEMail, email, contact)
}
}
},
@ -72,7 +58,7 @@ const mutations = {
* @param {Object} data.contact The contact to remove from the store
*/
removeContact(state, { contact }) {
for (const email of contact.email) {
for (const email of contact.emails) {
if (state.contactByEMail[email] === contact) {
Vue.delete(state.contactByEMail, email)
}

View File

@ -1,5 +1,5 @@
/**
* @copyright Copyright (c) 2019 Georg Ehrke
* @copyright Copyright (c) 2020 Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
@ -21,7 +21,7 @@
*/
const state = {
minimumDate: '1970-01-01T00:00:00Z',
maximumDate: '2036-12-31T23:59:59Z',
maximumDate: '2037-12-31T23:59:59Z',
}
const mutations = {
@ -30,11 +30,13 @@ const mutations = {
* Initialize restrictions imposed by CalDAV server
*
* @param {Object} state The Vuex state
* @param {Object} davRestrictions The full settings object
* @param {Object} data The destructuring object
* @param {String} data.minimumDate The minimum-date allowed by the CalDAV server
* @param {String} data.maximumDate The maximum-date allowed by the CalDAV server
*/
loadDavRestrictionsFromServer(state, davRestrictions) {
state.minimumDate = davRestrictions.minimumDate
state.maximumDate = davRestrictions.maximumDate
loadDavRestrictionsFromServer(state, { minimumDate, maximumDate }) {
state.minimumDate = minimumDate
state.maximumDate = maximumDate
},
}

View File

@ -1,6 +1,6 @@
/**
* @copyright Copyright (c) 2019 Team Popcorn <teampopcornberlin@gmail.com>
* @copyright Copyright (c) 2019 Georg Ehrke
* @copyright Copyright (c) 2020 Georg Ehrke
*
* @author Team Popcorn <teampopcornberlin@gmail.com>
* @author Georg Ehrke <oc.list@georgehrke.com>
@ -59,23 +59,6 @@ const mutations = {
Vue.set(state.importFilesById, file.id, file)
},
/**
* Removes one file from state
*
* @param {Object} state The vuex state
* @param {Object} data The destructuring object
* @param {Number} data.fileId Id of the file to remove
*/
removeFile(state, { fileId }) {
const object = state.importFilesById[fileId]
const index = state.importFiles.indexOf(object)
if (index !== 1) {
state.importFiles.slice(index, 1)
Vue.delete(state.importFilesById, fileId)
}
},
/**
* Sets a calendar for the file
*

View File

@ -1,6 +1,6 @@
/**
* @copyright Copyright (c) 2019 Team Popcorn <teampopcornberlin@gmail.com>
* @copyright Copyright (c) 2019 Georg Ehrke
* @copyright Copyright (c) 2020 Georg Ehrke
*
* @author Team Popcorn <teampopcornberlin@gmail.com>
* @author Georg Ehrke <oc.list@georgehrke.com>
@ -21,12 +21,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { IMPORT_STAGE_DEFAULT } from '../models/consts.js'
const state = {
total: 0,
accepted: 0,
denied: 0,
stage: 'default',
stage: IMPORT_STAGE_DEFAULT,
}
const mutations = {
@ -53,7 +54,7 @@ const mutations = {
* Set the total number of calendar-objects
*
* @param {Object} state the store data
* @param {string} total the total number of calendar-objects to import
* @param {Number} total the total number of calendar-objects to import
*/
setTotal(state, total) {
state.total = total
@ -63,7 +64,7 @@ const mutations = {
* Change stage to the indicated one
*
* @param {Object} state the store data
* @param {string} stage the name of the stage ('default', 'importing', 'parsing', 'done')
* @param {String} stage the name of the stage, see /src/models/consts.js
*/
changeStage(state, stage) {
state.stage = stage
@ -78,7 +79,7 @@ const mutations = {
state.total = 0
state.accepted = 0
state.denied = 0
state.stage = 'default'
state.stage = IMPORT_STAGE_DEFAULT
},
}

View File

@ -20,9 +20,15 @@
*
*/
import Vue from 'vue'
import client from '../services/caldavService.js'
import {
findPrincipalByUrl,
getCurrentUserPrincipal,
} from '../services/caldavService.js'
import logger from '../utils/logger.js'
import { getDefaultPrincipalObject, mapDavToPrincipal } from '../models/principal'
import {
getDefaultPrincipalObject,
mapDavToPrincipal,
} from '../models/principal'
const state = {
principals: [],
@ -112,7 +118,7 @@ const actions = {
return
}
const principal = await client.findPrincipal(url)
const principal = await findPrincipalByUrl(url)
if (!principal) {
// TODO - handle error
return
@ -130,7 +136,7 @@ const actions = {
* @returns {Promise<void>}
*/
async fetchCurrentUserPrincipal(context) {
const currentUserPrincipal = client.currentUserPrincipal
const currentUserPrincipal = getCurrentUserPrincipal()
if (!currentUserPrincipal) {
// TODO - handle error
return

View File

@ -1,5 +1,5 @@
/**
* @copyright Copyright (c) 2019 Georg Ehrke
* @copyright Copyright (c) 2020 Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
@ -19,26 +19,29 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import HttpClient from '@nextcloud/axios'
import client from '../services/caldavService.js'
import { getLinkToConfig } from '../utils/settings.js'
import { enableBirthdayCalendar } from '../services/caldavService.js'
import { mapDavCollectionToCalendar } from '../models/calendar'
import detectTimezone from '../services/timezoneDetectionService'
import { setConfig } from 'calendar-js'
import { detectTimezone } from '../services/timezoneDetectionService'
import { setConfig as setCalendarJsConfig } from 'calendar-js'
import { setConfig } from '../services/settings.js'
import { logInfo } from '../utils/logger.js'
const state = {
// env
appVersion: null,
eventLimit: null,
firstRun: null,
momentLocale: 'en',
talkEnabled: false,
// user-defined calendar settings
eventLimit: null,
showTasks: null,
showWeekends: null,
showWeekNumbers: null,
skipPopover: null,
slotDuration: null,
talkEnabled: false,
tasksEnabled: false,
timezone: null,
timezone: 'automatic',
// user-defined Nextcloud settings
momentLocale: 'en',
}
const mutations = {
@ -114,48 +117,64 @@ const mutations = {
* Initialize settings
*
* @param {Object} state The Vuex state
* @param {Object} settings The full settings object
* @param {Object} data The destructuring object
* @param {String} data.appVersion The version of the Nextcloud app
* @param {Boolean} data.eventLimit Whether or not to limit number of visible events in grid view
* @param {Boolean} data.firstRun Whether or not this is the first run
* @param {Boolean} data.showWeekNumbers Whether or not to show week numbers
* @param {Boolean} data.showTasks Whether or not to display tasks with a due-date
* @param {Boolean} data.showWeekends Whether or not to display weekends
* @param {Boolean} data.skipPopover Whether or not to skip the simple event popover
* @param {String} data.slotDuration The duration of one slot in the agendaView
* @param {Boolean} data.talkEnabled Whether or not the talk app is enabled
* @param {Boolean} data.tasksEnabled Whether ot not the tasks app is enabled
* @param {String} data.timezone The timezone to view the calendar in. Either an Olsen timezone or "automatic"
*/
loadSettingsFromServer(state, settings) {
console.debug('Initial settings:', settings)
loadSettingsFromServer(state, { appVersion, eventLimit, firstRun, showWeekNumbers, showTasks, showWeekends, skipPopover, slotDuration, talkEnabled, tasksEnabled, timezone }) {
logInfo(`
Initial settings:
- AppVersion: ${appVersion}
- EventLimit: ${eventLimit}
- FirstRun: ${firstRun}
- ShowWeekNumbers: ${showWeekNumbers}
- ShowTasks: ${showTasks}
- ShowWeekends: ${showWeekends}
- SkipPopover: ${skipPopover}
- SlotDuration: ${slotDuration}
- TalkEnabled: ${talkEnabled}
- TasksEnabled: ${tasksEnabled}
- Timezone: ${timezone}
`)
state.appVersion = settings.appVersion
state.eventLimit = settings.eventLimit
state.firstRun = settings.firstRun
state.showWeekNumbers = settings.showWeekNumbers
state.showTasks = settings.showTasks
state.showWeekends = settings.showWeekends
state.skipPopover = settings.skipPopover
state.slotDuration = settings.slotDuration
state.talkEnabled = settings.talkEnabled
state.tasksEnabled = settings.tasksEnabled
state.timezone = settings.timezone
state.appVersion = appVersion
state.eventLimit = eventLimit
state.firstRun = firstRun
state.showWeekNumbers = showWeekNumbers
state.showTasks = showTasks
state.showWeekends = showWeekends
state.skipPopover = skipPopover
state.slotDuration = slotDuration
state.talkEnabled = talkEnabled
state.tasksEnabled = tasksEnabled
state.timezone = timezone
},
/**
* Sets the name of the moment.js locale to be used
*
* @param {Object} state The Vuex state
* @param {String} locale The moment.js locale to be used
* @param {Object} data The destructuring object
* @param {String} data.locale The moment.js locale to be used
*/
setMomentLocale(state, locale) {
setMomentLocale(state, { locale }) {
logInfo(`Updated moment locale: ${locale}`)
state.momentLocale = locale
},
}
const getters = {
/**
* Whether or not a birthday calendar exists
*
* @param {Object} state The Vuex state
* @param {Object} getters the vuex getters
* @returns {boolean}
*/
hasBirthdayCalendar: (state, getters) => {
return !!getters.getBirthdayCalendar
},
/**
* Gets the resolved timezone.
* If the timezone is set to automatic, it returns the user's current timezone
@ -174,33 +193,37 @@ const actions = {
/**
* Updates the user's setting for visibility of birthday calendar
*
* @param {Object} context The Vuex context
* @param {Object} vuex The Vuex destructuring object
* @param {Object} vuex.getters The Vuex Getters
* @param {Function} vuex.commit The Vuex commit Function
* @param {Function} vuex.dispatch The Vuex dispatch Function
* @returns {Promise<void>}
*/
async toggleBirthdayCalendarEnabled(context) {
if (context.getters.hasBirthdayCalendar) {
const calendar = context.getters.getBirthdayCalendar
return context.dispatch('deleteCalendar', { calendar })
async toggleBirthdayCalendarEnabled({ getters, commit, dispatch }) {
if (getters.hasBirthdayCalendar) {
const calendar = getters.getBirthdayCalendar
await dispatch('deleteCalendar', { calendar })
} else {
await client.calendarHomes[0].enableBirthdayCalendar()
const davCalendar = await client.calendarHomes[0].find('contact_birthdays')
const davCalendar = await enableBirthdayCalendar()
const calendar = mapDavCollectionToCalendar(davCalendar)
context.commit('addCalendar', { calendar })
commit('addCalendar', { calendar })
}
},
/**
* Updates the user's setting for event limit
*
* @param {Object} context The Vuex context
* @param {Object} vuex The Vuex destructuring object
* @param {Object} vuex.state The Vuex state
* @param {Function} vuex.commit The Vuex commit Function
* @returns {Promise<void>}
*/
async toggleEventLimitEnabled(context) {
const newState = !context.state.eventLimit
async toggleEventLimitEnabled({ state, commit }) {
const newState = !state.eventLimit
const value = newState ? 'yes' : 'no'
await HttpClient.post(getLinkToConfig('eventLimit'), { value })
context.commit('toggleEventLimitEnabled')
await setConfig('eventLimit', value)
commit('toggleEventLimitEnabled')
},
/**
@ -209,12 +232,12 @@ const actions = {
* @param {Object} context The Vuex context
* @returns {Promise<void>}
*/
async togglePopoverEnabled(context) {
const newState = !context.state.skipPopover
async togglePopoverEnabled({ state, commit }) {
const newState = !state.skipPopover
const value = newState ? 'yes' : 'no'
await HttpClient.post(getLinkToConfig('skipPopover'), { value })
context.commit('togglePopoverEnabled')
await setConfig('skipPopover', value)
commit('togglePopoverEnabled')
},
/**
@ -223,94 +246,96 @@ const actions = {
* @param {Object} context The Vuex context
* @returns {Promise<void>}
*/
async toggleWeekendsEnabled(context) {
const newState = !context.state.showWeekends
async toggleWeekendsEnabled({ state, commit }) {
const newState = !state.showWeekends
const value = newState ? 'yes' : 'no'
await HttpClient.post(getLinkToConfig('showWeekends'), { value })
context.commit('toggleWeekendsEnabled')
await setConfig('showWeekends', value)
commit('toggleWeekendsEnabled')
},
/**
* Updates the user's setting for visibility of tasks
*
* @param {Object} context The Vuex context
* @param {Object} vuex The Vuex destructuring object
* @param {Object} vuex.state The Vuex state
* @param {Function} vuex.commit The Vuex commit Function
* @returns {Promise<void>}
*/
async toggleTasksEnabled(context) {
const newState = !context.state.showTasks
async toggleTasksEnabled({ state, commit }) {
const newState = !state.showTasks
const value = newState ? 'yes' : 'no'
await HttpClient.post(getLinkToConfig('showTasks'), { value })
context.commit('toggleTasksEnabled')
context.commit('clearFetchedTimeRanges')
context.commit('incrementModificationCount')
await setConfig('showTasks', value)
commit('toggleTasksEnabled')
commit('clearFetchedTimeRanges')
commit('incrementModificationCount')
},
/**
* Updates the user's setting for visibility of week numbers
*
* @param {Object} context The Vuex context
* @param {Object} vuex The Vuex destructuring object
* @param {Object} vuex.state The Vuex state
* @param {Function} vuex.commit The Vuex commit Function
* @returns {Promise<void>}
*/
async toggleWeekNumberEnabled(context) {
const newState = !context.state.showWeekNumbers
async toggleWeekNumberEnabled({ state, commit }) {
const newState = !state.showWeekNumbers
const value = newState ? 'yes' : 'no'
await HttpClient.post(getLinkToConfig('showWeekNr'), { value })
context.commit('toggleWeekNumberEnabled')
await setConfig('showWeekNr', value)
commit('toggleWeekNumberEnabled')
},
/**
* Updates the view to be used as initial view when opening
* the calendar app again
*
* @param {Object} context The Vuex context
* @param {Object} context The Vuex destructuring object
* @param {Object} data The destructuring object
* @param {String} data.initialView New view to be used as initial view
* @returns {Promise<void>}
*/
async setInitialView(context, { initialView }) {
await HttpClient.post(getLinkToConfig('view'), {
value: initialView,
})
await setConfig('view', initialView)
},
/**
* Updates the user's preferred slotDuration
*
* @param {Object} context The Vuex context
* @param {Object} vuex The Vuex destructuring object
* @param {Object} vuex.state The Vuex state
* @param {Function} vuex.commit The Vuex commit Function
* @param {Object} data The destructuring object
* @param {String} data.slotDuration The new slot duration
*/
async setSlotDuration(context, { slotDuration }) {
if (context.state.slotDuration === slotDuration) {
async setSlotDuration({ state, commit }, { slotDuration }) {
if (state.slotDuration === slotDuration) {
return
}
await HttpClient.post(getLinkToConfig('slotDuration'), {
value: slotDuration,
})
context.commit('setSlotDuration', { slotDuration })
await setConfig('slotDuration', slotDuration)
commit('setSlotDuration', { slotDuration })
},
/**
* Updates the user's timezone
*
* @param {Object} context The Vuex context
* @param {Object} vuex The Vuex destructuring object
* @param {Object} vuex.state The Vuex state
* @param {Function} vuex.commit The Vuex commit Function
* @param {Object} data The destructuring object
* @param {String} data.timezoneId The new timezone
* @returns {Promise<void>}
*/
async setTimezone(context, { timezoneId }) {
if (context.state.timezone === timezoneId) {
async setTimezone({ state, commit }, { timezoneId }) {
if (state.timezone === timezoneId) {
return
}
await HttpClient.post(getLinkToConfig('timezone'), {
value: timezoneId,
})
context.commit('setTimezone', { timezoneId })
await setConfig('timezone', timezoneId)
commit('setTimezone', { timezoneId })
},
/**
@ -320,8 +345,8 @@ const actions = {
* @param {Object} vuex.state The Vuex state
*/
initializeCalendarJsConfig({ state }) {
setConfig('PRODID', `-//IDN nextcloud.com//Calendar app ${state.appVersion}//EN`)
setConfig('property-list-significant-change', [
setCalendarJsConfig('PRODID', `-//IDN nextcloud.com//Calendar app ${state.appVersion}//EN`)
setCalendarJsConfig('property-list-significant-change', [
'SUMMARY',
'LOCATION',
'DESCRIPTION',

View File

@ -20,17 +20,67 @@
*
*/
import { getLoggerBuilder } from '@nextcloud/logger'
import { getCurrentUser } from '@nextcloud/auth'
const builder = getLoggerBuilder()
const user = getCurrentUser()
const logger = getLoggerBuilder()
.setApp('calendar')
.detectUser()
.build()
builder.setApp('calendar')
if (user) {
builder.setUid('authenticated:' + user.uid)
} else {
builder.setUid('unauthenticated')
/**
* Logs a debug message
*
* @param {String} message The message to log
* @param {Object=} context Additional context if needed
*/
const logDebug = (message, context = {}) => {
logger.debug(message, context)
}
/**
* Logs an error message
*
* @param {String} message The message to log
* @param {Object=} context Additional context if needed
*/
const logError = (message, context = {}) => {
logger.error(message, context)
}
/**
* Logs a fatal message
*
* @param {String} message The message to log
* @param {Object=} context Additional context if needed
*/
const logFatal = (message, context = {}) => {
logger.fatal(message, context)
}
/**
* Logs an info message
*
* @param {String} message The message to log
* @param {Object=} context Additional context if needed
*/
const logInfo = (message, context = {}) => {
logger.info(message, context)
}
/**
* Logs a warn message
*
* @param {String} message The message to log
* @param {Object=} context Additional context if needed
*/
const logWarn = (message, context = {}) => {
logger.warn(message, context)
}
const logger = builder.build()
export default logger
export {
logDebug,
logError,
logFatal,
logInfo,
logWarn,
}

View File

@ -98,7 +98,10 @@ import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import Content from '@nextcloud/vue/dist/Components/Content'
import debounce from 'debounce'
import { uidToHexColor } from '../utils/color.js'
import client from '../services/caldavService.js'
import {
initializeClientForPublicView,
initializeClientForUserView,
} from '../services/caldavService.js'
import {
dateFactory,
getUnixTimestampFromDate,
@ -311,7 +314,7 @@ export default {
this.$store.dispatch('initializeCalendarJsConfig')
if (this.$route.name.startsWith('Public') || this.$route.name.startsWith('Embed')) {
client._createPublicCalendarHome()
await initializeClientForPublicView()
const tokens = this.$route.params.tokens.split('-')
const calendars = await this.$store.dispatch('getPublicCalendars', { tokens })
this.loadingCalendars = false
@ -320,7 +323,7 @@ export default {
this.showEmptyCalendarScreen = true
}
} else {
await client.connect({ enableCalDAV: true })
await initializeClientForUserView()
await this.$store.dispatch('fetchCurrentUserPrincipal')
const calendars = await this.$store.dispatch('getCalendars')
const owners = []
@ -438,8 +441,8 @@ export default {
* @returns {Promise<void>}
*/
async loadMomentLocale() {
const momentLocale = await loadMomentLocalization()
this.$store.commit('setMomentLocale', momentLocale)
const locale = await loadMomentLocalization()
this.$store.commit('setMomentLocale', { locale })
},
},
}

View File

@ -0,0 +1,50 @@
/**
* @copyright Copyright (c) 2020 Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
* @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/>.
*
*/
import HttpClient from '@nextcloud/axios'
import { getLinkToConfig } from '../../../../src/utils/settings.js'
import {setConfig} from "../../../../src/services/settings.js";
jest.mock('@nextcloud/axios')
jest.mock('../../../../src/utils/settings.js')
describe('Test suite: Settings service (services/settings.js)', () => {
beforeEach(() => {
HttpClient.post.mockClear()
getLinkToConfig.mockClear()
})
it('should provide a setConfig method', () => {
getLinkToConfig.mockReturnValueOnce('url-to-config-key')
setConfig('key42', 'value1337')
expect(getLinkToConfig).toHaveBeenCalledTimes(1)
expect(getLinkToConfig).toHaveBeenNthCalledWith(1, 'key42')
expect(HttpClient.post).toHaveBeenCalledTimes(1)
expect(HttpClient.post).toHaveBeenNthCalledWith(1, 'url-to-config-key', {
value: 'value1337'
})
})
})

View File

@ -165,7 +165,7 @@ describe('services/windowTitleService', () => {
store.subscribe.mock.calls[0][0]({
type: 'setMomentLocale',
payload: 'momentLocaleFromPayload'
payload: { locale: 'momentLocaleFromPayload' }
})
expect(document.title).toEqual('formatted date range - Standard Nextcloud title')

View File

@ -19,11 +19,125 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import contactsStore from '../../../../src/store/contacts.js'
describe('store/contacts test suite', () => {
it('should be true', () => {
expect(true).toEqual(true)
it('should provide a default state', () => {
expect(contactsStore.state.contacts).toEqual([])
expect(contactsStore.state.contactByEMail).toEqual({})
})
it('should provide a mutation to add a contact', () => {
const state = {
contacts: [],
contactByEMail: {},
}
const contact1 = {
name: 'John Doe',
emails: ['john.doe@example.com'],
}
const contact2 = {
name: 'Jane Doe',
emails: ['jane.doe@example.com'],
}
const contact3 = {
name: 'John Doe Doppelgänger',
emails: [
'john.doe@example.com',
'john.doe.doppelganger@example.com',
],
}
contactsStore.mutations.appendContact(state, { contact: contact1 })
contactsStore.mutations.appendContact(state, { contact: contact2 })
contactsStore.mutations.appendContact(state, { contact: contact3 })
// It should not add the same again:
contactsStore.mutations.appendContact(state, { contact: contact1 })
expect(state.contacts).toEqual([
contact1,
contact2,
contact3,
])
expect(state.contactByEMail).toEqual({
'john.doe@example.com': contact1,
'jane.doe@example.com': contact2,
'john.doe.doppelganger@example.com': contact3,
})
})
it('should provide a mutation to remove a contact - existing', () => {
const contact1 = {
name: 'John Doe',
emails: ['john.doe@example.com'],
}
const contact2 = {
name: 'Jane Doe',
emails: ['jane.doe@example.com'],
}
const state = {
contacts: [
contact1,
contact2,
],
contactByEMail: {
'john.doe@example.com': contact1,
'jane.doe@example.com': contact2,
},
}
contactsStore.mutations.removeContact(state, { contact: contact1 })
expect(state.contacts).toEqual([
contact2,
])
expect(state.contactByEMail).toEqual({
'jane.doe@example.com': contact2,
})
})
it('should provide a mutation to remove a contact - non-existing', () => {
const contact1 = {
name: 'John Doe',
emails: ['john.doe@example.com'],
}
const contact2 = {
name: 'Jane Doe',
emails: ['jane.doe@example.com'],
}
const state = {
contacts: [
contact1,
contact2,
],
contactByEMail: {
'john.doe@example.com': contact1,
'jane.doe@example.com': contact2,
},
}
const unknownContact = {
name: 'Foo Bar',
emails: ['foo.bar@example.com'],
}
contactsStore.mutations.removeContact(state, { contact: unknownContact })
expect(state.contacts).toEqual([
contact1,
contact2,
])
expect(state.contactByEMail).toEqual({
'john.doe@example.com': contact1,
'jane.doe@example.com': contact2,
})
})
})

View File

@ -19,11 +19,35 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import davRestrictionsStore from '../../../../src/store/davRestrictions.js'
describe('store/davRestrictions test suite', () => {
it('should be true', () => {
expect(true).toEqual(true)
it('should provide a default state', () => {
// Minimum Date should be the start of Unix-Date
expect(davRestrictionsStore.state.minimumDate).toEqual('1970-01-01T00:00:00Z')
// Maximum Date should prevent the Year 2038 problem
expect(davRestrictionsStore.state.maximumDate).toEqual('2037-12-31T23:59:59Z')
})
it('should provide a mutation to set the default value', () => {
const state = {
minimumDate: '1970-01-01T00:00:00Z',
maximumDate: '2037-12-31T23:59:59Z',
otherProp: 'foo',
}
davRestrictionsStore.mutations.loadDavRestrictionsFromServer(state, {
minimumDate: '2010-01-01T00:00:00Z',
maximumDate: '2019-12-31T23:59:59Z',
})
expect(state).toEqual({
minimumDate: '2010-01-01T00:00:00Z',
maximumDate: '2019-12-31T23:59:59Z',
otherProp: 'foo',
})
})
})

View File

@ -1,5 +1,5 @@
/**
* @copyright Copyright (c) 2019 Georg Ehrke
* @copyright Copyright (c) 2020 Georg Ehrke
*
* @author Georg Ehrke <oc.list@georgehrke.com>
*
@ -19,11 +19,124 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import importFilesStore from '../../../../src/store/importFiles.js'
describe('store/importFiles test suite', () => {
it('should be true', () => {
expect(true).toEqual(true)
it('should provide a default state', () => {
expect(importFilesStore.state).toEqual({
lastFileInsertId: -1,
importFiles: [],
importFilesById: {},
importCalendarRelation: {},
})
})
it('should provide a mutation to add a file', () => {
const state = {
lastFileInsertId: 41,
importFiles: [],
importFilesById: {},
}
const file1 = {
contents: 'BEGIN:VCALENDAR...',
lastModified: 1590151056,
name: 'file-1.ics',
parser: {},
size: 1337,
type: 'text/calendar',
}
const file2 = {
contents: '{}',
lastModified: 1590151056,
name: 'file-2.ics',
parser: {},
size: 42,
type: 'application/json+calendar',
}
importFilesStore.mutations.addFile(state, file1)
importFilesStore.mutations.addFile(state, file2)
expect(state.importFiles).toEqual([
{
...file1,
id: 42,
}, {
...file2,
id: 43,
}
])
expect(state.importFilesById[42]).toEqual({
...file1,
id: 42,
})
expect(state.importFilesById[43]).toEqual({
...file2,
id: 43,
})
})
it('should provide a mutation to set a calendarId for a file', () => {
const state = {
importCalendarRelation: {},
}
importFilesStore.mutations.setCalendarForFileId(state, { fileId: 0, calendarId: 'CALENDAR-ID-1' })
importFilesStore.mutations.setCalendarForFileId(state, { fileId: 42, calendarId: 'CALENDAR-ID-1' })
expect(state.importCalendarRelation).toEqual({
0: 'CALENDAR-ID-1',
42: 'CALENDAR-ID-1',
})
})
it('should provide a mutation to remove all files', () => {
const file1 = {
id: 0,
contents: 'BEGIN:VCALENDAR...',
lastModified: 1590151056,
name: 'file-1.ics',
parser: {},
size: 1337,
type: 'text/calendar',
}
const file2 = {
id: 1,
contents: '{}',
lastModified: 1590151056,
name: 'file-2.ics',
parser: {},
size: 42,
type: 'application/json+calendar',
}
const state = {
lastFileInsertId: 1,
importFiles: [
file1,
file2,
],
importFilesById: {
0: file1,
1: file2,
},
importCalendarRelation: {
0: 'CALENDAR-ID-1',
1: 'CALENDAR-ID-1',
},
}
importFilesStore.mutations.removeAllFiles(state)
expect(state).toEqual({
lastFileInsertId: 1,
importFiles: [],
importFilesById: {},
importCalendarRelation: {},
})
})
})

View File

@ -19,11 +19,89 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import importStateStore from '../../../../src/store/importState.js'
import {
IMPORT_STAGE_AWAITING_USER_SELECT,
IMPORT_STAGE_DEFAULT,
IMPORT_STAGE_IMPORTING,
IMPORT_STAGE_PROCESSING
} from "../../../../src/models/consts.js";
describe('store/importState test suite', () => {
it('should be true', () => {
expect(true).toEqual(true)
it('should provide a default state', () => {
expect(importStateStore.state).toEqual({
total: 0,
accepted: 0,
denied: 0,
stage: IMPORT_STAGE_DEFAULT,
})
})
it('should provide a mutation to increment the amount of accepted imports', () => {
const state = {
accepted: 5,
}
importStateStore.mutations.incrementAccepted(state)
expect(state.accepted).toEqual(6)
})
it('should provide a mutation to increment the amount of rejected imports', () => {
const state = {
denied: 41,
}
importStateStore.mutations.incrementDenied(state)
expect(state.denied).toEqual(42)
})
it('should provide a mutation to increment the total amount of objects', () => {
const state = {
total: 5,
}
importStateStore.mutations.setTotal(state, 1337)
expect(state.total).toEqual(1337)
})
it('should provide a mutation to change the stage of the import', () => {
const state = {
stage: null,
}
importStateStore.mutations.changeStage(state, IMPORT_STAGE_DEFAULT)
expect(state.stage).toEqual(IMPORT_STAGE_DEFAULT)
importStateStore.mutations.changeStage(state, IMPORT_STAGE_PROCESSING)
expect(state.stage).toEqual(IMPORT_STAGE_PROCESSING)
importStateStore.mutations.changeStage(state, IMPORT_STAGE_IMPORTING)
expect(state.stage).toEqual(IMPORT_STAGE_IMPORTING)
importStateStore.mutations.changeStage(state, IMPORT_STAGE_AWAITING_USER_SELECT)
expect(state.stage).toEqual(IMPORT_STAGE_AWAITING_USER_SELECT)
})
it('should provide a mutation to reset the state', () => {
const state = {
stage: IMPORT_STAGE_AWAITING_USER_SELECT,
total: 1337,
accepted: 42,
denied: 500,
}
importStateStore.mutations.resetState(state)
expect(state).toEqual({
total: 0,
accepted: 0,
denied: 0,
stage: IMPORT_STAGE_DEFAULT,
})
})
})

View File

@ -19,11 +19,568 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import settingsStore from '../../../../src/store/settings.js'
import { enableBirthdayCalendar } from '../../../../src/services/caldavService.js'
import { mapDavCollectionToCalendar } from '../../../../src/models/calendar.js'
import { detectTimezone } from '../../../../src/services/timezoneDetectionService.js'
import { setConfig as setCalendarJsConfig } from 'calendar-js'
import { setConfig } from '../../../../src/services/settings.js'
import { logInfo } from '../../../../src/utils/logger.js'
jest.mock('../../../../src/services/caldavService.js')
jest.mock('../../../../src/models/calendar.js')
jest.mock('../../../../src/services/timezoneDetectionService.js')
jest.mock('calendar-js')
jest.mock('../../../../src/services/settings.js')
jest.mock('../../../../src/utils/logger.js')
describe('store/settings test suite', () => {
it('should be true', () => {
expect(true).toEqual(true)
beforeEach(() => {
enableBirthdayCalendar.mockClear()
mapDavCollectionToCalendar.mockClear()
detectTimezone.mockClear()
setCalendarJsConfig.mockClear()
setConfig.mockClear()
logInfo.mockClear()
})
it('should provide a default state', () => {
expect(settingsStore.state).toEqual({
appVersion: null,
firstRun: null,
talkEnabled: false,
eventLimit: null,
showTasks: null,
showWeekends: null,
showWeekNumbers: null,
skipPopover: null,
slotDuration: null,
tasksEnabled: false,
timezone: 'automatic',
momentLocale: 'en',
})
})
it('should provide a mutation to toggle the eventLimit setting', () => {
const state = {
eventLimit: false,
}
settingsStore.mutations.toggleEventLimitEnabled(state)
expect(state.eventLimit).toEqual(true)
settingsStore.mutations.toggleEventLimitEnabled(state)
expect(state.eventLimit).toEqual(false)
})
it('should provide a mutation to toggle the popover setting', () => {
const state = {
skipPopover: false,
}
settingsStore.mutations.togglePopoverEnabled(state)
expect(state.skipPopover).toEqual(true)
settingsStore.mutations.togglePopoverEnabled(state)
expect(state.skipPopover).toEqual(false)
})
it('should provide a mutation to toggle the task setting', () => {
const state = {
showTasks: false,
}
settingsStore.mutations.toggleTasksEnabled(state)
expect(state.showTasks).toEqual(true)
settingsStore.mutations.toggleTasksEnabled(state)
expect(state.showTasks).toEqual(false)
})
it('should provide a mutation to toggle the weekends setting', () => {
const state = {
showWeekends: false,
}
settingsStore.mutations.toggleWeekendsEnabled(state)
expect(state.showWeekends).toEqual(true)
settingsStore.mutations.toggleWeekendsEnabled(state)
expect(state.showWeekends).toEqual(false)
})
it('should provide a mutation to toggle the week-number setting', () => {
const state = {
showWeekNumbers: false,
}
settingsStore.mutations.toggleWeekNumberEnabled(state)
expect(state.showWeekNumbers).toEqual(true)
settingsStore.mutations.toggleWeekNumberEnabled(state)
expect(state.showWeekNumbers).toEqual(false)
})
it('should provide a mutation to set the slot duration setting', () => {
const state = {
slotDuration: 'previousValue',
}
settingsStore.mutations.setSlotDuration(state, { slotDuration: '00:30:00' })
expect(state.slotDuration).toEqual('00:30:00')
})
it('should provide a mutation to set the timezone setting', () => {
const state = {
timezone: 'previousValue',
}
settingsStore.mutations.setTimezone(state, { timezoneId: 'Europe/Berlin' })
expect(state.timezone).toEqual('Europe/Berlin')
})
it('should provide a mutation to set the settings initially', () => {
const state = {
appVersion: null,
firstRun: null,
talkEnabled: false,
eventLimit: null,
showTasks: null,
showWeekends: null,
showWeekNumbers: null,
skipPopover: null,
slotDuration: null,
tasksEnabled: false,
timezone: 'automatic',
momentLocale: 'en',
otherProp: 'bar',
}
const settings = {
appVersion: '2.1.0',
eventLimit: false,
firstRun: true,
showWeekNumbers: true,
showTasks: false,
showWeekends: true,
skipPopover: true,
slotDuration: '00:30:00',
talkEnabled: false,
tasksEnabled: true,
timezone: 'Europe/Berlin',
otherUnknownSetting: 'foo',
}
settingsStore.mutations.loadSettingsFromServer(state, settings)
expect(logInfo).toHaveBeenCalledTimes(1)
expect(logInfo).toHaveBeenNthCalledWith(1, `
Initial settings:
- AppVersion: 2.1.0
- EventLimit: false
- FirstRun: true
- ShowWeekNumbers: true
- ShowTasks: false
- ShowWeekends: true
- SkipPopover: true
- SlotDuration: 00:30:00
- TalkEnabled: false
- TasksEnabled: true
- Timezone: Europe/Berlin
`)
expect(state).toEqual({
appVersion: '2.1.0',
eventLimit: false,
firstRun: true,
showWeekNumbers: true,
showTasks: false,
showWeekends: true,
skipPopover: true,
slotDuration: '00:30:00',
talkEnabled: false,
tasksEnabled: true,
timezone: 'Europe/Berlin',
momentLocale: 'en',
otherProp: 'bar',
})
})
it('should provide a mutation to set the resolved moment locale', () => {
const state = {
appVersion: null,
firstRun: null,
talkEnabled: false,
eventLimit: null,
showTasks: null,
showWeekends: null,
showWeekNumbers: null,
skipPopover: null,
slotDuration: null,
tasksEnabled: false,
timezone: 'automatic',
momentLocale: 'en',
otherProp: 'bar',
}
settingsStore.mutations.setMomentLocale(state, { locale: 'de' })
expect(logInfo).toHaveBeenCalledTimes(1)
expect(logInfo).toHaveBeenNthCalledWith(1, `Updated moment locale: de`)
expect(state).toEqual({
appVersion: null,
firstRun: null,
talkEnabled: false,
eventLimit: null,
showTasks: null,
showWeekends: null,
showWeekNumbers: null,
skipPopover: null,
slotDuration: null,
tasksEnabled: false,
timezone: 'automatic',
momentLocale: 'de',
otherProp: 'bar',
})
})
it('should provide a getter the get the resolved timezone - automatic', () => {
const state = {
timezone: 'automatic'
}
detectTimezone
.mockReturnValueOnce('Europe/Berlin')
expect(settingsStore.getters.getResolvedTimezone(state)).toEqual('Europe/Berlin')
expect(detectTimezone).toHaveBeenCalledTimes(1)
})
it('should provide a getter the get the resolved timezone - non-automatic', () => {
const state = {
timezone: 'Europe/Berlin'
}
expect(settingsStore.getters.getResolvedTimezone(state)).toEqual('Europe/Berlin')
expect(detectTimezone).toHaveBeenCalledTimes(0)
})
it('should provide an action to toggle the birthday calendar - enabled to disabled', async () => {
expect.assertions(3)
const getters = {
hasBirthdayCalendar: true,
getBirthdayCalendar: {
id: 'contact_birthdays'
}
}
const commit = jest.fn()
const dispatch = jest.fn()
dispatch.mockResolvedValueOnce()
await settingsStore.actions.toggleBirthdayCalendarEnabled({ getters, commit, dispatch })
expect(dispatch).toHaveBeenCalledTimes(1)
expect(dispatch).toHaveBeenNthCalledWith(1, 'deleteCalendar', { calendar: getters.getBirthdayCalendar })
expect(commit).toHaveBeenCalledTimes(0)
})
it('should provide an action to toggle the birthday calendar - disabled to enabled', async () => {
expect.assertions(5)
const getters = {
hasBirthdayCalendar: false,
getBirthdayCalendar: null,
}
const commit = jest.fn()
const dispatch = jest.fn()
const davCalendar = {
davCalendar: true
}
enableBirthdayCalendar.mockResolvedValueOnce(davCalendar)
const calendar = {
id: 'new-birthday-calendar'
}
mapDavCollectionToCalendar.mockReturnValueOnce(calendar)
await settingsStore.actions.toggleBirthdayCalendarEnabled({ getters, commit, dispatch })
expect(enableBirthdayCalendar).toHaveBeenCalledTimes(1)
expect(mapDavCollectionToCalendar).toHaveBeenCalledTimes(1)
expect(mapDavCollectionToCalendar).toHaveBeenNthCalledWith(1, davCalendar)
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'addCalendar', { calendar })
})
it('should provide an action to toggle the event limit setting - false to true', async () => {
expect.assertions(4)
const state = {
eventLimit: false,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.toggleEventLimitEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'eventLimit', 'yes')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'toggleEventLimitEnabled')
})
it('should provide an action to toggle the event limit setting - true to false', async () => {
expect.assertions(4)
const state = {
eventLimit: true,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.toggleEventLimitEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'eventLimit', 'no')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'toggleEventLimitEnabled')
})
it('should provide an action to toggle the popover setting - false to true', async () => {
expect.assertions(4)
const state = {
skipPopover: false,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.togglePopoverEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'skipPopover', 'yes')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'togglePopoverEnabled')
})
it('should provide an action to toggle the popover setting - true to false', async () => {
expect.assertions(4)
const state = {
skipPopover: true,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.togglePopoverEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'skipPopover', 'no')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'togglePopoverEnabled')
})
it('should provide an action to toggle the weekends setting - false to true', async () => {
expect.assertions(4)
const state = {
showWeekends: false,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.toggleWeekendsEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'showWeekends', 'yes')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'toggleWeekendsEnabled')
})
it('should provide an action to toggle the weekends setting - true to false', async () => {
expect.assertions(4)
const state = {
showWeekends: true,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.toggleWeekendsEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'showWeekends', 'no')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'toggleWeekendsEnabled')
})
it('should provide an action to toggle the week-number setting - false to true', async () => {
expect.assertions(4)
const state = {
showWeekNumbers: false,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.toggleWeekNumberEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'showWeekNr', 'yes')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'toggleWeekNumberEnabled')
})
it('should provide an action to toggle the week-number setting - true to false', async () => {
expect.assertions(4)
const state = {
showWeekNumbers: true,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.toggleWeekNumberEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'showWeekNr', 'no')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'toggleWeekNumberEnabled')
})
it('should provide an action to toggle the tasks-enabled setting - false to true', async () => {
expect.assertions(6)
const state = {
showTasks: false,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.toggleTasksEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'showTasks', 'yes')
expect(commit).toHaveBeenCalledTimes(3)
expect(commit).toHaveBeenNthCalledWith(1, 'toggleTasksEnabled')
expect(commit).toHaveBeenNthCalledWith(2, 'clearFetchedTimeRanges')
expect(commit).toHaveBeenNthCalledWith(3, 'incrementModificationCount')
})
it('should provide an action to toggle the tasks-enabled setting - true to false', async () => {
expect.assertions(6)
const state = {
showTasks: true,
}
const commit = jest.fn()
setConfig.mockReturnValueOnce()
await settingsStore.actions.toggleTasksEnabled({ state, commit })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'showTasks', 'no')
expect(commit).toHaveBeenCalledTimes(3)
expect(commit).toHaveBeenNthCalledWith(1, 'toggleTasksEnabled')
expect(commit).toHaveBeenNthCalledWith(2, 'clearFetchedTimeRanges')
expect(commit).toHaveBeenNthCalledWith(3, 'incrementModificationCount')
})
it('should provide an action to set the last used view', async () => {
expect.assertions(2)
setConfig.mockReturnValueOnce()
await settingsStore.actions.setInitialView({}, { initialView: 'agendaDay' })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'view', 'agendaDay')
})
it('should provide an action to set the slot duration setting - same value', async () => {
expect.assertions(2)
const state = {
slotDuration: '00:15:00'
}
const commit = jest.fn()
await settingsStore.actions.setSlotDuration({ state, commit }, { slotDuration: '00:15:00' })
expect(setConfig).toHaveBeenCalledTimes(0)
expect(commit).toHaveBeenCalledTimes(0)
})
it('should provide an action to set the slot duration setting - different value', async () => {
expect.assertions(4)
const state = {
slotDuration: '00:15:00'
}
const commit = jest.fn()
setConfig.mockResolvedValueOnce()
await settingsStore.actions.setSlotDuration({ state, commit }, { slotDuration: '00:30:00' })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'slotDuration', '00:30:00')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'setSlotDuration', { slotDuration: '00:30:00' })
})
it('should provide an action to set the timezone setting - same value', async () => {
expect.assertions(2)
const state = {
timezone: 'automatic'
}
const commit = jest.fn()
await settingsStore.actions.setTimezone({ state, commit }, { timezoneId: 'automatic' })
expect(setConfig).toHaveBeenCalledTimes(0)
expect(commit).toHaveBeenCalledTimes(0)
})
it('should provide an action to set the timezone setting - different value', async () => {
expect.assertions(4)
const state = {
timezone: 'automatic'
}
const commit = jest.fn()
setConfig.mockResolvedValueOnce()
await settingsStore.actions.setTimezone({ state, commit }, { timezoneId: 'Europe/Berlin' })
expect(setConfig).toHaveBeenCalledTimes(1)
expect(setConfig).toHaveBeenNthCalledWith(1, 'timezone', 'Europe/Berlin')
expect(commit).toHaveBeenCalledTimes(1)
expect(commit).toHaveBeenNthCalledWith(1, 'setTimezone', { timezoneId: 'Europe/Berlin' })
})
it('should provide an action to initialize the calendar-js config', () => {
const state = {
appVersion: '2.3.4'
}
settingsStore.actions.initializeCalendarJsConfig({ state })
expect(setCalendarJsConfig).toHaveBeenCalledTimes(2)
expect(setCalendarJsConfig).toHaveBeenNthCalledWith(1, 'PRODID', '-//IDN nextcloud.com//Calendar app 2.3.4//EN')
expect(setCalendarJsConfig).toHaveBeenNthCalledWith(2, 'property-list-significant-change', [
'SUMMARY',
'LOCATION',
'DESCRIPTION',
])
})
})