diff --git a/documentation/html/ExternalRESTClients.html b/documentation/html/ExternalRESTClients.html
index 10fc104964..937351fefe 100644
--- a/documentation/html/ExternalRESTClients.html
+++ b/documentation/html/ExternalRESTClients.html
@@ -414,7 +414,7 @@
Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/GraphQLClient.html b/documentation/html/GraphQLClient.html
index e29141b167..79ddd85de6 100644
--- a/documentation/html/GraphQLClient.html
+++ b/documentation/html/GraphQLClient.html
@@ -912,7 +912,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/RESTClient.html b/documentation/html/RESTClient.html
index 87d44c61ce..e24372e1d8 100644
--- a/documentation/html/RESTClient.html
+++ b/documentation/html/RESTClient.html
@@ -10217,7 +10217,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/Static.html b/documentation/html/Static.html
index ae4d56fcf6..bbfe91e8fb 100644
--- a/documentation/html/Static.html
+++ b/documentation/html/Static.html
@@ -391,7 +391,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/classes.list.html b/documentation/html/classes.list.html
index c803560872..04cfe29bae 100644
--- a/documentation/html/classes.list.html
+++ b/documentation/html/classes.list.html
@@ -2984,7 +2984,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_CuratorRecordsAwaitingApproval.vue.html b/documentation/html/components_Curators_CuratorRecordsAwaitingApproval.vue.html
index cfdccb8660..2bebd1b06f 100644
--- a/documentation/html/components_Curators_CuratorRecordsAwaitingApproval.vue.html
+++ b/documentation/html/components_Curators_CuratorRecordsAwaitingApproval.vue.html
@@ -833,7 +833,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_DownLoadRecordsComponents_CuratorSummaryStatistics.vue.html b/documentation/html/components_Curators_DownLoadRecordsComponents_CuratorSummaryStatistics.vue.html
index b96ea4f24a..6be409a62d 100644
--- a/documentation/html/components_Curators_DownLoadRecordsComponents_CuratorSummaryStatistics.vue.html
+++ b/documentation/html/components_Curators_DownLoadRecordsComponents_CuratorSummaryStatistics.vue.html
@@ -199,7 +199,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_DownLoadRecordsComponents_RecordEditsByMonth.vue.html b/documentation/html/components_Curators_DownLoadRecordsComponents_RecordEditsByMonth.vue.html
index fbab801f3b..935f0c3f20 100644
--- a/documentation/html/components_Curators_DownLoadRecordsComponents_RecordEditsByMonth.vue.html
+++ b/documentation/html/components_Curators_DownLoadRecordsComponents_RecordEditsByMonth.vue.html
@@ -202,7 +202,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsCreatedByMonth.vue.html b/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsCreatedByMonth.vue.html
index b0d3006869..15f3a147ce 100644
--- a/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsCreatedByMonth.vue.html
+++ b/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsCreatedByMonth.vue.html
@@ -204,7 +204,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsNeedingReview.vue.html b/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsNeedingReview.vue.html
index 47eb81cb89..03ced4b9c8 100644
--- a/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsNeedingReview.vue.html
+++ b/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsNeedingReview.vue.html
@@ -199,7 +199,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsWithoutDois.vue.html b/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsWithoutDois.vue.html
index d455277d69..be409a71ae 100644
--- a/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsWithoutDois.vue.html
+++ b/documentation/html/components_Curators_DownLoadRecordsComponents_RecordsWithoutDois.vue.html
@@ -205,7 +205,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_HiddenRecords.vue.html b/documentation/html/components_Curators_HiddenRecords.vue.html
index 043c22f32a..825d42136d 100644
--- a/documentation/html/components_Curators_HiddenRecords.vue.html
+++ b/documentation/html/components_Curators_HiddenRecords.vue.html
@@ -299,7 +299,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_IncompleteRecords.vue.html b/documentation/html/components_Curators_IncompleteRecords.vue.html
index 56b951ebed..fbc9d8f8d9 100644
--- a/documentation/html/components_Curators_IncompleteRecords.vue.html
+++ b/documentation/html/components_Curators_IncompleteRecords.vue.html
@@ -385,7 +385,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_MaintenanceRequests.vue.html b/documentation/html/components_Curators_MaintenanceRequests.vue.html
index b9205b32e6..cc802db9a7 100644
--- a/documentation/html/components_Curators_MaintenanceRequests.vue.html
+++ b/documentation/html/components_Curators_MaintenanceRequests.vue.html
@@ -672,7 +672,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_RecentCuratorCreation.vue.html b/documentation/html/components_Curators_RecentCuratorCreation.vue.html
index 57ae88787f..7592d43952 100644
--- a/documentation/html/components_Curators_RecentCuratorCreation.vue.html
+++ b/documentation/html/components_Curators_RecentCuratorCreation.vue.html
@@ -294,7 +294,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_SystemMessages.vue.html b/documentation/html/components_Curators_SystemMessages.vue.html
index 2d2d70c097..87f30e59a2 100644
--- a/documentation/html/components_Curators_SystemMessages.vue.html
+++ b/documentation/html/components_Curators_SystemMessages.vue.html
@@ -474,7 +474,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Curators_UserRecordsAwaitingApproval.vue.html b/documentation/html/components_Curators_UserRecordsAwaitingApproval.vue.html
index bc03f58468..e128d59c00 100644
--- a/documentation/html/components_Curators_UserRecordsAwaitingApproval.vue.html
+++ b/documentation/html/components_Curators_UserRecordsAwaitingApproval.vue.html
@@ -839,7 +839,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Record_Collections.vue.html b/documentation/html/components_Records_Record_Collections.vue.html
index 2f7262bd8d..355305c0a0 100644
--- a/documentation/html/components_Records_Record_Collections.vue.html
+++ b/documentation/html/components_Records_Record_Collections.vue.html
@@ -462,7 +462,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Record_GeneralInfo_SavedSearches.vue.html b/documentation/html/components_Records_Record_GeneralInfo_SavedSearches.vue.html
index a7c154da7b..79c1a46ee6 100644
--- a/documentation/html/components_Records_Record_GeneralInfo_SavedSearches.vue.html
+++ b/documentation/html/components_Records_Record_GeneralInfo_SavedSearches.vue.html
@@ -319,7 +319,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Record_RelatedContent.vue.html b/documentation/html/components_Records_Record_RelatedContent.vue.html
index bfe443255c..60f228344b 100644
--- a/documentation/html/components_Records_Record_RelatedContent.vue.html
+++ b/documentation/html/components_Records_Record_RelatedContent.vue.html
@@ -454,7 +454,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Header_FilterChips.vue.html b/documentation/html/components_Records_Search_Header_FilterChips.vue.html
index 4eb5c670e2..76bed49902 100644
--- a/documentation/html/components_Records_Search_Header_FilterChips.vue.html
+++ b/documentation/html/components_Records_Search_Header_FilterChips.vue.html
@@ -251,7 +251,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Header_Pagination.vue.html b/documentation/html/components_Records_Search_Header_Pagination.vue.html
index 2cbd765b3e..66572d54c7 100644
--- a/documentation/html/components_Records_Search_Header_Pagination.vue.html
+++ b/documentation/html/components_Records_Search_Header_Pagination.vue.html
@@ -262,7 +262,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Header_Sorting.vue.html b/documentation/html/components_Records_Search_Header_Sorting.vue.html
index 7e7c9ddcba..76321fa7f6 100644
--- a/documentation/html/components_Records_Search_Header_Sorting.vue.html
+++ b/documentation/html/components_Records_Search_Header_Sorting.vue.html
@@ -219,7 +219,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_AdvancedSearchDialogBox.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_AdvancedSearchDialogBox.vue.html
index c8e4973227..034bd24ec2 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_AdvancedSearchDialogBox.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_AdvancedSearchDialogBox.vue.html
@@ -487,7 +487,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Countries.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Countries.vue.html
index a203a04ed4..5caad10fe2 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Countries.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Countries.vue.html
@@ -225,7 +225,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Domains.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Domains.vue.html
index e759f31cb9..57c9129579 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Domains.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Domains.vue.html
@@ -218,7 +218,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Licences.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Licences.vue.html
index 3a3e5fe5a3..27f67ba891 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Licences.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Licences.vue.html
@@ -218,7 +218,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_ObjectTypes.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_ObjectTypes.vue.html
index 020c3665ca..431b8ebd41 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_ObjectTypes.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_ObjectTypes.vue.html
@@ -220,7 +220,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Organisations.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Organisations.vue.html
index f051ddfca1..fe647c3c86 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Organisations.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Organisations.vue.html
@@ -229,7 +229,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Subject.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Subject.vue.html
index 3010e0a0a0..13494c9f07 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Subject.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Subject.vue.html
@@ -218,7 +218,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Taxonomies.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Taxonomies.vue.html
index 6b99f20938..a04916de20 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Taxonomies.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_Taxonomies.vue.html
@@ -224,7 +224,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_UserDefinedTag.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_UserDefinedTag.vue.html
index e26686168f..f7cf8af867 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_UserDefinedTag.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GeneralComponents_UserDefinedTag.vue.html
@@ -224,7 +224,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_DatabaseRule.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_DatabaseRule.vue.html
index 28edb01249..132627d34e 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_DatabaseRule.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_DatabaseRule.vue.html
@@ -315,7 +315,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_FairassistRule.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_FairassistRule.vue.html
index f6a04f88f2..4e214dde44 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_FairassistRule.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_FairassistRule.vue.html
@@ -252,7 +252,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_GeneralRule.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_GeneralRule.vue.html
index 6092e2cbce..8f71a07faf 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_GeneralRule.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_GeneralRule.vue.html
@@ -272,7 +272,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_GroupCtrlSlot.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_GroupCtrlSlot.vue.html
index 44be1009b4..155bbf9311 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_GroupCtrlSlot.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_GroupCtrlSlot.vue.html
@@ -199,7 +199,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_PolicyRule.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_PolicyRule.vue.html
index ac79a5c303..0bd0cac247 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_PolicyRule.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_PolicyRule.vue.html
@@ -328,7 +328,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_StandardRule.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_StandardRule.vue.html
index 3f5979be98..e6f0b286ff 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_StandardRule.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderComponents_GroupCtrlSlot_StandardRule.vue.html
@@ -230,7 +230,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderView.vue.html b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderView.vue.html
index 7d1cdcdd38..763461866b 100644
--- a/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderView.vue.html
+++ b/documentation/html/components_Records_Search_Input_AdvancedSearch_QueryBuilderView.vue.html
@@ -676,7 +676,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_FilterAutocomplete.vue.html b/documentation/html/components_Records_Search_Input_FilterAutocomplete.vue.html
index 950f4d0b2f..40ba5b84ab 100644
--- a/documentation/html/components_Records_Search_Input_FilterAutocomplete.vue.html
+++ b/documentation/html/components_Records_Search_Input_FilterAutocomplete.vue.html
@@ -376,7 +376,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_FilterButton.vue.html b/documentation/html/components_Records_Search_Input_FilterButton.vue.html
index a448b2ac84..26440398a5 100644
--- a/documentation/html/components_Records_Search_Input_FilterButton.vue.html
+++ b/documentation/html/components_Records_Search_Input_FilterButton.vue.html
@@ -287,7 +287,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_Input_SearchInput.vue.html b/documentation/html/components_Records_Search_Input_SearchInput.vue.html
index 9d8a522d27..a55bf493eb 100644
--- a/documentation/html/components_Records_Search_Input_SearchInput.vue.html
+++ b/documentation/html/components_Records_Search_Input_SearchInput.vue.html
@@ -284,7 +284,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_SaveSearch_SaveSearchButton.vue.html b/documentation/html/components_Records_Search_SaveSearch_SaveSearchButton.vue.html
index 8c222b410a..01e9d276fc 100644
--- a/documentation/html/components_Records_Search_SaveSearch_SaveSearchButton.vue.html
+++ b/documentation/html/components_Records_Search_SaveSearch_SaveSearchButton.vue.html
@@ -182,7 +182,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_SaveSearch_SaveSearchStepper.vue.html b/documentation/html/components_Records_Search_SaveSearch_SaveSearchStepper.vue.html
index 98d419e011..862352eb21 100644
--- a/documentation/html/components_Records_Search_SaveSearch_SaveSearchStepper.vue.html
+++ b/documentation/html/components_Records_Search_SaveSearch_SaveSearchStepper.vue.html
@@ -464,7 +464,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_OrganisationStepper.vue.html b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_OrganisationStepper.vue.html
index f0c0237794..be81b14644 100644
--- a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_OrganisationStepper.vue.html
+++ b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_OrganisationStepper.vue.html
@@ -259,7 +259,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_PolicyStepper.vue.html b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_PolicyStepper.vue.html
index 032b16b49b..1624444bac 100644
--- a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_PolicyStepper.vue.html
+++ b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_PolicyStepper.vue.html
@@ -259,7 +259,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_ResultCard.vue.html b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_ResultCard.vue.html
index efdea90e51..8b52dca4bc 100644
--- a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_ResultCard.vue.html
+++ b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_ResultCard.vue.html
@@ -259,7 +259,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_StepperDialogHeader.vue.html b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_StepperDialogHeader.vue.html
index 510f82584d..0589679282 100644
--- a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_StepperDialogHeader.vue.html
+++ b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_StepperDialogHeader.vue.html
@@ -182,7 +182,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_UserStepper.vue.html b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_UserStepper.vue.html
index 29d7b64173..8f8ef85f8f 100644
--- a/documentation/html/components_Records_Search_SaveSearch_StepperComponents_UserStepper.vue.html
+++ b/documentation/html/components_Records_Search_SaveSearch_StepperComponents_UserStepper.vue.html
@@ -211,7 +211,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/components_Users_Profiles_Private_ViewSavedSearchesTable.vue.html b/documentation/html/components_Users_Profiles_Private_ViewSavedSearchesTable.vue.html
index 9e3b9726e3..44f80458e9 100644
--- a/documentation/html/components_Users_Profiles_Private_ViewSavedSearchesTable.vue.html
+++ b/documentation/html/components_Users_Profiles_Private_ViewSavedSearchesTable.vue.html
@@ -509,7 +509,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/global.html b/documentation/html/global.html
index 5d7c0f624f..4f6c4faa00 100644
--- a/documentation/html/global.html
+++ b/documentation/html/global.html
@@ -2964,7 +2964,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/index.html b/documentation/html/index.html
index 14c5709d59..4252cde104 100644
--- a/documentation/html/index.html
+++ b/documentation/html/index.html
@@ -216,7 +216,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/lib_Client_ExternalClients.js.html b/documentation/html/lib_Client_ExternalClients.js.html
index ad87fa86ca..a8fd4a9792 100644
--- a/documentation/html/lib_Client_ExternalClients.js.html
+++ b/documentation/html/lib_Client_ExternalClients.js.html
@@ -256,7 +256,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/lib_Client_RESTClient.js.html b/documentation/html/lib_Client_RESTClient.js.html
index ce0a02737b..d520e4818b 100644
--- a/documentation/html/lib_Client_RESTClient.js.html
+++ b/documentation/html/lib_Client_RESTClient.js.html
@@ -1284,7 +1284,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/lib_GraphClient_GraphClient.js.html b/documentation/html/lib_GraphClient_GraphClient.js.html
index 51d6c37cb4..203765670b 100644
--- a/documentation/html/lib_GraphClient_GraphClient.js.html
+++ b/documentation/html/lib_GraphClient_GraphClient.js.html
@@ -285,7 +285,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/lib_GraphClient_GraphClientSEO.js.html b/documentation/html/lib_GraphClient_GraphClientSEO.js.html
index 207f0fbec1..138198ffb3 100644
--- a/documentation/html/lib_GraphClient_GraphClientSEO.js.html
+++ b/documentation/html/lib_GraphClient_GraphClientSEO.js.html
@@ -286,7 +286,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/module-Home.html b/documentation/html/module-Home.html
index 5c22b1c6ff..a50b095197 100644
--- a/documentation/html/module-Home.html
+++ b/documentation/html/module-Home.html
@@ -220,7 +220,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/module-Login.html b/documentation/html/module-Login.html
index 262cb08329..88c320c669 100644
--- a/documentation/html/module-Login.html
+++ b/documentation/html/module-Login.html
@@ -220,7 +220,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/module-NewRecord.html b/documentation/html/module-NewRecord.html
index b59425fc4c..e7e3892713 100644
--- a/documentation/html/module-NewRecord.html
+++ b/documentation/html/module-NewRecord.html
@@ -220,7 +220,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/module-Pagination.html b/documentation/html/module-Pagination.html
index 9f2505ffd7..ef08864178 100644
--- a/documentation/html/module-Pagination.html
+++ b/documentation/html/module-Pagination.html
@@ -527,7 +527,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/module-Signup.html b/documentation/html/module-Signup.html
index a720d14734..faf41c1bfb 100644
--- a/documentation/html/module-Signup.html
+++ b/documentation/html/module-Signup.html
@@ -220,7 +220,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/module-User.html b/documentation/html/module-User.html
index ba94522cc7..a3df91ad22 100644
--- a/documentation/html/module-User.html
+++ b/documentation/html/module-User.html
@@ -228,7 +228,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/modules.list.html b/documentation/html/modules.list.html
index 1089d26429..a45d2d1e5f 100644
--- a/documentation/html/modules.list.html
+++ b/documentation/html/modules.list.html
@@ -2984,7 +2984,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/namespaces.list.html b/documentation/html/namespaces.list.html
index 07891c03fe..38922a7a27 100644
--- a/documentation/html/namespaces.list.html
+++ b/documentation/html/namespaces.list.html
@@ -2984,7 +2984,7 @@ Search results
Documentation generated by JSDoc 4.0.5
- on Thu Jul 16th 2026
+ on Tue Jul 28th 2026
using the DocStrap template.
diff --git a/documentation/html/quicksearch.html b/documentation/html/quicksearch.html
index c101744fc1..d1c81c13ec 100644
--- a/documentation/html/quicksearch.html
+++ b/documentation/html/quicksearch.html
@@ -7,7 +7,7 @@
+
+
diff --git a/tests/setup.js b/tests/setup.js
index c160aa508f..92f5360909 100644
--- a/tests/setup.js
+++ b/tests/setup.js
@@ -190,19 +190,21 @@ global.IntersectionObserver = class IntersectionObserver {
// --- 3. window.matchMedia Polyfill ---
// CRITICAL for $vuetify.display (mdAndDown, lgAndUp, etc.)
// We default 'matches' to false, meaning the test assumes "Desktop" view by default.
-Object.defineProperty(window, "matchMedia", {
- writable: true,
- value: vi.fn().mockImplementation((query) => ({
- matches: false,
- media: query,
- onchange: null,
- addListener: vi.fn(), // deprecated
- removeListener: vi.fn(), // deprecated
- addEventListener: vi.fn(),
- removeEventListener: vi.fn(),
- dispatchEvent: vi.fn(),
- })),
-});
+if (typeof window !== "undefined") {
+ Object.defineProperty(window, "matchMedia", {
+ writable: true,
+ value: vi.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(), // deprecated
+ removeListener: vi.fn(), // deprecated
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ })),
+ });
+}
// --- 4. Mock global CSS support ---
// Prevents errors in some Vuetify computed properties
@@ -224,14 +226,16 @@ if (!Object.groupBy) {
};
}
-// Prevent accidental network access to local API endpoints in unit tests.
+// Prevent unit tests from reaching API and embedded-service endpoints.
const nativeFetch = globalThis.fetch ? globalThis.fetch.bind(globalThis) : null;
const mockedFetch = async (input, init) => {
const url = typeof input === "string" ? input : (input && input.url) || "";
if (
url.includes("localhost:3000") ||
url.includes("127.0.0.1:3000") ||
- url.includes("undefined/graphql")
+ url.includes("undefined/graphql") ||
+ url.includes("dev-api.fairsharing.org") ||
+ url.includes("aicc.uksouth.cloudapp.azure.com")
) {
return new Response(JSON.stringify({}), {
status: 200,
@@ -246,14 +250,15 @@ if (typeof window !== "undefined") {
window.fetch = globalThis.fetch;
}
-// Short-circuit GraphQL requests that would otherwise target localhost in tests.
+// Short-circuit GraphQL requests to configured development endpoints in tests.
const originalGetData = GraphQLClient.prototype.getData;
GraphQLClient.prototype.getData = async function (queryString) {
const url = this.url || "";
if (
url.includes("localhost:3000") ||
url.includes("127.0.0.1:3000") ||
- url.includes("undefined/graphql")
+ url.includes("undefined/graphql") ||
+ url.includes("dev-api.fairsharing.org")
) {
return { data: { data: {} } };
}
@@ -266,6 +271,7 @@ RESTClient.prototype.executeQuery = async function (query) {
if (
url.includes("localhost:3000") ||
url.includes("127.0.0.1:3000") ||
+ url.includes("dev-api.fairsharing.org") ||
url.startsWith("undefined") ||
url.includes("/undefined/")
) {
diff --git a/tests/unit/components/ChatBot/FionaChat.spec.js b/tests/unit/components/ChatBot/FionaChat.spec.js
index 926e6f9911..80d4aa1c2e 100644
--- a/tests/unit/components/ChatBot/FionaChat.spec.js
+++ b/tests/unit/components/ChatBot/FionaChat.spec.js
@@ -12,6 +12,9 @@ describe("FionaChat", () => {
document.body.innerHTML = '';
wrapper = mount(FionaChat, {
attachTo: "#app",
+ data: () => ({
+ chatUrl: "about:blank#",
+ }),
global: {
stubs: {
VBtn: {
diff --git a/tests/unit/components/Curators/DownLoadRecordComponents/CuratorSummaryStatistics.spec.js b/tests/unit/components/Curators/DownLoadRecordComponents/CuratorSummaryStatistics.spec.js
index c149ffa408..8df7f01867 100644
--- a/tests/unit/components/Curators/DownLoadRecordComponents/CuratorSummaryStatistics.spec.js
+++ b/tests/unit/components/Curators/DownLoadRecordComponents/CuratorSummaryStatistics.spec.js
@@ -15,7 +15,9 @@ const { mockClientInstance } = vi.hoisted(() => {
vi.mock("@/lib/GraphClient/GraphClient", () => {
return {
- default: vi.fn().mockImplementation(() => mockClientInstance),
+ default: vi.fn(function () {
+ return mockClientInstance;
+ }),
};
});
diff --git a/tests/unit/components/Curators/DownLoadRecordComponents/RecordEditsByMonth.spec.js b/tests/unit/components/Curators/DownLoadRecordComponents/RecordEditsByMonth.spec.js
index f4eb14cbcc..f2c70c5e98 100644
--- a/tests/unit/components/Curators/DownLoadRecordComponents/RecordEditsByMonth.spec.js
+++ b/tests/unit/components/Curators/DownLoadRecordComponents/RecordEditsByMonth.spec.js
@@ -10,9 +10,11 @@ const { mockGetEditByMonth } = vi.hoisted(() => ({
// 2. Mock RestClient
vi.mock("@/lib/Client/RESTClient", () => {
return {
- default: vi.fn().mockImplementation(() => ({
- getEditByMonth: mockGetEditByMonth,
- })),
+ default: vi.fn(function () {
+ return {
+ getEditByMonth: mockGetEditByMonth,
+ };
+ }),
};
});
diff --git a/tests/unit/components/Curators/DownLoadRecordComponents/RecordsCreatedByMonth.spec.js b/tests/unit/components/Curators/DownLoadRecordComponents/RecordsCreatedByMonth.spec.js
index f169466e4e..9746ded3d1 100644
--- a/tests/unit/components/Curators/DownLoadRecordComponents/RecordsCreatedByMonth.spec.js
+++ b/tests/unit/components/Curators/DownLoadRecordComponents/RecordsCreatedByMonth.spec.js
@@ -9,9 +9,11 @@ const { mockGetRecordCreatedByMonth } = vi.hoisted(() => ({
vi.mock("@/lib/Client/RESTClient", () => {
return {
- default: vi.fn().mockImplementation(() => ({
- getRecordCreatedByMonth: mockGetRecordCreatedByMonth,
- })),
+ default: vi.fn(function () {
+ return {
+ getRecordCreatedByMonth: mockGetRecordCreatedByMonth,
+ };
+ }),
};
});
diff --git a/tests/unit/components/Curators/DownLoadRecordComponents/RecordsNeedingReview.spec.js b/tests/unit/components/Curators/DownLoadRecordComponents/RecordsNeedingReview.spec.js
index f5970fba10..3b363a0d9c 100644
--- a/tests/unit/components/Curators/DownLoadRecordComponents/RecordsNeedingReview.spec.js
+++ b/tests/unit/components/Curators/DownLoadRecordComponents/RecordsNeedingReview.spec.js
@@ -13,7 +13,9 @@ const { mockClientInstance } = vi.hoisted(() => ({
vi.mock("@/lib/GraphClient/GraphClient", () => {
return {
- default: vi.fn().mockImplementation(() => mockClientInstance),
+ default: vi.fn(function () {
+ return mockClientInstance;
+ }),
};
});
diff --git a/tests/unit/components/Curators/DownLoadRecordComponents/RecordsWithoutDois.spec.js b/tests/unit/components/Curators/DownLoadRecordComponents/RecordsWithoutDois.spec.js
index 1736be80fc..858e322d52 100644
--- a/tests/unit/components/Curators/DownLoadRecordComponents/RecordsWithoutDois.spec.js
+++ b/tests/unit/components/Curators/DownLoadRecordComponents/RecordsWithoutDois.spec.js
@@ -9,9 +9,11 @@ const { mockGetRecordsWoDOIs } = vi.hoisted(() => ({
vi.mock("@/lib/Client/RESTClient", () => {
return {
- default: vi.fn().mockImplementation(() => ({
- getRecordsWoDOIs: mockGetRecordsWoDOIs,
- })),
+ default: vi.fn(function () {
+ return {
+ getRecordsWoDOIs: mockGetRecordsWoDOIs,
+ };
+ }),
};
});
diff --git a/tests/unit/components/Curators/MaintenanceRequests.spec.js b/tests/unit/components/Curators/MaintenanceRequests.spec.js
index 9f065d5fce..5635086f63 100644
--- a/tests/unit/components/Curators/MaintenanceRequests.spec.js
+++ b/tests/unit/components/Curators/MaintenanceRequests.spec.js
@@ -24,13 +24,17 @@ const restClientMock = vi.hoisted(() => ({
// Mock RESTClient and GraphClient classes
vi.mock("@/lib/Client/RESTClient.js", () => {
return {
- default: vi.fn().mockImplementation(() => restClientMock),
+ default: vi.fn(function () {
+ return restClientMock;
+ }),
};
});
vi.mock("@/lib/GraphClient/GraphClient", () => {
return {
- default: vi.fn().mockImplementation(() => graphClientMock),
+ default: vi.fn(function () {
+ return graphClientMock;
+ }),
};
});
diff --git a/tests/unit/components/Curators/UserRecordsAwaitingApproval.spec.js b/tests/unit/components/Curators/UserRecordsAwaitingApproval.spec.js
index 37d758281f..53849f3769 100644
--- a/tests/unit/components/Curators/UserRecordsAwaitingApproval.spec.js
+++ b/tests/unit/components/Curators/UserRecordsAwaitingApproval.spec.js
@@ -1,4 +1,4 @@
-import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
+import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { shallowMount } from "@vue/test-utils";
import sinon from "sinon";
import Vuex from "vuex";
@@ -119,7 +119,7 @@ describe("Curator -> UserRecordsAwaitingApproval.vue", () => {
});
});
- afterEach(() => {
+ afterAll(() => {
graphStub.restore();
restStub.restore();
});
@@ -168,7 +168,6 @@ describe("Curator -> UserRecordsAwaitingApproval.vue", () => {
expect(wrapper.vm.approvalRequiredProcessed[0].curator).toMatch("Michae");
// TODO: Check that the curator are updated in the record
- restStub = sinon.stub(Client.prototype, "executeQuery");
restStub.returns({ data: { error: { response: "Im an error" } } });
await wrapper.vm.assignCurator(11, 1, "Michael Smith");
expect(wrapper.vm.error.recordID).toBe(11);
@@ -179,7 +178,6 @@ describe("Curator -> UserRecordsAwaitingApproval.vue", () => {
it("can approve a record", async () => {
//Correct approval
wrapper.vm.dialogs.recordID = 100;
- restStub = sinon.stub(Client.prototype, "executeQuery");
restStub.returns({ data: { error: false } });
await wrapper.vm.confirmApproval();
expect(wrapper.vm.approvalRequiredProcessed.length).toBe(2);
@@ -193,9 +191,7 @@ describe("Curator -> UserRecordsAwaitingApproval.vue", () => {
expect(wrapper.vm.dialogs.approveChanges).toBe(false);
expect(wrapper.vm.approvalRequiredProcessed[0].id).toBe(11);
//There is an error in the Client query
- restStub.restore();
wrapper.vm.dialogs.recordID = 101;
- restStub = sinon.stub(Client.prototype, "executeQuery");
restStub.returns({ data: { error: { response: { data: "error" } } } });
await wrapper.vm.confirmApproval();
expect(wrapper.vm.approvalRequiredProcessed.length).toBe(1);
@@ -208,9 +204,7 @@ describe("Curator -> UserRecordsAwaitingApproval.vue", () => {
expect(wrapper.vm.approvalRequiredProcessed.length).toBe(1);
expect(wrapper.vm.dialogs.approveChanges).toBe(false);
- restStub.restore();
wrapper.vm.dialogs.recordID = 102;
- restStub = sinon.stub(Client.prototype, "executeQuery");
restStub.returns({ data: { error: { response: { data: "error" } } } });
await wrapper.vm.confirmApproval();
expect(wrapper.vm.approvalRequiredProcessed.length).toBe(1);
@@ -220,7 +214,6 @@ describe("Curator -> UserRecordsAwaitingApproval.vue", () => {
it("can delete a record", async () => {
//There is an error in the Client query
wrapper.vm.dialogs.recordID = 102;
- restStub = sinon.stub(Client.prototype, "executeQuery");
restStub.returns({ data: { error: { response: { data: "error" } } } });
await wrapper.vm.confirmDelete();
expect(wrapper.vm.approvalRequiredProcessed.length).toBe(1);
@@ -230,8 +223,6 @@ describe("Curator -> UserRecordsAwaitingApproval.vue", () => {
//Correct deleted
wrapper.vm.dialogs.recordID = 102;
- restStub.restore();
- restStub = sinon.stub(Client.prototype, "executeQuery");
restStub.returns({ data: { error: false } });
await wrapper.vm.confirmDelete();
expect(wrapper.vm.approvalRequiredProcessed.length).toBe(0);
diff --git a/tests/unit/components/Editor/DataAccess/EditDataAccess.spec.js b/tests/unit/components/Editor/DataAccess/EditDataAccess.spec.js
index 8f484e8eab..39a119533f 100644
--- a/tests/unit/components/Editor/DataAccess/EditDataAccess.spec.js
+++ b/tests/unit/components/Editor/DataAccess/EditDataAccess.spec.js
@@ -165,6 +165,8 @@ describe("Edit -> DataAccess.vue", function () {
it("handles save and exit with redirect", async () => {
const btn = { textContent: "Save and exit" };
const pushCalls = $router.push.mock.calls.length;
+ restStub = sinon.stub(RestClient.prototype, "executeQuery");
+ restStub.returns({ data: "GG!" });
await wrapper.vm.saveRecord(true, btn);
expect($router.push).toHaveBeenCalledTimes(pushCalls + 1);
});
diff --git a/tests/unit/components/Navigation/Header.spec.js b/tests/unit/components/Navigation/Header.spec.js
index 5a587035a9..fa97f0dad7 100644
--- a/tests/unit/components/Navigation/Header.spec.js
+++ b/tests/unit/components/Navigation/Header.spec.js
@@ -4,6 +4,7 @@ import {createVuetify} from "vuetify";
import Vuex from "vuex";
import Header from "@/components/Navigation/Header.vue";
+import RESTClient from "@/lib/Client/RESTClient.js";
import uiControllerStore from "@/store/uiController.js";
import usersStore from "@/store/users.js";
@@ -120,6 +121,9 @@ describe("Header.vue", function () {
it("logs out and redirects to Login", async () => {
const push = vi.fn();
+ const logoutSpy = vi
+ .spyOn(RESTClient.prototype, "logout")
+ .mockResolvedValue({ error: false });
const localStore = new Vuex.Store({
modules: {
@@ -143,5 +147,6 @@ describe("Header.vue", function () {
await localWrapper.vm.logoutUser();
expect(push).toHaveBeenCalledWith({ name: "Login" });
+ logoutSpy.mockRestore();
});
});
diff --git a/tests/unit/components/Records/Record/GeneralInfo/DOITitle.spec.js b/tests/unit/components/Records/Record/GeneralInfo/DOITitle.spec.js
index 9ab6ff76ff..29fe04f424 100644
--- a/tests/unit/components/Records/Record/GeneralInfo/DOITitle.spec.js
+++ b/tests/unit/components/Records/Record/GeneralInfo/DOITitle.spec.js
@@ -37,7 +37,9 @@ describe("DOITitle.vue", function () {
readAsDataURL,
target: fileContents,
};
- window.Image = vi.fn(() => dummyFileReader);
+ window.Image = vi.fn(function () {
+ return dummyFileReader;
+ });
});
it("can be instantiated", async () => {
diff --git a/tests/unit/components/Records/Record/GeneralInfo/SavedSearches.spec.js b/tests/unit/components/Records/Record/GeneralInfo/SavedSearches.spec.js
index 4afdcd90ec..4d66d1e09c 100644
--- a/tests/unit/components/Records/Record/GeneralInfo/SavedSearches.spec.js
+++ b/tests/unit/components/Records/Record/GeneralInfo/SavedSearches.spec.js
@@ -33,9 +33,11 @@ const { mockUpdateSaveSearch } = vi.hoisted(() => {
// Now Vitest can safely use it here!
vi.mock("@/lib/Client/RESTClient.js", () => ({
- default: vi.fn().mockImplementation(() => ({
- updateSaveSearch: mockUpdateSaveSearch,
- })),
+ default: vi.fn(function () {
+ return {
+ updateSaveSearch: mockUpdateSaveSearch,
+ };
+ }),
}));
describe("SavedSearches.vue", () => {
diff --git a/tests/unit/components/Records/Search/SaveSearch/SaveSearchStepper.spec.js b/tests/unit/components/Records/Search/SaveSearch/SaveSearchStepper.spec.js
index 74507fdc0c..b6f1f75b6f 100644
--- a/tests/unit/components/Records/Search/SaveSearch/SaveSearchStepper.spec.js
+++ b/tests/unit/components/Records/Search/SaveSearch/SaveSearchStepper.spec.js
@@ -11,9 +11,11 @@ const { mockSaveSearchAPI } = vi.hoisted(() => ({
vi.mock("@/lib/Client/RESTClient", () => {
return {
- default: vi.fn(() => ({
- saveSearch: mockSaveSearchAPI,
- })),
+ default: vi.fn(function () {
+ return {
+ saveSearch: mockSaveSearchAPI,
+ };
+ }),
};
});
diff --git a/tests/unit/components/Records/Search/SaveSearch/StepperComponents/PolicyStepper.spec.js b/tests/unit/components/Records/Search/SaveSearch/StepperComponents/PolicyStepper.spec.js
index 1c4a5c7791..7e9c10633f 100644
--- a/tests/unit/components/Records/Search/SaveSearch/StepperComponents/PolicyStepper.spec.js
+++ b/tests/unit/components/Records/Search/SaveSearch/StepperComponents/PolicyStepper.spec.js
@@ -48,6 +48,9 @@ describe("PolicyStepper.vue", () => {
},
saveSearch: {
namespaced: true,
+ mutations: {
+ setPolicySelected: vi.fn(),
+ },
getters: {
getPolicyRecords: getters.getPolicyRecords,
getLoadingStatus: getters.getLoadingStatus,
diff --git a/tests/unit/components/Users/Profiles/Private/ViewSavedSearchesTable.spec.js b/tests/unit/components/Users/Profiles/Private/ViewSavedSearchesTable.spec.js
index 764784a4f3..724d153c46 100644
--- a/tests/unit/components/Users/Profiles/Private/ViewSavedSearchesTable.spec.js
+++ b/tests/unit/components/Users/Profiles/Private/ViewSavedSearchesTable.spec.js
@@ -13,10 +13,12 @@ const { mockDelete, mockUpdate } = vi.hoisted(() => {
vi.mock("@/lib/Client/RESTClient", () => {
return {
- default: vi.fn(() => ({
- deleteSavedSearch: mockDelete,
- updateSaveSearch: mockUpdate,
- })),
+ default: vi.fn(function () {
+ return {
+ deleteSavedSearch: mockDelete,
+ updateSaveSearch: mockUpdate,
+ };
+ }),
};
});
diff --git a/tests/unit/lib/GraphClient/GraphClientSEO.spec.js b/tests/unit/lib/GraphClient/GraphClientSEO.spec.js
new file mode 100644
index 0000000000..5dda924373
--- /dev/null
+++ b/tests/unit/lib/GraphClient/GraphClientSEO.spec.js
@@ -0,0 +1,91 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import axios from "axios";
+import GraphClientSEO from "@/lib/GraphClient/GraphClientSEO.js";
+
+vi.mock("axios", () => ({
+ default: vi.fn(),
+}));
+
+describe("GraphClientSEO", () => {
+ let client;
+
+ beforeEach(() => {
+ GraphClientSEO._instance = undefined;
+ process.env.VITE_API_ENDPOINT = "https://api.example.org/";
+ process.env.VITE_CLIENT_ID = "client-id";
+ axios.mockReset();
+ client = new GraphClientSEO();
+ });
+
+ it("initializes headers, normalizes the endpoint, and remains a singleton", () => {
+ expect(client.url).toBe("https://api.example.org/graphql");
+ expect(client.headers["X-Client-Id"]).toBe("client-id");
+ expect(new GraphClientSEO()).toBe(client);
+
+ client.setHeader("token");
+ expect(client.headers.Authorization).toBe("Bearer token");
+ });
+
+ it("omits the optional client ID header", () => {
+ GraphClientSEO._instance = undefined;
+ delete process.env.VITE_CLIENT_ID;
+
+ const clientWithoutId = new GraphClientSEO();
+
+ expect(clientWithoutId.headers).not.toHaveProperty("X-Client-Id");
+ });
+
+ it("builds parameters, fragments, and nested fields", () => {
+ const query = {
+ queryName: "records",
+ queryParam: {
+ active: true,
+ page: 2,
+ name: "Example",
+ filter: '{status: "ready"}',
+ ids: [1, "two"],
+ },
+ fields: [
+ "id",
+ { $ref: "keywords" },
+ { $ref: "objectTypes" },
+ {
+ name: "owner",
+ fields: ["name", { queryName: "profile", fields: ["id"] }],
+ },
+ ],
+ };
+
+ const output = client.buildQuery(query);
+
+ expect(output).toContain("records(");
+ expect(output).toContain("active:true");
+ expect(output).toContain("page:2");
+ expect(output).toContain('name:"Example"');
+ expect(output).toContain('filter:{status: "ready"}');
+ expect(output).toContain('ids:[1,"two"]');
+ expect(output).toContain("owner{name profile{ id}}");
+ expect(output).toContain("objectTypes{");
+ expect(client.buildQuery({ queryName: "ping" })).toBe("ping");
+ });
+
+ it("posts queries and returns GraphQL data or errors", async () => {
+ axios.mockResolvedValueOnce({ data: { data: { records: [1] } } });
+
+ await expect(
+ client.executeQuery({ queryName: "records", fields: ["id"] }),
+ ).resolves.toEqual({ records: [1] });
+ expect(axios).toHaveBeenCalledWith({
+ method: "post",
+ url: "https://api.example.org/graphql",
+ data: { query: "{records{ id}}" },
+ headers: client.headers,
+ });
+
+ axios.mockResolvedValueOnce({ data: { errors: [{ message: "bad query" }] } });
+ await expect(client.executeQuery({ queryName: "broken" })).resolves.toEqual([
+ { message: "bad query" },
+ ]);
+ });
+});
diff --git a/tests/unit/lib/Prerender/prerenderUtils.spec.js b/tests/unit/lib/Prerender/prerenderUtils.spec.js
new file mode 100644
index 0000000000..3c6f95a979
--- /dev/null
+++ b/tests/unit/lib/Prerender/prerenderUtils.spec.js
@@ -0,0 +1,74 @@
+// @vitest-environment node
+
+import fs from "node:fs";
+import path from "node:path";
+import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
+
+const mockPaths = vi.hoisted(() => {
+ const root = "/tmp/fairsharing-prerender-utils-tests";
+ return {
+ root,
+ records: `${root}/records.json`,
+ organisations: `${root}/organisations.json`,
+ jsonld: `${root}/jsonld`,
+ };
+});
+
+vi.mock("@/lib/Prerender/build-context.json", () => ({
+ default: {
+ recordsFile: mockPaths.records,
+ organisationsFile: mockPaths.organisations,
+ jsonldDir: mockPaths.jsonld,
+ },
+}));
+
+import {
+ readGeneratedOrganisations,
+ readGeneratedRecords,
+ readGeneratedSchemaOrg,
+} from "@/lib/Prerender/prerenderUtils.server.js";
+import { normalizeDoi } from "@/lib/Prerender/prerenderUtils.shared.js";
+
+describe("prerender utilities", () => {
+ beforeAll(() => {
+ fs.mkdirSync(mockPaths.jsonld, { recursive: true });
+ fs.writeFileSync(mockPaths.records, JSON.stringify([{ id: 1 }]));
+ fs.writeFileSync(
+ mockPaths.organisations,
+ JSON.stringify([{ id: 2, name: "Example" }]),
+ );
+ fs.writeFileSync(
+ path.join(mockPaths.jsonld, "1.jsonld"),
+ JSON.stringify({ "@type": "Dataset" }),
+ );
+ });
+
+ afterAll(() => {
+ fs.rmSync(mockPaths.root, { recursive: true, force: true });
+ });
+
+ it("reads generated records, organisations, and schema.org data", () => {
+ expect(readGeneratedRecords()).toEqual([{ id: 1 }]);
+ expect(readGeneratedOrganisations()).toEqual([{ id: 2, name: "Example" }]);
+ expect(readGeneratedSchemaOrg(1)).toEqual({ "@type": "Dataset" });
+ });
+
+ it("returns the schema fallback when a generated file is absent", () => {
+ const warning = vi.spyOn(console, "warn").mockImplementation(() => {});
+
+ expect(readGeneratedSchemaOrg("missing")).toBeNull();
+ expect(warning).toHaveBeenCalledWith(
+ expect.stringContaining("missing.jsonld"),
+ );
+
+ warning.mockRestore();
+ });
+
+ it.each([
+ [" https://doi.org/10.1234/example/ ", "10.1234/example"],
+ ["http://dx.doi.org/10.1234%2Fencoded", "10.1234/encoded"],
+ ["doi:/10.1234/example/", "10.1234/example"],
+ ])("normalizes DOI value %s", (input, expected) => {
+ expect(normalizeDoi(input)).toBe(expected);
+ });
+});
diff --git a/tests/unit/pages/data.spec.js b/tests/unit/pages/data.spec.js
new file mode 100644
index 0000000000..f9115ec730
--- /dev/null
+++ b/tests/unit/pages/data.spec.js
@@ -0,0 +1,119 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const fixtures = vi.hoisted(() => ({
+ records: [
+ {
+ id: 1,
+ doi: "10.1234/example",
+ name: "Example record",
+ description: "Record description",
+ },
+ { id: 2, doi: "", name: "Second record", description: "" },
+ { id: "invalid", doi: "10.1234/other", name: "Other record" },
+ ],
+ organisations: [
+ { id: 1, name: "First organisation" },
+ { id: 2, name: "Second organisation" },
+ ],
+ schemaOrg: { "@type": "Dataset" },
+}));
+
+vi.mock("@/lib/Prerender/build-context.json", () => ({
+ default: { skipFull: false, batch: 1, batchSize: 2 },
+}));
+vi.mock("@/lib/Prerender/prerenderUtils.server.js", () => ({
+ readGeneratedRecords: vi.fn(async () => fixtures.records),
+ readGeneratedOrganisations: vi.fn(async () => fixtures.organisations),
+ readGeneratedSchemaOrg: vi.fn(() => fixtures.schemaOrg),
+}));
+
+import { data as recordData } from "@/pages/all/+data.js";
+import { onBeforePrerenderStart } from "@/pages/all/+onBeforePrerenderStart.js";
+import { data as organisationsData } from "@/pages/organisations/+data.js";
+import { data as organisationData } from "@/pages/organisations/@id/+data.js";
+
+describe("prerender page data", () => {
+ beforeEach(() => {
+ vi.stubEnv("SSR", true);
+ });
+
+ it("returns empty metadata for empty and unknown record routes", async () => {
+ await expect(
+ recordData({ routeParams: { "*": "index" } }),
+ ).resolves.toMatchObject({ record: null, schemaOrg: null });
+ await expect(
+ recordData({ routeParams: { "*": "not-found" } }),
+ ).resolves.toMatchObject({ record: null, canonicalUrl: null });
+ });
+
+ it("finds numeric and DOI record routes and builds canonical metadata", async () => {
+ const numeric = await recordData({ routeParams: { "*": "1" } });
+ const doiAlias = await recordData({ routeParams: { "*": "example" } });
+
+ expect(numeric).toMatchObject({
+ record: fixtures.records[0],
+ schemaOrg: fixtures.schemaOrg,
+ canonicalUrl: "https://fairsharing.org/10.1234/example",
+ seoTitle: "Example record | FAIRsharing",
+ seoDescription: "Record description",
+ });
+ expect(doiAlias.record).toEqual(fixtures.records[0]);
+ });
+
+ it("returns record-only data in the browser", async () => {
+ vi.stubEnv("SSR", false);
+
+ await expect(recordData({ routeParams: { "*": "2" } })).resolves.toEqual({
+ record: fixtures.records[1],
+ schemaOrg: null,
+ });
+ });
+
+ it("builds unique static, record, DOI, and organisation prerender routes", async () => {
+ const log = vi.spyOn(console, "log").mockImplementation(() => {});
+ const routes = await onBeforePrerenderStart();
+
+ expect(routes).toContain("/");
+ expect(routes).toContain("/1");
+ expect(routes).toContain("/10.1234/example");
+ expect(routes).toContain("/example");
+ expect(routes).toContain("/2");
+ expect(routes).toContain("/organisations/2");
+ expect(new Set(routes).size).toBe(routes.length);
+ expect(log).toHaveBeenCalledWith(expect.stringContaining("Building Batch #1"));
+
+ log.mockRestore();
+ });
+
+ it("logs when a batch contains no record routes", async () => {
+ const records = [...fixtures.records];
+ const organisations = [...fixtures.organisations];
+ fixtures.records.splice(0);
+ fixtures.organisations.splice(0);
+ const log = vi.spyOn(console, "log").mockImplementation(() => {});
+
+ const routes = await onBeforePrerenderStart();
+
+ expect(routes).toContain("/");
+ expect(log).toHaveBeenCalledWith("No records found for batch #1.");
+
+ fixtures.records.push(...records);
+ fixtures.organisations.push(...organisations);
+ log.mockRestore();
+ });
+
+ it("returns organisation collections and selects an organisation by ID", async () => {
+ await expect(organisationsData()).resolves.toEqual({
+ organisations: fixtures.organisations,
+ });
+ await expect(
+ organisationData({ routeParams: { id: "2" } }),
+ ).resolves.toEqual({ organisation: fixtures.organisations[1] });
+ await expect(
+ organisationData({ routeParams: {} }),
+ ).resolves.toEqual({ organisation: null });
+ await expect(
+ organisationData({ routeParams: { id: "99" } }),
+ ).resolves.toEqual({ organisation: null });
+ });
+});
diff --git a/tests/unit/pages/indexData.spec.js b/tests/unit/pages/indexData.spec.js
new file mode 100644
index 0000000000..e0e0786cf8
--- /dev/null
+++ b/tests/unit/pages/indexData.spec.js
@@ -0,0 +1,47 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({
+ getHomepageJsonld: vi.fn(),
+ setConfig: vi.fn(),
+}));
+
+vi.mock("@/lib/Client/RESTClient.js", () => ({
+ default: vi.fn(function () {
+ return { getHomepageJsonld: mocks.getHomepageJsonld };
+ }),
+}));
+vi.mock("vike-vue/useConfig", () => ({
+ useConfig: () => mocks.setConfig,
+}));
+
+import { data } from "@/pages/index/+data.js";
+
+describe("homepage data", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("adds non-empty schema.org data to the page head", async () => {
+ mocks.getHomepageJsonld.mockResolvedValue({
+ "@type": "WebSite",
+ description: "",
+ });
+
+ const result = await data();
+ const head = mocks.setConfig.mock.calls[0][0].Head;
+
+ expect(result.JSONLD).toEqual({
+ "@type": "WebSite",
+ description: "",
+ });
+ expect(head.type).toBe("script");
+ expect(head.props.innerHTML).toContain("\\u003cunsafe>");
+ });
+
+ it("returns null and leaves the head unchanged without schema.org data", async () => {
+ mocks.getHomepageJsonld.mockResolvedValue(null);
+
+ await expect(data()).resolves.toEqual({ JSONLD: null });
+ expect(mocks.setConfig).not.toHaveBeenCalled();
+ });
+});
diff --git a/tests/unit/pages/metadata.spec.js b/tests/unit/pages/metadata.spec.js
new file mode 100644
index 0000000000..2396dbe63f
--- /dev/null
+++ b/tests/unit/pages/metadata.spec.js
@@ -0,0 +1,162 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const pageData = vi.hoisted(() => ({ current: {} }));
+
+vi.mock("vike-vue/useData", () => ({
+ useData: () => pageData.current,
+}));
+vi.mock("vike-vue/config", () => ({ default: { name: "vike-vue" } }));
+vi.mock("@/pages/+Layout.vue", () => ({ default: { name: "Layout" } }));
+
+import config from "@/pages/+config.js";
+import Head from "@/pages/all/+Head.js";
+import description from "@/pages/all/+description.js";
+import route from "@/pages/all/+route.js";
+import title from "@/pages/all/+title.js";
+import organisationDescription from "@/pages/organisations/@id/+description.js";
+import organisationTitle from "@/pages/organisations/@id/+title.js";
+
+describe("page metadata", () => {
+ beforeEach(() => {
+ pageData.current = {};
+ });
+
+ it("exports the root Vike configuration and catch-all route", () => {
+ expect(config.extends).toEqual([{ name: "vike-vue" }]);
+ expect(config.Layout).toEqual({ name: "Layout" });
+ expect(config.ssr).toBe(true);
+ expect(config.prerender).toMatchObject({ partial: true, parallel: false });
+ expect(route).toBe("/*");
+ });
+
+ it("does not create record head elements without a record", () => {
+ expect(Head()).toBeNull();
+ });
+
+ it("creates canonical, Open Graph, and escaped schema.org head elements", () => {
+ pageData.current = {
+ record: {
+ doi: "10.1234/example",
+ name: "Example record",
+ description: "Description ",
+ },
+ schemaOrg: { "@context": "https://schema.org", ignoredName: "old" },
+ canonicalUrl: "https://fairsharing.org/10.1234/example",
+ seoTitle: "Example record | FAIRsharing",
+ seoDescription: "Example description",
+ };
+
+ const elements = Head();
+ const script = elements.find((element) => element.type === "script");
+
+ expect(elements).toHaveLength(7);
+ expect(elements.find((element) => element.type === "title").children).toBe(
+ "Example record | FAIRsharing",
+ );
+ expect(script.props.innerHTML).toContain("\\u003cunsafe>");
+ expect(script.props.innerHTML).toContain(
+ '"url":"https://fairsharing.org/10.1234/example"',
+ );
+ });
+
+ it("filters optional head elements and schema when values are absent", () => {
+ pageData.current = { record: { name: "Minimal" } };
+
+ expect(Head()).toEqual([]);
+ });
+
+ it("uses schema.org fallbacks when DOI and description are absent", () => {
+ pageData.current = {
+ record: { name: "Minimal" },
+ schemaOrg: { "@context": "https://schema.org" },
+ canonicalUrl: "https://fairsharing.org/1",
+ };
+
+ const script = Head().find((element) => element.type === "script");
+ const schema = JSON.parse(script.props.innerHTML);
+
+ expect(schema.identifier).toEqual([]);
+ expect(schema.description).toBe("");
+ });
+
+ it("returns record, static, search, and fallback titles", () => {
+ expect(
+ title({ urlPathname: "/anything", data: { record: { name: "Record" } } }),
+ ).toBe("Record | FAIRsharing");
+ expect(title({ urlPathname: "/privacy" })).toBe(
+ "Privacy Policy | FAIRsharing",
+ );
+ expect(
+ title({
+ urlPathname: "/search",
+ urlParsed: { search: "?fairsharingRegistry=DATABASE" },
+ }),
+ ).toBe("Databases | FAIRsharing");
+ expect(
+ title({
+ urlPathname: "/search",
+ urlParsed: { search: "?fairsharingRegistry=unknown" },
+ }),
+ ).toBe("Search | FAIRsharing");
+ expect(title({ urlPathname: "/unknown" })).toBe("FAIRsharing");
+ });
+
+ it("returns record, static, search, and fallback descriptions", () => {
+ expect(
+ description({
+ urlPathname: "/anything",
+ data: { record: { name: "Record", description: "Record description" } },
+ }),
+ ).toBe("Record description");
+ expect(
+ description({
+ urlPathname: "/anything",
+ data: { record: { name: "Record" } },
+ }),
+ ).toContain("Record");
+ expect(description({ urlPathname: "/organisations" })).toBe(
+ "List of Organisations",
+ );
+ expect(
+ description({
+ urlPathname: "/search",
+ urlParsed: { search: "?fairsharingRegistry=POLICY" },
+ }),
+ ).toContain("data preservation");
+ expect(description({ urlPathname: "/search" })).toContain(
+ "advanced filtering",
+ );
+ expect(
+ description({
+ urlPathname: "/search",
+ urlParsed: { search: "?fairsharingRegistry=unknown" },
+ }),
+ ).toContain("advanced filtering");
+ expect(description({ urlPathname: "/unknown" })).toContain(
+ "A curated, informative",
+ );
+ });
+
+ it("creates organisation titles and descriptions with fallbacks", () => {
+ const context = {
+ data: {
+ organisation: {
+ name: "Example Organisation",
+ homepage: "https://example.org",
+ },
+ },
+ };
+
+ expect(organisationTitle(context)).toBe(
+ "Example Organisation | FAIRsharing",
+ );
+ expect(organisationDescription(context)).toBe("https://example.org");
+ expect(
+ organisationDescription({
+ data: { organisation: { name: "No homepage", length: 1 } },
+ }),
+ ).toContain("1 organisations available");
+ expect(organisationTitle({})).toBe("Organisations | FAIRsharing");
+ expect(organisationDescription({})).toContain("A curated, informative");
+ });
+});
diff --git a/tests/unit/pages/onCreateApp.spec.js b/tests/unit/pages/onCreateApp.spec.js
new file mode 100644
index 0000000000..bceb07e3f4
--- /dev/null
+++ b/tests/unit/pages/onCreateApp.spec.js
@@ -0,0 +1,106 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({
+ store: { name: "store" },
+ router: {
+ push: vi.fn(),
+ isReady: vi.fn().mockResolvedValue(undefined),
+ },
+ bootstrapApp: vi.fn().mockResolvedValue(undefined),
+ vuetify: { name: "vuetify" },
+ metaManager: { name: "meta" },
+ sanitize: vi.fn((value) => `clean:${value}`),
+ accessibilityInit: vi.fn(),
+ loadFull: vi.fn().mockResolvedValue(undefined),
+}));
+
+vi.mock("@/store", () => ({ createMyStore: () => mocks.store }));
+vi.mock("@/router", () => ({ createMyRouter: () => mocks.router }));
+vi.mock("@/plugins/vuetify", () => ({ default: () => mocks.vuetify }));
+vi.mock("@/utils/setupUtils", () => ({
+ bootstrapApp: mocks.bootstrapApp,
+ globalFilters: { capitalize: vi.fn() },
+}));
+vi.mock("vue-meta", () => ({ createMetaManager: () => mocks.metaManager }));
+vi.mock("vue-3-sanitize", () => ({ default: { name: "sanitize-plugin" } }));
+vi.mock("vue-3-linkify", () => ({ default: { name: "linkify" } }));
+vi.mock("dompurify", () => ({ default: { sanitize: mocks.sanitize } }));
+vi.mock("@/components/Static/APIDoc/CodeBlock.vue", () => ({
+ default: { name: "CodeBlock" },
+}));
+vi.mock("highcharts-vue", () => ({ default: { name: "highcharts-vue" } }));
+vi.mock("highcharts", () => ({ default: { name: "highcharts" } }));
+vi.mock("highcharts/modules/accessibility", () => ({
+ default: mocks.accessibilityInit,
+}));
+vi.mock("simple-analytics-vue", () => ({ default: { name: "analytics" } }));
+vi.mock("vue-gtag", () => ({ default: { name: "gtag" } }));
+vi.mock("vue-scrollto", () => ({ default: { name: "scroll-to" } }));
+vi.mock("@tsparticles/vue3", () => ({ default: { name: "particles" } }));
+vi.mock("tsparticles", () => ({ loadFull: mocks.loadFull }));
+
+import onCreateApp from "@/pages/+onCreateApp.js";
+
+function createApp() {
+ const directives = {};
+ const app = {
+ use: vi.fn(() => app),
+ component: vi.fn(),
+ directive: vi.fn((name, handler) => {
+ directives[name] = handler;
+ }),
+ config: { globalProperties: {} },
+ };
+ return { app, directives };
+}
+
+describe("onCreateApp", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("initializes the isolated store and router for server rendering", async () => {
+ vi.stubEnv("SSR", true);
+ const { app, directives } = createApp();
+
+ await onCreateApp({ app, urlPathname: "/privacy" });
+
+ expect(app.use).toHaveBeenCalledWith(mocks.store);
+ expect(app.use).toHaveBeenCalledWith(mocks.router);
+ expect(mocks.bootstrapApp).toHaveBeenCalledWith(mocks.store, mocks.router);
+ expect(mocks.router.push).toHaveBeenCalledWith("/privacy");
+ expect(mocks.router.isReady).toHaveBeenCalled();
+ expect(app.component).toHaveBeenCalledWith(
+ "CodeBlock",
+ expect.objectContaining({ name: "CodeBlock" }),
+ );
+
+ const element = { innerHTML: "" };
+ directives["safe-html"](element, { value: "