diff --git a/VERSIONS_WIP/188879 b/VERSIONS_WIP/188879
new file mode 100644
index 0000000000000000000000000000000000000000..94556d7437752b86f41f8a191a20973b33190129
--- /dev/null
+++ b/VERSIONS_WIP/188879
@@ -0,0 +1 @@
+ - fonctionnalité #188879 : Agenda : Amélioration de la gestion du bouton retour du navigateur avec la boite agenda du magasin de thèmes.
\ No newline at end of file
diff --git a/application/modules/opac/controllers/CmsController.php b/application/modules/opac/controllers/CmsController.php
index b2751d30a4dddd48de97bdfbe5843bdc694b5143..0f64de2f5998c7f42075c1d625b0b7d0ff563858 100644
--- a/application/modules/opac/controllers/CmsController.php
+++ b/application/modules/opac/controllers/CmsController.php
@@ -242,16 +242,8 @@ class CmsController extends ZendAfi_Controller_Action {
 
   public function calendarAction() {
     $this->_initCalendarAndPreferences();
-
-    $cache_key = [$this->_request->getParams(),
-                  Class_Profil::getCurrentProfil()->getId(),
-                  self::class,
-                  __FUNCTION__];
-
-    $this->view->content = (new Storm_Cache)
-      ->memoize(json_encode($cache_key,JSON_PARTIAL_OUTPUT_ON_ERROR),
-                fn() => $this->view->calendarContent($this->view->calendar,
-                                                    $this->view->preferences));
+    $this->view->content = $this->view->calendarContent($this->view->calendar,
+                                                        $this->view->preferences);
   }
 
 
diff --git a/library/Class/Calendar.php b/library/Class/Calendar.php
index 9d8bc3210977a34a554c5141b4461da77a9baf71..1a87c054f8eb7e5ad09aa8997827e42b9ca33d9e 100644
--- a/library/Class/Calendar.php
+++ b/library/Class/Calendar.php
@@ -23,30 +23,37 @@
 class Class_Calendar {
   use Trait_TimeSource, Trait_Translator;
 
-  protected $year;
-  protected $month;
-  protected $place;
-  protected $custom_fields;
-  protected $admin_selected_categories;
-  protected
-    $id_module,
-    $preferences,
-    $params,
-    $select_id_cat = 'all',
-    $id_categorie = '',
-    $id_bib,
-    $year_month,
-    $day,
-    $_article_event_helper,
-    $_has_date_filter = true;
+  protected $year = '';
+  protected $month = '';
+  protected $place = '';
+  protected $custom_fields = [];
+  protected $admin_selected_categories = [];
+  protected $id_module = '';
+  protected $preferences = [];
+  protected $params = [];
+  protected string $select_id_cat = 'all';
+  protected string $id_categorie = '';
+  protected $id_bib = '';
+  protected $year_month = '';
+  protected $day = '';
+
+  protected bool $_has_date_filter = true;
+
+  protected $_article_event_helper;
+
 
   public function __construct($id_module, $preferences, $params=[], $custom_fields=[]) {
     $this->id_module = $id_module;
     $this->preferences = $preferences;
-    $this->params = $params;
 
-    $this->_initDates();
+    if ($this->_forMe($params['id_module'] ?? ''))
+    {
+      $this->custom_fields = $custom_fields;
+      $this->params = $params;
+      $this->place = $this->_getParam('place');
+    }
 
+    $this->_initDates();
     $this->year = (int)substr($this->year_month, 0, 4);
     $this->month = (int)substr($this->year_month, -2);
 
@@ -56,10 +63,15 @@ class Class_Calendar {
       $this->preferences['display_cat_select'] = false;
 
     $this->_initSelectIdCat();
+  }
+
 
-    $this->place = $this->_getParam('place');
+  protected function _forMe(string $id_module): bool
+  {
+    if ( ! $id_module)
+      return true;
 
-    $this->custom_fields = $custom_fields;
+    return $this->id_module == $id_module;
   }
 
 
@@ -91,7 +103,7 @@ class Class_Calendar {
     if (array_isset('id_categorie', $this->preferences))
       $this->id_categorie = $this->preferences['id_categorie'];
 
-    if ($this->preferences['display_cat_select']
+    if (($this->preferences['display_cat_select'] ?? '')
         && ($select_id_cat = $this->_getParam('select_id_categorie')))
       return $this->select_id_cat = $select_id_cat;
 
@@ -99,7 +111,7 @@ class Class_Calendar {
       return null;
 
     if (array_isset('select_id_categorie', $this->preferences))
-      return $this->select_id_cat = $this->preferences['select_id_categorie'];
+      return $this->select_id_cat = ($this->preferences['select_id_categorie'] ?? '');
     return null;
   }
 
@@ -129,7 +141,7 @@ class Class_Calendar {
     if ($tag = $this->_getParam('tag', null))
       $prefs['tag'] = $tag;
 
-    $preferences = ['display_order' => ($this->preferences['display_order'] ?? $this->preferences['order']),
+    $preferences = ['display_order' => ($this->preferences['display_order'] ?? $this->preferences['order'] ?? ''),
                     'id_categorie' => $this->_getCategoriesIds(),
                     'events_only' => true,
                     'event_date' => $this->year_month,
@@ -167,7 +179,7 @@ class Class_Calendar {
 
 
   protected function getPlaceParam() {
-    return $this->place ?? '';
+    return $this->place;
   }
 
 
@@ -184,7 +196,7 @@ class Class_Calendar {
 
 
   public function getCustomFields() {
-    return $this->custom_fields ?? [];
+    return $this->custom_fields;
   }
 
 
@@ -220,7 +232,7 @@ class Class_Calendar {
 
 
   protected function _nextLoad() : array {
-    return ('none' === $this->preferences['event_filter'])
+    return ('none' === ($this->preferences['event_filter'] ?? ''))
       ? $this->_loadArticles(['event_start_after' => $this->year_month,
                               'event_date' => '',
                               'event_end_after' => ''])
@@ -253,7 +265,7 @@ class Class_Calendar {
 
 
   public function getAdminSelectedCategories() {
-    if (property_exists($this, 'admin_selected_categories') && $this->admin_selected_categories !== null)
+    if ($this->admin_selected_categories)
       return $this->admin_selected_categories;
 
     if (!array_isset('id_categorie', $this->preferences))
@@ -261,7 +273,7 @@ class Class_Calendar {
 
     $this->admin_selected_categories = [];
 
-    $ids = array_filter(explode('-', $this->preferences['id_categorie']));
+    $ids = array_filter(explode('-', ($this->preferences['id_categorie'] ?? '')));
 
     $params = ($ids === [])
       ? ['ID_CAT_MERE' => 0, 'order' => 'LIBELLE']
@@ -286,7 +298,7 @@ class Class_Calendar {
 
 
   protected function _eventDayOrMonth() : string {
-    return $this->day ?? $this->year_month;
+    return $this->day ?: $this->year_month;
   }
 
 
@@ -296,7 +308,7 @@ class Class_Calendar {
 
     return !array_key_exists('event_filter', $this->preferences)
       || (array_isset('event_filter', $this->preferences)
-          && 'none' === $this->preferences['event_filter']);
+          && 'none' === ($this->preferences['event_filter'] ?? ''));
   }
 
 
@@ -337,7 +349,7 @@ class Class_Calendar {
 
 
   public function getEnabledFilters() {
-    return array_filter(explode(';', $this->preferences['enabled_filters']));
+    return array_filter(explode(';', ($this->preferences['enabled_filters'] ?? '')));
   }
 
 
@@ -406,4 +418,18 @@ class Class_Calendar {
     $this->preferences['display_full_page'] = $boolean;
     return $this;
   }
+
+
+  public function initScripts(Class_ScriptLoader $script_loader): self
+  {
+    $script_loader
+      ->addOPACScript('calendrier')
+      ->addJQueryReady(sprintf('$("#boite_%s").ajaxify_calendar(%s);',
+                               $this->getIdModule(),
+                               json_encode(['profile_url' => Class_Profil::getCurrentProfil()->getUrl(),
+                                            'ajax_url' => Class_Url::relative(['module' => 'opac',
+                                                                               'controller' => 'cms',
+                                                                               'action' => 'calendar'])])));
+    return $this;
+  }
 }
diff --git a/library/Class/Profil.php b/library/Class/Profil.php
index 5361ff85ebf749e5b9ba4b58be050c03b8c9114b..cb91dd9659053a6945b01df669a6374658e1a90b 100644
--- a/library/Class/Profil.php
+++ b/library/Class/Profil.php
@@ -170,6 +170,31 @@ class ProfilLoader extends Storm_Model_Loader {
   }
 
 
+  public function findByControllerName(string $uri): ?Class_Profil
+  {
+    if (!$url = array_filter(explode('/', $uri)))
+      return null;
+
+    if (  !$controller = reset($url))
+      return null;
+
+    if ( 1 === count($url))
+      return null;
+
+    if (! $profile = Class_Profil::findFirstBy(['rewrite_url' => $controller]))
+      return null;
+
+    if ( ! $action = next($url) ?? '')
+      return null;
+
+    if ( Class_Profil::findFirstBy(['rewrite_url' => $action,
+                                    'parent_id' => $profile->getId()]))
+      return null;
+
+    return $profile;
+  }
+
+
   public function findInControllerActionOf($request) {
     $key = $request->getControllerName() . $request->getActionName();
     if (isset($this->_by_controller_action_cache[$key]))
diff --git a/library/Class/Url.php b/library/Class/Url.php
index 2b4450d5744790b00318ad5af5f9dbf4e603f3f8..b225b79766398347b06d5668d6ca7a1a5feceb95 100644
--- a/library/Class/Url.php
+++ b/library/Class/Url.php
@@ -54,6 +54,14 @@ class Class_Url {
   }
 
 
+  public static function getParams(): array
+  {
+    if (!$request = Zend_Controller_Front::getInstance()->getRequest())
+      return [];
+    return (array) $request->getParams();
+  }
+
+
   public static function domain() {
     return static::getName() . static::getPort() . static::baseUrl();
   }
@@ -325,10 +333,6 @@ Bokeh Error : adminVar "NOM_DOMAINE" is empty. Bokeh in php cli mode is unable t
   }
 
 
-  protected function _siteUrl() {
-  }
-
-
   protected function _prepare($url_array_or_string, $name, $reset, $encode) {
     if (is_string($url_array_or_string))
       return static::_beginWithSlash($url_array_or_string);
diff --git a/library/ZendAfi/Controller/Plugin/DefineURLs.php b/library/ZendAfi/Controller/Plugin/DefineURLs.php
index 1bfc490dc2b53698052e230a362b3fcfdf2f2ba6..b6e8f8a1c20f96c31c8aafbc27b9ae9b985fd9eb 100644
--- a/library/ZendAfi/Controller/Plugin/DefineURLs.php
+++ b/library/ZendAfi/Controller/Plugin/DefineURLs.php
@@ -216,6 +216,18 @@ class ZendAfi_Controller_Plugin_DefineURLs extends Zend_Controller_Plugin_Abstra
       $session["id_bib"]=$id_bib;
       $_SESSION["admin"]["filtre_localisation"]=$session;
   }
+
+
+  public function routeStartup(Zend_Controller_Request_Abstract $request)
+  {
+    if ($profil = Class_Profil::findByControllerName($request->getRequestUri()))
+    {
+      $uri = str_replace($profil->getRewriteUrl(), $profil->getRewriteUrl(). '/index', $request->getRequestUri());
+      $request
+        ->setPathInfo($uri)
+        ->setRequestUri($uri);
+    }
+  }
 }
 
 
diff --git a/library/ZendAfi/Controller/Plugin/InitModule.php b/library/ZendAfi/Controller/Plugin/InitModule.php
index e61be98d6051b17f31b9fd6730c118ae06e6a4d0..39d4d5fbe69143505bf54c9e112413c94629c6f5 100644
--- a/library/ZendAfi/Controller/Plugin/InitModule.php
+++ b/library/ZendAfi/Controller/Plugin/InitModule.php
@@ -50,4 +50,4 @@ class ZendAfi_Controller_Plugin_InitModule extends Zend_Controller_Plugin_Abstra
     $request->setParam('current_module', $current_module);
   }
 
-}
\ No newline at end of file
+}
diff --git a/library/ZendAfi/View/Helper/CalendarContent.php b/library/ZendAfi/View/Helper/CalendarContent.php
index 5751cc60c3cbfaa36b1bc88c9e27121e6a18dcbf..a3c2acd7162614ce1cc943023539a726fc7a8a05 100644
--- a/library/ZendAfi/View/Helper/CalendarContent.php
+++ b/library/ZendAfi/View/Helper/CalendarContent.php
@@ -38,6 +38,8 @@ class ZendAfi_View_Helper_CalendarContent extends ZendAfi_View_Helper_BaseHelper
     $this->calendar = $calendar;
     $calendar->setTimeSource($this->getTimeSource());
 
+    $calendar->initScripts(Class_ScriptLoader::getInstance());
+
     return $this->view->div(['class' => 'calendar'],
                             $this->_renderHTML($calendar, $settings));
   }
diff --git a/library/ZendAfi/View/Helper/Filters/Element.php b/library/ZendAfi/View/Helper/Filters/Element.php
index 21f402aef04f14e37529acf97b11097d0639285a..03e7a31da4ae9f4854efbf274f8e069f4869220a 100644
--- a/library/ZendAfi/View/Helper/Filters/Element.php
+++ b/library/ZendAfi/View/Helper/Filters/Element.php
@@ -125,9 +125,10 @@ abstract class ZendAfi_View_Helper_Filters_Element extends ZendAfi_View_Helper_B
   }
 
 
-  public function isSelected($label) {
-
-    return $this->isActive() && in_array($label, $this->_active_filters[$this->_custom_field_id]);
+  public function isSelected(string $label): bool
+  {
+    $haystack = $this->_getCustomFieldArray();
+    return $this->isActive() && in_array($label, $haystack);
   }
 
 
@@ -146,15 +147,24 @@ abstract class ZendAfi_View_Helper_Filters_Element extends ZendAfi_View_Helper_B
   }
 
 
-  public function isActive() {
-    return isset($this->_active_filters[$this->_custom_field_id]);
+  public function isActive(): bool
+  {
+    return (bool) ($this->_getCustomFieldArray());
+  }
+
+
+  public function _getCustomFieldArray() : array
+  {
+    return $this->_active_filters[$this->_filter_key]
+    ?? $this->_active_filters[$this->_custom_field_id]
+    ?? [];
   }
 
 
-  public function getValue() {
-    return isset($this->_active_filters[$this->_custom_field_id])
-      ? array_shift($this->_active_filters[$this->_custom_field_id])
-      : '';
+  public function getValue() : string
+  {
+    $array = $this->_getCustomFieldArray();
+    return array_shift($array) ?? '';
   }
 
 
diff --git a/library/ZendAfi/View/Helper/Filters/Element/CustomField.php b/library/ZendAfi/View/Helper/Filters/Element/CustomField.php
index 8c4571a44391277c348084fb7ea066f9af541a4a..73ed8928f4993cf3024a98090da91f680c1a3e24 100644
--- a/library/ZendAfi/View/Helper/Filters/Element/CustomField.php
+++ b/library/ZendAfi/View/Helper/Filters/Element/CustomField.php
@@ -20,10 +20,13 @@
  */
 
 
-class ZendAfi_View_Helper_Filters_Element_CustomField extends ZendAfi_View_Helper_Filters_Element {
+class ZendAfi_View_Helper_Filters_Element_CustomField
+  extends ZendAfi_View_Helper_Filters_Element
+{
   public function elements() {
     if ( ! $this->_custom_field_id)
       return [];
+
     $field = Class_CustomField::find($this->_custom_field_id);
     $values = Class_CustomField_Value::findAllBy(['custom_field_id' => $this->_custom_field_id]);
 
diff --git a/library/ZendAfi/View/Helper/Filters/Strategy/Checkbox.php b/library/ZendAfi/View/Helper/Filters/Strategy/Checkbox.php
index 5fd4039ffc7e173f5fb31ae2cc75ebcf1db7b538..bd9882b5dae751b86838aaae48f8cdebc44fef68 100644
--- a/library/ZendAfi/View/Helper/Filters/Strategy/Checkbox.php
+++ b/library/ZendAfi/View/Helper/Filters/Strategy/Checkbox.php
@@ -42,7 +42,7 @@ class ZendAfi_View_Helper_Filters_Strategy_Checkbox extends ZendAfi_View_Helper_
     $checked = $this->_renderFilterItemChecked($selected);
 
     $url_params = array_filter(array_merge($this->_getUrlParams(),
-                                           [$this->_getFilterKey() => implode(';', array_values($values))]));
+                                           [$this->_getFilterKey() => array_pop($values)]));
 
     $url = $this->view->url($url_params, null, true);
     return $this->_renderFilterItemHTML($id_element,$label, $url, $checked);
diff --git a/library/ZendAfi/View/Helper/Template/CalendarContent.php b/library/ZendAfi/View/Helper/Template/CalendarContent.php
index 0038be375e5993bb8c18114b5003aa3b103d4c32..b10c3a06e176a8b4748263cc461c05c04a065f00 100644
--- a/library/ZendAfi/View/Helper/Template/CalendarContent.php
+++ b/library/ZendAfi/View/Helper/Template/CalendarContent.php
@@ -23,11 +23,8 @@
 class ZendAfi_View_Helper_Template_CalendarContent extends ZendAfi_View_Helper_CalendarContent {
 
   protected function _renderHTML($calendar, $settings) {
-    Class_ScriptLoader::getInstance()->addOPACScript('calendrier');
-
     if (isset($settings['layout'])
-        &&
-        ($settings['layout'] == Intonation_Library_Widget_Carousel_Definition::CALENDAR))
+        && ($settings['layout'] == Intonation_Library_Widget_Carousel_Definition::CALENDAR))
       return $this->view->calendar_Table($calendar);
 
     $widget = (new Intonation_Library_Widget_Carousel_Agenda_View($calendar->getIdModule(),
@@ -37,7 +34,6 @@ class ZendAfi_View_Helper_Template_CalendarContent extends ZendAfi_View_Helper_C
 
     return
       $this->renderFilters($calendar, $calendar->getArticles())
-      .
-      $widget->renderElements();
+      . $widget->renderElements();
   }
 }
diff --git a/library/templates/Intonation/Library/PaginatedCollectionHelper.php b/library/templates/Intonation/Library/PaginatedCollectionHelper.php
index 7d325b460cd61a004266474b96686082762ffbf7..d129f4ca4f3816ef9792292fb179184a96a22b7a 100644
--- a/library/templates/Intonation/Library/PaginatedCollectionHelper.php
+++ b/library/templates/Intonation/Library/PaginatedCollectionHelper.php
@@ -101,9 +101,12 @@ class Intonation_Library_PaginatedCollectionHelper
 
   protected function _initExternalAjaxHelper(): self
   {
+    if ( ! $user = Class_Users::getIdentity())
+      return $this;
+
     if ( !$helper = (('' !== $this->_external_ajax_helper_classname)
                      ? $this->_external_ajax_helper_classname
-                     : Class_Users::getIdentity()->getExternalAjaxLoansHelperClassname()))
+                     : $user->getExternalAjaxLoansHelperClassname()))
       return $this;
 
     $this->_external_ajax_helper = new $helper($this->_view);
diff --git a/library/templates/Intonation/Library/Widget/Carousel/Agenda/View.php b/library/templates/Intonation/Library/Widget/Carousel/Agenda/View.php
index 30d01d56b4c3cf100aa31422d71827db9bfbf00f..f3d76c8c6665d2584063b1d6b654fb968adf4a3e 100644
--- a/library/templates/Intonation/Library/Widget/Carousel/Agenda/View.php
+++ b/library/templates/Intonation/Library/Widget/Carousel/Agenda/View.php
@@ -20,7 +20,8 @@
  */
 
 
-class Intonation_Library_Widget_Carousel_Agenda_View extends Intonation_Library_Widget_Carousel_View
+class Intonation_Library_Widget_Carousel_Agenda_View
+  extends Intonation_Library_Widget_Carousel_View
 {
   use Trait_TimeSource;
 
@@ -30,13 +31,20 @@ class Intonation_Library_Widget_Carousel_Agenda_View extends Intonation_Library_
   protected $id_module;
   protected $_calendar;
 
+
   protected function _renderHeadScriptsOn(Class_ScriptLoader $script_loader) : self {
-    $script_loader->addOPACScript('calendrier');
+    $this->_initCalendar();
+    $this->_calendar->initScripts($script_loader);
     parent::_renderHeadScriptsOn($script_loader);
     return $this;
   }
 
 
+  public function shouldCacheContent() : bool {
+    return false;
+  }
+
+
   protected function _findElements() {
     $this->_initCalendar();
     return $this->_calendar->getArticles();
@@ -65,11 +73,10 @@ class Intonation_Library_Widget_Carousel_Agenda_View extends Intonation_Library_
 
 
   protected function _initCalendar() {
-    if ($this->_calendar)
-      return $this->_calendar;
-
-    $this->_calendar = new Class_Calendar($this->id_module, $this->preferences);
-    return $this;
+    return $this->_calendar ??= new Class_Calendar($this->id_module,
+                                                   $this->preferences,
+                                                   Class_Url::getParams(),
+                                                   (new ZendAfi_Controller_Action_Helper_SelectedFilters)->selectedFilters());
   }
 
 
diff --git a/library/templates/Intonation/View/Calendar/Table.php b/library/templates/Intonation/View/Calendar/Table.php
index ee72b74a8b642cd9266a24cdc14cedc2200beb36..57e84d6516a0ba6418867cf060d11a0b49efda58 100644
--- a/library/templates/Intonation/View/Calendar/Table.php
+++ b/library/templates/Intonation/View/Calendar/Table.php
@@ -61,4 +61,4 @@ class Intonation_View_Calendar_Table extends ZendAfi_View_Helper_Calendar_Table
     $day = date('j', $time);
     return $this->_isTimestampToday($time) ? $this->_tag('b', $day) : $day;
   }
-}
\ No newline at end of file
+}
diff --git a/public/opac/js/calendrier.js b/public/opac/js/calendrier.js
index f1ccc2aa77928c7816bd146fcdd481394156c9ef..d1109b34eb0cb3a0b1cac6b8a4b4750a443b1dc0 100644
--- a/public/opac/js/calendrier.js
+++ b/public/opac/js/calendrier.js
@@ -1,81 +1,100 @@
-var ajaxify_calendars = function () {
-  var month_link = $(".calendar_ajax_ready, a.calendar_title_month_clickable:first-child, a.calendar_title_month_clickable:last-child, .calendar .month_list a:not(.no_event), .calendar .filters a");
-
-  month_link.click(function(event) {
-    var url = $(this).attr('href');
-    
-    if(!url) {
-      event.preventDefault();
-      return false;
-    }
-    
-    if (url == '#')
-      url = $(this).jqmData('href');
-
-    // JQuery allows to put selector in url parameter
-    // of load() method. The purpose is to put content of
-    // received calendar div into the existing calendar div
-    // instead of insert another calendar div into the existing.
-    // This should be tested before removed.
-    // See http://afi-forge.afi-sa.fr/issues/18602
-    $(this).closest(".calendar").load(url+'/render/ajax .calendar > *',
-				      ajaxify_calendars);
-    event.preventDefault();
-  });
-
-  if (undefined != $.fn.masonry)
-    $(this).closest(".calendar").masonry();
-
-  var month_no_event = $('.calendar .month_list a.no_event');
-  month_no_event.click(function(event) {
-    event.preventDefault();
-  });
-
-  $('.calendar').bind('swiperight', 
-		      function () {
-			$("a.calendar_title_month_clickable:first-child").click();
-		      }); 
-  $('.calendar').bind('swipeleft', 
-		      function () {
-			$("a.calendar_title_month_clickable:last-child").click();
-		      }); 
-
-
-  $("form#calendar_select_categorie").change(function(event) {
-    var url = $(this).attr('action')  + 'render/ajax';
-    $(this).closest(".calendar").load(url, 
-				      {'select_id_categorie':$(this).children('select').val(),
-				       'id_module':$(this).children('input').val()},
-				      ajaxify_calendars);
-  });
-  
-  var checkbox = $('.calendar .filters input:checkbox'); 
-  checkbox.change(function(event) {
-    var custom_field_id = $(this).closest('li[class*="custom_field_"]').attr('data-id');
-    var custom_field_value = $(this).attr('value');
-    var url = $(this).attr('data-url');
-
-    if(!url)
-      return event.preventDefault(); 
-
-    if(url == "#") 
-      url = $(this).jqmData("href");
-
-    url = url + "/render/ajax .calendar > *";
-
-    $(this).closest(".calendar").load(url, ajaxify_calendars); 
-    event.preventDefault();
-  });
-
-
-  if (undefined != window.resize_func) {
-    $(document).ready(window.resize_func);
-  }
-
-  initializePopups();
-  if (undefined != window.calendrierAfterLoad)
-    calendrierAfterLoad();
-
-};
-
-$(ajaxify_calendars);
+/**
+ * Copyright (c) 2024, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+ */
+
+(function ( $ ) {
+    $.fn.ajaxify_calendar = function (params) {
+	var widget = $(this);
+	var id = widget.attr('id');
+	
+	var anchors = $(widget).find("a.calendar_title_month_clickable:first-child, a.calendar_title_month_clickable:last-child, .month_list a:not(.no_event), .filters a, a.filter_reset");
+
+	var filters = $(widget).find('.filters input:checkbox'); 
+
+	$(widget).find('.month_list a.no_event').off('click');
+
+	var load_page = function(element, url, push)
+	{
+	    if (url == '#')
+		url = $(element).jqmData('href');
+	    
+	    if(!url)
+		return;
+	    
+	    var history_url = url;
+
+	    var browser_url = params.profile_url + history_url.replace(params.ajax_url, '');
+
+	    if (push)
+		window.history.pushState({url: history_url,
+					  id: id},
+					 '',
+					 browser_url);
+	    
+	    var url = url + '/render/ajax';
+
+	    $.get(url, (data) => $(widget).find('.calendar').replaceWith(data));
+	};
+
+	$(anchors)
+	    .off('click')
+	    .on('click', function(event) {
+		event.preventDefault();
+		var url = $(this).attr('href');
+		load_page(this, url, true);
+	    });
+
+	$(filters).on('change', function(event) {
+	    event.preventDefault();
+	    var custom_field_id = $(this).closest('li[class*="custom_field_"]').attr('data-id');
+	    var custom_field_value = $(this).attr('value');
+	    var url = $(this).attr('data-url');
+	    load_page(this, url, true);
+	});
+
+	$(widget).bind('swiperight', 
+		       () => $(widget).find("a.calendar_title_month_clickable:first-child").trigger('click'));
+	$(widget).bind('swipeleft', 
+		       () => $(widget).find("a.calendar_title_month_clickable:last-child").trigger('click'));
+	
+	$(widget).find("#calendar_select_categorie").on('change', (event) => load_page(this, $(this).attr('action'), true));
+
+	timeout = null;
+	window.addEventListener('popstate', function(e) {
+	    if (!e.state)
+		return;
+
+	    if ( ! e.state.id)
+		return ;
+
+	    var state_id = e.state.id;
+
+	    if ( id != state_id)
+		return;
+
+	    var url;
+	    if (url = e.state.url)
+	    {
+		clearTimeout(timeout);
+		timeout = setTimeout(() => load_page(widget, url, false), 50);
+	    }
+	},
+				{once: true});
+    };
+} (jQuery));
diff --git a/public/opac/js/calendrier/calendrier_tests.html b/public/opac/js/calendrier/calendrier_tests.html
new file mode 100644
index 0000000000000000000000000000000000000000..6355194018a10cb10641c996a2fb9f98c33a4a70
--- /dev/null
+++ b/public/opac/js/calendrier/calendrier_tests.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+    /**
+    * Copyright (c) 2024, Agence Française Informatique (AFI). All rights reserved.
+    *
+    * BOKEH is free software; you can redistribute it and/or modify
+    * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+    * the Free Software Foundation.
+    *
+    * There are special exceptions to the terms and conditions of the AGPL as it
+    * is applied to this software (see README file).
+    *
+    * BOKEH is distributed in the hope that it will be useful,
+    * but WITHOUT ANY WARRANTY; without even the implied warranty of
+    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+    *
+    * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+    * along with BOKEH; if not, write to the Free Software
+    * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA 
+    */
+  -->
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>ajaxifyCalendar QUnit tests</title>
+    <link rel="stylesheet" href="../../../qunit-git.css">
+
+    <script src="../../../qunit-1.13.0.js"></script>
+    <script src="../../../tarteaucitron/tarteaucitron.js"></script>
+    <script src="../../../admin/js/jquery-3.6.0.min.js"></script>
+    
+    <script src="../calendrier.js"></script>
+    <script src="calendrier_tests.js"></script>
+  </head>
+  <body>
+    <div id="qunit"></div>
+    <div id="test">
+      <div id="boite_3" class="boite calendar mx-auto no_background no_border no_border_radius no_footer_card no_shadow no_description widget col-12 col-md-11 card"><div class="widget-header card-header no_background" role="heading" aria-level="2">Agenda</div><div class="widget-body card-body"><div class="calendar"><div class="filters tabs mb-3"><div class="container-fluid"><div class="align-items-between text-left row no-gutters"><div class="col day"><div id="dropdown_674f0a855d878" class="dropdown"><button id="dropdown_button_674f0a855d878" data-toggle="dropdown" aria-haspopup="true" type="button" class="btn dropdown-toggle button_Date">Date</button><div class="dropdown-menu" aria-labelledby="dropdown_button_674f0a855d878"><div class="py-0 px-3"><div><table class="calendar_main table table-stripped table-hover table-responsive-md table-sm"><caption style="display: none"><details><summary>Calendrier</summary></details></caption><tbody><tr class="calendar_title"><td class="calendar_title_left_arrow"></td><td class="calendar_title_month"><div class="text-center container-fluid"><div class="row no-gutters"><div class="col-3"><a href="/cms/calendar/date/2024-11/day/2024-11/id_module/3/id_profil/576/select_id_categorie/all" class="calendar_title_month_clickable text-second" title="Mois précédent"><i class="fas fa-chevron-left utils" aria-hidden="true"></i></a></div><div class="col-6"><a href="/cms/calendar/date/2024-12/day/2024-12/id_module/3/id_profil/576/select_id_categorie/all" class="calendar_title_month text-second" title="Mois courant">décembre 2024</a></div><div class="col-3"><a href="/cms/calendar/date/2025-01/day/2025-01/id_module/3/id_profil/576/select_id_categorie/all" class="calendar_title_month_clickable text-second" title="Mois suivant"><i class="fas fa-chevron-right utils" aria-hidden="true"></i></a></div></div></div></td><td class="calendar_title_right_arrow"></td></tr><tr><td colspan="3"><table class="calendar_table table table-stripped table-hover table-responsive-md table-sm"><caption style="display: none"><details><summary>Calendrier en jours du mois</summary></details></caption><tbody><tr><th scope="col">lun</th><th scope="col">mar</th><th scope="col">mer</th><th scope="col">jeu</th><th scope="col">ven</th><th scope="col">sam</th><th scope="col">dim</th></tr><tr><td><span class="calendar_other_month">25</span></td><td><a href="/cms/calendar/day/2024-11-26/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 26/11" class="calendar_other_month day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">26</a></td><td><span class="calendar_other_month">27</span></td><td><span class="calendar_other_month">28</span></td><td><span class="calendar_other_month">29</span></td><td class="calendar_weekend"><span class="calendar_other_month">30</span></td><td class="calendar_weekend"><span class="calendar_day_non_clickable">1</span></td></tr><tr><td><span class="calendar_day_non_clickable">2</span></td><td><span class="calendar_today_clickable"><b>3</b></span></td><td><a href="/cms/calendar/day/2024-12-04/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 4/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">4</a></td><td><a href="/cms/calendar/day/2024-12-05/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 5/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">5</a></td><td><a href="/cms/calendar/day/2024-12-06/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 6/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">6</a></td><td class="calendar_weekend"><a href="/cms/calendar/day/2024-12-07/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 7/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">7</a></td><td class="calendar_weekend"><span class="calendar_day_non_clickable">8</span></td></tr><tr><td><span class="calendar_day_non_clickable">9</span></td><td><a href="/cms/calendar/day/2024-12-10/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 10/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">10</a></td><td><a href="/cms/calendar/day/2024-12-11/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 11/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">11</a></td><td><a href="/cms/calendar/day/2024-12-12/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 12/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">12</a></td><td><a href="/cms/calendar/day/2024-12-13/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 13/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">13</a></td><td class="calendar_weekend"><a href="/cms/calendar/day/2024-12-14/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 14/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">14</a></td><td class="calendar_weekend"><span class="calendar_day_non_clickable">15</span></td></tr><tr><td><span class="calendar_day_non_clickable">16</span></td><td><a href="/cms/calendar/day/2024-12-17/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 17/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">17</a></td><td><a href="/cms/calendar/day/2024-12-18/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 18/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">18</a></td><td><a href="/cms/calendar/day/2024-12-19/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 19/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">19</a></td><td><a href="/cms/calendar/day/2024-12-20/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 20/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">20</a></td><td class="calendar_weekend"><a href="/cms/calendar/day/2024-12-21/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 21/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">21</a></td><td class="calendar_weekend"><span class="calendar_day_non_clickable">22</span></td></tr><tr><td><span class="calendar_day_non_clickable">23</span></td><td><a href="/cms/calendar/day/2024-12-24/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 24/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">24</a></td><td><span class="calendar_day_non_clickable">25</span></td><td><a href="/cms/calendar/day/2024-12-26/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 26/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">26</a></td><td><a href="/cms/calendar/day/2024-12-27/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 27/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">27</a></td><td class="calendar_weekend"><a href="/cms/calendar/day/2024-12-28/id_module/3/id_profil/576/select_id_categorie/all" title="Voir les événements du 28/12" class="calendar_day_non_clickable day_clickable calendar_day_event_start text-success font-weight-bold border-bottom border-info">28</a></td><td class="calendar_weekend"><span class="calendar_day_non_clickable">29</span></td></tr><tr><td><span class="calendar_day_non_clickable">30</span></td><td><span class="calendar_day_non_clickable">31</span></td><td><span class="calendar_other_month">1</span></td><td><span class="calendar_other_month">2</span></td><td><span class="calendar_other_month">3</span></td><td class="calendar_weekend"><span class="calendar_other_month">4</span></td><td class="calendar_weekend"><span class="calendar_other_month">5</span></td></tr></tbody></table></td></tr></tbody></table></div></div></div></div></div><div class="col date"><div id="dropdown_674f0a855ef20" class="dropdown"><button id="dropdown_button_674f0a855ef20" data-toggle="dropdown" aria-haspopup="true" type="button" class="btn dropdown-toggle button_Mois">Mois</button><div class="dropdown-menu" aria-labelledby="dropdown_button_674f0a855ef20"><div class="py-0 px-3"><ul class="list-unstyled"><li class="selected"><a title="Filtrer la liste par Tous" href="/cms/calendar/id_module/3" class="text-second">Tous</a></li><li><a title="Filtrer la liste par Décembre" href="/cms/calendar/id_module/3/date/2024-12" class="text-second">Décembre</a></li><li><a title="Filtrer la liste par Janvier" href="/cms/calendar/id_module/3/date/2025-01" class="text-second">Janvier</a></li><li><a title="Filtrer la liste par Février" href="/cms/calendar/id_module/3/date/2025-02" class="text-second">Février</a></li><li><a title="Filtrer la liste par Mars" href="/cms/calendar/id_module/3/date/2025-03" class="text-second">Mars</a></li><li><a title="Filtrer la liste par Avril" href="/cms/calendar/id_module/3/date/2025-04" class="text-second">Avril</a></li><li><a title="Filtrer la liste par Mai" href="/cms/calendar/id_module/3/date/2025-05" class="text-second">Mai</a></li><li><a title="Filtrer la liste par Juin" href="/cms/calendar/id_module/3/date/2025-06" class="text-second">Juin</a></li><li><a title="Filtrer la liste par Juillet" href="/cms/calendar/id_module/3/date/2025-07" class="text-second">Juillet</a></li><li><a title="Filtrer la liste par Août" href="/cms/calendar/id_module/3/date/2025-08" class="text-second">Août</a></li><li><a title="Filtrer la liste par Septembre" href="/cms/calendar/id_module/3/date/2025-09" class="text-second">Septembre</a></li><li><a title="Filtrer la liste par Octobre" href="/cms/calendar/id_module/3/date/2025-10" class="text-second">Octobre</a></li><li><a title="Filtrer la liste par Novembre" href="/cms/calendar/id_module/3/date/2025-11" class="text-second">Novembre</a></li></ul></div></div></div></div>
+		</div>
+	      </div>
+	    </div>
+	  </div>
+	</div>
+      </div>
+    </div>
+  </body>    
+</head>
+</html>
diff --git a/public/opac/js/calendrier/calendrier_tests.js b/public/opac/js/calendrier/calendrier_tests.js
new file mode 100644
index 0000000000000000000000000000000000000000..a927e034f2113cd1f2314f48af0f4daa4619cd70
--- /dev/null
+++ b/public/opac/js/calendrier/calendrier_tests.js
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2012-2024, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+QUnit.module('ajaxifyCalendar');
+
+var calendar;
+var url = '';
+var state;
+
+
+moduleStart( function() {
+    $.get = function(ajax_url){url = ajax_url;};
+    window.history.pushState = function(history_state) {state = history_state;};
+    calendar = $('.boite.calendar');
+    calendar.ajaxify_calendar({"profile_url":"\/accueil\/agenda","ajax_url":"\/cms\/calendar"});
+});
+
+
+test('click on month should call get with cms/calendar/', function() {
+    calendar.find('a.calendar_title_month').first().click();
+    equal(url, '/cms/calendar/date/2024-12/day/2024-12/id_module/3/id_profil/576/select_id_categorie/all/render/ajax');
+});
+
+
+test('click on month should pushState.url with /cms/calendar/date/2024-12/day/2024', function() {
+    calendar.find('a.calendar_title_month').first().click();
+    equal(state.url, '/cms/calendar/date/2024-12/day/2024-12/id_module/3/id_profil/576/select_id_categorie/all');
+});
+
+
+test('click on month should pushState.id boite_3', function() {
+    calendar.find('a.calendar_title_month').first().click();
+    equal(state.id, 'boite_3');
+});
diff --git a/tests/application/modules/opac/controllers/BibControllerTest.php b/tests/application/modules/opac/controllers/BibControllerTest.php
index 11e5b35270b7dea18ffaa43bbb8202100f7a824a..4e443a255ab0322bb036d047ff38db22d08b61f2 100644
--- a/tests/application/modules/opac/controllers/BibControllerTest.php
+++ b/tests/application/modules/opac/controllers/BibControllerTest.php
@@ -227,7 +227,7 @@ class BibControllerIndexWithShowNewsTest extends BibControllerWithZoneTestCase {
 
   /** @test */
   public function actualitesShouldBeVisible() {
-    $this->assertXPathContentContains("//h2", "Actualités :",$this->_response->getBody());
+    $this->assertXPathContentContains("//h2", "Actualités :");
   }
 
 
@@ -239,8 +239,7 @@ class BibControllerIndexWithShowNewsTest extends BibControllerWithZoneTestCase {
 
   /** @test */
   function hrefForArticleEcrivezDesTestsShouldLinkToArticleView() {
-    $this->assertXPath('//li//a[contains(@href, "cms/articleview/id/2")]',
-                       $this->_response->getBody());
+    $this->assertXPath('//li//a[contains(@href, "cms/articleview/id/2")]');
   }
 }
 
@@ -612,7 +611,7 @@ class BibControllerBibSelectionWithBibsIdsInSessionTest extends BibControllerSel
 
   /** @test */
   public function cairoShouldBeChecked() {
-    $this->assertXPath('//li/input[@checked="checked"][@value="1"]', $this->_response->getBody());
+    $this->assertXPath('//li/input[@checked="checked"][@value="1"]');
   }
 
 
@@ -1148,12 +1147,22 @@ class BibControllerWidgetSelectHauteSavoieTest extends BibControllerWidgetPageTe
 
 
 
-class BibControllerSelectionOrderDisplayAndFiltersTest extends BibControllerWidgetPageTestCase {
-  protected $_response;
+class BibControllerSelectionOrderDisplayAndFiltersTest extends BibControllerWidgetPageTestCase
+{
   use Trait_ManageCustomFields;
+
+  protected $_response;
+
+
   public function setUp() {
     parent::setUp();
-    $this->dispatch('/bib/widget-page/id_module/1/id_division/2/page/1/custom_field_7/'.$this->getSetupValueIdForString('Wifi').';'. $this->getSetupValueIdForString('Restauration').'/custom_field_3/'.$this->getSetupValueIdForString('Jeunes').'/', true);
+    $this->dispatch('/bib/widget-page/id_module/1/id_division/2/page/1/custom_field_7/'
+                    . $this->getSetupValueIdForString('Wifi')
+                    . ';'
+                    . $this->getSetupValueIdForString('Restauration')
+                    . '/custom_field_3/'
+                    . $this->getSetupValueIdForString('Jeunes')
+                    . '/');
   }
 
 
@@ -1191,7 +1200,7 @@ class BibControllerSelectionOrderDisplayAndFiltersTest extends BibControllerWidg
   /** @test */
   public function customfieldServiceShouldContainsCheckBoxParking() {
 
-    $this->assertXPath('//input[@type="checkbox"][@name="Parking"][contains(@data-url, "custom_field_7/'.implode('%3B',[$this->getSetupValueIdForString('Restauration'),$this->getSetupValueIdForString('Wifi'),$this->getSetupValueIdForString('Parking')]).'")]');
+    $this->assertXPath('//input[@type="checkbox"][@name="Parking"][contains(@data-url, "custom_field_7/'.$this->getSetupValueIdForString('Parking').'")]');
   }
 
 
@@ -1275,7 +1284,7 @@ class BibControllerWidgetSearchASpecialTermTest extends BibControllerWidgetPageT
   public function setUp() {
     parent::setUp();
 
-    $this->dispatch('/bib/widget-page/id_module/1/id_division/2/search/Cran', true);
+    $this->dispatch('/bib/widget-page/id_module/1/id_division/2/search/Cran');
   }
 
 
@@ -1552,7 +1561,7 @@ class BibControllerMapActionWithNoLocationTest extends AbstractControllerTestCas
 
   /** @test */
   public function leafletJsLibraryShouldNotBeInDom() {
-    $this->assertNotXPath('//script[contains(@src, "leaflet.js")]', $this->_response->getBody());
+    $this->assertNotXPath('//script[contains(@src, "leaflet.js")]');
   }
 }
 
@@ -1580,6 +1589,6 @@ class BibControllerWithDefaultFiltersCorruptedTest extends BibControllerWidgetPa
 
   /** @test */
   public function librariesShouldBePresent() {
-    $this->assertXPathContentContains('//h2' , 'Annecy', $this->_response->getBody());
+    $this->assertXPathContentContains('//h2' , 'Annecy');
   }
 }
diff --git a/tests/application/modules/opac/controllers/CmsControllerCalendarActionTest.php b/tests/application/modules/opac/controllers/CmsControllerCalendarActionTest.php
index ccc3aa881895d73c7d175db9675c7267447bac50..5630d788396aedd2e9932706807c06ec023c3d53 100644
--- a/tests/application/modules/opac/controllers/CmsControllerCalendarActionTest.php
+++ b/tests/application/modules/opac/controllers/CmsControllerCalendarActionTest.php
@@ -1517,18 +1517,6 @@ class CmsControllerCalendarActionWithOutDateTest extends AbstractControllerTestC
   public function getArticleByPreferencesShouldBeCallWithEventDateAfter() {
     $this->assertXPathContentContains('//div', 'Kitchen');
   }
-
-
-  /** @test */
-  public function ajaxCalendarContentShouldBeCached() {
-    $this->bootstrap();
-    Class_Article::getLoader()
-      ->clearAllRedirections()
-      ->whenCalled('getArticlesByPreferences')
-      ->never();
-    $this->dispatch('/cms/calendar/render/ajax');
-    $this->assertXPathContentContains('//div', 'Kitchen');
-  }
 }
 
 
diff --git a/tests/js/CalendrierTest.php b/tests/js/CalendrierTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7069fff2d9cd3fdc0188ff0ac40ab3093bc34225
--- /dev/null
+++ b/tests/js/CalendrierTest.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Copyright (c) 2012-2024, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class CalendrierTest extends BrowserTest {
+  protected $_test_path = 'public/opac/js/calendrier/calendrier_tests.html';
+}
diff --git a/tests/scenarios/Templates/TemplatesWidgetCalendarTest.php b/tests/scenarios/Templates/TemplatesWidgetCalendarTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..570ba9196dd06da6d286647e5d47f45ac67f51f9
--- /dev/null
+++ b/tests/scenarios/Templates/TemplatesWidgetCalendarTest.php
@@ -0,0 +1,478 @@
+<?php
+/**
+ * Copyright (c) 2012-2024, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class TemplatesWidgetCalendarDispatchTest
+  extends AbstractControllerTestCase
+{
+
+  protected $_vacances;
+
+  public function setUp()
+  {
+    parent::setUp();
+    $this->_buildIntonation();
+
+    Class_Profil::find(1999)
+      ->setCfgAccueil(['modules' =>
+                       ['21' => ['division' => 3,
+                                 'type_module' => 'CALENDAR',
+                                 'preferences' => ['id_categorie' => 22]]]])
+      ->assertSave();
+
+    $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
+    ZendAfi_View_Helper_Template_CalendarContent::setTimeSource($time_source);
+    Class_Calendar::setTimeSource($time_source);
+
+    $this->_vacances = $this->fixture(Class_Article::class,
+                                      ['id' => 78,
+                                       'titre' => 'Les vacances',
+                                       'contenu' => 'À la mer']);
+
+    $this->onLoaderOfModel(Class_Article::class)
+         ->whenCalled('getArticlesByPreferences')
+         ->answers([]);
+  }
+
+
+  /** @test */
+  public function editWidgetShouldContainTitreTextInput()
+  {
+    $this->dispatch('/admin/widget/edit-widget/id/21/id_profil/1999');
+    $this->assertXpath('//input[@name="titre"][@type="text"]');
+  }
+
+
+  /** @test */
+  public function calendarWithoutFiltersShouldBePresentAndQueryAllFuturEvents()
+  {
+    Class_Article::getLoader()
+      ->whenCalled('getArticlesByPreferences') // events of current month not ended yet
+      ->with(['display_order' => 'EventDebut',
+              'id_categorie' => '22',
+              'events_only' => true,
+              'event_date' => '2020-04',
+              'id_bib' => [''],
+              'id_lieu' => '',
+              'custom_fields' => [],
+              'published' => true,
+              'event_end_after' => '2020-04-12'])
+      ->answers([$this->_vacances])
+
+      ->whenCalled('getArticlesByPreferences') // events after current month
+      ->with(['display_order' => 'EventDebut',
+              'id_categorie' => '22',
+              'events_only' => true,
+              'event_date' => '',
+              'id_bib' => [''],
+              'id_lieu' => '',
+              'custom_fields' => [],
+              'published' => true,
+              'event_start_after' => '2020-04',
+              'event_end_after' => ''])
+      ->answers([$this->_vacances])
+
+      ->whenCalled('getArticlesByPreferences') // events of current month for calendar filter
+      ->with(['display_order' => 'EventDebut',
+              'id_categorie' => '22',
+              'events_only' => true,
+              'event_date' => '2020-04',
+              'id_bib' => [''],
+              'id_lieu' => '',
+              'custom_fields' => [],
+              'published' => true])
+      ->answers([$this->_vacances])
+
+      ->beStrict();
+
+    $this->dispatch('/opac/index/index/id_profil/1999');
+    $this->assertXPath('//div', 'À la mer');
+  }
+
+
+  /** @test */
+  public function ajaxCmsCalendarActionShouldRenderFilters()
+  {
+    $this->dispatch('/opac/cms/calendar/id_profil/1999/id_module/21/render/ajax');
+    $this->assertXPathContentContains('//div[contains(@class, "filters")]//a[contains(@href, "/cms/calendar/")]',
+                                      'Janvier');
+  }
+
+
+  /** @test */
+  public function ajaxCmsCalendarActionWithDayShouldRenderItInFilters()
+  {
+    $this->dispatch('/opac/cms/calendar/id_profil/1999/id_module/21/day/2019-06-01/render/ajax');
+    $this->assertXPathContentContains('//div[contains(@class, "filters")]//button/span',
+                                      '01/06/2019');
+  }
+
+
+  /** @test */
+  public function ajaxCmsCalendarActionWithMonthShouldRenderItInFilters()
+  {
+    $this->dispatch('/opac/cms/calendar/id_profil/1999/id_module/21/day/2019-06/render/ajax');
+    $this->assertXPathContentContains('//div[contains(@class, "filters")]//button/span',
+                                      '06/2019');
+  }
+}
+
+
+
+
+class TemplatesWidgetCalendarCmsActionDispatchTest
+  extends AbstractControllerTestCase
+{
+
+  public function setUp()
+  {
+    parent::setUp();
+    $this->_buildIntonation();
+
+    Class_Profil::find(1999)
+      ->setCfgAccueil(['modules' =>
+                       ['21' => ['division' => 2,
+                                 'type_module' => 'CALENDAR',
+                                 'preferences' => ['id_categorie' => 22,
+                                                   'layout' => 'wall']]]])
+      ->assertSave();
+
+    $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
+    ZendAfi_View_Helper_Template_CalendarContent::setTimeSource($time_source);
+    Class_Calendar::setTimeSource($time_source);
+
+    $this->fixture(Class_Article::class,
+                   ['id' => 78,
+                    'titre' => 'Les vacances',
+                    'contenu' => 'À la mer',
+                    'status' => Class_Article::STATUS_VALIDATED,
+                    'debut' => '2020-02-16 15:00:00',
+                    'fin' => '2020-06-06 10:00:00',
+                    'events_debut' => '2020-04-16 15:00:00',
+                    'events_fin' => '2020-04-16 18:00:00',
+                    'id_lieu' => 52]);
+
+    $this->onLoaderOfModel(Class_Article::class)
+         ->whenCalled('getArticlesByPreferences')
+         ->answers([Class_Article::find(78)]);
+
+    $this->dispatch('/opac/cms/calendar/id_profil/1999/id_module/21/place_town/Cran/render/ajax');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsArticleLesVacances()
+  {
+    $this->assertXPathContentContains('//body', 'Les vacances');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsCalendrierJs()
+  {
+    $this->assertXPath('//script[contains(@src,"public/opac/js/calendrier.js")]');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsScriptAjaxifyCalendar()
+  {
+    $json = json_encode(['profile_url' => Class_Url::absolute('/index/index/id_profil/1999'),
+                         'ajax_url' => '/cms/calendar']);
+
+    $this->assertXPathContentContains('//script', '$("#boite_21").ajaxify_calendar('.$json.')');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsMasonryScript()
+  {
+    $this->assertXPath('//script[contains(@src,"/Intonation/Assets/js/masonry.js")]');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsScriptMasonry()
+  {
+    $this->assertXPathContentContains('//script', '$("#21").parent().masonry();');
+  }
+}
+
+
+
+
+class TemplatesWidgetCalendarInPageDispatchTest
+  extends AbstractControllerTestCase
+{
+
+  public function setUp()
+  {
+    parent::setUp();
+    $this->_buildIntonation();
+
+    Class_Profil::find(1999)
+      ->setRewriteUrl('agenda')
+      ->setCfgAccueil(['modules' =>
+                       ['21' => ['division' => 2,
+                                 'type_module' => 'CALENDAR',
+                                 'preferences' => ['id_categorie' => 22,
+                                                   'layout' => 'wall']]]])
+      ->assertSave();
+
+    $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
+    ZendAfi_View_Helper_Template_CalendarContent::setTimeSource($time_source);
+    Class_Calendar::setTimeSource($time_source);
+
+    $this->fixture(Class_Article::class,
+                   ['id' => 78,
+                    'titre' => 'Les vacances',
+                    'contenu' => 'À la mer',
+                    'status' => Class_Article::STATUS_VALIDATED,
+                    'debut' => '2020-02-16 15:00:00',
+                    'fin' => '2020-06-06 10:00:00',
+                    'events_debut' => '2020-04-16 15:00:00',
+                    'events_fin' => '2020-04-16 18:00:00',
+                    'id_lieu' => 52]);
+
+    $this->onLoaderOfModel(Class_Article::class)
+         ->whenCalled('getArticlesByPreferences')
+         ->answers([Class_Article::find(78)]);
+
+    $this->dispatch('/agenda');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsArticleLesVacances()
+  {
+    $this->assertXPathContentContains('//body', 'Les vacances');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsCalendrierJs()
+  {
+    $this->assertXPath('//script[contains(@src,"public/opac/js/calendrier.js")]');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsScriptAjaxifyCalendar()
+  {
+    $json = json_encode(['profile_url' => Class_Url::absolute('/agenda'),
+                         'ajax_url' => '/cms/calendar']);
+
+    $this->assertXPathContentContains('//script', '$("#boite_21").ajaxify_calendar('.$json.')');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsMasonryScript()
+  {
+    $this->assertXPath('//script[contains(@src,"/Intonation/Assets/js/masonry.js")]');
+  }
+
+
+  /** @test */
+  public function pageShouldContainsScriptMasonry()
+  {
+    $this->assertXPathContentContains('//script', '$("#21").parent().masonry();');
+  }
+}
+
+
+
+
+
+class TemplatesWidgetCalendarInPageWithParamsDispatchTest
+  extends AbstractControllerTestCase
+{
+
+  public function setUp()
+  {
+    parent::setUp();
+    $this->_buildIntonation();
+
+    $this->_buildTemplateProfil(['id' => 2000]);
+
+    Class_Profil::find(1999)
+      ->setRewriteUrl('home')
+      ->assertSave();
+
+    Class_Profil::find(2000)
+      ->setParentId(1999)
+      ->setRewriteUrl('agenda')
+      ->setCfgAccueil(['modules' =>
+                       ['21' => ['division' => 2,
+                                 'type_module' => 'CALENDAR',
+                                 'preferences' => ['id_categorie' => 22,
+                                                   'layout' => 'wall']]]])
+      ->assertSave();
+
+    $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
+    ZendAfi_View_Helper_Template_CalendarContent::setTimeSource($time_source);
+    Class_Calendar::setTimeSource($time_source);
+
+    $this->fixture(Class_Article::class,
+                   ['id' => 78,
+                    'titre' => 'Les vacances',
+                    'contenu' => 'À la mer',
+                    'status' => Class_Article::STATUS_VALIDATED,
+                    'debut' => '2020-02-16 15:00:00',
+                    'fin' => '2020-06-06 10:00:00',
+                    'events_debut' => '2020-04-16 15:00:00',
+                    'events_fin' => '2020-04-16 18:00:00',
+                    'id_lieu' => 52]);
+
+    $this->onLoaderOfModel(Class_Article::class)
+         ->whenCalled('getArticlesByPreferences')
+         ->answers([Class_Article::find(78)]);
+  }
+
+
+  public function urlToDisptach() : array {
+    return [['/index/index/id_profil/2000/date/2020-11'],
+            ['/agenda/date/2020-11'],
+            ['/home/agenda/date/2020-11']];
+  }
+
+
+  /**
+   * @test
+   * @dataProvider urlToDisptach
+   */
+  public function novembreShouldBeSelected($url)
+  {
+    $this->dispatch($url);
+    $this->assertXPathContentContains('//div[@class="calendar"]//div[@class="col day"]//button/span',
+                                      '11/2020');
+  }
+}
+
+
+
+
+class TemplatesWidgetCalendarInPageWithParamsCustomFieldDispatchTest
+  extends AbstractControllerTestCase
+{
+
+  public function setUp()
+  {
+    parent::setUp();
+    $this->_buildIntonation();
+
+    $this->_buildTemplateProfil(['id' => 2000]);
+
+    Class_Profil::find(1999)
+      ->setRewriteUrl('home')
+      ->assertSave();
+
+    Class_Profil::find(2000)
+      ->setParentId(1999)
+      ->setRewriteUrl('agenda')
+      ->setCfgAccueil(['modules' =>
+                       ['21' => ['division' => 2,
+                                 'type_module' => 'CALENDAR',
+                                 'preferences' => ['id_categorie' => 22,
+                                                   'enabled_filters' => 'day;date;place_town;custom_field_8;',
+                                                   'layout' => 'wall']]]])
+      ->assertSave();
+
+
+    $setup_value = $this
+      ->fixture(Class_CustomField_SetupValue::class,
+                ['id' => 39,
+                 'source_id' =>'SIGB',
+                 'meta_id' => 13,
+                 'label' => 'Jeunesse'
+                ]);
+
+    $this->fixture(Class_CustomField::class,
+                   ['id' => 8,
+                    'meta' => $this->fixture(Class_CustomField_Meta::class,
+                                             ['id' => 13,
+                                              'label' => 'Public',
+                                              'field_type' => Class_CustomField_Meta::MULTI_CHECKBOX,
+                                              'indexable' => 1,
+                                              'setup_values' =>
+                                              [
+                                               $setup_value
+                                              ]
+                                             ]),
+                    'priority' => 3,
+                    'model' => 'Article'
+                   ]);
+    $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
+    ZendAfi_View_Helper_Template_CalendarContent::setTimeSource($time_source);
+    Class_Calendar::setTimeSource($time_source);
+
+    $this
+      ->fixture(Class_Article::class,
+                ['id' => 78,
+                 'titre' => 'Les vacances',
+                 'contenu' => 'À la mer',
+                 'status' => Class_Article::STATUS_VALIDATED,
+                 'debut' => '2020-02-16 15:00:00',
+                 'fin' => '2020-06-06 10:00:00',
+                 'events_debut' => '2020-04-16 15:00:00',
+                 'events_fin' => '2020-04-16 18:00:00',
+                 'id_lieu' => 52])
+      ->addCustomFieldValues(8, Class_CustomField_SetupValue::find(39) )
+      ->saveWithCustomFields();
+
+    $this->onLoaderOfModel(Class_Article::class)
+         ->whenCalled('getArticlesByPreferences')
+         ->answers([Class_Article::find(78)]);
+  }
+
+
+  /**
+   * @test
+   */
+  public function onPageCalendarCustomFieldJeunesseValueShouldBeSelected()
+  {
+    $this->dispatch('/cms/calendar/id_module/21/place_town/Tourcoing/custom_field_8/39');
+    $this->assertXPathContentContains('//div[@class="calendar"]//div[@class="col custom_field_8"]//button',
+                                      'Public<span> : Jeunesse');
+  }
+
+
+  /**
+   * @test
+   */
+  public function pageAgendaCustomFieldJeunesseValueShouldBeSelected()
+  {
+    $this->dispatch('/agenda/id_module/21/place_town/Tourcoing/custom_field_8/39');
+    $this->assertXPathContentContains('//div[@class="calendar"]//div[@class="col custom_field_8"]//button',
+                                      'Public<span> : Jeunesse');
+  }
+
+
+    /**
+   * @test
+   */
+  public function pageAgendaCustomFieldJeunesseValueShouldBeNotBeSelectedWithAnOtherIdModule()
+  {
+    $this->dispatch('/agenda/id_module/38/place_town/Tourcoing/custom_field_8/39');
+    $this->assertNotXPathContentContains('//div[@class="calendar"]//div[@class="col custom_field_8"]//button',
+                                      'Public<span> : Jeunesse');
+  }
+}
diff --git a/tests/scenarios/Templates/TemplatesWidgetTest.php b/tests/scenarios/Templates/TemplatesWidgetTest.php
index 3860a8cd6dd4a72044735df7c160f5a8754b67ff..c577da81a3be76648e921ccfda43a64aa8f59453 100644
--- a/tests/scenarios/Templates/TemplatesWidgetTest.php
+++ b/tests/scenarios/Templates/TemplatesWidgetTest.php
@@ -967,115 +967,6 @@ class TemplatesDispatchLibraryAgendaWidgetTest extends TemplatesIntonationTestCa
 
 
 
-class TemplatesWidgetCalendarTest extends TemplatesIntonationTestCase {
-
-  protected $_vacances;
-
-  public function setUp() {
-    parent::setUp();
-
-    Class_Profil::find(72)
-      ->setCfgAccueil(['modules' =>
-                       ['21' => ['division' => 3,
-                                 'type_module' => 'CALENDAR',
-                                 'preferences' => ['id_categorie' => 22]]]])
-      ->assertSave();
-
-    $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
-    ZendAfi_View_Helper_Template_CalendarContent::setTimeSource($time_source);
-    Class_Calendar::setTimeSource($time_source);
-
-    $this->_vacances = $this->fixture(Class_Article::class,
-                                      ['id' => 78,
-                                       'titre' => 'Les vacances',
-                                       'contenu' => 'À la mer']);
-
-    $this->onLoaderOfModel(Class_Article::class)
-         ->whenCalled('getArticlesByPreferences')
-         ->answers([]);
-  }
-
-
-  /** @test */
-  public function editWidgetShouldContainTitreTextInput() {
-    $this->dispatch('/admin/widget/edit-widget/id/21/id_profil/72');
-    $this->assertXpath('//input[@name="titre"][@type="text"]');
-  }
-
-
-  /** @test */
-  public function calendarWithoutFiltersShouldBePresentAndQueryAllFuturEvents() {
-    Class_Article::getLoader()
-      ->whenCalled('getArticlesByPreferences') // events of current month not ended yet
-      ->with(['display_order' => 'EventDebut',
-              'id_categorie' => '22',
-              'events_only' => true,
-              'event_date' => '2020-04',
-              'id_bib' => [''],
-              'id_lieu' => '',
-              'custom_fields' => [],
-              'published' => true,
-              'event_end_after' => '2020-04-12'])
-      ->answers([$this->_vacances])
-
-      ->whenCalled('getArticlesByPreferences') // events after current month
-      ->with(['display_order' => 'EventDebut',
-              'id_categorie' => '22',
-              'events_only' => true,
-              'event_date' => '',
-              'id_bib' => [''],
-              'id_lieu' => '',
-              'custom_fields' => [],
-              'published' => true,
-              'event_start_after' => '2020-04',
-              'event_end_after' => ''])
-      ->answers([$this->_vacances])
-
-      ->whenCalled('getArticlesByPreferences') // events of current month for calendar filter
-      ->with(['display_order' => 'EventDebut',
-              'id_categorie' => '22',
-              'events_only' => true,
-              'event_date' => '2020-04',
-              'id_bib' => [''],
-              'id_lieu' => '',
-              'custom_fields' => [],
-              'published' => true])
-      ->answers([$this->_vacances])
-
-      ->beStrict();
-
-    $this->dispatch('/opac/index/index/id_profil/72');
-    $this->assertXPath('//div', 'À la mer');
-  }
-
-
-  /** @test */
-  public function ajaxCmsCalendarActionShouldRenderFilters() {
-    $this->dispatch('/opac/cms/calendar/id_profil/72/id_module/21/render/ajax');
-    $this->assertXPathContentContains('//div[contains(@class, "filters")]//a[contains(@href, "/cms/calendar/")]',
-                                      'Janvier');
-  }
-
-
-  /** @test */
-  public function ajaxCmsCalendarActionWithDayShouldRenderItInFilters() {
-    $this->dispatch('/opac/cms/calendar/id_profil/72/id_module/21/day/2019-06-01/render/ajax');
-    $this->assertXPathContentContains('//div[contains(@class, "filters")]//button/span',
-                                      '01/06/2019');
-  }
-
-
-  /** @test */
-  public function ajaxCmsCalendarActionWithMonthShouldRenderItInFilters() {
-    $this->dispatch('/opac/cms/calendar/id_profil/72/id_module/21/day/2019-06/render/ajax');
-    $this->assertXPathContentContains('//div[contains(@class, "filters")]//button/span',
-                                      '06/2019');
-  }
-}
-
-
-
-
 class TemplatesDispatchDomainWidgetTest extends TemplatesIntonationTestCase {
 
   public function setUp() {