Skip to content

Commit

Permalink
MDL-58911 calendar: use the same callbacks in unittests and prod
Browse files Browse the repository at this point in the history
Remove unittest-specific callbacks for checking access and displaying
the calendar events on the dashboard.

This will allow plugin developers unittest the full behavior
of how their plugins add events to the dashboard.

Reset all static caches between unittests.
  • Loading branch information
marinaglancy committed Jun 20, 2017
1 parent 6bb80a1 commit 63e9e38
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 98 deletions.
172 changes: 78 additions & 94 deletions calendar/classes/local/event/container.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ class container {
*/
protected static $eventretrievalstrategy;

/**
* @var array A list of callbacks to use.
*/
protected static $callbacks = array();

/**
* @var \stdClass[] An array of cached courses to use with the event factory.
*/
Expand All @@ -91,16 +86,6 @@ class container {
*/
private static function init() {
if (empty(self::$eventfactory)) {
// When testing the container's components, we need to make sure
// the callback implementations in modules are not executed, since
// we cannot control their output from PHPUnit. To do this we have
// a set of 'testing' callbacks that the factory can use. This way
// we know exactly how the factory behaves when being tested.
$getcallback = function($which) {
return self::$callbacks[PHPUNIT_TEST ? 'testing' : 'production'][$which];
};

self::initcallbacks();
self::$actionfactory = new action_factory();
self::$eventmapper = new event_mapper(
// The event mapper we return from here needs to know how to
Expand Down Expand Up @@ -129,8 +114,8 @@ function() {
);

self::$eventfactory = new event_factory(
$getcallback('action'),
$getcallback('visibility'),
[self::class, 'apply_component_provide_event_action'],
[self::class, 'apply_component_is_event_visible'],
function ($dbrow) {
// At present we only have a bail-out check for events in course modules.
if (empty($dbrow->modulename)) {
Expand Down Expand Up @@ -183,6 +168,19 @@ function ($dbrow) {
}
}

/**
* Reset all static caches, called between tests.
*/
public static function reset_caches() {
self::$eventfactory = null;
self::$eventmapper = null;
self::$eventvault = null;
self::$actionfactory = null;
self::$eventretrievalstrategy = null;
self::$coursecache = [];
self::$modulecache = [];
}

/**
* Gets the event factory.
*
Expand Down Expand Up @@ -214,88 +212,74 @@ public static function get_event_vault() {
}

/**
* Initialises the callbacks.
* Calls callback 'core_calendar_provide_event_action' from the component responsible for the event
*
* There are two sets here, one is used during PHPUnit runs.
* See the comment at the start of the init method for more
* detail.
* If no callback is present or callback returns null, there is no action on the event
* and it will not be displayed on the dashboard.
*
* @param event_interface $event
* @return action_event|event_interface
*/
private static function initcallbacks() {
self::$callbacks = array(
'testing' => array(
// Always return an action event.
'action' => function (event_interface $event) {
return new action_event(
$event,
new \core_calendar\local\event\value_objects\action(
'test',
new \moodle_url('http://example.com'),
420,
true
));
},
// Always be visible.
'visibility' => function (event_interface $event) {
return true;
}
),
'production' => array(
// This function has type event_interface -> event_interface.
// This is enforced by the event_factory.
'action' => function (event_interface $event) {
// Callbacks will get supplied a "legacy" version
// of the event class.
$mapper = self::$eventmapper;
$action = null;
if ($event->get_course_module()) {
// TODO MDL-58866 Only activity modules currently support this callback.
// Any other event will not be displayed on the dashboard.
$action = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_provide_event_action',
[
$mapper->from_event_to_legacy_event($event),
self::$actionfactory
]
);
}
public static function apply_component_provide_event_action(event_interface $event) {
// Callbacks will get supplied a "legacy" version
// of the event class.
$mapper = self::$eventmapper;
$action = null;
if ($event->get_course_module()) {
// TODO MDL-58866 Only activity modules currently support this callback.
// Any other event will not be displayed on the dashboard.
$action = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_provide_event_action',
[
$mapper->from_event_to_legacy_event($event),
self::$actionfactory
]
);
}

// If we get an action back, return an action event, otherwise
// continue piping through the original event.
//
// If a module does not implement the callback, component_callback
// returns null.
return $action ? new action_event($event, $action) : $event;
},
// This function has type event_interface -> bool.
// This is enforced by the event_factory.
'visibility' => function (event_interface $event) {
$mapper = self::$eventmapper;
$eventvisible = null;
if ($event->get_course_module()) {
// TODO MDL-58866 Only activity modules currently support this callback.
$eventvisible = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_is_event_visible',
[
$mapper->from_event_to_legacy_event($event)
]
);
}
// If we get an action back, return an action event, otherwise
// continue piping through the original event.
//
// If a module does not implement the callback, component_callback
// returns null.
return $action ? new action_event($event, $action) : $event;
}

// Do not display the event if there is nothing to action.
if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
return false;
}
/**
* Calls callback 'core_calendar_is_event_visible' from the component responsible for the event
*
* The visibility callback is optional, if not present it is assumed as visible.
* If it is an actionable event but the get_item_count() returns 0 the visibility
* is set to false.
*
* @param event_interface $event
* @return bool
*/
public static function apply_component_is_event_visible(event_interface $event) {
$mapper = self::$eventmapper;
$eventvisible = null;
if ($event->get_course_module()) {
// TODO MDL-58866 Only activity modules currently support this callback.
$eventvisible = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_is_event_visible',
[
$mapper->from_event_to_legacy_event($event)
]
);
}

// Module does not implement the callback, event should be visible.
if (is_null($eventvisible)) {
return true;
}
// Do not display the event if there is nothing to action.
if ($event instanceof action_event_interface && $event->get_action()->get_item_count() === 0) {
return false;
}

// Module does not implement the callback, event should be visible.
if (is_null($eventvisible)) {
return true;
}

return $eventvisible ? true : false;
}
),
);
return $eventvisible ? true : false;
}
}
11 changes: 7 additions & 4 deletions calendar/tests/externallib_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -674,13 +674,15 @@ public function test_get_calendar_action_events_by_timesort_before_time() {
*/
public function test_get_calendar_events_override() {
$user = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$teacher = $this->getDataGenerator()->create_user();
$anotheruser = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$moduleinstance = $generator->create_instance(['course' => $course->id]);

$this->getDataGenerator()->enrol_user($user->id, $course->id);
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
$this->resetAfterTest(true);
$this->setAdminUser();
Expand All @@ -692,11 +694,12 @@ public function test_get_calendar_events_override() {
];

$now = time();
// Create two events - one for everybody in the course and one only for the first student.
$event1 = $this->create_calendar_event('Base event', 0, 'due', 0, $now + DAYSECS, $params + ['courseid' => $course->id]);
$event2 = $this->create_calendar_event('User event', $user->id, 'due', 0, $now + 2*DAYSECS, $params + ['courseid' => 0]);

// Retrieve course events for teacher - only one "Base event" is returned.
$this->setUser($teacher);
// Retrieve course events for the second student - only one "Base event" is returned.
$this->setUser($user2);
$paramevents = array('courseids' => array($course->id));
$options = array ('siteevents' => true, 'userevents' => true);
$events = core_calendar_external::get_calendar_events($paramevents, $options);
Expand All @@ -705,7 +708,7 @@ public function test_get_calendar_events_override() {
$this->assertEquals(0, count($events['warnings']));
$this->assertEquals('Base event', $events['events'][0]['name']);

// Retrieve events for user - both events are returned.
// Retrieve events for the first student - both events are returned.
$this->setUser($user);
$events = core_calendar_external::get_calendar_events($paramevents, $options);
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
Expand Down
5 changes: 5 additions & 0 deletions lib/phpunit/classes/util.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ public static function reset_all_data($detectchanges = false) {
// Reset internal users.
core_user::reset_internal_users();

// Clear static caches in calendar container.
if (class_exists('\core_calendar\local\event\container', false)) {
core_calendar\local\event\container::reset_caches();
}

//TODO MDL-25290: add more resets here and probably refactor them to new core function

// Reset course and module caches.
Expand Down

0 comments on commit 63e9e38

Please sign in to comment.