merge from master

This commit is contained in:
Sean Kenny
2014-08-31 15:30:31 +01:00
parent 4614abca7c
commit a624fbcb56
1172 changed files with 204153 additions and 8605 deletions
+589 -589
View File
File diff suppressed because it is too large Load Diff
+7877 -7877
View File
File diff suppressed because it is too large Load Diff
+32 -32
View File
@@ -1,32 +1,32 @@
/*!
* FullCalendar v1.6.3 Print Stylesheet
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw, Sean Kenny - resource view
*/
/*
* Include this stylesheet on your page to get a more printer-friendly calendar.
* When including this stylesheet, use the media='print' attribute of the <link> tag.
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
*/
/* Events
-----------------------------------------------------*/
.fc-event {
background: #fff !important;
color: #000 !important;
}
/* for vertical events */
.fc-event-bg {
display: none !important;
}
.fc-event .ui-resizable-handle {
display: none !important;
}
/*!
* FullCalendar v1.6.3 Print Stylesheet
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw, Sean Kenny - resource view
*/
/*
* Include this stylesheet on your page to get a more printer-friendly calendar.
* When including this stylesheet, use the media='print' attribute of the <link> tag.
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
*/
/* Events
-----------------------------------------------------*/
.fc-event {
background: #fff !important;
color: #000 !important;
}
/* for vertical events */
.fc-event-bg {
display: none !important;
}
.fc-event .ui-resizable-handle {
display: none !important;
}
+107 -107
View File
@@ -1,107 +1,107 @@
/*!
* FullCalendar v1.6.3 Google Calendar Plugin
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw, Sean Kenny - resource view
*/
(function($) {
var fc = $.fullCalendar;
var formatDate = fc.formatDate;
var parseISO8601 = fc.parseISO8601;
var addDays = fc.addDays;
var applyAll = fc.applyAll;
fc.sourceNormalizers.push(function(sourceOptions) {
if (sourceOptions.dataType == 'gcal' ||
sourceOptions.dataType === undefined &&
(sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) {
sourceOptions.dataType = 'gcal';
if (sourceOptions.editable === undefined) {
sourceOptions.editable = false;
}
}
});
fc.sourceFetchers.push(function(sourceOptions, start, end) {
if (sourceOptions.dataType == 'gcal') {
return transformOptions(sourceOptions, start, end);
}
});
function transformOptions(sourceOptions, start, end) {
var success = sourceOptions.success;
var data = $.extend({}, sourceOptions.data || {}, {
'start-min': formatDate(start, 'u'),
'start-max': formatDate(end, 'u'),
'singleevents': true,
'max-results': 9999
});
var ctz = sourceOptions.currentTimezone;
if (ctz) {
data.ctz = ctz = ctz.replace(' ', '_');
}
return $.extend({}, sourceOptions, {
url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?',
dataType: 'jsonp',
data: data,
startParam: false,
endParam: false,
success: function(data) {
var events = [];
if (data.feed.entry) {
$.each(data.feed.entry, function(i, entry) {
var startStr = entry['gd$when'][0]['startTime'];
var start = parseISO8601(startStr, true);
var end = parseISO8601(entry['gd$when'][0]['endTime'], true);
var allDay = startStr.indexOf('T') == -1;
var url;
$.each(entry.link, function(i, link) {
if (link.type == 'text/html') {
url = link.href;
if (ctz) {
url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + ctz;
}
}
});
if (allDay) {
addDays(end, -1); // make inclusive
}
events.push({
id: entry['gCal$uid']['value'],
title: entry['title']['$t'],
url: url,
start: start,
end: end,
allDay: allDay,
location: entry['gd$where'][0]['valueString'],
description: entry['content']['$t']
});
});
}
var args = [events].concat(Array.prototype.slice.call(arguments, 1));
var res = applyAll(success, this, args);
if ($.isArray(res)) {
return res;
}
return events;
}
});
}
// legacy
fc.gcalFeed = function(url, sourceOptions) {
return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
};
})(jQuery);
/*!
* FullCalendar v1.6.3 Google Calendar Plugin
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2013 Adam Shaw, Sean Kenny - resource view
*/
(function($) {
var fc = $.fullCalendar;
var formatDate = fc.formatDate;
var parseISO8601 = fc.parseISO8601;
var addDays = fc.addDays;
var applyAll = fc.applyAll;
fc.sourceNormalizers.push(function(sourceOptions) {
if (sourceOptions.dataType == 'gcal' ||
sourceOptions.dataType === undefined &&
(sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) {
sourceOptions.dataType = 'gcal';
if (sourceOptions.editable === undefined) {
sourceOptions.editable = false;
}
}
});
fc.sourceFetchers.push(function(sourceOptions, start, end) {
if (sourceOptions.dataType == 'gcal') {
return transformOptions(sourceOptions, start, end);
}
});
function transformOptions(sourceOptions, start, end) {
var success = sourceOptions.success;
var data = $.extend({}, sourceOptions.data || {}, {
'start-min': formatDate(start, 'u'),
'start-max': formatDate(end, 'u'),
'singleevents': true,
'max-results': 9999
});
var ctz = sourceOptions.currentTimezone;
if (ctz) {
data.ctz = ctz = ctz.replace(' ', '_');
}
return $.extend({}, sourceOptions, {
url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?',
dataType: 'jsonp',
data: data,
startParam: false,
endParam: false,
success: function(data) {
var events = [];
if (data.feed.entry) {
$.each(data.feed.entry, function(i, entry) {
var startStr = entry['gd$when'][0]['startTime'];
var start = parseISO8601(startStr, true);
var end = parseISO8601(entry['gd$when'][0]['endTime'], true);
var allDay = startStr.indexOf('T') == -1;
var url;
$.each(entry.link, function(i, link) {
if (link.type == 'text/html') {
url = link.href;
if (ctz) {
url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + ctz;
}
}
});
if (allDay) {
addDays(end, -1); // make inclusive
}
events.push({
id: entry['gCal$uid']['value'],
title: entry['title']['$t'],
url: url,
start: start,
end: end,
allDay: allDay,
location: entry['gd$where'][0]['valueString'],
description: entry['content']['$t']
});
});
}
var args = [events].concat(Array.prototype.slice.call(arguments, 1));
var res = applyAll(success, this, args);
if ($.isArray(res)) {
return res;
}
return events;
}
});
}
// legacy
fc.gcalFeed = function(url, sourceOptions) {
return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
};
})(jQuery);
+602
View File
@@ -0,0 +1,602 @@
v2.0.2 (2014-06-24)
-------------------
- bug with persisting addEventSource calls ([2191])
- bug with persisting removeEvents calls with an array source ([2187])
- bug with removeEvents method when called with 0 removes all events ([2082])
[2191]: https://code.google.com/p/fullcalendar/issues/detail?id=2191
[2187]: https://code.google.com/p/fullcalendar/issues/detail?id=2187
[2082]: https://code.google.com/p/fullcalendar/issues/detail?id=2082
v2.0.1 (2014-06-15)
-------------------
- `delta` parameters reintroduced in `eventDrop` and `eventResize` handlers ([2156])
- **Note**: this changes the argument order for `revertFunc`
- wrongfully triggering a windowResize when resizing an agenda view event ([1116])
- `this` values in event drag-n-drop/resize handlers consistently the DOM node ([1177])
- `displayEventEnd` - v2 workaround to force display of an end time ([2090])
- don't modify passed-in eventSource items ([954])
- destroy method now removes fc-ltr class ([2033])
- weeks of last/next month still visible when weekends are hidden ([2095])
- fixed memory leak when destroying calendar with selectable/droppable ([2137])
- Icelandic language ([2180])
- Bahasa Indonesia language ([PR 172])
[1116]: https://code.google.com/p/fullcalendar/issues/detail?id=1116
[1177]: https://code.google.com/p/fullcalendar/issues/detail?id=1177
[2090]: https://code.google.com/p/fullcalendar/issues/detail?id=2090
[954]: https://code.google.com/p/fullcalendar/issues/detail?id=954
[2033]: https://code.google.com/p/fullcalendar/issues/detail?id=2033
[2095]: https://code.google.com/p/fullcalendar/issues/detail?id=2095
[2137]: https://code.google.com/p/fullcalendar/issues/detail?id=2137
[2156]: https://code.google.com/p/fullcalendar/issues/detail?id=2156
[2180]: https://code.google.com/p/fullcalendar/issues/detail?id=2180
[PR 172]: https://github.com/arshaw/fullcalendar/pull/172
v2.0.0 (2014-06-01)
-------------------
Internationalization support, timezone support, and [MomentJS] integration. Extensive changes, many
of which are backwards incompatible.
[Full list of changes][Upgrading-to-v2] | [Affected Issues][Date-Milestone]
An automated testing framework has been set up ([Karma] + [Jasmine]) and tests have been written
which cover about half of FullCalendar's functionality. Special thanks to @incre-d, @vidbina, and
@sirrocco for the help.
In addition, the main development repo has been repurposed to also include the built distributable
JS/CSS for the project and will serve as the new [Bower] endpoint.
[MomentJS]: http://momentjs.com/
[Upgrading-to-v2]: http://arshaw.com/fullcalendar/wiki/Upgrading-to-v2/
[Date-Milestone]: https://code.google.com/p/fullcalendar/issues/list?can=1&q=milestone%3Ddate
[Karma]: http://karma-runner.github.io/
[Jasmine]: http://jasmine.github.io/
[Bower]: http://bower.io/
v1.6.4 (2013-09-01)
-------------------
- better algorithm for positioning timed agenda events ([1115])
- `slotEventOverlap` option to tweak timed agenda event overlapping ([218])
- selection bug when slot height is customized ([1035])
- supply view argument in `loading` callback ([1018])
- fixed week number not displaying in agenda views ([1951])
- fixed fullCalendar not initializing with no options ([1356])
- NPM's `package.json`, no more warnings or errors ([1762])
- building the bower component should output `bower.json` instead of `component.json` ([PR 125])
- use bower internally for fetching new versions of jQuery and jQuery UI
[1115]: https://code.google.com/p/fullcalendar/issues/detail?id=1115
[218]: https://code.google.com/p/fullcalendar/issues/detail?id=218
[1035]: https://code.google.com/p/fullcalendar/issues/detail?id=1035
[1018]: https://code.google.com/p/fullcalendar/issues/detail?id=1018
[1951]: https://code.google.com/p/fullcalendar/issues/detail?id=1951
[1356]: https://code.google.com/p/fullcalendar/issues/detail?id=1356
[1762]: https://code.google.com/p/fullcalendar/issues/detail?id=1762
[PR 125]: https://github.com/arshaw/fullcalendar/pull/125
v1.6.3 (2013-08-10)
-------------------
- `viewRender` callback ([PR 15])
- `viewDestroy` callback ([PR 15])
- `eventDestroy` callback ([PR 111])
- `handleWindowResize` option ([PR 54])
- `eventStartEditable`/`startEditable` options ([PR 49])
- `eventDurationEditable`/`durationEditable` options ([PR 49])
- specify function for `$.ajax` `data` parameter for JSON event sources ([PR 59])
- fixed bug with agenda event dropping in wrong column ([PR 55])
- easier event element z-index customization ([PR 58])
- classNames on past/future days ([PR 88])
- allow `null`/`undefined` event titles ([PR 84])
- small optimize for agenda event rendering ([PR 56])
- deprecated:
- `viewDisplay`
- `disableDragging`
- `disableResizing`
- bundled with latest jQuery (1.10.2) and jQuery UI (1.10.3)
[PR 15]: https://github.com/arshaw/fullcalendar/pull/15
[PR 111]: https://github.com/arshaw/fullcalendar/pull/111
[PR 54]: https://github.com/arshaw/fullcalendar/pull/54
[PR 49]: https://github.com/arshaw/fullcalendar/pull/49
[PR 59]: https://github.com/arshaw/fullcalendar/pull/59
[PR 55]: https://github.com/arshaw/fullcalendar/pull/55
[PR 58]: https://github.com/arshaw/fullcalendar/pull/58
[PR 88]: https://github.com/arshaw/fullcalendar/pull/88
[PR 84]: https://github.com/arshaw/fullcalendar/pull/84
[PR 56]: https://github.com/arshaw/fullcalendar/pull/56
v1.6.2 (2013-07-18)
-------------------
- `hiddenDays` option ([686])
- bugfix: when `eventRender` returns `false`, incorrect stacking of events ([762])
- bugfix: couldn't change `event.backgroundImage` when calling `updateEvent` (thx @stephenharris)
[686]: https://code.google.com/p/fullcalendar/issues/detail?id=686
[762]: https://code.google.com/p/fullcalendar/issues/detail?id=762
v1.6.1 (2013-04-14)
-------------------
- fixed event inner content overflow bug ([1783])
- fixed table header className bug [1772]
- removed text-shadow on events (better for general use, thx @tkrotoff)
[1783]: https://code.google.com/p/fullcalendar/issues/detail?id=1783
[1772]: https://code.google.com/p/fullcalendar/issues/detail?id=1772
v1.6.0 (2013-03-18)
-------------------
- visual facelift, with bootstrap-inspired buttons and colors
- simplified HTML/CSS for events and buttons
- `dayRender`, for modifying a day cell ([191], thx @althaus)
- week numbers on side of calendar ([295])
- `weekNumber`
- `weekNumberCalculation`
- `weekNumberTitle`
- `W` formatting variable
- finer snapping granularity for agenda view events ([495], thx @ms-doodle-com)
- `eventAfterAllRender` ([753], thx @pdrakeweb)
- `eventDataTransform` (thx @joeyspo)
- `data-date` attributes on cells (thx @Jae)
- expose `$.fullCalendar.dateFormatters`
- when clicking fast on buttons, prevent text selection
- bundled with latest jQuery (1.9.1) and jQuery UI (1.10.2)
- Grunt/Lumbar build system for internal development
- build for Bower package manager
- build for jQuery plugin site
[191]: https://code.google.com/p/fullcalendar/issues/detail?id=191
[295]: https://code.google.com/p/fullcalendar/issues/detail?id=295
[495]: https://code.google.com/p/fullcalendar/issues/detail?id=495
[753]: https://code.google.com/p/fullcalendar/issues/detail?id=753
v1.5.4 (2012-09-05)
-------------------
- made compatible with jQuery 1.8.* (thx @archaeron)
- bundled with jQuery 1.8.1 and jQuery UI 1.8.23
v1.5.3 (2012-02-06)
-------------------
- fixed dragging issue with jQuery UI 1.8.16 ([1168])
- bundled with jQuery 1.7.1 and jQuery UI 1.8.17
[1168]: https://code.google.com/p/fullcalendar/issues/detail?id=1168
v1.5.2 (2011-08-21)
-------------------
- correctly process UTC "Z" ISO8601 date strings ([750])
[750]: https://code.google.com/p/fullcalendar/issues/detail?id=750
v1.5.1 (2011-04-09)
-------------------
- more flexible ISO8601 date parsing ([814])
- more flexible parsing of UNIX timestamps ([826])
- FullCalendar now buildable from source on a Mac ([795])
- FullCalendar QA'd in FF4 ([883])
- upgraded to jQuery 1.5.2 (which supports IE9) and jQuery UI 1.8.11
[814]: https://code.google.com/p/fullcalendar/issues/detail?id=814
[826]: https://code.google.com/p/fullcalendar/issues/detail?id=826
[795]: https://code.google.com/p/fullcalendar/issues/detail?id=795
[883]: https://code.google.com/p/fullcalendar/issues/detail?id=883
v1.5 (2011-03-19)
-----------------
- slicker default styling for buttons
- reworked a lot of the calendar's HTML and accompanying CSS (solves [327] and [395])
- more printer-friendly (fullcalendar-print.css)
- fullcalendar now inherits styles from jquery-ui themes differently.
styles for buttons are distinct from styles for calendar cells.
(solves [299])
- can now color events through FullCalendar options and Event-Object properties ([117])
THIS IS NOW THE PREFERRED METHOD OF COLORING EVENTS (as opposed to using className and CSS)
- FullCalendar options:
- eventColor (changes both background and border)
- eventBackgroundColor
- eventBorderColor
- eventTextColor
- Event-Object options:
- color (changes both background and border)
- backgroundColor
- borderColor
- textColor
- can now specify an event source as an *object* with a `url` property (json feed) or
an `events` property (function or array) with additional properties that will
be applied to the entire event source:
- color (changes both background and border)
- backgroudColor
- borderColor
- textColor
- className
- editable
- allDayDefault
- ignoreTimezone
- startParam (for a feed)
- endParam (for a feed)
- ANY OF THE JQUERY $.ajax OPTIONS
allows for easily changing from GET to POST and sending additional parameters ([386])
allows for easily attaching ajax handlers such as `error` ([754])
allows for turning caching on ([355])
- Google Calendar feeds are now specified differently:
- specify a simple string of your feed's URL
- specify an *object* with a `url` property of your feed's URL.
you can include any of the new Event-Source options in this object.
- the old `$.fullCalendar.gcalFeed` method still works
- no more IE7 SSL popup ([504])
- remove `cacheParam` - use json event source `cache` option instead
- latest jquery/jquery-ui
[327]: https://code.google.com/p/fullcalendar/issues/detail?id=327
[395]: https://code.google.com/p/fullcalendar/issues/detail?id=395
[299]: https://code.google.com/p/fullcalendar/issues/detail?id=299
[117]: https://code.google.com/p/fullcalendar/issues/detail?id=117
[386]: https://code.google.com/p/fullcalendar/issues/detail?id=386
[754]: https://code.google.com/p/fullcalendar/issues/detail?id=754
[355]: https://code.google.com/p/fullcalendar/issues/detail?id=355
[504]: https://code.google.com/p/fullcalendar/issues/detail?id=504
v1.4.11 (2011-02-22)
--------------------
- fixed rerenderEvents bug ([790])
- fixed bug with faulty dragging of events from all-day slot in agenda views
- bundled with jquery 1.5 and jquery-ui 1.8.9
[790]: https://code.google.com/p/fullcalendar/issues/detail?id=790
v1.4.10 (2011-01-02)
--------------------
- fixed bug with resizing event to different week in 5-day month view ([740])
- fixed bug with events not sticking after a removeEvents call ([757])
- fixed bug with underlying parseTime method, and other uses of parseInt ([688])
[740]: https://code.google.com/p/fullcalendar/issues/detail?id=740
[757]: https://code.google.com/p/fullcalendar/issues/detail?id=757
[688]: https://code.google.com/p/fullcalendar/issues/detail?id=688
v1.4.9 (2010-11-16)
-------------------
- new algorithm for vertically stacking events ([111])
- resizing an event to a different week ([306])
- bug: some events not rendered with consecutive calls to addEventSource ([679])
[111]: https://code.google.com/p/fullcalendar/issues/detail?id=111
[306]: https://code.google.com/p/fullcalendar/issues/detail?id=306
[679]: https://code.google.com/p/fullcalendar/issues/detail?id=679
v1.4.8 (2010-10-16)
-------------------
- ignoreTimezone option (set to `false` to process UTC offsets in ISO8601 dates)
- bugfixes
- event refetching not being called under certain conditions ([417], [554])
- event refetching being called multiple times under certain conditions ([586], [616])
- selection cannot be triggered by right mouse button ([558])
- agenda view left axis sized incorrectly ([465])
- IE js error when calendar is too narrow ([517])
- agenda view looks strange when no scrollbars ([235])
- improved parsing of ISO8601 dates with UTC offsets
- $.fullCalendar.version
- an internal refactor of the code, for easier future development and modularity
[417]: https://code.google.com/p/fullcalendar/issues/detail?id=417
[554]: https://code.google.com/p/fullcalendar/issues/detail?id=554
[586]: https://code.google.com/p/fullcalendar/issues/detail?id=586
[616]: https://code.google.com/p/fullcalendar/issues/detail?id=616
[558]: https://code.google.com/p/fullcalendar/issues/detail?id=558
[465]: https://code.google.com/p/fullcalendar/issues/detail?id=465
[517]: https://code.google.com/p/fullcalendar/issues/detail?id=517
[235]: https://code.google.com/p/fullcalendar/issues/detail?id=235
v1.4.7 (2010-07-05)
-------------------
- "dropping" external objects onto the calendar
- droppable (boolean, to turn on/off)
- dropAccept (to filter which events the calendar will accept)
- drop (trigger)
- selectable options can now be specified with a View Option Hash
- bugfixes
- dragged & reverted events having wrong time text ([406])
- bug rendering events that have an endtime with seconds, but no hours/minutes ([477])
- gotoDate date overflow bug ([429])
- wrong date reported when clicking on edge of last column in agenda views [412]
- support newlines in event titles
- select/unselect callbacks now passes native js event
[406]: https://code.google.com/p/fullcalendar/issues/detail?id=406
[477]: https://code.google.com/p/fullcalendar/issues/detail?id=477
[429]: https://code.google.com/p/fullcalendar/issues/detail?id=429
[412]: https://code.google.com/p/fullcalendar/issues/detail?id=412
v1.4.6 (2010-05-31)
-------------------
- "selecting" days or timeslots
- options: selectable, selectHelper, unselectAuto, unselectCancel
- callbacks: select, unselect
- methods: select, unselect
- when dragging an event, the highlighting reflects the duration of the event
- code compressing by Google Closure Compiler
- bundled with jQuery 1.4.2 and jQuery UI 1.8.1
v1.4.5 (2010-02-21)
-------------------
- lazyFetching option, which can force the calendar to fetch events on every view/date change
- scroll state of agenda views are preserved when switching back to view
- bugfixes
- calling methods on an uninitialized fullcalendar throws error
- IE6/7 bug where an entire view becomes invisible ([320])
- error when rendering a hidden calendar (in jquery ui tabs for example) in IE ([340])
- interconnected bugs related to calendar resizing and scrollbars
- when switching views or clicking prev/next, calendar would "blink" ([333])
- liquid-width calendar's events shifted (depending on initial height of browser) ([341])
- more robust underlying algorithm for calendar resizing
[320]: https://code.google.com/p/fullcalendar/issues/detail?id=320
[340]: https://code.google.com/p/fullcalendar/issues/detail?id=340
[333]: https://code.google.com/p/fullcalendar/issues/detail?id=333
[341]: https://code.google.com/p/fullcalendar/issues/detail?id=341
v1.4.4 (2010-02-03)
-------------------
- optimized event rendering in all views (events render in 1/10 the time)
- gotoDate() does not force the calendar to unnecessarily rerender
- render() method now correctly readjusts height
v1.4.3 (2009-12-22)
-------------------
- added destroy method
- Google Calendar event pages respect currentTimezone
- caching now handled by jQuery's ajax
- protection from setting aspectRatio to zero
- bugfixes
- parseISO8601 and DST caused certain events to display day before
- button positioning problem in IE6
- ajax event source removed after recently being added, events still displayed
- event not displayed when end is an empty string
- dynamically setting calendar height when no events have been fetched, throws error
v1.4.2 (2009-12-02)
-------------------
- eventAfterRender trigger
- getDate & getView methods
- height & contentHeight options (explicitly sets the pixel height)
- minTime & maxTime options (restricts shown hours in agenda view)
- getters [for all options] and setters [for height, contentHeight, and aspectRatio ONLY! stay tuned..]
- render method now readjusts calendar's size
- bugfixes
- lightbox scripts that use iframes (like fancybox)
- day-of-week classNames were off when firstDay=1
- guaranteed space on right side of agenda events (even when stacked)
- accepts ISO8601 dates with a space (instead of 'T')
v1.4.1 (2009-10-31)
-------------------
- can exclude weekends with new 'weekends' option
- gcal feed 'currentTimezone' option
- bugfixes
- year/month/date option sometimes wouldn't set correctly (depending on current date)
- daylight savings issue caused agenda views to start at 1am (for BST users)
- cleanup of gcal.js code
v1.4 (2009-10-19)
-----------------
- agendaWeek and agendaDay views
- added some options for agenda views:
- allDaySlot
- allDayText
- firstHour
- slotMinutes
- defaultEventMinutes
- axisFormat
- modified some existing options/triggers to work with agenda views:
- dragOpacity and timeFormat can now accept a "View Hash" (a new concept)
- dayClick now has an allDay parameter
- eventDrop now has an an allDay parameter
(this will affect those who use revertFunc, adjust parameter list)
- added 'prevYear' and 'nextYear' for buttons in header
- minor change for theme users, ui-state-hover not applied to active/inactive buttons
- added event-color-changing example in docs
- better defaults for right-to-left themed button icons
v1.3.2 (2009-10-13)
-------------------
- Bugfixes (please upgrade from 1.3.1!)
- squashed potential infinite loop when addMonths and addDays
is called with an invalid date
- $.fullCalendar.parseDate() now correctly parses IETF format
- when switching views, the 'today' button sticks inactive, fixed
- gotoDate now can accept a single Date argument
- documentation for changes in 1.3.1 and 1.3.2 now on website
v1.3.1 (2009-09-30)
-------------------
- Important Bugfixes (please upgrade from 1.3!)
- When current date was late in the month, for long months, and prev/next buttons
were clicked in month-view, some months would be skipped/repeated
- In certain time zones, daylight savings time would cause certain days
to be misnumbered in month-view
- Subtle change in way week interval is chosen when switching from month to basicWeek/basicDay view
- Added 'allDayDefault' option
- Added 'changeView' and 'render' methods
v1.3 (2009-09-21)
-----------------
- different 'views': month/basicWeek/basicDay
- more flexible 'header' system for buttons
- themable by jQuery UI themes
- resizable events (require jQuery UI resizable plugin)
- rescoped & rewritten CSS, enhanced default look
- cleaner css & rendering techniques for right-to-left
- reworked options & API to support multiple views / be consistent with jQuery UI
- refactoring of entire codebase
- broken into different JS & CSS files, assembled w/ build scripts
- new test suite for new features, uses firebug-lite
- refactored docs
- Options
- + date
- + defaultView
- + aspectRatio
- + disableResizing
- + monthNames (use instead of $.fullCalendar.monthNames)
- + monthNamesShort (use instead of $.fullCalendar.monthAbbrevs)
- + dayNames (use instead of $.fullCalendar.dayNames)
- + dayNamesShort (use instead of $.fullCalendar.dayAbbrevs)
- + theme
- + buttonText
- + buttonIcons
- x draggable -> editable/disableDragging
- x fixedWeeks -> weekMode
- x abbrevDayHeadings -> columnFormat
- x buttons/title -> header
- x eventDragOpacity -> dragOpacity
- x eventRevertDuration -> dragRevertDuration
- x weekStart -> firstDay
- x rightToLeft -> isRTL
- x showTime (use 'allDay' CalEvent property instead)
- Triggered Actions
- + eventResizeStart
- + eventResizeStop
- + eventResize
- x monthDisplay -> viewDisplay
- x resize -> windowResize
- 'eventDrop' params changed, can revert if ajax cuts out
- CalEvent Properties
- x showTime -> allDay
- x draggable -> editable
- 'end' is now INCLUSIVE when allDay=true
- 'url' now produces a real <a> tag, more native clicking/tab behavior
- Methods:
- + renderEvent
- x prevMonth -> prev
- x nextMonth -> next
- x prevYear/nextYear -> moveDate
- x refresh -> rerenderEvents/refetchEvents
- x removeEvent -> removeEvents
- x getEventsByID -> clientEvents
- Utilities:
- 'formatDate' format string completely changed (inspired by jQuery UI datepicker + datejs)
- 'formatDates' added to support date-ranges
- Google Calendar Options:
- x draggable -> editable
- Bugfixes
- gcal extension fetched 25 results max, now fetches all
v1.2.1 (2009-06-29)
-------------------
- bugfixes
- allows and corrects invalid end dates for events
- doesn't throw an error in IE while rendering when display:none
- fixed 'loading' callback when used w/ multiple addEventSource calls
- gcal className can now be an array
v1.2 (2009-05-31)
-----------------
- expanded API
- 'className' CalEvent attribute
- 'source' CalEvent attribute
- dynamically get/add/remove/update events of current month
- locale improvements: change month/day name text
- better date formatting ($.fullCalendar.formatDate)
- multiple 'event sources' allowed
- dynamically add/remove event sources
- options for prevYear and nextYear buttons
- docs have been reworked (include addition of Google Calendar docs)
- changed behavior of parseDate for number strings
(now interpets as unix timestamp, not MS times)
- bugfixes
- rightToLeft month start bug
- off-by-one errors with month formatting commands
- events from previous months sticking when clicking prev/next quickly
- Google Calendar API changed to work w/ multiple event sources
- can also provide 'className' and 'draggable' options
- date utilties moved from $ to $.fullCalendar
- more documentation in source code
- minified version of fullcalendar.js
- test suit (available from svn)
- top buttons now use `<button>` w/ an inner `<span>` for better css cusomization
- thus CSS has changed. IF UPGRADING FROM PREVIOUS VERSIONS,
UPGRADE YOUR FULLCALENDAR.CSS FILE
v1.1 (2009-05-10)
-----------------
- Added the following options:
- weekStart
- rightToLeft
- titleFormat
- timeFormat
- cacheParam
- resize
- Fixed rendering bugs
- Opera 9.25 (events placement & window resizing)
- IE6 (window resizing)
- Optimized window resizing for ALL browsers
- Events on same day now sorted by start time (but first by timespan)
- Correct z-index when dragging
- Dragging contained in overflow DIV for IE6
- Modified fullcalendar.css
- for right-to-left support
- for variable start-of-week
- for IE6 resizing bug
- for THEAD and TBODY (in 1.0, just used TBODY, restructured in 1.1)
- IF UPGRADING FROM FULLCALENDAR 1.0, YOU MUST UPGRADE FULLCALENDAR.CSS
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultDate: '2014-06-12',
editable: true,
events: [
{
title: 'All Day Event',
start: '2014-06-01'
},
{
title: 'Long Event',
start: '2014-06-07',
end: '2014-06-10'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-09T16:00:00'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-16T16:00:00'
},
{
title: 'Meeting',
start: '2014-06-12T10:30:00',
end: '2014-06-12T12:30:00'
},
{
title: 'Lunch',
start: '2014-06-12T12:00:00'
},
{
title: 'Birthday Party',
start: '2014-06-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2014-06-28'
}
]
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
font-size: 14px;
}
#calendar {
width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
+88
View File
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
defaultDate: '2014-06-12',
editable: true,
events: [
{
title: 'All Day Event',
start: '2014-06-01'
},
{
title: 'Long Event',
start: '2014-06-07',
end: '2014-06-10'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-09T16:00:00'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-16T16:00:00'
},
{
title: 'Meeting',
start: '2014-06-12T10:30:00',
end: '2014-06-12T12:30:00'
},
{
title: 'Lunch',
start: '2014-06-12T12:00:00'
},
{
title: 'Birthday Party',
start: '2014-06-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2014-06-28'
}
]
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
font-size: 14px;
}
#calendar {
width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
+83
View File
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
defaultDate: '2014-06-12',
editable: true,
events: [
{
title: 'All Day Event',
start: '2014-06-01'
},
{
title: 'Long Event',
start: '2014-06-07',
end: '2014-06-10'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-09T16:00:00'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-16T16:00:00'
},
{
title: 'Meeting',
start: '2014-06-12T10:30:00',
end: '2014-06-12T12:30:00'
},
{
title: 'Lunch',
start: '2014-06-12T12:00:00'
},
{
title: 'Birthday Party',
start: '2014-06-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2014-06-28'
}
]
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
font-size: 14px;
}
#calendar {
width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
@@ -0,0 +1,157 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script>
$(document).ready(function() {
/* initialize the external events
-----------------------------------------------------------------*/
$('#external-events div.external-event').each(function() {
// create an Event Object (http://arshaw.com/fullcalendar/docs/event_data/Event_Object/)
// it doesn't need to have a start or end
var eventObject = {
title: $.trim($(this).text()) // use the element's text as the event title
};
// store the Event Object in the DOM element so we can get to it later
$(this).data('eventObject', eventObject);
// make the event draggable using jQuery UI
$(this).draggable({
zIndex: 999,
revert: true, // will cause the event to go back to its
revertDuration: 0 // original position after the drag
});
});
/* initialize the calendar
-----------------------------------------------------------------*/
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
editable: true,
droppable: true, // this allows things to be dropped onto the calendar !!!
drop: function(date) { // this function is called when something is dropped
// retrieve the dropped element's stored Event Object
var originalEventObject = $(this).data('eventObject');
// we need to copy it, so that multiple events don't have a reference to the same object
var copiedEventObject = $.extend({}, originalEventObject);
// assign it the date that was reported
copiedEventObject.start = date;
// render the event on the calendar
// the last `true` argument determines if the event "sticks" (http://arshaw.com/fullcalendar/docs/event_rendering/renderEvent/)
$('#calendar').fullCalendar('renderEvent', copiedEventObject, true);
// is the "remove after drop" checkbox checked?
if ($('#drop-remove').is(':checked')) {
// if so, remove the element from the "Draggable Events" list
$(this).remove();
}
}
});
});
</script>
<style>
body {
margin-top: 40px;
text-align: center;
font-size: 14px;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
}
#wrap {
width: 1100px;
margin: 0 auto;
}
#external-events {
float: left;
width: 150px;
padding: 0 10px;
border: 1px solid #ccc;
background: #eee;
text-align: left;
}
#external-events h4 {
font-size: 16px;
margin-top: 0;
padding-top: 1em;
}
.external-event { /* try to mimick the look of a real event */
margin: 10px 0;
padding: 2px 4px;
background: #3366CC;
color: #fff;
font-size: .85em;
cursor: pointer;
}
#external-events p {
margin: 1.5em 0;
font-size: 11px;
color: #666;
}
#external-events p input {
margin: 0;
vertical-align: middle;
}
#calendar {
float: right;
width: 900px;
}
</style>
</head>
<body>
<div id='wrap'>
<div id='external-events'>
<h4>Draggable Events</h4>
<div class='external-event'>My Event 1</div>
<div class='external-event'>My Event 2</div>
<div class='external-event'>My Event 3</div>
<div class='external-event'>My Event 4</div>
<div class='external-event'>My Event 5</div>
<p>
<input type='checkbox' id='drop-remove' />
<label for='drop-remove'>remove after drop</label>
</p>
</div>
<div id='calendar'></div>
<div style='clear:both'></div>
</div>
</body>
</html>
+65
View File
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script src='../gcal.js'></script>
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
// US Holidays
events: 'http://www.google.com/calendar/feeds/usa__en%40holiday.calendar.google.com/public/basic',
eventClick: function(event) {
// opens events in a popup window
window.open(event.url, 'gcalevent', 'width=700,height=600');
return false;
},
loading: function(bool) {
$('#loading').toggle(bool);
}
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
font-size: 14px;
}
#loading {
display: none;
position: absolute;
top: 10px;
right: 10px;
}
#calendar {
width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<div id='loading'>loading...</div>
<div id='calendar'></div>
</body>
</html>
+83
View File
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultDate: '2014-06-12',
editable: true,
events: {
url: 'php/get-events.php',
error: function() {
$('#script-warning').show();
}
},
loading: function(bool) {
$('#loading').toggle(bool);
}
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
font-size: 14px;
}
#script-warning {
display: none;
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
text-align: center;
font-weight: bold;
font-size: 12px;
color: red;
}
#loading {
display: none;
position: absolute;
top: 10px;
right: 10px;
}
#calendar {
width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<div id='script-warning'>
<code>php/get-events.php</code> must be running.
</div>
<div id='loading'>loading...</div>
<div id='calendar'></div>
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
[{
"title": "R1-R2: Lunch 12.15-14.45",
"start": "2014-08-19T12:15:00",
"end": "2014-08-19T14:45:00",
"allDay": false,
"resources": ["resource1", "resource2"]
}, {
"title": "R1: All day",
"start": "2014-08-19T10:30:00",
"resources": "resource1"
}, {
"title": "R2: All Day",
"start": "2014-08-19T11:00:00",
"resources": "resource2"
}, {
"title": "R1/R2: Lunch 12-14",
"start": "2014-08-19T12:00:00",
"end": "2014-08-19T14:00:00",
"allDay": false,
"resources": ["resource1", "resource2"]
}, {
"id": 999,
"title": "Repeating Event",
"start": "2014-08-19T16:00:00-05:00",
"end": "2014-08-19T17:00:00-05:00",
"allDay": false,
"resources": "resource2"
}, {
"id": 999,
"title": "Repeating Event",
"start": "2014-08-19T16:00:00-05:00",
"end": "2014-08-19T17:00:00-05:00",
"allDay": false,
"resources": "resource2"
}]
+131
View File
@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link rel='stylesheet' href='../lib/cupertino/jquery-ui.min.css' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script src='../lang-all.js'></script>
<script>
$(document).ready(function() {
var currentLangCode = 'en';
// build the language selector's options
$.each($.fullCalendar.langs, function(langCode) {
$('#lang-selector').append(
$('<option/>')
.attr('value', langCode)
.prop('selected', langCode == currentLangCode)
.text(langCode)
);
});
// rerender the calendar when the selected option changes
$('#lang-selector').on('change', function() {
if (this.value) {
currentLangCode = this.value;
$('#calendar').fullCalendar('destroy');
renderCalendar();
}
});
function renderCalendar() {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultDate: '2014-06-12',
lang: currentLangCode,
buttonIcons: false, // show the prev/next text
weekNumbers: true,
editable: true,
events: [
{
title: 'All Day Event',
start: '2014-06-01'
},
{
title: 'Long Event',
start: '2014-06-07',
end: '2014-06-10'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-09T16:00:00'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-16T16:00:00'
},
{
title: 'Meeting',
start: '2014-06-12T10:30:00',
end: '2014-06-12T12:30:00'
},
{
title: 'Lunch',
start: '2014-06-12T12:00:00'
},
{
title: 'Birthday Party',
start: '2014-06-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2014-06-28'
}
]
});
}
renderCalendar();
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
font-size: 14px;
}
#top {
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
font-size: 12px;
}
#calendar {
width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<div id='top'>
Language:
<select id='lang-selector'></select>
</div>
<div id='calendar'></div>
</body>
</html>
@@ -0,0 +1,50 @@
<?php
//--------------------------------------------------------------------------------------------------
// This script reads event data from a JSON file and outputs those events which are within the range
// supplied by the "start" and "end" GET parameters.
//
// An optional "timezone" GET parameter will force all ISO8601 date stings to a given timezone.
//
// Requires PHP 5.2.0 or higher.
//--------------------------------------------------------------------------------------------------
// Require our Event class and datetime utilities
require dirname(__FILE__) . '/utils.php';
// Short-circuit if the client did not give us a date range.
if (!isset($_GET['start']) || !isset($_GET['end'])) {
die("Please provide a date range.");
}
// Parse the start/end parameters.
// These are assumed to be ISO8601 strings with no time nor timezone, like "2013-12-29".
// Since no timezone will be present, they will parsed as UTC.
$range_start = parseDateTime($_GET['start']);
$range_end = parseDateTime($_GET['end']);
// Parse the timezone parameter if it is present.
$timezone = null;
if (isset($_GET['timezone'])) {
$timezone = new DateTimeZone($_GET['timezone']);
}
// Read and parse our events JSON file into an array of event data arrays.
$json = file_get_contents(dirname(__FILE__) . '/../json/events.json');
$input_arrays = json_decode($json, true);
// Accumulate an output array of event data arrays.
$output_arrays = array();
foreach ($input_arrays as $array) {
// Convert the input array into a useful Event object
$event = new Event($array, $timezone);
// If the event is in-bounds, add it to the output
if ($event->isWithinDayRange($range_start, $range_end)) {
$output_arrays[] = $event->toArray();
}
}
// Send JSON to the client.
echo json_encode($output_arrays);
@@ -0,0 +1,9 @@
<?php
//--------------------------------------------------------------------------------------------------
// This script outputs a JSON array of all timezones (like "America/Chicago") that PHP supports.
//
// Requires PHP 5.2.0 or higher.
//--------------------------------------------------------------------------------------------------
echo json_encode(DateTimeZone::listIdentifiers());
+130
View File
@@ -0,0 +1,130 @@
<?php
//--------------------------------------------------------------------------------------------------
// Utilities for our event-fetching scripts.
//
// Requires PHP 5.2.0 or higher.
//--------------------------------------------------------------------------------------------------
// PHP will fatal error if we attempt to use the DateTime class without this being set.
date_default_timezone_set('UTC');
class Event {
// Tests whether the given ISO8601 string has a time-of-day or not
const ALL_DAY_REGEX = '/^\d{4}-\d\d-\d\d$/'; // matches strings like "2013-12-29"
public $title;
public $allDay; // a boolean
public $start; // a DateTime
public $end; // a DateTime, or null
public $properties = array(); // an array of other misc properties
// Constructs an Event object from the given array of key=>values.
// You can optionally force the timezone of the parsed dates.
public function __construct($array, $timezone=null) {
$this->title = $array['title'];
if (isset($array['allDay'])) {
// allDay has been explicitly specified
$this->allDay = (bool)$array['allDay'];
}
else {
// Guess allDay based off of ISO8601 date strings
$this->allDay = preg_match(self::ALL_DAY_REGEX, $array['start']) &&
(!isset($array['end']) || preg_match(self::ALL_DAY_REGEX, $array['end']));
}
if ($this->allDay) {
// If dates are allDay, we want to parse them in UTC to avoid DST issues.
$timezone = null;
}
// Parse dates
$this->start = parseDateTime($array['start'], $timezone);
$this->end = isset($array['end']) ? parseDateTime($array['end'], $timezone) : null;
// Record misc properties
foreach ($array as $name => $value) {
if (!in_array($name, array('title', 'allDay', 'start', 'end'))) {
$this->properties[$name] = $value;
}
}
}
// Returns whether the date range of our event intersects with the given all-day range.
// $rangeStart and $rangeEnd are assumed to be dates in UTC with 00:00:00 time.
public function isWithinDayRange($rangeStart, $rangeEnd) {
// Normalize our event's dates for comparison with the all-day range.
$eventStart = stripTime($this->start);
$eventEnd = isset($this->end) ? stripTime($this->end) : null;
if (!$eventEnd) {
// No end time? Only check if the start is within range.
return $eventStart < $rangeEnd && $eventStart >= $rangeStart;
}
else {
// Check if the two ranges intersect.
return $eventStart < $rangeEnd && $eventEnd > $rangeStart;
}
}
// Converts this Event object back to a plain data array, to be used for generating JSON
public function toArray() {
// Start with the misc properties (don't worry, PHP won't affect the original array)
$array = $this->properties;
$array['title'] = $this->title;
// Figure out the date format. This essentially encodes allDay into the date string.
if ($this->allDay) {
$format = 'Y-m-d'; // output like "2013-12-29"
}
else {
$format = 'c'; // full ISO8601 output, like "2013-12-29T09:00:00+08:00"
}
// Serialize dates into strings
$array['start'] = $this->start->format($format);
if (isset($this->end)) {
$array['end'] = $this->end->format($format);
}
return $array;
}
}
// Date Utilities
//----------------------------------------------------------------------------------------------
// Parses a string into a DateTime object, optionally forced into the given timezone.
function parseDateTime($string, $timezone=null) {
$date = new DateTime(
$string,
$timezone ? $timezone : new DateTimeZone('UTC')
// Used only when the string is ambiguous.
// Ignored if string has a timezone offset in it.
);
if ($timezone) {
// If our timezone was ignored above, force it.
$date->setTimezone($timezone);
}
return $date;
}
// Takes the year/month/date values of the given DateTime and converts them to a new DateTime,
// but in UTC.
function stripTime($datetime) {
return new DateTime($datetime->format('Y-m-d'));
}
@@ -0,0 +1,184 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'/>
<link href='../fullcalendar.css' rel='stylesheet'/>
<link href='../fullcalendar.print.css' rel='stylesheet' media='print'/>
<style type="text/css">
body {
margin: 5px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
background-color: #ffffff;
}
form {
background-color: #444;
color: #fff;
width: 500px;
margin: auto;
white-space: pre-line;
padding: 15px 30px;
}
input {
cursor: pointer;
}
.red {
background-color: rgb(255, 190, 190);
}
</style>
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script>
$(document).ready(function () {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,resourceDay'
},
defaultView: 'resourceDay',
editable: true,
droppable: true,
resources: [
{
'id': 'resource1',
'name': 'Resource 1'
},
{
'id': 'resource2',
'name': 'Resource 2'
},
{
'id': 'resource3',
'name': 'Resource 3',
'className': ['red']
}
],
resourceFilter: function (resource) {
var active = $("input").map(function(){
return this.checked ? this.name : null;
}).get();
return $.inArray(resource.id, active) > -1;
},
events: [
{
title: 'R1-R2: Lunch 12.15-14.45',
start: new Date(y, m, d, 12, 15),
end: new Date(y, m, d, 14, 45),
allDay: false,
resources: ['resource1', 'resource2']
},
{
title: 'R1: All day',
start: new Date(y, m, d, 10, 30),
end: new Date(y, m, d, 11, 0),
allDay: true,
resources: 'resource1'
},
{
title: 'R2: Meeting 11.00',
start: new Date(y, m, d, 11, 0),
allDay: true,
resources: 'resource2'
},
{
title: 'R1/R2: Lunch 12-14',
start: new Date(y, m, d, 12, 0),
end: new Date(y, m, d, 14, 0),
allDay: false,
resources: ['resource1', 'resource2']
},
{
id: 777,
title: 'R1: Lunch',
start: new Date(y, m, d, 12, 0),
end: new Date(y, m, d, 14, 0),
allDay: false,
resources: ['resource1']
},
{
title: 'R3: Breakfast',
start: new Date(y, m, d, 8, 0),
end: new Date(y, m, d, 8, 30),
allDay: false,
resources: ['resource3']
},
{
id: 999,
title: 'Repeating Event',
start: new Date(y, m, d - 3, 16, 0),
allDay: false,
resources: 'resource2'
},
{
id: 999,
title: 'Repeating Event',
start: new Date(y, m, d + 4, 16, 0),
allDay: false,
resources: 'resource2'
}
],
// the 'ev' parameter is the mouse event rather than the resource 'event'
// the ev.data is the resource column clicked upon
selectable: true,
selectHelper: true,
select: function (start, end, ev) {
console.log(start);
console.log(end);
console.log(ev.data); // resources
},
eventClick: function (event) {
console.log(event);
},
eventDrop: function (event, delta, revertFunc) {
console.log(event);
}
});
});
$(document).ready(function() {
$('input:checkbox').change(function(){
$('#calendar').fullCalendar('render');
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande", Helvetica, Arial, Verdana, sans-serif;
font-size: 14px;
}
#calendar {
width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<form>Select the resources you wanna filter:
<input name="resource1" type="checkbox" checked /> Resource 1
<input name="resource2" type="checkbox" /> Resource 2
<input name="resource3" type="checkbox" checked /> Resource 3
</form>
<div id='calendar'></div>
</body>
</html>
+103
View File
@@ -0,0 +1,103 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultDate: '2014-06-12',
selectable: true,
selectHelper: true,
select: function(start, end) {
var title = prompt('Event Title:');
var eventData;
if (title) {
eventData = {
title: title,
start: start,
end: end
};
$('#calendar').fullCalendar('renderEvent', eventData, true); // stick? = true
}
$('#calendar').fullCalendar('unselect');
},
editable: true,
events: [
{
title: 'All Day Event',
start: '2014-06-01'
},
{
title: 'Long Event',
start: '2014-06-07',
end: '2014-06-10'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-09T16:00:00'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-16T16:00:00'
},
{
title: 'Meeting',
start: '2014-06-12T10:30:00',
end: '2014-06-12T12:30:00'
},
{
title: 'Lunch',
start: '2014-06-12T12:00:00'
},
{
title: 'Birthday Party',
start: '2014-06-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2014-06-28'
}
]
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
font-size: 14px;
}
#calendar {
width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
+90
View File
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link rel='stylesheet' href='../lib/cupertino/jquery-ui.min.css' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script>
$(document).ready(function() {
$('#calendar').fullCalendar({
theme: true,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultDate: '2014-06-12',
editable: true,
events: [
{
title: 'All Day Event',
start: '2014-06-01'
},
{
title: 'Long Event',
start: '2014-06-07',
end: '2014-06-10'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-09T16:00:00'
},
{
id: 999,
title: 'Repeating Event',
start: '2014-06-16T16:00:00'
},
{
title: 'Meeting',
start: '2014-06-12T10:30:00',
end: '2014-06-12T12:30:00'
},
{
title: 'Lunch',
start: '2014-06-12T12:00:00'
},
{
title: 'Birthday Party',
start: '2014-06-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2014-06-28'
}
]
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
font-size: 14px;
}
#calendar {
width: 900px;
margin: 40px auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
+127
View File
@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../fullcalendar.css' rel='stylesheet' />
<link href='../fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='../lib/moment.min.js'></script>
<script src='../lib/jquery.min.js'></script>
<script src='../lib/jquery-ui.custom.min.js'></script>
<script src='../fullcalendar.min.js'></script>
<script>
$(document).ready(function() {
var currentTimezone = false;
// load the list of available timezones
$.getJSON('php/get-timezones.php', function(timezones) {
$.each(timezones, function(i, timezone) {
if (timezone != 'UTC') { // UTC is already in the list
$('#timezone-selector').append(
$("<option/>").text(timezone).attr('value', timezone)
);
}
});
});
// when the timezone selector changes, rerender the calendar
$('#timezone-selector').on('change', function() {
currentTimezone = this.value || false;
$('#calendar').fullCalendar('destroy');
renderCalendar();
});
function renderCalendar() {
$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
defaultDate: '2014-06-12',
timezone: currentTimezone,
editable: true,
events: {
url: 'php/get-events.php',
error: function() {
$('#script-warning').show();
}
},
loading: function(bool) {
$('#loading').toggle(bool);
},
eventRender: function(event, el) {
// render the timezone offset below the event title
if (event.start.hasZone()) {
el.find('.fc-event-title').after(
$('<div class="tzo"/>').text(event.start.format('Z'))
);
}
}
});
}
renderCalendar();
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
font-size: 14px;
}
#top {
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
font-size: 12px;
}
.left { float: left }
.right { float: right }
.clear { clear: both }
#script-warning, #loading { display: none }
#script-warning { font-weight: bold; color: red }
#calendar {
width: 900px;
margin: 40px auto;
}
.tzo {
color: #000;
}
</style>
</head>
<body>
<div id='top'>
<div class='left'>
Timezone:
<select id='timezone-selector'>
<option value='' selected>none</option>
<option value='local'>local</option>
<option value='UTC'>UTC</option>
</select>
</div>
<div class='right'>
<span id='loading'>loading...</span>
<span id='script-warning'><code>php/get-events.php</code> must be running.</span>
</div>
<div class='clear'></div>
</div>
<div id='calendar'></div>
</body>
</html>
+601
View File
@@ -0,0 +1,601 @@
/*!
* FullCalendar v2.0.2 Stylesheet
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2014 Adam Shaw, Sean Kenny
*/
.fc {
direction: ltr;
text-align: left;
}
.fc table {
border-collapse: collapse;
border-spacing: 0;
}
html .fc,
.fc table {
font-size: 1em;
}
.fc td,
.fc th {
padding: 0;
vertical-align: top;
}
/* Header
------------------------------------------------------------------------*/
.fc-header td {
white-space: nowrap;
}
.fc-header-left {
width: 25%;
text-align: left;
}
.fc-header-center {
text-align: center;
}
.fc-header-right {
width: 25%;
text-align: right;
}
.fc-header-title {
display: inline-block;
vertical-align: top;
}
.fc-header-title h2 {
margin-top: 0;
white-space: nowrap;
}
.fc .fc-header-space {
padding-left: 10px;
}
.fc-header .fc-button {
margin-bottom: 1em;
vertical-align: top;
}
/* buttons edges butting together */
.fc-header .fc-button {
margin-right: -1px;
}
.fc-header .fc-corner-right, /* non-theme */
.fc-header .ui-corner-right { /* theme */
margin-right: 0; /* back to normal */
}
/* button layering (for border precedence) */
.fc-header .fc-state-hover,
.fc-header .ui-state-hover {
z-index: 2;
}
.fc-header .fc-state-down {
z-index: 3;
}
.fc-header .fc-state-active,
.fc-header .ui-state-active {
z-index: 4;
}
/* Content
------------------------------------------------------------------------*/
.fc-content {
position: relative;
z-index: 1; /* scopes all other z-index's to be inside this container */
clear: both;
zoom: 1; /* for IE7, gives accurate coordinates for [un]freezeContentHeight */
}
.fc-view {
position: relative;
width: 100%;
overflow: hidden;
}
/* Cell Styles
------------------------------------------------------------------------*/
.fc-widget-header, /* <th>, usually */
.fc-widget-content { /* <td>, usually */
border: 1px solid #ddd;
}
.fc-state-highlight { /* <td> today cell */ /* TODO: add .fc-today to <th> */
background: #fcf8e3;
}
.fc-cell-overlay { /* semi-transparent rectangle while dragging */
background: #bce8f1;
opacity: .3;
filter: alpha(opacity=30); /* for IE */
}
/* Buttons
------------------------------------------------------------------------*/
.fc-button {
position: relative;
display: inline-block;
padding: 0 .6em;
overflow: hidden;
height: 1.9em;
line-height: 1.9em;
white-space: nowrap;
cursor: pointer;
}
.fc-state-default { /* non-theme */
border: 1px solid;
}
.fc-state-default.fc-corner-left { /* non-theme */
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.fc-state-default.fc-corner-right { /* non-theme */
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
/*
Our default prev/next buttons use HTML entities like &lsaquo; &rsaquo; &laquo; &raquo;
and we'll try to make them look good cross-browser.
*/
.fc-button .fc-icon {
margin: 0 .1em;
font-size: 2em;
font-family: "Courier New", Courier, monospace;
vertical-align: baseline; /* for IE7 */
}
.fc-icon-left-single-arrow:after {
content: "\02039";
font-weight: bold;
}
.fc-icon-right-single-arrow:after {
content: "\0203A";
font-weight: bold;
}
.fc-icon-left-double-arrow:after {
content: "\000AB";
}
.fc-icon-right-double-arrow:after {
content: "\000BB";
}
/* icon (for jquery ui) */
.fc-button .ui-icon {
position: relative;
top: 50%;
float: left;
margin-top: -8px; /* we know jqui icons are always 16px tall */
}
/*
button states
borrowed from twitter bootstrap (http://twitter.github.com/bootstrap/)
*/
.fc-state-default {
background-color: #f5f5f5;
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
background-repeat: repeat-x;
border-color: #e6e6e6 #e6e6e6 #bfbfbf;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
color: #333;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.fc-state-hover,
.fc-state-down,
.fc-state-active,
.fc-state-disabled {
color: #333333;
background-color: #e6e6e6;
}
.fc-state-hover {
color: #333333;
text-decoration: none;
background-position: 0 -15px;
-webkit-transition: background-position 0.1s linear;
-moz-transition: background-position 0.1s linear;
-o-transition: background-position 0.1s linear;
transition: background-position 0.1s linear;
}
.fc-state-down,
.fc-state-active {
background-color: #cccccc;
background-image: none;
outline: 0;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.fc-state-disabled {
cursor: default;
background-image: none;
opacity: 0.65;
filter: alpha(opacity=65);
box-shadow: none;
}
/* Global Event Styles
------------------------------------------------------------------------*/
.fc-event-container > * {
z-index: 8;
}
.fc-event-container > .ui-draggable-dragging,
.fc-event-container > .ui-resizable-resizing {
z-index: 9;
}
.fc-event {
border: 1px solid #3a87ad; /* default BORDER color */
background-color: #3a87ad; /* default BACKGROUND color */
color: #fff; /* default TEXT color */
font-size: .85em;
cursor: default;
}
a.fc-event {
text-decoration: none;
}
a.fc-event,
.fc-event-draggable {
cursor: pointer;
}
.fc-rtl .fc-event {
text-align: right;
}
.fc-event-inner {
width: 100%;
height: 100%;
overflow: hidden;
}
.fc-event-time,
.fc-event-title {
padding: 0 1px;
}
.fc .ui-resizable-handle {
display: block;
position: absolute;
z-index: 99999;
overflow: hidden; /* hacky spaces (IE6/7) */
font-size: 300%; /* */
line-height: 50%; /* */
}
/* Horizontal Events
------------------------------------------------------------------------*/
.fc-event-hori {
border-width: 1px 0;
margin-bottom: 1px;
}
.fc-ltr .fc-event-hori.fc-event-start,
.fc-rtl .fc-event-hori.fc-event-end {
border-left-width: 1px;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
.fc-ltr .fc-event-hori.fc-event-end,
.fc-rtl .fc-event-hori.fc-event-start {
border-right-width: 1px;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
/* resizable */
.fc-event-hori .ui-resizable-e {
top: 0 !important; /* importants override pre jquery ui 1.7 styles */
right: -3px !important;
width: 7px !important;
height: 100% !important;
cursor: e-resize;
}
.fc-event-hori .ui-resizable-w {
top: 0 !important;
left: -3px !important;
width: 7px !important;
height: 100% !important;
cursor: w-resize;
}
.fc-event-hori .ui-resizable-handle {
_padding-bottom: 14px; /* IE6 had 0 height */
}
/* Reusable Separate-border Table
------------------------------------------------------------*/
table.fc-border-separate {
border-collapse: separate;
}
.fc-border-separate th,
.fc-border-separate td {
border-width: 1px 0 0 1px;
}
.fc-border-separate th.fc-last,
.fc-border-separate td.fc-last {
border-right-width: 1px;
}
.fc-border-separate tr.fc-last th,
.fc-border-separate tr.fc-last td {
border-bottom-width: 1px;
}
.fc-border-separate tbody tr.fc-first td,
.fc-border-separate tbody tr.fc-first th {
border-top-width: 0;
}
/* Month View, Basic Week View, Basic Day View
------------------------------------------------------------------------*/
.fc-grid th {
text-align: center;
}
.fc .fc-week-number {
width: 22px;
text-align: center;
}
.fc .fc-week-number div {
padding: 0 2px;
}
.fc-grid .fc-day-number {
float: right;
padding: 0 2px;
}
.fc-grid .fc-other-month .fc-day-number {
opacity: 0.3;
filter: alpha(opacity=30); /* for IE */
/* opacity with small font can sometimes look too faded
might want to set the 'color' property instead
making day-numbers bold also fixes the problem */
}
.fc-grid .fc-day-content {
clear: both;
padding: 2px 2px 1px; /* distance between events and day edges */
}
/* event styles */
.fc-grid .fc-event-time {
font-weight: bold;
}
/* right-to-left */
.fc-rtl .fc-grid .fc-day-number {
float: left;
}
.fc-rtl .fc-grid .fc-event-time {
float: right;
}
/* Agenda Week View, Agenda Day View
------------------------------------------------------------------------*/
.fc-agenda table {
border-collapse: separate;
}
.fc-agenda-days th {
text-align: center;
}
.fc-agenda .fc-agenda-axis {
width: 50px;
padding: 0 4px;
vertical-align: middle;
text-align: right;
font-weight: normal;
}
.fc-agenda-slots .fc-agenda-axis {
white-space: nowrap;
}
.fc-agenda .fc-week-number {
font-weight: bold;
}
.fc-agenda .fc-day-content {
padding: 2px 2px 1px;
}
/* make axis border take precedence */
.fc-agenda-days .fc-agenda-axis {
border-right-width: 1px;
}
.fc-agenda-days .fc-col0 {
border-left-width: 0;
}
/* all-day area */
.fc-agenda-allday th {
border-width: 0 1px;
}
.fc-agenda-allday .fc-day-content {
min-height: 34px; /* TODO: doesnt work well in quirksmode */
_height: 34px;
}
/* divider (between all-day and slots) */
.fc-agenda-divider-inner {
height: 2px;
overflow: hidden;
}
.fc-widget-header .fc-agenda-divider-inner {
background: #eee;
}
/* slot rows */
.fc-agenda-slots th {
border-width: 1px 1px 0;
}
.fc-agenda-slots td {
border-width: 1px 0 0;
background: none;
}
.fc-agenda-slots td div {
height: 20px;
}
.fc-agenda-slots tr.fc-slot0 th,
.fc-agenda-slots tr.fc-slot0 td {
border-top-width: 0;
}
.fc-agenda-slots tr.fc-minor th,
.fc-agenda-slots tr.fc-minor td {
border-top-style: dotted;
}
.fc-agenda-slots tr.fc-minor th.ui-widget-header {
*border-top-style: solid; /* doesn't work with background in IE6/7 */
}
/* Vertical Events
------------------------------------------------------------------------*/
.fc-event-vert {
border-width: 0 1px;
}
.fc-event-vert.fc-event-start {
border-top-width: 1px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.fc-event-vert.fc-event-end {
border-bottom-width: 1px;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
}
.fc-event-vert .fc-event-time {
white-space: nowrap;
font-size: 10px;
}
.fc-event-vert .fc-event-inner {
position: relative;
z-index: 2;
}
.fc-event-vert .fc-event-bg { /* makes the event lighter w/ a semi-transparent overlay */
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
opacity: .25;
filter: alpha(opacity=25);
}
.fc .ui-draggable-dragging .fc-event-bg, /* TODO: something nicer like .fc-opacity */
.fc-select-helper .fc-event-bg {
display: none\9; /* for IE6/7/8. nested opacity filters while dragging don't work */
}
/* resizable */
.fc-event-vert .ui-resizable-s {
bottom: 0 !important; /* importants override pre jquery ui 1.7 styles */
width: 100% !important;
height: 8px !important;
overflow: hidden !important;
line-height: 8px !important;
font-size: 11px !important;
font-family: monospace;
text-align: center;
cursor: s-resize;
}
.fc-agenda .ui-resizable-resizing { /* TODO: better selector */
_overflow: hidden;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+32
View File
@@ -0,0 +1,32 @@
/*!
* FullCalendar v2.0.2 Print Stylesheet
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2014 Adam Shaw, Sean Kenny
*/
/*
* Include this stylesheet on your page to get a more printer-friendly calendar.
* When including this stylesheet, use the media='print' attribute of the <link> tag.
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
*/
/* Events
-----------------------------------------------------*/
.fc-event {
background: #fff !important;
color: #000 !important;
}
/* for vertical events */
.fc-event-bg {
display: none !important;
}
.fc-event .ui-resizable-handle {
display: none !important;
}
+100
View File
@@ -0,0 +1,100 @@
/*!
* FullCalendar v2.0.2 Google Calendar Plugin
* Docs & License: http://arshaw.com/fullcalendar/
* (c) 2014 Adam Shaw, Sean Kenny
*/
(function(factory) {
if (typeof define === 'function' && define.amd) {
define([ 'jquery' ], factory);
}
else {
factory(jQuery);
}
})(function($) {
var fc = $.fullCalendar;
var applyAll = fc.applyAll;
fc.sourceNormalizers.push(function(sourceOptions) {
if (sourceOptions.dataType == 'gcal' ||
sourceOptions.dataType === undefined &&
(sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) {
sourceOptions.dataType = 'gcal';
if (sourceOptions.editable === undefined) {
sourceOptions.editable = false;
}
}
});
fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) {
if (sourceOptions.dataType == 'gcal') {
return transformOptions(sourceOptions, start, end, timezone);
}
});
function transformOptions(sourceOptions, start, end, timezone) {
var success = sourceOptions.success;
var data = $.extend({}, sourceOptions.data || {}, {
singleevents: true,
'max-results': 9999
});
return $.extend({}, sourceOptions, {
url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?',
dataType: 'jsonp',
data: data,
timezoneParam: 'ctz',
startParam: 'start-min',
endParam: 'start-max',
success: function(data) {
var events = [];
if (data.feed.entry) {
$.each(data.feed.entry, function(i, entry) {
var url;
$.each(entry.link, function(i, link) {
if (link.type == 'text/html') {
url = link.href;
if (timezone && timezone != 'local') {
url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + encodeURIComponent(timezone);
}
}
});
events.push({
id: entry.gCal$uid.value,
title: entry.title.$t,
start: entry.gd$when[0].startTime,
end: entry.gd$when[0].endTime,
url: url,
location: entry.gd$where[0].valueString,
description: entry.content.$t
});
});
}
var args = [events].concat(Array.prototype.slice.call(arguments, 1));
var res = applyAll(success, this, args);
if ($.isArray(res)) {
return res;
}
return events;
}
});
}
// legacy
fc.gcalFeed = function(url, sourceOptions) {
return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
};
});
+1
View File
@@ -0,0 +1 @@
(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){t.lang("en"),e.fullCalendar.lang("en"),e.datepicker&&e.datepicker.setDefaults(e.datepicker.regional[""])});
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+21
View File
@@ -0,0 +1,21 @@
Copyright (c) 2013 Adam Shaw
Copyright (c) 2013 Sean Kenny
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+15
View File
@@ -0,0 +1,15 @@
(function(factory) {
if (typeof define === "function" && define.amd) {
define([ "jquery", "moment" ], factory);
}
else {
factory(jQuery, moment);
}
})(function($, moment) {
moment.lang("en");
$.fullCalendar.lang("en");
if ($.datepicker) $.datepicker.setDefaults($.datepicker.regional[""]);
});
+27
View File
@@ -0,0 +1,27 @@
{
"name": "jasmine-fixture",
"version": "1.2.0",
"main": "dist/jasmine-fixture.js",
"ignore": [
"app",
"config",
"spec",
"tasks",
".travis.yml",
"Gruntfile.js",
"main.js",
"package.json"
],
"dependencies": {},
"devDependencies": {},
"homepage": "https://github.com/searls/jasmine-fixture",
"_release": "1.2.0",
"_resolution": {
"type": "version",
"tag": "1.2.0",
"commit": "4796206e77d230c9802b10ff8d2d1db386b57d16"
},
"_source": "git://github.com/searls/jasmine-fixture.git",
"_target": "~1.2.0",
"_originalSource": "jasmine-fixture"
}
+8
View File
@@ -0,0 +1,8 @@
.DS_Store
#ignore node_modules, as the node project is not "deployed" per se: http://www.mikealrogers.com/posts/nodemodules-in-git.html
/node_modules
/generated
.sass-cache
+9
View File
@@ -0,0 +1,9 @@
.DS_Store
#ignore node_modules, as the node project is not "deployed" per se: http://www.mikealrogers.com/posts/nodemodules-in-git.html
/node_modules
/dist
/generated
.sass-cache
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2013 Test Double, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+111
View File
@@ -0,0 +1,111 @@
# jasmine-fixture
[![Build Status](https://secure.travis-ci.org/searls/jasmine-fixture.png)](http://travis-ci.org/searls/jasmine-fixture)
jasmine-fixture helps you write specs that interact with the DOM by making it easier to inject (and then clean up) HTML fixtures using a syntax that's just like jQuery's selectors. (As a result, it requires jQuery.)
**[Download the latest version here](https://github.com/searls/jasmine-fixture/releases)**.
# The concept
Here's one way to think about it:
In *jQuery*, you give `$()` a CSS selector and it *finds* elements on the DOM.
In *jasmine-fixture*, you give `affix()` a CSS selector and it *adds* those elements to the DOM.
This is very useful for tests, because it means that after setting up the state of the DOM with `affix`, your subject code under test will have the elements it needs to do its work.
Finally, jasmine-fixture will help you avoid test pollution by tidying up and remove everything you affix to the DOM after each spec runs.
## affix() example
Let's say you want to write a Jasmine spec for some code that needs to select elements from the DOM with jQuery:
``` javascript
$('#toddler .hidden.toy input[name="toyName"][value="cuddle bunny"]')
```
In the good ol' days of manually crafting your HTML fixtures, you'd have to append some raw HTML to the DOM like this:
``` javascript
beforeEach(function(){
$('<div id="toddler"><div class="hidden toy"><input name="toyName" value="cuddle bunny"></div></div>').appendTo('body');
});
afterEach(function(){
$('#toddler').remove()
});
```
But jasmine-fixture's `affix` method lets you do this instead:
``` javascript
beforeEach(function(){
affix('#toddler .hidden.toy input[name="toyName"][value="cuddle bunny"]')
});
```
That's right, the spec setup above was able to *re-use the exact same ugly jQuery selector* and everything just worked! That means:
* No external (or worse, shared) fixture files means no risk that they become dumping grounds
* The spec can remain comprehensive (meaning that future readers can understand what the code does without referencing an external file)
* Less temptation to couple your unit test to your server or client-side HTML templates
* More one-line setup, fewer multi-line globs of HTML crowding out the behavior your spec is trying to describe
Bottom line: it's just like jQuery, but in reverse! (And, of course, it'll clean up after itself after your spec runs.)
## affixing the affixed by chaining invocations
`#affix` is both a globally available property on the `window` as well as a jQuery plugin that can be chained with existing jQuery objects (affixing itself beneath their results).
Let's say our subject has a `$container` element that's been added to the DOM like this:
``` javascript
var $container = affix('.container')
```
That means we can add things to the container by chaining a call to `affix`, like any other jQuery plugin:
``` javascript
var $content = $container.affix('#content')
```
Now our container with the class "container" has some content with the ID "content". Huzzah!
Note that for easy assignability, affix will always return the topmost element of the thing that it just appended, which in this example is `<div id="content"></div>` (which runs counter to most jQuery plugins, which return the original jQuery object).
## more examples
I heard you wanted more examples, so I pulled some from `affix`'s [specs](https://github.com/searls/jasmine-fixture/blob/master/spec/affix-spec.coffee).
``` coffeescript
'span' #<span></span>
'.foo' #<div class="foo"></div>
'.foo-hah' #<div class="foo-hah"></div>
'#baz' #<div id="baz"></div>
'h1.foo' #<h1 class="foo"></h1>
'h2#baz' #<h2 id="baz"></h2>
'h3#zing.zoom' #<h3 id="zing" class="zoom"></h3>
'h4.zoom#zing' #<h4 id="zing" class="zoom"></h4>
'div span ul li' #<div><span><ul><li></li></ul></span></div>
'a b c d e f g h i j k l m n o p q r s t u v w x y z' #<a><b><c><d><e><f><g><h><i><j><k><l><m><n><o><p><q><r><s><t><u><v><w><x><y><z></z></y></x></w></v></u></t></s></r></q></p></o></n></m></l></k></j></i></h></g></f></e></d></c></b></a>
'.boom.bang.pow#whoosh' #<div id="whoosh" class="boom bang pow"></div>
'#foo .panda' #<div id="foo"><div class="panda"></div></div>
'input#man .restroom' #<input id="man"></input>
'.pants.zipper' #<div class="pants zipper"></div>
'foo > bar > baz' #<foo><bar><baz></baz></bar></foo>
'input[value="12"]' #<input value="12">
'div[class="class1 class2 class3"] span[div="div1 div2 div3"]' #<div class="class1 class2 class3"><span div="div1 div2 div3"></span></div>
'form fieldset[name=ok] input#foo.sp1.sp1[foo="woo"][value="13"]' #<form><fieldset name="ok"><input foo="woo" value="13" id="foo" class="sp1 sp1"></fieldset></form>
'[name="foo"][bar="baz"]' #<name name="foo" bar="baz"></name>
'div[data-bind="my_item"]' #<div data-bind="my_item"></div>
'.ui-dialog[style="width: 1px; height: 5px"]' #<div style="width: 1px; height: 5px" class="ui-dialog"></div>
'#toddler .hidden.toy input[name="toyName"][value="cuddle bunny"]' #<div id="toddler"><div class="hidden toy"><input name="toyName" value="cuddle bunny"></div></div>
'div h1+h2' #<div><h1></h1><h2></h2></div>
```
# Thanks
I want to offer thanks to Mike Kent, whose [JavaScript port of ZenCoding](https://github.com/zodoz/jquery-ZenCoding) was the basis for parsing most of this (classes, ids, elements, etc.). I also want to thank [Peter Kananen](https://twitter.com/#!/pkananen), for pairing with me on the initial spike of the `affix` method.
+17
View File
@@ -0,0 +1,17 @@
{
"name": "jasmine-fixture",
"version": "1.2.0",
"main": "dist/jasmine-fixture.js",
"ignore": [
"app",
"config",
"spec",
"tasks",
".travis.yml",
"Gruntfile.js",
"main.js",
"package.json"
],
"dependencies": {},
"devDependencies": {}
}
@@ -0,0 +1,14 @@
describe "jasmine-fixture", ->
describe "affixing things", ->
invariants.passingSpec()
describe "a simple div", ->
Given -> createSpec """
describe 'foo', ->
Given -> @foo = 1
When -> @foo++
Then -> @foo == 2
"""
When (done) -> runSpec done, (result) ->
@result = result
Then -> expect(@result.stdout).toMatch(/ok.*- foo then this.foo === 2/)
@@ -0,0 +1,17 @@
root = global
root.invariants =
passingSpec: ->
Invariant -> @result.code == 0
Invariant -> @result.stderr == ""
Invariant -> expect(@result.stdout).toContain """
# fail 0
# ok
"""
failingSpecs: (numberOfFailures = 1) ->
Invariant -> @result.code != 0
Invariant -> expect(@result.stdout).toContain """
# fail #{numberOfFailures}
"""
@@ -0,0 +1,24 @@
root = global
grunt = require('grunt')
specPath = "spec-e2e/tmp/example-spec.coffee"
root.createSpec = (specSource) ->
grunt.file.write(specPath, specSource)
root.readSpec = ->
grunt.file.read(specPath, encoding: "UTF-8")
root.runSpec = (done, callback) ->
grunt.util.spawn
cmd: "node_modules/.bin/testem",
args: ["ci", "-f", "spec-e2e/support/jasmine#{process.env.MAJOR_JASMINE_VERSION || 1}-testem-config.json"]
, (error, result, code) ->
callback.call jasmine.getEnv().currentSpec,
error: error
stdout: result.stdout
stderr: result.stderr
code: code
done?()
@@ -0,0 +1,8 @@
grunt = require('grunt')
_ = grunt.util._
grunt.file.mkdir("spec-e2e/tmp")
afterEach ->
_(grunt.file.expand("spec-e2e/tmp/**/*")).each (f) ->
grunt.file.delete(f)
@@ -0,0 +1,11 @@
{
"framework" : "jasmine",
"launch_in_dev" : ["Chrome"],
"launch_in_ci" : ["PhantomJS"],
"before_tests": "node_modules/.bin/coffee -c spec-e2e/tmp/example-spec.coffee",
"src_files" : [
"generated/js/app.js",
"spec/helpers/*.js",
"spec-e2e/tmp/example-spec.js"
]
}
@@ -0,0 +1,11 @@
{
"framework" : "jasmine2",
"launch_in_dev" : ["Chrome"],
"launch_in_ci" : ["PhantomJS"],
"before_tests": "node_modules/.bin/coffee -c spec-e2e/tmp/example-spec.coffee",
"src_files" : [
"generated/js/app.js",
"spec/helpers/*.js",
"spec-e2e/tmp/example-spec.js"
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+26
View File
@@ -0,0 +1,26 @@
{
"name": "jasmine-jquery",
"version": "2.0.5",
"main": "lib/jasmine-jquery.js",
"ignore": [
"test",
"vendor",
".*",
"HISTORY.md",
"SpecRunner.html"
],
"dependencies": {
"jasmine": ">=2",
"jquery": ">=2"
},
"homepage": "https://github.com/velesin/jasmine-jquery",
"_release": "2.0.5",
"_resolution": {
"type": "version",
"tag": "2.0.5",
"commit": "6abe7e3a329c4332067db9d69b0cca43a605ff46"
},
"_source": "git://github.com/velesin/jasmine-jquery.git",
"_target": "~2.0.3",
"_originalSource": "jasmine-jquery"
}
+28
View File
@@ -0,0 +1,28 @@
# Contributing to Jasmine-jQuery
Looking to contribute something to Jasmine-jQuery? **Here's how you can help.**
## Reporting issues
We only accept issues that are bug reports or feature requests. Bugs must be isolated and reproducible problems that we can fix within the Jasmine-jQuery. Please read the following guidelines before opening any issue.
1. **Search for existing issues.** Somemeone else may have reported the same issue. Moreover, the issue may have already been resolved with a fix available.
2. **Create an isolated and reproducible test case.** Be sure the problem exists in Jasmine-jQuery's code with a [reduced test case](http://css-tricks.com/reduced-test-cases/) that should be included in each bug report.
3. **Include a live example.** Make use of jsFiddle or jsBin to share your isolated test cases.
4. **Share as much information as possible.** Include operating system and version, browser and version, version of Jasmine-jQuery. Also include steps to reproduce the bug.
## Pull requests
- Try not to pollute your pull request with unintended changes--keep them simple and small
- Please squash your commits when appropriate. This simplifies future cherry picks, and also keeps the git log clean.
- Include tests that fail without your code, and pass with it.
- Update the documentation, examples elsewhere, and the guides: whatever is affected by your contribution.
- If you can, have another developer sanity check your change.
## Coding standards
- No semicolons
- Comma first
- 2 spaces (no tabs)
- strict mode
- "Attractive"
+33
View File
@@ -0,0 +1,33 @@
/* jshint node: true */
module.exports = function (grunt) {
"use strict";
grunt.initConfig({
pkg: grunt.file.readJSON('package.json')
, jshint: {
all: [
"Gruntfile.js"
, "lib/**/*.js"
, "spec/**/*.js"
]
, options: {
jshintrc: '.jshintrc'
},
}
, jasmine: {
src: "lib/**/*.js"
, options: {
specs: "spec/**/*.js"
, vendor: "vendor/**/*.js"
, version: '2.0.0'
}
}
})
grunt.loadNpmTasks('grunt-contrib-jshint')
grunt.loadNpmTasks('grunt-contrib-jasmine')
grunt.registerTask('test', ['jshint', 'jasmine'])
grunt.registerTask('default', ['test'])
};
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2010-2014 Wojciech Zawistowski, Travis Jeffery
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+362
View File
@@ -0,0 +1,362 @@
# jasmine-jquery [![Build Status](https://travis-ci.org/velesin/jasmine-jquery.png)](https://travis-ci.org/velesin/jasmine-jquery)
jasmine-jquery provides two extensions for [Jasmine](http://pivotal.github.com/jasmine/) JavaScript Testing Framework:
- a set of custom matchers for jQuery framework
- an API for handling HTML, CSS, and JSON fixtures in your specs
## Installation
Simply download _jasmine-jquery.js_ from [here](https://raw.github.com/velesin/jasmine-jquery/master/lib/jasmine-jquery.js) and include it in your Jasmine's test runner file (or add it to _jasmine.yml_ file if you're using Ruby with [jasmine-gem](http://github.com/pivotal/jasmine-gem)). Remember to include also jQuery library as jasmine-jquery relies on it.
For Ruby on Rails, use this [gem](https://github.com/travisjeffery/jasmine-jquery-rails) or I recommend to comply with the standard RSpec and Jasmine frameworks dir structure and keep your tests in `spec/javascripts/` dir. I put jasmine-jquery (and other libraries like jasmine-ajax) into `spec/javascripts/helpers` dir (so they are automatically loaded) and fixtures into `spec/javascripts/fixtures` dir.
## jQuery matchers
jasmine-jquery provides following custom matchers (in alphabetical order):
- `toBeChecked()`
- only for tags that have checked attribute
- e.g. `expect($('<input type="checkbox" checked="checked"/>')).toBeChecked()`
- `toBeDisabled()`
- e.g. `expect('<input type="submit" disabled="disabled"/>').toBeDisabled()`
- `toBeEmpty()`
- Checks for child DOM elements or text.
- `toBeFocused()`
- e.g. `expect($('<input type="text" />').focus()).toBeFocused()`
- `toBeHidden()`
Elements can be considered hidden for several reasons:
- They have a CSS `display` value of `none`.
- They are form elements with `type` equal to `hidden`.
- Their `width` and `height` are explicitly set to `0`.
- An ancestor element is hidden, so the element is not shown on the page.
- `toBeInDOM()`
- Checksto see if the matched element is attached to the DOM
- e.g. `expect($('#id-name')[0]).toBeInDOM()`
- `toBeMatchedBy(jQuerySelector)`
- Check to see if the set of matched elements matches the given selector
- e.g. `expect($('<span></span>').addClass('js-something')).toBeMatchedBy('.js-something')`
- true if the dom contains the element
- `toBeSelected()`
- only for tags that have selected attribute
- e.g. `expect($('<option selected="selected"></option>')).toBeSelected()`
- `toBeVisible()`
- Elements are considered visible if they consume space in the document. Visible elements have a width or height that is greater than zero.
- `toContainElement(jQuerySelector)`
- e.g. `expect($('<div><span class="some-class"></span></div>')).toContainElement('span.some-class')`
- `toContainHtml(string)`
- e.g. `expect($('<div><ul></ul><h1>header</h1></div>')).toContainHtml('<ul></ul>')`
- `toContainText(string)`
- e.g. `expect($('<div><ul></ul><h1>header</h1></div>')).toContainText('header')`
- `toEqual(jQuerySelector)`
- e.g. `expect($('<div id="some-id"></div>')).toEqual('div')`
- e.g. `expect($('<div id="some-id"></div>')).toEqual('div#some-id')`
- `toExist()`
- true if element exists in or out of the dom
- `toHandle(eventName)`
- e.g. `expect($form).toHandle("submit")`
- `toHandleWith(eventName, eventHandler)`
- e.g. `expect($form).toHandleWith("submit", yourSubmitCallback)`
- `toHaveAttr(attributeName, attributeValue)`
- attribute value is optional, if omitted it will check only if attribute exists
- `toHaveBeenTriggeredOn(selector)`
- if event has been triggered on `selector` (see "Event Spies", below)
- `toHaveBeenTriggered()`
- if event has been triggered on `selector` (see "Event Spies", below)
- `toHaveBeenTriggeredOnAndWith(selector, extraParameters)`
- if event has been triggered on `selector` and with `extraParameters`
- `toHaveBeenPreventedOn(selector)`
- if event has been prevented on `selector` (see "Event Spies", below)
- `toHaveBeenPrevented()`
- if event has been prevented on `selector` (see "Event Spies", below)
- `toHaveClass(className)`
- e.g. `expect($('<div class="some-class"></div>')).toHaveClass("some-class")`
- `toHaveCss(css)`
- e.g. `expect($('<div style="display: none; margin: 10px;"></div>')).toHaveCss({display: "none", margin: "10px"})`
- e.g. `expect($('<div style="display: none; margin: 10px;"></div>')).toHaveCss({margin: "10px"})`
- `toHaveData(key, value)`
- value is optional, if omitted it will check only if an entry for that key exists
- `toHaveHtml(string)`
- e.g. `expect($('<div><span></span></div>')).toHaveHtml('<span></span>')`
- `toHaveId(id)`
- e.g. `expect($('<div id="some-id"></div>')).toHaveId("some-id")`
- `toHaveLength(value)`
- e.g. `expect($('ul > li')).toHaveLength(3)`
- `toHaveProp(propertyName, propertyValue)`
- property value is optional, if omitted it will check only if property exists
- `toHaveText(string)`
- accepts a String or regular expression
- e.g. `expect($('<div>some text</div>')).toHaveText('some text')`
- `toHaveValue(value)`
- only for elements on which `val` can be called (`input`, `textarea`, etc)
- e.g. `expect($('<input type="text" value="some text"/>')).toHaveValue('some text')`
The same as with standard Jasmine matchers, all of above custom matchers may be inverted by using `.not` prefix, e.g.:
expect($('<div>some text</div>')).not.toHaveText(/other/)
## HTML Fixtures
The Fixture module of jasmine-jquery allows you to load HTML content to be used by your tests. The overall workflow is like follows:
In _myfixture.html_ file:
<div id="my-fixture">some complex content here</div>
Inside your test:
loadFixtures('myfixture.html')
$('#my-fixture').myTestedPlugin()
expect($('#my-fixture')).to...
By default, fixtures are loaded from `spec/javascripts/fixtures`. You can configure this path: `jasmine.getFixtures().fixturesPath = 'my/new/path';`.
Your fixture is being loaded into `<div id="jasmine-fixtures"></div>` container that is automatically added to the DOM by the Fixture module (If you _REALLY_ must change id of this container, try: `jasmine.getFixtures().containerId = 'my-new-id';` in your test runner). To make tests fully independent, fixtures container is automatically cleaned-up between tests, so you don't have to worry about left-overs from fixtures loaded in preceeding test. Also, fixtures are internally cached by the Fixture module, so you can load the same fixture file in several tests without penalty to your test suite's speed.
To invoke fixture related methods, obtain Fixtures singleton through a factory and invoke a method on it:
jasmine.getFixtures().load(...)
There are also global short cut functions available for the most used methods, so the above example can be rewritten to just:
loadFixtures(...)
Several methods for loading fixtures are provided:
- `load(fixtureUrl[, fixtureUrl, ...])`
- Loads fixture(s) from one or more files and automatically appends them to the DOM (to the fixtures container).
- `appendLoad(fixtureUrl[, fixtureUrl, ...])`
- Same as load, but adds the fixtures to the pre-existing fixture container.
- `read(fixtureUrl[, fixtureUrl, ...])`
- Loads fixture(s) from one or more files but instead of appending them to the DOM returns them as a string (useful if you want to process fixture's content directly in your test).
- `set(html)`
- Doesn't load fixture from file, but instead gets it directly as a parameter (html parameter may be a string or a jQuery element, so both `set('<div></div>')` and `set($('<div/>'))` will work). Automatically appends fixture to the DOM (to the fixtures container). It is useful if your fixture is too simple to keep it in an external file or is constructed procedurally, but you still want Fixture module to automatically handle DOM insertion and clean-up between tests for you.
- `appendSet(html)`
- Same as set, but adds the fixtures to the pre-existing fixture container.
- `preload(fixtureUrl[, fixtureUrl, ...])`
- Pre-loads fixture(s) from one or more files and stores them into cache, without returning them or appending them to the DOM. All subsequent calls to `load` or `read` methods will then get fixtures content from cache, without making any AJAX calls (unless cache is manually purged by using `clearCache` method). Pre-loading all fixtures before a test suite is run may be useful when working with libraries like jasmine-ajax that block or otherwise modify the inner workings of JS or jQuery AJAX calls.
All of above methods have matching global short cuts:
- `loadFixtures(fixtureUrl[, fixtureUrl, ...])`
- `appendLoadFixtures(fixtureUrl[, fixtureUrl, ...])`
- `readFixtures(fixtureUrl[, fixtureUrl, ...])`
- `setFixtures(html)`
- `appendSetFixtures(html)`
``` javascript
var fixture = setFixtures('<div class="post">foo</div>')
var post = fixture.find('.post')
```
Also, a helper method for creating HTML elements for your tests is provided:
- `sandbox([{attributeName: value[, attributeName: value, ...]}])`
It creates an empty DIV element with a default id="sandbox". If a hash of attributes is provided, they will be set for this DIV tag. If a hash of attributes contains id attribute it will override the default value. Custom attributes can also be set. So e.g.:
sandbox()
Will return:
<div id="sandbox"></div>
And:
sandbox({
id: 'my-id',
class: 'my-class',
myattr: 'my-attr'
})
Will return:
<div id="my-id" class="my-class" myattr="my-attr"></div>
Sandbox method is useful if you want to quickly create simple fixtures in your tests without polluting them with HTML strings:
setFixtures(sandbox({class: 'my-class'}))
$('#sandbox').myTestedClassRemoverPlugin()
expect($('#sandbox')).not.toHaveClass('my-class')
This method also has a global short cut available:
- `sandbox([{attributeName: value[, attributeName: value, ...]}])`
Additionally, two clean up methods are provided:
- `clearCache()`
- purges Fixture module internal cache (you should need it only in very special cases; typically, if you need to use it, it may indicate a smell in your test code)
- `cleanUp()`
- cleans-up fixtures container (this is done automatically between tests by Fixtures module, so there is no need to ever invoke this manually, unless you're testing a really fancy special case and need to clean-up fixtures in the middle of your test)
These two methods do not have global short cut functions.
## Style Fixtures
The StyleFixtures module is pretty much like the Fixtures module, but it allows you to load CSS content on the page while testing. It may be useful if your tests expect that certain css rules are applied to elements that you are testing. The overall workflow is typically the same:
In _mycssfixture.css_ file:
.elem { position: absolute }
Inside your test:
loadStyleFixtures('mycssfixture.css')
$('#my-fixture').myTestedPlugin()
expect($('#my-fixture .elem')).toHaveCss({left: "300px"})
Notice that if you haven't applied the `position: absolute` rule to the `.elem` and try to test its left position in some browsers (e.g. GoogleChrome) you will allways get the value `auto` even if your plugin did everything correct and applied positioning. So that's why you might need to load style fixtures. In Firefox though you will get the correct value even without the `position: absolute`.
By default, style fixtures are loaded from `spec/javascripts/fixtures`. You can configure this path: `jasmine.getStyleFixtures().fixturesPath = 'my/new/path';`.
Like in Fixtures module, StyleFixtures are also automatically cleaned-up between tests and are internally cached, so you can load the same fixture file in several tests without penalty to your test suite's speed.
To invoke fixture related methods, obtain StyleFixtures singleton through a factory and invoke a method on it:
jasmine.getStyleFixtures().load(...)
There are also global short cut functions available for the most used methods, so the above example can be rewritten to just:
loadStyleFixtures(...)
Several methods for loading fixtures are provided:
- `load(fixtureUrl[, fixtureUrl, ...])`
- Loads fixture(s) from one or more files and automatically appends them to the DOM into the HEAD element. This method will remove all existing fixtures loaded previously, if any.
- `appendLoad(fixtureUrl[, fixtureUrl, ...])`
- Same as load, but it won't remove fixtures you added earlier.
- `set(css)`
- Doesn't load fixture from file, but instead gets it directly as a parameter (e.g. `set('body {background: red}')`). Automatically appends style to the DOM. It is useful if your css fixture is too simple to keep it in an external file. This method will remove all existing fixtures loaded previously, if any.
- `appendSet(css)`
- Same as set, but it won't remove fixtures you added earlier.
- `preload(fixtureUrl[, fixtureUrl, ...])`
- Pre-loads fixture(s) from one or more files and stores them into cache, without returning them or appending them to the DOM. All subsequent calls to `load` methods will then get fixtures content from cache, without making any AJAX calls (unless cache is manually purged by using `clearCache` method).
All of above methods have matching global short cuts:
- `loadStyleFixtures(fixtureUrl[, fixtureUrl, ...])`
- `appendLoadStyleFixtures(fixtureUrl[, fixtureUrl, ...])`
- `setStyleFixtures(css)`
- `appendSetStyleFixtures(css)`
Additionally, two clean up methods are provided:
- `clearCache()`
- purges StyleFixture module internal cache (you should need it only in very special cases; typically, if you need to use it, it may indicate a smell in your test code)
- `cleanUp()`
- cleans-up all existing style fixtures (this is done automatically between tests, so there is no need to ever invoke this manually, unless you're testing a really fancy special case and need to clean-up fixtures in the middle of your test)
These two methods do not have global short cut functions.
## JSON Fixtures
The JSONFixtures modules allows you to load JSON data from file (instead of putting huge blocks of data in the spec files).
In _myjsonfixture.json_ file:
{"property1":"value1", "array1":[1,2,3]}
Inside your test:
var data = getJSONFixture('myjsonfixture.json')
// or load and get the JSON two-step
var fixtures = loadJSONFixtures('myjsonfixture.json')
var data = fixtures['myjsonfixture.json']
expect(myDataManipulator.processData(test_data)).to...)
By default, fixtures are loaded from `spec/javascripts/fixtures/json`. You can configure this path: `jasmine.getJSONFixtures().fixturesPath = 'my/new/path';`.
Your fixture data is loaded into an object stashed by the JSONFixtures structure. You fetch the data using the filename as the key. This allows you to load multiple chunks of test data in a spec.
Because a deep copy of Javascript objects can be a little tricky, this module will refetch data each time you call `load`. If you modify the data within a spec, you must call `load` or `loadJSONFixtures` again to repopulate the data.
To invoke fixture related methods, obtain Fixtures singleton through a factory and invoke a method on it:
jasmine.getJSONFixtures().load(...)
There are also global short cut functions available for the most used methods, so the above example can be rewritten to just:
loadJSONFixtures(...)
Several methods for loading fixtures are provided:
- `load(fixtureUrl[, fixtureUrl, ...])`
- Loads fixture(s) from one or more files and automatically adds them to the fixture list. This method returns the entire set of fixtures keyed by their filename.
All of above methods have matching global short cuts:
- `loadJSONFixtures(fixtureUrl[, fixtureUrl, ...])`
- `getJSONFixture(fixtureUrl)`
- After you've loaded fixture files, this global helper will retrieve the fixture data given the fixtureUrl
## Event Spies
Spying on jQuery events can be done with `spyOnEvent` and
`expect(eventName).toHaveBeenTriggeredOn(selector)` or
`expect(spyEvent).toHaveBeenTriggered()` . First, spy on the event:
var spyEvent = spyOnEvent('#some_element', 'click')
$('#some_element').click()
expect('click').toHaveBeenTriggeredOn('#some_element')
expect(spyEvent).toHaveBeenTriggered()
You can reset spy events
var spyEvent = spyOnEvent('#some_element', 'click')
$('#some_element').click()
expect('click').toHaveBeenTriggeredOn('#some_element')
expect(spyEvent).toHaveBeenTriggered()
// reset spy events
spyEvent.reset()
expect('click').not.toHaveBeenTriggeredOn('#some_element')
expect(spyEvent).not.toHaveBeenTriggered()
You can similarly check if triggered event was prevented:
var spyEvent = spyOnEvent('#some_element', 'click')
$('#some_element').click(function (event){event.preventDefault();})
$('#some_element').click()
expect('click').toHaveBeenPreventedOn('#some_element')
expect(spyEvent).toHaveBeenPrevented()
You can also check if the triggered event was stopped:
var spyEvent = spyOnEvent('#some_element', 'click')
$('#some_element').click(function (event){event.stopPropagation();})
$('#some_element').click()
expect('click').toHaveBeenStoppedOn('#some_element')
expect(spyEvent).toHaveBeenStopped()
Much thanks to Luiz Fernando Ribeiro for his
[article on Jasmine event spies](http://luizfar.wordpress.com/2011/01/10/testing-events-on-jquery-objects-with-jasmine/).
## Dependencies
jasmine-jquery v2.0.0+ is to be used with jasmine v2.0.0+. jasmine-jquery v1.7.0 is to be used with jasmine < v2.0.0.
jasmine-jquery is tested with jQuery 2.0 on IE, FF, Chrome, and Safari. There is a high chance it will work with older versions and other browsers as well, but I don't typically run test suite against them when adding new features.
## Cross domain policy problems under Chrome
Newer versions of Chrome don't allow file:// URIs read other file:// URIs. In effect, jasmine-jquery cannot properly load fixtures under some versions of Chrome. An override for this is to run Chrome with a switch `--allow-file-access-from-files`. (https://github.com/velesin/jasmine-jquery/issues/4). Quit open Chromes before running Chrome with that switch. (https://github.com/velesin/jasmine-jquery/issues/179).
Under Windows 7, you have to launch `C:\Users\[UserName]\AppData\Local\Google\Chrome[ SxS]\Application\chrome.exe --allow-file-access-from-files`
## Mocking with jasmine-ajax
[jasmine-ajax](https://github.com/pivotal/jasmine-ajax) library doesn't let user to manually start / stop XMLHttpRequest mocking, but instead it overrides XMLHttpRequest automatically when loaded. This breaks jasmine-jquery fixtures as fixture loading mechanism uses jQuery.ajax, that stops to function the very moment jasmine-ajax is loaded. A workaround for this may be to invoke jasmine-jquery `preloadFixtures` function (specifying all required fixtures) before jasmine-ajax is loaded. This way subsequent calls to `loadFixtures` or `readFixtures` methods will get fixtures content from cache, without need to use jQuery.ajax and thus will work correctly even after jasmine-ajax is loaded.
## Testing with Javascript Test Driver
When using [jstd](http://code.google.com/p/js-test-driver/) and the jasmine adapter you will need to include jasmine-jquery.js after your jasmine-jstd-adapter files, otherwise jasmine-jquery matchers will not be available when tests are executed. Check out [this issue](https://github.com/velesin/jasmine-jquery/issues/95#issuecomment-9293180) for a thorough configuration example too.
## Maintainer
[Travis Jeffery](http://travisjeffery.com/): [Twitter](http://twitter.com/travisjeffery), [GitHub](http://github.com/travisjeffery).
## [Contributing](./CONTRIBUTING.md)
+16
View File
@@ -0,0 +1,16 @@
{
"name": "jasmine-jquery",
"version": "2.0.5",
"main": "lib/jasmine-jquery.js",
"ignore": [
"test",
"vendor",
".*",
"HISTORY.md",
"SpecRunner.html"
],
"dependencies": {
"jasmine": ">=2",
"jquery": ">=2"
}
}
+813
View File
@@ -0,0 +1,813 @@
/*!
Jasmine-jQuery: a set of jQuery helpers for Jasmine tests.
Version 2.0.5
https://github.com/velesin/jasmine-jquery
Copyright (c) 2010-2014 Wojciech Zawistowski, Travis Jeffery
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
+function (window, jasmine, $) { "use strict";
jasmine.spiedEventsKey = function (selector, eventName) {
return [$(selector).selector, eventName].toString()
}
jasmine.getFixtures = function () {
return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures()
}
jasmine.getStyleFixtures = function () {
return jasmine.currentStyleFixtures_ = jasmine.currentStyleFixtures_ || new jasmine.StyleFixtures()
}
jasmine.Fixtures = function () {
this.containerId = 'jasmine-fixtures'
this.fixturesCache_ = {}
this.fixturesPath = 'spec/javascripts/fixtures'
}
jasmine.Fixtures.prototype.set = function (html) {
this.cleanUp()
return this.createContainer_(html)
}
jasmine.Fixtures.prototype.appendSet= function (html) {
this.addToContainer_(html)
}
jasmine.Fixtures.prototype.preload = function () {
this.read.apply(this, arguments)
}
jasmine.Fixtures.prototype.load = function () {
this.cleanUp()
this.createContainer_(this.read.apply(this, arguments))
}
jasmine.Fixtures.prototype.appendLoad = function () {
this.addToContainer_(this.read.apply(this, arguments))
}
jasmine.Fixtures.prototype.read = function () {
var htmlChunks = []
, fixtureUrls = arguments
for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]))
}
return htmlChunks.join('')
}
jasmine.Fixtures.prototype.clearCache = function () {
this.fixturesCache_ = {}
}
jasmine.Fixtures.prototype.cleanUp = function () {
$('#' + this.containerId).remove()
}
jasmine.Fixtures.prototype.sandbox = function (attributes) {
var attributesToSet = attributes || {}
return $('<div id="sandbox" />').attr(attributesToSet)
}
jasmine.Fixtures.prototype.createContainer_ = function (html) {
var container = $('<div>')
.attr('id', this.containerId)
.html(html)
$(document.body).append(container)
return container
}
jasmine.Fixtures.prototype.addToContainer_ = function (html){
var container = $(document.body).find('#'+this.containerId).append(html)
if (!container.length) {
this.createContainer_(html)
}
}
jasmine.Fixtures.prototype.getFixtureHtml_ = function (url) {
if (typeof this.fixturesCache_[url] === 'undefined') {
this.loadFixtureIntoCache_(url)
}
return this.fixturesCache_[url]
}
jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) {
var self = this
, url = this.makeFixtureUrl_(relativeUrl)
, htmlText = ''
, request = $.ajax({
async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
cache: false,
url: url,
success: function (data, status, $xhr) {
htmlText = $xhr.responseText
}
}).fail(function ($xhr, status, err) {
throw new Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')')
})
var scripts = $($.parseHTML(htmlText, true)).find('script[src]') || [];
scripts.each(function(){
$.ajax({
async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
cache: false,
dataType: 'script',
url: $(this).attr('src'),
success: function (data, status, $xhr) {
htmlText += '<script>' + $xhr.responseText + '</script>'
},
error: function ($xhr, status, err) {
throw new Error('Script could not be loaded: ' + scriptSrc + ' (status: ' + status + ', message: ' + err.message + ')')
}
});
})
self.fixturesCache_[relativeUrl] = htmlText;
}
jasmine.Fixtures.prototype.makeFixtureUrl_ = function (relativeUrl){
return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
}
jasmine.Fixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) {
return this[methodName].apply(this, passedArguments)
}
jasmine.StyleFixtures = function () {
this.fixturesCache_ = {}
this.fixturesNodes_ = []
this.fixturesPath = 'spec/javascripts/fixtures'
}
jasmine.StyleFixtures.prototype.set = function (css) {
this.cleanUp()
this.createStyle_(css)
}
jasmine.StyleFixtures.prototype.appendSet = function (css) {
this.createStyle_(css)
}
jasmine.StyleFixtures.prototype.preload = function () {
this.read_.apply(this, arguments)
}
jasmine.StyleFixtures.prototype.load = function () {
this.cleanUp()
this.createStyle_(this.read_.apply(this, arguments))
}
jasmine.StyleFixtures.prototype.appendLoad = function () {
this.createStyle_(this.read_.apply(this, arguments))
}
jasmine.StyleFixtures.prototype.cleanUp = function () {
while(this.fixturesNodes_.length) {
this.fixturesNodes_.pop().remove()
}
}
jasmine.StyleFixtures.prototype.createStyle_ = function (html) {
var styleText = $('<div></div>').html(html).text()
, style = $('<style>' + styleText + '</style>')
this.fixturesNodes_.push(style)
$('head').append(style)
}
jasmine.StyleFixtures.prototype.clearCache = jasmine.Fixtures.prototype.clearCache
jasmine.StyleFixtures.prototype.read_ = jasmine.Fixtures.prototype.read
jasmine.StyleFixtures.prototype.getFixtureHtml_ = jasmine.Fixtures.prototype.getFixtureHtml_
jasmine.StyleFixtures.prototype.loadFixtureIntoCache_ = jasmine.Fixtures.prototype.loadFixtureIntoCache_
jasmine.StyleFixtures.prototype.makeFixtureUrl_ = jasmine.Fixtures.prototype.makeFixtureUrl_
jasmine.StyleFixtures.prototype.proxyCallTo_ = jasmine.Fixtures.prototype.proxyCallTo_
jasmine.getJSONFixtures = function () {
return jasmine.currentJSONFixtures_ = jasmine.currentJSONFixtures_ || new jasmine.JSONFixtures()
}
jasmine.JSONFixtures = function () {
this.fixturesCache_ = {}
this.fixturesPath = 'spec/javascripts/fixtures/json'
}
jasmine.JSONFixtures.prototype.load = function () {
this.read.apply(this, arguments)
return this.fixturesCache_
}
jasmine.JSONFixtures.prototype.read = function () {
var fixtureUrls = arguments
for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
this.getFixtureData_(fixtureUrls[urlIndex])
}
return this.fixturesCache_
}
jasmine.JSONFixtures.prototype.clearCache = function () {
this.fixturesCache_ = {}
}
jasmine.JSONFixtures.prototype.getFixtureData_ = function (url) {
if (!this.fixturesCache_[url]) this.loadFixtureIntoCache_(url)
return this.fixturesCache_[url]
}
jasmine.JSONFixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) {
var self = this
, url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
$.ajax({
async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
cache: false,
dataType: 'json',
url: url,
success: function (data) {
self.fixturesCache_[relativeUrl] = data
},
error: function ($xhr, status, err) {
throw new Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')')
}
})
}
jasmine.JSONFixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) {
return this[methodName].apply(this, passedArguments)
}
jasmine.jQuery = function () {}
jasmine.jQuery.browserTagCaseIndependentHtml = function (html) {
return $('<div/>').append(html).html()
}
jasmine.jQuery.elementToString = function (element) {
return $(element).map(function () { return this.outerHTML; }).toArray().join(', ')
}
var data = {
spiedEvents: {}
, handlers: []
}
jasmine.jQuery.events = {
spyOn: function (selector, eventName) {
var handler = function (e) {
data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = jasmine.util.argsToArray(arguments)
}
$(selector).on(eventName, handler)
data.handlers.push(handler)
return {
selector: selector,
eventName: eventName,
handler: handler,
reset: function (){
delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
}
}
},
args: function (selector, eventName) {
var actualArgs = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
if (!actualArgs) {
throw "There is no spy for " + eventName + " on " + selector.toString() + ". Make sure to create a spy using spyOnEvent."
}
return actualArgs
},
wasTriggered: function (selector, eventName) {
return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)])
},
wasTriggeredWith: function (selector, eventName, expectedArgs, util, customEqualityTesters) {
var actualArgs = jasmine.jQuery.events.args(selector, eventName).slice(1)
if (Object.prototype.toString.call(expectedArgs) !== '[object Array]')
actualArgs = actualArgs[0]
return util.equals(expectedArgs, actualArgs, customEqualityTesters)
},
wasPrevented: function (selector, eventName) {
var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
, e = args ? args[0] : undefined
return e && e.isDefaultPrevented()
},
wasStopped: function (selector, eventName) {
var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
, e = args ? args[0] : undefined
return e && e.isPropagationStopped()
},
cleanUp: function () {
data.spiedEvents = {}
data.handlers = []
}
}
var hasProperty = function (actualValue, expectedValue) {
if (expectedValue === undefined)
return actualValue !== undefined
return actualValue === expectedValue
}
beforeEach(function () {
jasmine.addMatchers({
toHaveClass: function () {
return {
compare: function (actual, className) {
return { pass: $(actual).hasClass(className) }
}
}
},
toHaveCss: function () {
return {
compare: function (actual, css) {
for (var prop in css){
var value = css[prop]
// see issue #147 on gh
;if (value === 'auto' && $(actual).get(0).style[prop] === 'auto') continue
if ($(actual).css(prop) !== value) return { pass: false }
}
return { pass: true }
}
}
},
toBeVisible: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':visible') }
}
}
},
toBeHidden: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':hidden') }
}
}
},
toBeSelected: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':selected') }
}
}
},
toBeChecked: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':checked') }
}
}
},
toBeEmpty: function () {
return {
compare: function (actual) {
return { pass: $(actual).is(':empty') }
}
}
},
toBeInDOM: function () {
return {
compare: function (actual) {
return { pass: $.contains(document.documentElement, $(actual)[0]) }
}
}
},
toExist: function () {
return {
compare: function (actual) {
return { pass: $(actual).length }
}
}
},
toHaveLength: function () {
return {
compare: function (actual, length) {
return { pass: $(actual).length === length }
}
}
},
toHaveAttr: function () {
return {
compare: function (actual, attributeName, expectedAttributeValue) {
return { pass: hasProperty($(actual).attr(attributeName), expectedAttributeValue) }
}
}
},
toHaveProp: function () {
return {
compare: function (actual, propertyName, expectedPropertyValue) {
return { pass: hasProperty($(actual).prop(propertyName), expectedPropertyValue) }
}
}
},
toHaveId: function () {
return {
compare: function (actual, id) {
return { pass: $(actual).attr('id') == id }
}
}
},
toHaveHtml: function () {
return {
compare: function (actual, html) {
return { pass: $(actual).html() == jasmine.jQuery.browserTagCaseIndependentHtml(html) }
}
}
},
toContainHtml: function () {
return {
compare: function (actual, html) {
var actualHtml = $(actual).html()
, expectedHtml = jasmine.jQuery.browserTagCaseIndependentHtml(html)
return { pass: (actualHtml.indexOf(expectedHtml) >= 0) }
}
}
},
toHaveText: function () {
return {
compare: function (actual, text) {
var actualText = $(actual).text()
var trimmedText = $.trim(actualText)
if (text && $.isFunction(text.test)) {
return { pass: text.test(actualText) || text.test(trimmedText) }
} else {
return { pass: (actualText == text || trimmedText == text) }
}
}
}
},
toContainText: function () {
return {
compare: function (actual, text) {
var trimmedText = $.trim($(actual).text())
if (text && $.isFunction(text.test)) {
return { pass: text.test(trimmedText) }
} else {
return { pass: trimmedText.indexOf(text) != -1 }
}
}
}
},
toHaveValue: function () {
return {
compare: function (actual, value) {
return { pass: $(actual).val() === value }
}
}
},
toHaveData: function () {
return {
compare: function (actual, key, expectedValue) {
return { pass: hasProperty($(actual).data(key), expectedValue) }
}
}
},
toContainElement: function () {
return {
compare: function (actual, selector) {
if (window.debug) debugger
return { pass: $(actual).find(selector).length }
}
}
},
toBeMatchedBy: function () {
return {
compare: function (actual, selector) {
return { pass: $(actual).filter(selector).length }
}
}
},
toBeDisabled: function () {
return {
compare: function (actual, selector) {
return { pass: $(actual).is(':disabled') }
}
}
},
toBeFocused: function (selector) {
return {
compare: function (actual, selector) {
return { pass: $(actual)[0] === $(actual)[0].ownerDocument.activeElement }
}
}
},
toHandle: function () {
return {
compare: function (actual, event) {
var events = $._data($(actual).get(0), "events")
if (!events || !event || typeof event !== "string") {
return { pass: false }
}
var namespaces = event.split(".")
, eventType = namespaces.shift()
, sortedNamespaces = namespaces.slice(0).sort()
, namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)")
if (events[eventType] && namespaces.length) {
for (var i = 0; i < events[eventType].length; i++) {
var namespace = events[eventType][i].namespace
if (namespaceRegExp.test(namespace))
return { pass: true }
}
} else {
return { pass: (events[eventType] && events[eventType].length > 0) }
}
return { pass: false }
}
}
},
toHandleWith: function () {
return {
compare: function (actual, eventName, eventHandler) {
var normalizedEventName = eventName.split('.')[0]
, stack = $._data($(actual).get(0), "events")[normalizedEventName]
for (var i = 0; i < stack.length; i++) {
if (stack[i].handler == eventHandler) return { pass: true }
}
return { pass: false }
}
}
},
toHaveBeenTriggeredOn: function () {
return {
compare: function (actual, selector) {
var result = { pass: jasmine.jQuery.events.wasTriggered(selector, actual) }
result.message = result.pass ?
"Expected event " + $(actual) + " not to have been triggered on " + selector :
"Expected event " + $(actual) + " to have been triggered on " + selector
return result;
}
}
},
toHaveBeenTriggered: function (){
return {
compare: function (actual) {
var eventName = actual.eventName
, selector = actual.selector
, result = { pass: jasmine.jQuery.events.wasTriggered(selector, eventName) }
result.message = result.pass ?
"Expected event " + eventName + " not to have been triggered on " + selector :
"Expected event " + eventName + " to have been triggered on " + selector
return result
}
}
},
toHaveBeenTriggeredOnAndWith: function (j$, customEqualityTesters) {
return {
compare: function (actual, selector, expectedArgs) {
var wasTriggered = jasmine.jQuery.events.wasTriggered(selector, actual)
, result = { pass: wasTriggered && jasmine.jQuery.events.wasTriggeredWith(selector, actual, expectedArgs, j$, customEqualityTesters) }
if (wasTriggered) {
var actualArgs = jasmine.jQuery.events.args(selector, actual, expectedArgs)[1]
result.message = result.pass ?
"Expected event " + actual + " not to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) :
"Expected event " + actual + " to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs)
} else {
// todo check on this
result.message = result.pass ?
"Expected event " + actual + " not to have been triggered on " + selector :
"Expected event " + actual + " to have been triggered on " + selector
}
return result
}
}
},
toHaveBeenPreventedOn: function () {
return {
compare: function (actual, selector) {
var result = { pass: jasmine.jQuery.events.wasPrevented(selector, actual) }
result.message = result.pass ?
"Expected event " + actual + " not to have been prevented on " + selector :
"Expected event " + actual + " to have been prevented on " + selector
return result
}
}
},
toHaveBeenPrevented: function () {
return {
compare: function (actual) {
var eventName = actual.eventName
, selector = actual.selector
, result = { pass: jasmine.jQuery.events.wasPrevented(selector, eventName) }
result.message = result.pass ?
"Expected event " + eventName + " not to have been prevented on " + selector :
"Expected event " + eventName + " to have been prevented on " + selector
return result
}
}
},
toHaveBeenStoppedOn: function () {
return {
compare: function (actual, selector) {
var result = { pass: jasmine.jQuery.events.wasStopped(selector, actual) }
result.message = result.pass ?
"Expected event " + actual + " not to have been stopped on " + selector :
"Expected event " + actual + " to have been stopped on " + selector
return result;
}
}
},
toHaveBeenStopped: function () {
return {
compare: function (actual) {
var eventName = actual.eventName
, selector = actual.selector
, result = { pass: jasmine.jQuery.events.wasStopped(selector, eventName) }
result.message = result.pass ?
"Expected event " + eventName + " not to have been stopped on " + selector :
"Expected event " + eventName + " to have been stopped on " + selector
return result
}
}
}
})
jasmine.getEnv().addCustomEqualityTester(function(a, b) {
if (a && b) {
if (a instanceof $ || jasmine.isDomNode(a)) {
var $a = $(a)
if (b instanceof $)
return $a.length == b.length && a.is(b)
return $a.is(b);
}
if (b instanceof $ || jasmine.isDomNode(b)) {
var $b = $(b)
if (a instanceof $)
return a.length == $b.length && $b.is(a)
return $(b).is(a);
}
}
})
jasmine.getEnv().addCustomEqualityTester(function (a, b) {
if (a instanceof $ && b instanceof $ && a.size() == b.size())
return a.is(b)
})
})
afterEach(function () {
jasmine.getFixtures().cleanUp()
jasmine.getStyleFixtures().cleanUp()
jasmine.jQuery.events.cleanUp()
})
window.readFixtures = function () {
return jasmine.getFixtures().proxyCallTo_('read', arguments)
}
window.preloadFixtures = function () {
jasmine.getFixtures().proxyCallTo_('preload', arguments)
}
window.loadFixtures = function () {
jasmine.getFixtures().proxyCallTo_('load', arguments)
}
window.appendLoadFixtures = function () {
jasmine.getFixtures().proxyCallTo_('appendLoad', arguments)
}
window.setFixtures = function (html) {
return jasmine.getFixtures().proxyCallTo_('set', arguments)
}
window.appendSetFixtures = function () {
jasmine.getFixtures().proxyCallTo_('appendSet', arguments)
}
window.sandbox = function (attributes) {
return jasmine.getFixtures().sandbox(attributes)
}
window.spyOnEvent = function (selector, eventName) {
return jasmine.jQuery.events.spyOn(selector, eventName)
}
window.preloadStyleFixtures = function () {
jasmine.getStyleFixtures().proxyCallTo_('preload', arguments)
}
window.loadStyleFixtures = function () {
jasmine.getStyleFixtures().proxyCallTo_('load', arguments)
}
window.appendLoadStyleFixtures = function () {
jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments)
}
window.setStyleFixtures = function (html) {
jasmine.getStyleFixtures().proxyCallTo_('set', arguments)
}
window.appendSetStyleFixtures = function (html) {
jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments)
}
window.loadJSONFixtures = function () {
return jasmine.getJSONFixtures().proxyCallTo_('load', arguments)
}
window.getJSONFixture = function (url) {
return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url]
}
}(window, window.jasmine, window.jQuery);
+26
View File
@@ -0,0 +1,26 @@
{
"name": "jasmine-jquery"
, "description": "jQuery matchers and fixture loader for Jasmine framework"
, "version": "2.0.5"
, "keywords": ["jasmine", "jquery"]
, "homepage": "http://github.com/velesin/jasmine-jquery"
, "author": {
"name": "Travis Jeffery"
, "email": "tj@travisjeffery.com"
, "url": "travisjeffery.com"
}
, "scripts": { "test": "grunt test" }
, "repository": {
"type": "git"
, "url": "https://github.com/velesin/jasmine-jquery.git"
}
, "bugs": {
"url": "http://github.com/velesin/jasmine-jquery/issues"
}
, "license": "MIT"
, "devDependencies": {
"grunt": "~0.4.1"
, "grunt-contrib-jshint": "~0.6.0"
, "grunt-contrib-jasmine": "git://github.com/travisjeffery/grunt-contrib-jasmine#9d5874dd592cf41e8268918fe9baeb261afd75b2"
}
}
@@ -0,0 +1 @@
<div id="anchor_01"><script src="spec/fixtures/javascripts/jasmine_javascript_click.js"></script><script src="spec/fixtures/javascripts/jasmine_javascript_hover.js"></script></div>
@@ -0,0 +1 @@
<div id="anchor_01"><script type="text/template">{{name}</script></div>
@@ -0,0 +1 @@
$(function (){ $('#anchor_01').click(function(){ $(this).addClass('foo'); }) });
@@ -0,0 +1 @@
$(function (){ $('#anchor_01').on('hover', function(){ $(this).addClass('bar'); }) });
@@ -0,0 +1 @@
[1,2,3]
@@ -0,0 +1 @@
<div id="real_non_mocked_fixture"></div>
@@ -0,0 +1 @@
body { background: red; }
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
{
"name": "jasmine",
"homepage": "https://github.com/pivotal/jasmine",
"version": "2.0.1",
"_release": "2.0.1",
"_resolution": {
"type": "version",
"tag": "v2.0.1",
"commit": "77c84a860c90d88bd13540a6b8794ee34bde44e5"
},
"_source": "git://github.com/pivotal/jasmine.git",
"_target": ">=2",
"_originalSource": "jasmine"
}
+24
View File
@@ -0,0 +1,24 @@
.idea/
.svn/
.DS_Store
site/
.bundle/
.pairs
.rvmrc
.ruby-gemset
.ruby-version
*.gem
.bundle
tags
Gemfile.lock
pkg/*
.sass-cache/*
src/html/.sass-cache/*
node_modules/
*.pyc
sauce_connect.log
*.swp
build/
*.egg-info/
dist/*.tar.gz
nbproject/
+3
View File
@@ -0,0 +1,3 @@
[submodule "pages"]
path = pages
url = https://github.com/pivotal/jasmine.git
+9
View File
@@ -0,0 +1,9 @@
{
"bitwise": true,
"curly": true,
"immed": true,
"newcap": true,
"trailing": true,
"loopfunc": true,
"quotmark": "single"
}
+23
View File
@@ -0,0 +1,23 @@
dist/
grunt/
images/
node_modules
release_notes/
spec/
src/
Gemfile
Gemfile.lock
Rakefile
jasmine-core.gemspec
.rspec
.travis.yml
.jshintrc
.gitignore
*.sh
Gruntfile.js
lib/jasmine-core/boot/
lib/jasmine-core/boot.js
lib/jasmine-core/spec
lib/jasmine-core/*.css
lib/jasmine-core/jasmine-html.js
lib/jasmine-core/version.rb
+1
View File
@@ -0,0 +1 @@
--color
+51
View File
@@ -0,0 +1,51 @@
---
script: $TEST_COMMAND
language: ruby
cache: bundler
rvm: 1.9.3
env:
global:
- USE_SAUCE=true
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
- TEST_COMMAND="bash travis-core-script.sh"
- JASMINE_BROWSER="firefox"
- SAUCE_OS="Linux"
- SAUCE_BROWSER_VERSION=''
- secure: WSPWhlnC4mWSnSPquX+m1/BCu5ch5NygkaHuM2Nea7lD8oS3XLX8QncZZAsQ4lnNfqoDDuBOizG0AESiqNvE4y6x5qvLLTS6q+ce255ZEMZ71TBdZgDEEvGMEjOPPsVXiXyTQOP1lwOPlrbZvaPgWV7e11KIBab6DfFcQpnvDgo=
- secure: SW7CJhZnwaNT749Gdnhvqb5rbXlAOsygUAzh9qhtyvbqXKkmJdBIEsO01YF6pbju1X2twE9JvWCOxeZju43NgQChJlPsGbjY2j3k/TdQeTAJesQe2K7ytwghunI30gjEovtRH0T3w1EmcKPH8yj5eBIcB2OYoJHx8KEC7e68q1g=
matrix:
include:
- env:
- USE_SAUCE=false
- TEST_COMMAND="bash travis-node-script.sh"
- env:
- JASMINE_BROWSER="safari"
- SAUCE_OS="OS X 10.8"
- SAUCE_BROWSER_VERSION=6
- env:
- JASMINE_BROWSER="safari"
- SAUCE_OS="OS X 10.6"
- SAUCE_BROWSER_VERSION=5
- env:
- JASMINE_BROWSER="internet explorer"
- SAUCE_OS="Windows 8"
- SAUCE_BROWSER_VERSION=10
- env:
- JASMINE_BROWSER="internet explorer"
- SAUCE_OS="Windows 7"
- SAUCE_BROWSER_VERSION=9
- env:
- JASMINE_BROWSER="internet explorer"
- SAUCE_OS="Windows 7"
- SAUCE_BROWSER_VERSION=8
- env:
- JASMINE_BROWSER="chrome"
- SAUCE_OS="Linux"
- SAUCE_BROWSER_VERSION=''
- env:
- JASMINE_BROWSER="phantomjs"
- USE_SAUCE=false
- env:
- USE_SAUCE=false
- JASMINE_BROWSER="phantomjs"
- TEST_COMMAND="bash travis-docs-script.sh"
+121
View File
@@ -0,0 +1,121 @@
# Developing for Jasmine Core
We welcome your contributions - Thanks for helping make Jasmine a better project for everyone. Please review the backlog and discussion lists (the main group - [http://groups.google.com/group/jasmine-js](http://groups.google.com/group/jasmine-js) and the developer's list - [http://groups.google.com/group/jasmine-js-dev](http://groups.google.com/group/jasmine-js-dev)) before starting work - what you're looking for may already have been done. If it hasn't, the community can help make your contribution better.
## General Workflow
Please submit pull requests via feature branches using the semi-standard workflow of:
1. Fork it
1. Clone your fork: (`git clone git@github.com:yourUserName/jasmine.git`)
1. Change directory: (`cd jasmine`)
1. Asign original repository to a remote named 'upstream': (`git remote add
upstream https://github.com/pivotal/jasmine.git`)
1. Pull in changes not present in your local repository: (`git fetch upstream`)
1. Create your feature branch (`git checkout -b my-new-feature`)
1. Commit your changes (`git commit -am 'Add some feature'`)
1. Push to the branch (`git push origin my-new-feature`)
1. Create new Pull Request
We favor pull requests with very small, single commits with a single purpose.
## Background
### Directory Structure
* `/src` contains all of the source files
* `/src/console` - Node.js-specific files
* `/src/core` - generic source files
* `/src/html` - browser-specific files
* `/spec` contains all of the tests
* mirrors the source directory
* there are some additional files
* `/dist` contains the standalone distributions as zip files
* `/lib` contains the generated files for distribution as the Jasmine Rubygem and the Python package
### Self-testing
Note that Jasmine tests itself. The files in `lib` are loaded first, defining the reference `jasmine`. Then the files in `src` are loaded, defining the reference `j$`. So there are two copies of the code loaded under test.
The tests should always use `j$` to refer to the objects and functions that are being tested. But the tests can use functions on `jasmine` as needed. _Be careful how you structure any new test code_. Copy the patterns you see in the existing code - this ensures that the code you're testing is not leaking into the `jasmine` reference and vice-versa.
### `boot.js`
__This is new for Jasmine 2.0.__
This file does all of the setup necessary for Jasmine to work. It loads all of the code, creates an `Env`, attaches the global functions, and builds the reporter. It also sets up the execution of the `Env` - for browsers this is in `window.onload`. While the default in `lib` is appropriate for browsers, projects may wish to customize this file.
For example, for Jasmine development there is a different `dev_boot.js` for Jasmine development that does more work.
### Compatibility
* Browser Minimum
* IE8
* Firefox 3.x
* Chrome ??
* Safari 5
## Development
All source code belongs in `src/`. The `core/` directory contains the bulk of Jasmine's functionality. This code should remain browser- and environment-agnostic. If your feature or fix cannot be, as mentioned above, please degrade gracefully. Any code that should only be in a non-browser environment should live in `src/console/`. Any code that depends on a browser (specifically, it expects `window` to be the global or `document` is present) should live in `src/html/`.
### Install Dependencies
Jasmine Core relies on Ruby and Node.js.
To install the Ruby dependencies, you will need Ruby, Rubygems, and Bundler available. Then:
$ bundle
...will install all of the Ruby dependencies.
To install the Node dependencies, you will need Node.js, Npm, and [Grunt](http://gruntjs.com/), the [grunt-cli](https://github.com/gruntjs/grunt-cli) and ensure that `grunt` is on your path.
$ npm install --local
...will install all of the node modules locally. If when you run
$ grunt
...you see that JSHint runs your system is ready.
### How to write new Jasmine code
Or, How to make a successful pull request
* _Do not change the public interface_. Lots of projects depend on Jasmine and if you aren't careful you'll break them
* _Be environment agnostic_ - server-side developers are just as important as browser developers
* _Be browser agnostic_ - if you must rely on browser-specific functionality, please write it in a way that degrades gracefully
* _Write specs_ - Jasmine's a testing framework; don't add functionality without test-driving it
* _Write code in the style of the rest of the repo_ - Jasmine should look like a cohesive whole
* _Ensure the *entire* test suite is green_ in all the big browsers, Node, and JSHint - your contribution shouldn't break Jasmine for other users
Follow these tips and your pull request, patch, or suggestion is much more likely to be integrated.
### Running Specs
Jasmine uses the [Jasmine Ruby gem](http://github.com/pivotal/jasmine-gem) to test itself in browser.
$ rake jasmine
...and then visit `http://localhost:8888` to run specs.
Jasmine uses Node.js with a custom runner to test outside of a browser.
$ grunt execSpecsInNode
...and then the results will print to the console. All specs run except those that expect a browser (the specs in `spec/html` are ignored).
## Before Committing or Submitting a Pull Request
1. Ensure all specs are green in browser *and* node
1. Ensure JSHint is green with `grunt jsHint`
1. Build `jasmine.js` with `grunt buildDistribution` and run all specs again - this ensures that your changes self-test well
## Submitting a Pull Request
1. Revert your changes to `jasmine.js` and `jasmine-html.js`
* We do this because `jasmine.js` and `jasmine-html.js` are auto-generated (as you've seen in the previous steps) and accepting multiple pull requests when this auto-generated file changes causes lots of headaches.
1. When we accept your pull request, we will generate these files as a separate commit and merge the entire branch into master.
Note that we use Travis for Continuous Integration. We only accept green pull requests.
+64
View File
@@ -0,0 +1,64 @@
# (Vague) Jasmine 2.0 Goals/(Guidelines)
1. No globals!
* jasmine library is entirely inside `jasmine` namespace
* globals required for backwards compatibility should be added in `boot.js` (EG, var describe = jasmine.getCurrentEnv().describe lives in boot.js)
1. Don't use properties as getters. Use methods.
* Properties aren't encapsulated -- can be mutated, unsafe.
1. Reporters get data objects (no methods).
* easier to refactor as needed
1. More unit tests - fewer nasty integration tests
## Remaining non-story-able work:
* Make a `TODO` list
### Hard
* Finish killing Globals
* Guidelines:
* New objects can have constructors on `jasmine`
* Top level functions can live on `jasmine`
* Top level (i.e., any `jasmine` property) should only be referenced inside the `Env` constructor
* should better allow any object to get jasmine code (Node-friendly)
* review everything in base.js
* Remove isA functions:
* isArray_ - used in matchers and spies
* isString_
* isDOMNode_
* isA_
* unimplementedMethod_, used by PrettyPrinter
* jasmine.util should be util closure inside of env or something
* argsToArray is used for Spies and matching (and can be replaced)
* inherit is only for PrettyPrinter now
* formatException is used only inside Env/spec
* htmlEscape is for messages in matchers - should this be HTML at all?
* Pretty printing
* move away from pretty printer and to a JSON.stringify implementation?
* jasmineToString vs. custom toString ?
### Easy
* unify params to ctors: options vs. attrs.
* This will be a lot of the TODOs, but clean up & simplify Env.js (is this a 2.1 task?)
### DONE
* Matchers improvements
* unit testable DONE
* better equality (from Underscore) DONE
* addCustomMatchers doesn't explode stack DONE
* refactor equals function so that it just loops & recurses over a list of fns (custom and built-in) - 2.1? (Tracker story)
* Spies
* break these out into their own tests/file DONE
## Other Topics
* Docs
* Docco has gone over well. Should we annotate all the sources and then have Pages be more complex, having tutorials and annotated source like Backbone? Are we small enough?
* Need examples for:
* How to build a Custom Matcher
* How to add a custom equality tester
+12
View File
@@ -0,0 +1,12 @@
source 'https://rubygems.org'
gem "jasmine", :git => 'https://github.com/pivotal/jasmine-gem.git'
# gem "jasmine", path: "/Users/pivotal/workspace/jasmine-gem"
unless ENV["TRAVIS"]
group :debug do
gem 'debugger'
end
end
gemspec
gem "anchorman"
+53
View File
@@ -0,0 +1,53 @@
module.exports = function(grunt) {
var pkg = require("./package.json");
global.jasmineVersion = pkg.version;
grunt.initConfig({
pkg: pkg,
jshint: require('./grunt/config/jshint.js'),
concat: require('./grunt/config/concat.js'),
compass: require('./grunt/config/compass.js'),
compress: require('./grunt/config/compress.js')
});
require('load-grunt-tasks')(grunt);
grunt.loadTasks('grunt/tasks');
grunt.registerTask('default', ['jshint:all']);
var version = require('./grunt/tasks/version.js');
var standaloneBuilder = require('./grunt/tasks/build_standalone.js');
grunt.registerTask('build:copyVersionToGem',
"Propagates the version from package.json to version.rb",
version.copyToGem);
grunt.registerTask('buildDistribution',
'Builds and lints jasmine.js, jasmine-html.js, jasmine.css',
[
'compass',
'jshint:beforeConcat',
'concat',
'jshint:afterConcat',
'build:copyVersionToGem'
]
);
grunt.registerTask("execSpecsInNode",
"Run Jasmine core specs in Node.js",
function() {
var exitInfo = require("shelljs").exec("node_modules/.bin/jasmine");
if (exitInfo.code !== 0) {
grunt.fail.fatal("Specs Failed", exitInfo.code);
}
}
);
grunt.registerTask("execSpecsInNode:performance",
"Run Jasmine performance specs in Node.js",
function() {
require("shelljs").exec("node_modules/.bin/jasmine JASMINE_CONFIG_PATH=spec/support/jasmine-performance.json");
}
);
};
+5
View File
@@ -0,0 +1,5 @@
recursive-include . *.py
include lib/jasmine-core/*.js
include lib/jasmine-core/*.css
include images/*.png
include package.json
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2008-2014 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+64
View File
@@ -0,0 +1,64 @@
<a name="README">[<img src="https://rawgithub.com/pivotal/jasmine/master/images/jasmine-horizontal.svg" width="400px" />](http://jasmine.github.io)</a>
[![Build Status](https://travis-ci.org/pivotal/jasmine.png?branch=master)](https://travis-ci.org/pivotal/jasmine) [![Code Climate](https://codeclimate.com/github/pivotal/jasmine.png)](https://codeclimate.com/github/pivotal/jasmine)
=======
**A JavaScript Testing Framework**
Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, [Node.js](http://nodejs.org) projects, or anywhere that JavaScript can run.
Documentation & guides live here: [http://jasmine.github.io](http://jasmine.github.io/)
For a quick start guide of Jasmine 2.0, see the beginning of [http://jasmine.github.io/2.0/introduction.html](http://jasmine.github.io/2.0/introduction.html)
Upgrading from Jasmine 1.x? Check out the [2.0 release notes](https://github.com/pivotal/jasmine/blob/v2.0.0/release_notes/20.md) for a list of what's new (including breaking interface changes).
## Contributing
Please read the [contributors' guide](https://github.com/pivotal/jasmine/blob/master/CONTRIBUTING.md)
## Installation
To install Jasmine on your local box:
* Clone Jasmine - `git clone https://github.com/pivotal/jasmine.git`
* Create a Jasmine directory in your project - `mkdir my-project/jasmine`
* Move latest dist to your project directory - `mv jasmine/dist/jasmine-standalone-2.0.0.zip my-project/jasmine`
* Change directory - `cd my-project/jasmine`
* Unzip the dist - `unzip jasmine-standalone-2.0.0.zip`
Add the following to your HTML file:
<link rel="shortcut icon" type="image/png" href="jasmine/lib/jasmine-2.0.0/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="jasmine/lib/jasmine-2.0.0/jasmine.css">
<script type="text/javascript" src="jasmine/lib/jasmine-2.0.0/jasmine.js"></script>
<script type="text/javascript" src="jasmine/lib/jasmine-2.0.0/jasmine-html.js"></script>
<script type="text/javascript" src="jasmine/lib/jasmine-2.0.0/boot.js"></script>
For the Jasmine Ruby Gem:<br>
[https://github.com/pivotal/jasmine-gem](https://github.com/pivotal/jasmine-gem)
For the Jasmine Python Egg:<br>
[https://github.com/pivotal/jasmine-py](https://github.com/pivotal/jasmine-py)
## Support
* Search past discussions: [http://groups.google.com/group/jasmine-js](http://groups.google.com/group/jasmine-js)
* Send an email to the list: [jasmine-js@googlegroups.com](mailto:jasmine-js@googlegroups.com)
* View the project backlog at Pivotal Tracker: [http://www.pivotaltracker.com/projects/10606](http://www.pivotaltracker.com/projects/10606)
* Follow us on Twitter: [@JasmineBDD](http://twitter.com/JasmineBDD)
## Maintainers
* [Davis W. Frank](mailto:dwfrank@pivotallabs.com), Pivotal Labs
* [Rajan Agaskar](mailto:rajan@pivotallabs.com), Pivotal Labs
* [Sheel Choksi](mailto:schoksi@pivotallabs.com), Pivotal Labs
### Maintainers Emeritus
* [Christian Williams](mailto:antixian666@gmail.com), Square
Copyright (c) 2008-2014 Pivotal Labs. This software is licensed under the MIT License.
+66
View File
@@ -0,0 +1,66 @@
# How to work on a Jasmine Release
## Development
___Jasmine Core Maintainers Only___
Follow the instructions in `CONTRIBUTING.md` during development.
### Git Rules
Please work on feature branches.
Please attempt to keep commits to `master` small, but cohesive. If a feature is contained in a bunch of small commits (e.g., it has several wip commits or small work), please squash them when merging back to `master`.
### Version
We attempt to stick to [Semantic Versioning](http://semver.org/). Most of the time, development should be against a new minor version - fixing bugs and adding new features that are backwards compatible.
The current version lives in the file `/package.json`. This file should be set to the version that is _currently_ under development. That is, if version 1.0.0 is the current release then version should be incremented say, to 1.1.0.
This version is used by both `jasmine.js` and the `jasmine-core` Ruby gem.
Note that Jasmine should *not* use the "patch" version number. Let downstream projects rev their patch versions as needed, keeping their major and minor version numbers in sync with Jasmine core.
### Update the Github Pages (as needed)
___Note: This is going to change right after 2.0___
Github pages have to exist in a branch called `gh-pages` in order for their app to serve them. This repo adds that branch as a submodule under the `pages` directory. This is a bit of a hack, but it allows us to work with the pages and the source at the same time and with one set of rake tasks.
If you want to submit changes to this repo and aren't a Pivotal Labs employee, you can fork and work in the `gh-pages` branch. You won't be able to edit the pages in the submodule off of master.
## Release
When ready to release - specs are all green and the stories are done:
1. Update the release notes in `release_notes` - use the Anchorman gem to generate the markdown file and edit accordingly
1. Update the version in `package.json` to a release candidate
1. Update any links or top-level landing page for the Github Pages
### Build standalone distribution
1. Build the standalone distribution with `grunt buildStandaloneDist`
1. Make sure you add the new ZIP file to git
1. Should we still do this? Given we want to use guthub releases...
### Release the Python egg
1. `python setup.py register sdist upload` You will need pypi credentials to upload the egg.
### Release the Ruby gem
1. Copy version to the Ruby gem with `grunt build:copyVersionToGem`
1. __NOTE__: You will likely need to point to a local jasmine gem in order to run tests locally. _Do not_ push this version of the Gemfile.
1. __NOTE__: You will likely need to push a new jasmine gem with a dependent version right after this release.
1. Push these changes to GitHub and verify that this SHA is green
1. `rake release` - tags the repo with the version, builds the `jasmine-core` gem, pushes the gem to Rubygems.org. In order to release you will have to ensure you have rubygems creds locally.
### Finally
1. Visit the [Releases page for Jasmine](https://github.com/pivotal/jasmine/releases), find the tag just pushed.
1. Paste in a link to the correct release notes for this release. The link should reference the blob and tag correctly, and the markdown file for the notes.
1. If it is a pre-release, mark it as such.
1. Attach the standalone zipfile
There should be a post to Pivotal Labs blog and a tweet to that link.
+18
View File
@@ -0,0 +1,18 @@
require "bundler"
Bundler::GemHelper.install_tasks
require "json"
require "jasmine"
unless ENV["JASMINE_BROWSER"] == 'phantomjs'
require "jasmine_selenium_runner"
end
load "jasmine/tasks/jasmine.rake"
namespace :jasmine do
task :set_env do
ENV['JASMINE_CONFIG_PATH'] ||= 'spec/support/jasmine.yml'
end
end
task "jasmine:configure" => "jasmine:set_env"
task :default => "jasmine:ci"
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
jasmine: {
options: {
cssDir: 'lib/jasmine-core/',
sassDir: 'src/html',
outputStyle: 'compact',
noLineComments: true,
}
}
};
+66
View File
@@ -0,0 +1,66 @@
var standaloneLibDir = "lib/jasmine-" + jasmineVersion;
function root(path) { return "./" + path; }
function libJasmineCore(path) { return root("lib/jasmine-core/" + path); }
function libConsole() { return "lib/console/" }
function dist(path) { return root("dist/" + path); }
module.exports = {
standalone: {
options: {
archive: root("dist/jasmine-standalone-" + global.jasmineVersion + ".zip")
},
files: [
{ src: [ root("MIT.LICENSE") ] },
{
src: [ "jasmine_favicon.png"],
dest: standaloneLibDir,
expand: true,
cwd: root("images")
},
{
src: [
"jasmine.js",
"jasmine-html.js",
"jasmine.css"
],
dest: standaloneLibDir,
expand: true,
cwd: libJasmineCore("")
},
{
src: [
"console.js"
],
dest: standaloneLibDir,
expand: true,
cwd: libConsole()
},
{
src: [ "boot.js" ],
dest: standaloneLibDir,
expand: true,
cwd: libJasmineCore("boot")
},
{
src: [ "SpecRunner.html" ],
dest: root(""),
expand: true,
cwd: dist("tmp")
},
{
src: [ "*.js" ],
dest: "src",
expand: true,
cwd: libJasmineCore("example/src/")
},
{
src: [ "*.js" ],
dest: "spec",
expand: true,
cwd: libJasmineCore("example/spec/")
}
]
}
};
+61
View File
@@ -0,0 +1,61 @@
var grunt = require('grunt');
function license() {
var currentYear = "" + new Date(Date.now()).getFullYear();
return grunt.template.process(
grunt.file.read("grunt/templates/licenseBanner.js.jst"),
{ data: { currentYear: currentYear}});
}
module.exports = {
'jasmine-html': {
src: [
'src/html/requireHtml.js',
'src/html/HtmlReporter.js',
'src/html/HtmlSpecFilter.js',
'src/html/ResultsNode.js',
'src/html/QueryString.js'
],
dest: 'lib/jasmine-core/jasmine-html.js'
},
jasmine: {
src: [
'src/core/requireCore.js',
'src/core/matchers/requireMatchers.js',
'src/core/base.js',
'src/core/util.js',
'src/core/Spec.js',
'src/core/Env.js',
'src/core/JsApiReporter.js',
'src/core/PrettyPrinter',
'src/core/Suite',
'src/core/**/*.js',
'src/version.js'
],
dest: 'lib/jasmine-core/jasmine.js'
},
boot: {
src: ['lib/jasmine-core/boot/boot.js'],
dest: 'lib/jasmine-core/boot.js'
},
nodeBoot: {
src: ['lib/jasmine-core/boot/node_boot.js'],
dest: 'lib/jasmine-core/node_boot.js'
},
console: {
src: [
'src/console/requireConsole.js',
'src/console/ConsoleReporter.js'
],
dest: 'lib/console/console.js'
},
options: {
banner: license(),
process: {
data: {
version: global.jasmineVersion
}
}
}
};
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
beforeConcat: ['src/**/*.js'],
afterConcat: [
'lib/jasmine-core/jasmine-html.js',
'lib/jasmine-core/jasmine.js'
],
options: {
jshintrc: '.jshintrc'
},
all: ['src/**/*.js']
};
@@ -0,0 +1,31 @@
var grunt = require("grunt");
function standaloneTmpDir(path) { return "dist/tmp/" + path; }
grunt.registerTask("build:compileSpecRunner",
"Processes the spec runner template and writes to a tmp file",
function() {
var runnerHtml = grunt.template.process(
grunt.file.read("grunt/templates/SpecRunner.html.jst"),
{ data: { jasmineVersion: global.jasmineVersion }});
grunt.file.write(standaloneTmpDir("SpecRunner.html"), runnerHtml);
}
);
grunt.registerTask("build:cleanSpecRunner",
"Deletes the tmp spec runner file",
function() {
grunt.file.delete(standaloneTmpDir(""));
}
);
grunt.registerTask("buildStandaloneDist",
"Builds a standalone distribution",
[
"buildDistribution",
"build:compileSpecRunner",
"compress:standalone",
"build:cleanSpecRunner"
]
);
+14
View File
@@ -0,0 +1,14 @@
var grunt = require("grunt");
function gemLib(path) { return './lib/jasmine-core/' + path; }
function nodeToRuby(version) { return version.replace('-', '.'); }
module.exports = {
copyToGem: function() {
var versionRb = grunt.template.process(
grunt.file.read("grunt/templates/version.rb.jst"),
{ data: { jasmineVersion: nodeToRuby(global.jasmineVersion) }});
grunt.file.write(gemLib("version.rb"), versionRb);
}
};

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