import { Calendar, CalendarOptions } from '@fullcalendar/core'
import { createPlugin } from '@fullcalendar/core'
import { parseLocalDate, parseUtcDate } from './date-parsing.js'
// Other Important Global Stuff
// ---------------------------------------------------------------------------------------------------------------------
import './hacks.js'
import './simulate.js'
import './date-matchers.js'
// Setup / Teardown
// ---------------------------------------------------------------------------------------------------------------------
let optionsStack = null
beforeEach(() => {
optionsStack = []
})
afterEach(() => {
optionsStack = null
if (window.currentCalendar) {
window.currentCalendar.destroy()
window.currentCalendar = null
}
$('#calendar').remove()
})
// Calendar Options and Initialization
// ---------------------------------------------------------------------------------------------------------------------
function pushOptions(options: CalendarOptions) {
beforeEach(() => {
optionsStack.push(options)
})
}
// called within an `it`
// needs to be called *before* initCalendar
function spyOnCalendarCallback(name, func?) {
let options = {} as any
options[name] = func || (() => {})
spyOn(options, name).and.callThrough()
optionsStack.push(options)
return options[name]
}
function initCalendar(moreOptions?: CalendarOptions, el?) {
let $el
if (moreOptions) {
optionsStack.push(moreOptions)
}
if (el) {
$el = $(el)
} else {
$el = $('
').appendTo('body')
}
if (window.currentCalendar) {
window.currentCalendar.destroy()
}
let options = getCurrentOptions()
let newCalendar = null
options.plugins = options.plugins.concat([
createPlugin({
name: 'current-calendar-' + Date.now(), // ugh, might be called twice per calendar
contextInit(context) {
newCalendar = window.currentCalendar = context.calendarApi as Calendar
},
}),
])
let cool = new Calendar($el[0], options)
if (newCalendar === window.currentCalendar) {
newCalendar.render()
} else {
newCalendar.destroy()
}
return cool
}
function getCurrentOptions() {
let args = [{}].concat(optionsStack) as any
return $.extend.apply($, args) // eslint-disable-line prefer-spread
}
// Categorizing Tests
// ---------------------------------------------------------------------------------------------------------------------
/*
describeOptions(optionName, descriptionAndValueHash, callback)
describeOptions(descriptionAndOptionsHash, callback)
*/
function describeOptions(optName, hash?, callback?) {
if ($.type(optName) === 'object') {
callback = hash
hash = optName
optName = null
}
$.each(
hash,
(desc, val) => {
let opts
if (optName) {
opts = {}
opts[optName] = val
} else {
opts = val
}
opts = $.extend(true, {}, opts)
describe(desc as string, () => {
pushOptions(opts)
callback(val)
})
},
)
}
function describeValues(hash, callback) {
$.each(
hash,
/**
* @param desc {string}
*/
(desc, val) => {
describe(desc as string, () => {
callback(val)
})
},
)
}
// Timezone Tests (needed?)
// ---------------------------------------------------------------------------------------------------------------------
const timeZoneScenarios = {
local: {
description: 'when local timezone',
value: 'local',
parseDate: parseLocalDate,
},
UTC: {
description: 'when UTC timezone',
value: 'UTC',
parseDate: parseUtcDate,
},
}
function describeTimeZones(callback) {
$.each(timeZoneScenarios, (name, scenario) => {
describe(scenario.description, () => {
pushOptions({
timeZone: name,
})
callback(scenario)
})
})
}
function describeTimeZone(name, callback) {
let scenario = timeZoneScenarios[name]
describe(scenario.description, () => {
pushOptions({
timeZone: name,
})
callback(scenario)
})
}
// Misc
// ---------------------------------------------------------------------------------------------------------------------
function oneCall(func) {
let called
called = false
return function () { // eslint-disable-line func-names
if (!called) {
called = true
return func.apply(this, arguments) // eslint-disable-line prefer-rest-params
}
return null
}
}
function spyOnMethod(Class, methodName, dontCallThrough) {
let origMethod = Class.prototype.hasOwnProperty(methodName) // eslint-disable-line no-prototype-builtins
? Class.prototype[methodName]
: null
let spy = spyOn(Class.prototype, methodName)
if (!dontCallThrough) {
spy = spy.and.callThrough()
}
(spy as any).restore = () => {
if (origMethod) {
Class.prototype[methodName] = origMethod
} else {
delete Class.prototype[methodName]
}
}
return spy
}
// wraps an existing function in a spy, calling through to the function
function spyCall(func?) {
func = func || (() => {})
const obj = { func }
spyOn(obj, 'func').and.callThrough()
return obj.func
}
type spyOnCalendarCallbackType = typeof spyOnCalendarCallback
type pushOptionsType = typeof pushOptions
type initCalendarType = typeof initCalendar
type getCurrentOptionsType = typeof getCurrentOptions
type describeOptionsType = typeof describeOptions
type describeValuesType = typeof describeValues
type describeTimeZonesType = typeof describeTimeZones
type describeTimeZoneType = typeof describeTimeZone
type oneCallType = typeof oneCall
type spyOnMethodType = typeof spyOnMethod
type spyCallType = typeof spyCall
declare global {
let currentCalendar: Calendar
let spyOnCalendarCallback: spyOnCalendarCallbackType
let pushOptions: pushOptionsType
let initCalendar: initCalendarType
let getCurrentOptions: getCurrentOptionsType
let describeOptions: describeOptionsType
let describeValues: describeValuesType
let describeTimeZones: describeTimeZonesType
let describeTimeZone: describeTimeZoneType
let oneCall: oneCallType
let spyOnMethod: spyOnMethodType
let spyCall: spyCallType
interface Window { // how to unify this with the above let statements?
currentCalendar: Calendar
karmaConfig: any
}
interface Function {
calls: any // for jasmine spies
}
interface JQueryStatic {
simulate: any
simulateMouseClick: any
simulateTouchClick: any
simulateByPoint: any
_data: any
}
interface JQuery {
simulate: any
draggable: any
sortable: any
}
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace jasmine {
interface Matchers {
toEqualDate: any
toEqualLocalDate: any
toEqualNow: any
toBeBoundedBy: any
toIntersectWith: any
toBeAbove: any
toBeBelow: any
toBeRightOf: any
toBeLeftOf: any
toHaveScrollbars: any
toBeMostlyHBoundedBy: any
toBeMostlyAbove: any
toBeMostlyLeftOf: any
toBeMostlyRightOf: any
}
}
}
Object.assign(window, {
spyOnCalendarCallback,
pushOptions,
initCalendar,
getCurrentOptions,
describeOptions,
describeValues,
describeTimeZones,
describeTimeZone,
oneCall,
spyOnMethod,
spyCall,
})
pushOptions({
timeZone: 'UTC',
eventDisplay: 'auto',
})