+
- {{ pickListError }}
Task selections must be unique.
The following tasks are not unique:
@@ -106,12 +105,14 @@
+
+
+ >
+ {{ submitLabel }}
+
@@ -167,41 +171,49 @@
diff --git a/src/components/TaskPicker.vue b/src/components/TaskPicker.vue
index cfc524843..6f664b61f 100644
--- a/src/components/TaskPicker.vue
+++ b/src/components/TaskPicker.vue
@@ -182,11 +182,14 @@ const taskOptions = computed(() => {
watch(
() => props.inputVariants,
(newVariants) => {
+ // @TODO: Fix this as it's not working as expected. When updating the data set in the parent component, the data is
+ // added twice to the selectedVariants array, despite the _union call.
selectedVariants.value = _union(selectedVariants.value, newVariants);
// Update the conditions for the variants that were pre-existing
selectedVariants.value = selectedVariants.value.map((variant) => {
const preExistingInfo = props.preExistingAssessmentInfo.find((info) => info?.variantId === variant?.id);
+
if (preExistingInfo) {
return { ...variant, variant: { ...variant?.variant, conditions: preExistingInfo.conditions } };
}
@@ -205,7 +208,6 @@ const updateVariant = (variantId, conditions) => {
});
selectedVariants.value = updatedVariants;
return;
- // props.selectedVariant[]
};
const selectedVariants = ref([]);
diff --git a/src/composables/mutations/useUpsertAdministrationMutation.js b/src/composables/mutations/useUpsertAdministrationMutation.js
new file mode 100644
index 000000000..8691ceefc
--- /dev/null
+++ b/src/composables/mutations/useUpsertAdministrationMutation.js
@@ -0,0 +1,40 @@
+import { useMutation, useQueryClient } from '@tanstack/vue-query';
+import { useAuthStore } from '@/store/auth';
+import { ADMINISTRATION_UPSERT_MUTATION_KEY } from '@/constants/mutationKeys';
+import {
+ ADMINISTRATIONS_QUERY_KEY,
+ ADMINISTRATIONS_LIST_QUERY_KEY,
+ ADMINISTRATION_ASSIGNMENTS_QUERY_KEY,
+} from '@/constants/queryKeys';
+
+/**
+ * Upsert Administration mutation.
+ *
+ * TanStack mutation to update or insert an administration and automatically invalidate the corresponding queries.
+ *
+ * @returns {Object} The mutation object returned by `useMutation`.
+ */
+const useUpsertAdministrationMutation = () => {
+ const authStore = useAuthStore();
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: ADMINISTRATION_UPSERT_MUTATION_KEY,
+ mutationFn: async (data) => {
+ await authStore.roarfirekit.createAdministration(data);
+ },
+ onSuccess: () => {
+ // Invalidate the queries to refetch the administration data.
+ // @NOTE: Usually we would apply a more granular invalidation strategy including updating the specific
+ // adminitration record in the cache. However, unfortunately, given the nature of the data model and the data that
+ // is updated in the application, we would have to manually map the updated data, which could cause issues when
+ // the data model changes. Therefore, we invalidate the entire query to ensure the data is up-to-date.
+ queryClient.invalidateQueries({ queryKey: [ADMINISTRATIONS_QUERY_KEY] });
+ queryClient.invalidateQueries({ queryKey: [ADMINISTRATIONS_LIST_QUERY_KEY] });
+ queryClient.invalidateQueries({ queryKey: [ADMINISTRATION_ASSIGNMENTS_QUERY_KEY] });
+ console.warn('Administration upserted successfully.');
+ },
+ });
+};
+
+export default useUpsertAdministrationMutation;
diff --git a/src/composables/queries/useAdministrationsQuery.js b/src/composables/queries/useAdministrationsQuery.js
index 12e812792..5c9879a20 100644
--- a/src/composables/queries/useAdministrationsQuery.js
+++ b/src/composables/queries/useAdministrationsQuery.js
@@ -1,6 +1,7 @@
-import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
-import { fetchDocsById } from '@/helpers/query/utils';
+import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
+import { hasArrayEntries } from '@/helpers/hasArrayEntries';
+import { fetchDocumentsById } from '@/helpers/query/utils';
import { ADMINISTRATIONS_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
@@ -12,19 +13,15 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
* @returns {UseQueryResult} The TanStack query result.
*/
const useAdministrationsQuery = (administrationIds, queryOptions = undefined) => {
+ // Ensure all necessary data is available before enabling the query.
+ const conditions = [() => hasArrayEntries(administrationIds)];
+ const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);
+
return useQuery({
- queryKey: [ADMINISTRATIONS_QUERY_KEY, toValue(administrationIds)],
- queryFn: () =>
- fetchDocsById(
- toValue(administrationIds)?.map((administrationId) => {
- return {
- collection: FIRESTORE_COLLECTIONS.ADMINISTRATIONS,
- docId: administrationId,
- select: ['name', 'publicName', 'sequential', 'assessments', 'legal'],
- };
- }),
- ),
- ...queryOptions,
+ queryKey: [ADMINISTRATIONS_QUERY_KEY, administrationIds],
+ queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.ADMINISTRATIONS, administrationIds),
+ enabled: isQueryEnabled,
+ ...options,
});
};
diff --git a/src/composables/queries/useAdministrationsQuery.test.js b/src/composables/queries/useAdministrationsQuery.test.js
index 209910620..5ac1eec34 100644
--- a/src/composables/queries/useAdministrationsQuery.test.js
+++ b/src/composables/queries/useAdministrationsQuery.test.js
@@ -1,13 +1,13 @@
-import { ref, toValue } from 'vue';
+import { ref, nextTick } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { nanoid } from 'nanoid';
import { withSetup } from '@/test-support/withSetup.js';
-import { fetchDocsById } from '@/helpers/query/utils';
+import { fetchDocumentsById } from '@/helpers/query/utils';
import useAdministrationsQuery from './useAdministrationsQuery';
vi.mock('@/helpers/query/utils', () => ({
- fetchDocsById: vi.fn().mockImplementation(() => []),
+ fetchDocumentsById: vi.fn().mockImplementation(() => []),
}));
vi.mock('@tanstack/vue-query', async (getModule) => {
@@ -18,14 +18,6 @@ vi.mock('@tanstack/vue-query', async (getModule) => {
};
});
-const buildCollectionRequestPayload = (id) => {
- return {
- collection: 'administrations',
- docId: id,
- select: ['name', 'publicName', 'sequential', 'assessments', 'legal'],
- };
-};
-
describe('useAdministrationsQuery', () => {
let queryClient;
@@ -46,13 +38,14 @@ describe('useAdministrationsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['administrations', toValue(mockAdministrationIds)],
+ queryKey: ['administrations', mockAdministrationIds],
queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: true,
+ }),
});
- const expectedPayload = mockAdministrationIds.value.map((id) => buildCollectionRequestPayload(id));
-
- expect(fetchDocsById).toHaveBeenCalledWith(expectedPayload);
+ expect(fetchDocumentsById).toHaveBeenCalledWith('administrations', mockAdministrationIds);
});
it('should allow the query to be disabled via the passed query options', () => {
@@ -66,9 +59,60 @@ describe('useAdministrationsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['administrations', toValue(mockAdministrationIds)],
+ queryKey: ['administrations', mockAdministrationIds],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
+ });
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
+ });
+
+ it('should only fetch data if the administration IDs are available', async () => {
+ const mockAdministrationIds = ref(null);
+ const queryOptions = { enabled: true };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useAdministrationsQuery(mockAdministrationIds, queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['administrations', mockAdministrationIds],
queryFn: expect.any(Function),
- enabled: false,
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
});
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
+
+ mockAdministrationIds.value = [nanoid(), nanoid()];
+ await nextTick();
+
+ expect(fetchDocumentsById).toHaveBeenCalledWith('administrations', mockAdministrationIds);
+ });
+
+ it('should not let queryOptions override the internally computed value', async () => {
+ const mockAdministrationIds = ref(null);
+ const queryOptions = { enabled: true };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useAdministrationsQuery(mockAdministrationIds, queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['administrations', mockAdministrationIds],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
+ });
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
});
});
diff --git a/src/composables/queries/useAdministrationsStatsQuery.js b/src/composables/queries/useAdministrationsStatsQuery.js
index ca3e8b39e..f67f76aeb 100644
--- a/src/composables/queries/useAdministrationsStatsQuery.js
+++ b/src/composables/queries/useAdministrationsStatsQuery.js
@@ -1,5 +1,7 @@
import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
+import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
+import { hasArrayEntries } from '@/helpers/hasArrayEntries';
import { fetchDocsById } from '@/helpers/query/utils';
import { ADMINISTRATIONS_STATS_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
@@ -12,8 +14,12 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
* @returns {UseQueryResult} The TanStack query result.
*/
const useAdministrationsStatsQuery = (administrationIds, queryOptions = undefined) => {
+ // Ensure all necessary data is available before enabling the query.
+ const conditions = [() => hasArrayEntries(administrationIds)];
+ const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);
+
return useQuery({
- queryKey: [ADMINISTRATIONS_STATS_QUERY_KEY, toValue(administrationIds)],
+ queryKey: [ADMINISTRATIONS_STATS_QUERY_KEY, administrationIds],
queryFn: () =>
fetchDocsById(
toValue(administrationIds)?.map((administrationId) => {
@@ -23,7 +29,8 @@ const useAdministrationsStatsQuery = (administrationIds, queryOptions = undefine
};
}),
),
- ...queryOptions,
+ enabled: isQueryEnabled,
+ ...options,
});
};
diff --git a/src/composables/queries/useAdministrationsStatsQuery.test.js b/src/composables/queries/useAdministrationsStatsQuery.test.js
index 74d41bc2f..5184d7f6c 100644
--- a/src/composables/queries/useAdministrationsStatsQuery.test.js
+++ b/src/composables/queries/useAdministrationsStatsQuery.test.js
@@ -1,4 +1,4 @@
-import { ref, toValue } from 'vue';
+import { ref, nextTick } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { nanoid } from 'nanoid';
@@ -45,8 +45,11 @@ describe('useAdministrationsStatsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['administrations-stats', toValue(mockAdministrationIds)],
+ queryKey: ['administrations-stats', mockAdministrationIds],
queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: true,
+ }),
});
const expectedPayload = mockAdministrationIds.value.map((id) => buildCollectionRequestPayload(id));
@@ -65,9 +68,62 @@ describe('useAdministrationsStatsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['administrations-stats', toValue(mockAdministrationIds)],
+ queryKey: ['administrations-stats', mockAdministrationIds],
queryFn: expect.any(Function),
- enabled: false,
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
});
+
+ expect(fetchDocsById).not.toHaveBeenCalled();
+ });
+
+ it('should only fetch data if the administration IDs are available', async () => {
+ const mockAdministrationIds = ref(null);
+ const queryOptions = { enabled: true };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useAdministrationsStatsQuery(mockAdministrationIds, queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['administrations-stats', mockAdministrationIds],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
+ });
+
+ expect(fetchDocsById).not.toHaveBeenCalled();
+
+ mockAdministrationIds.value = [nanoid(), nanoid()];
+ await nextTick();
+
+ const expectedPayload = mockAdministrationIds.value.map((id) => buildCollectionRequestPayload(id));
+
+ expect(fetchDocsById).toHaveBeenCalledWith(expectedPayload);
+ });
+
+ it('should not let queryOptions override the internally computed value', async () => {
+ const mockAdministrationIds = ref(null);
+ const queryOptions = { enabled: true };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useAdministrationsStatsQuery(mockAdministrationIds, queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['administrations-stats', mockAdministrationIds],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
+ });
+
+ expect(fetchDocsById).not.toHaveBeenCalled();
});
});
diff --git a/src/composables/queries/useClassesQuery.js b/src/composables/queries/useClassesQuery.js
index 51e01e82b..90f01bb59 100644
--- a/src/composables/queries/useClassesQuery.js
+++ b/src/composables/queries/useClassesQuery.js
@@ -1,8 +1,7 @@
-import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
-import _isEmpty from 'lodash/isEmpty';
import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
import { fetchDocumentsById } from '@/helpers/query/utils';
+import { hasArrayEntries } from '@/helpers/hasArrayEntries';
import { CLASSES_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
@@ -15,12 +14,12 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
*/
const useClassesQuery = (classIds, queryOptions = undefined) => {
// Ensure all necessary data is loaded before enabling the query.
- const conditions = [() => !_isEmpty(classIds)];
+ const conditions = [() => hasArrayEntries(classIds)];
const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);
return useQuery({
queryKey: [CLASSES_QUERY_KEY, classIds],
- queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.CLASSES, toValue(classIds)),
+ queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.CLASSES, classIds),
enabled: isQueryEnabled,
...options,
});
diff --git a/src/composables/queries/useDistrictsListQuery.test.js b/src/composables/queries/useDistrictsListQuery.test.js
index 9c5c9a6d2..ac29119be 100644
--- a/src/composables/queries/useDistrictsListQuery.test.js
+++ b/src/composables/queries/useDistrictsListQuery.test.js
@@ -71,10 +71,10 @@ describe('useDistrictsListQuery', () => {
});
it('should only fetch districts only once user claims are loaded', async () => {
- const mockData = ref({});
- const mockIsLoading = ref(true);
+ const mockClaimsData = ref({});
+ const mockClaimsLoading = ref(true);
- vi.mocked(useUserClaimsQuery).mockReturnValue({ data: mockData, isLoading: mockIsLoading });
+ vi.mocked(useUserClaimsQuery).mockReturnValue({ data: mockClaimsData, isLoading: mockClaimsLoading });
vi.spyOn(VueQuery, 'useQuery');
vi.spyOn(orgFetcher, 'mockImplementation');
@@ -93,8 +93,8 @@ describe('useDistrictsListQuery', () => {
expect(orgFetcher).not.toHaveBeenCalled();
- mockData.value = mockSuperAdminUserClaims.value;
- mockIsLoading.value = false;
+ mockClaimsData.value = mockSuperAdminUserClaims.value;
+ mockClaimsLoading.value = false;
await nextTick();
diff --git a/src/composables/queries/useDistrictsQuery.js b/src/composables/queries/useDistrictsQuery.js
index 6c597534e..3cd38ee7e 100644
--- a/src/composables/queries/useDistrictsQuery.js
+++ b/src/composables/queries/useDistrictsQuery.js
@@ -1,7 +1,6 @@
-import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
-import _isEmpty from 'lodash/isEmpty';
import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
+import { hasArrayEntries } from '@/helpers/hasArrayEntries';
import { fetchDocumentsById } from '@/helpers/query/utils';
import { DISTRICTS_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
@@ -14,13 +13,13 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
* @returns {UseQueryResult} The TanStack query result.
*/
const useDistrictsQuery = (districtIds, queryOptions = undefined) => {
- // Ensure all necessary data is loaded before enabling the query.
- const conditions = [() => !_isEmpty(districtIds)];
+ // Ensure all necessary data is available before enabling the query.
+ const conditions = [() => hasArrayEntries(districtIds)];
const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);
return useQuery({
queryKey: [DISTRICTS_QUERY_KEY, districtIds],
- queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.DISTRICTS, toValue(districtIds)),
+ queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.DISTRICTS, districtIds),
enabled: isQueryEnabled,
...options,
});
diff --git a/src/composables/queries/useDistrictsQuery.test.js b/src/composables/queries/useDistrictsQuery.test.js
index ba489a4d1..81ac3ea11 100644
--- a/src/composables/queries/useDistrictsQuery.test.js
+++ b/src/composables/queries/useDistrictsQuery.test.js
@@ -1,3 +1,4 @@
+import { ref, nextTick } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { nanoid } from 'nanoid';
@@ -29,7 +30,7 @@ describe('useDistrictsQuery', () => {
});
it('should call query with correct parameters', () => {
- const districtIds = nanoid();
+ const districtIds = ref([nanoid()]);
vi.spyOn(VueQuery, 'useQuery');
@@ -49,7 +50,7 @@ describe('useDistrictsQuery', () => {
});
it('should allow the query to be disabled via the passed query options', () => {
- const districtIds = nanoid();
+ const districtIds = ref([nanoid()]);
const queryOptions = { enabled: false };
vi.spyOn(VueQuery, 'useQuery');
@@ -65,10 +66,38 @@ describe('useDistrictsQuery', () => {
_value: false,
}),
});
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
+ });
+
+ it('should only fetch data if the administration ID is available', async () => {
+ const districtIds = ref([]);
+ const queryOptions = { enabled: true };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useDistrictsQuery(districtIds, queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['districts', districtIds],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
+ });
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
+
+ districtIds.value = [nanoid()];
+ await nextTick();
+
+ expect(fetchDocumentsById).toHaveBeenCalledWith('districts', districtIds);
});
- it('should keep the query disabled if not district IDs are specified', () => {
- const districtIds = [];
+ it('should not let queryOptions override the internally computed value', async () => {
+ const districtIds = ref([]);
const queryOptions = { enabled: true };
vi.spyOn(VueQuery, 'useQuery');
diff --git a/src/composables/queries/useDsgfOrgQuery.js b/src/composables/queries/useDsgfOrgQuery.js
index fa06b33d1..e788d275b 100644
--- a/src/composables/queries/useDsgfOrgQuery.js
+++ b/src/composables/queries/useDsgfOrgQuery.js
@@ -1,7 +1,6 @@
+import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
import { DSGF_ORGS_QUERY_KEY } from '@/constants/queryKeys';
-import { storeToRefs } from 'pinia';
-import { useAuthStore } from '@/store/auth';
import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
import { fetchTreeOrgs } from '@/helpers/query/orgs';
@@ -17,12 +16,9 @@ import { fetchTreeOrgs } from '@/helpers/query/orgs';
* @returns {UseQueryResult} The TanStack query result.
*/
const useDsgfOrgQuery = (administrationId, assignedOrgs, queryOptions = undefined) => {
- const authStore = useAuthStore();
- const { uid } = storeToRefs(authStore);
-
- // Ensure the User ID is available and the query is enabled.
- const queryConditions = [() => !!uid.value];
- const { isQueryEnabled, options } = computeQueryOverrides(queryConditions, queryOptions);
+ // Ensure all necessary data is available before enabling the query.
+ const conditions = [() => !!toValue(administrationId)];
+ const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);
return useQuery({
queryKey: [DSGF_ORGS_QUERY_KEY, administrationId],
diff --git a/src/composables/queries/useDsgfOrgQuery.test.js b/src/composables/queries/useDsgfOrgQuery.test.js
index b05cbaa42..bd1fa89e8 100644
--- a/src/composables/queries/useDsgfOrgQuery.test.js
+++ b/src/composables/queries/useDsgfOrgQuery.test.js
@@ -93,14 +93,10 @@ describe('useDsgfOrgQuery', () => {
expect(fetchTreeOrgs).toHaveBeenCalledWith(mockAdministrationId, mockAssignedOrgs);
});
- it('should only fetch data if the user ID is available', async () => {
- const mockUserId = nanoid();
- const mockAdministrationId = nanoid();
+ it('should only fetch data if the administration ID is available', async () => {
+ const mockAdministrationId = ref();
const mockAssignedOrgs = [nanoid()];
- const authStore = useAuthStore(piniaInstance);
- authStore.uid = null;
-
const queryOptions = { enabled: true };
withSetup(() => useDsgfOrgQuery(mockAdministrationId, mockAssignedOrgs, queryOptions), {
@@ -118,19 +114,16 @@ describe('useDsgfOrgQuery', () => {
expect(fetchTreeOrgs).not.toHaveBeenCalled();
- authStore.uid = mockUserId;
+ mockAdministrationId.value = nanoid();
await nextTick();
expect(fetchTreeOrgs).toHaveBeenCalledWith(mockAdministrationId, mockAssignedOrgs);
});
it('should not let queryOptions override the internally computed value', async () => {
- const mockAdministrationId = nanoid();
+ const mockAdministrationId = null;
const mockAssignedOrgs = [nanoid()];
- const authStore = useAuthStore(piniaInstance);
- authStore.uid = null;
-
const queryOptions = { enabled: true };
withSetup(() => useDsgfOrgQuery(mockAdministrationId, mockAssignedOrgs, queryOptions), {
diff --git a/src/composables/queries/useFamiliesQuery.js b/src/composables/queries/useFamiliesQuery.js
index 3348a6f07..a481ae4ac 100644
--- a/src/composables/queries/useFamiliesQuery.js
+++ b/src/composables/queries/useFamiliesQuery.js
@@ -1,7 +1,6 @@
-import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
-import _isEmpty from 'lodash/isEmpty';
import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
+import { hasArrayEntries } from '@/helpers/hasArrayEntries';
import { fetchDocumentsById } from '@/helpers/query/utils';
import { FAMILIES_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
@@ -14,13 +13,13 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
* @returns {UseQueryResult} The TanStack query result.
*/
const useFamiliesQuery = (familyIds, queryOptions = undefined) => {
- // Ensure all necessary data is loaded before enabling the query.
- const conditions = [() => !_isEmpty(familyIds)];
+ // Ensure all necessary data is available before enabling the query.
+ const conditions = [() => hasArrayEntries(familyIds)];
const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);
return useQuery({
queryKey: [FAMILIES_QUERY_KEY, familyIds],
- queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.FAMILIES, toValue(familyIds)),
+ queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.FAMILIES, familyIds),
enabled: isQueryEnabled,
...options,
});
diff --git a/src/composables/queries/useFamiliesQuery.test.js b/src/composables/queries/useFamiliesQuery.test.js
index 3ff769409..9855c781c 100644
--- a/src/composables/queries/useFamiliesQuery.test.js
+++ b/src/composables/queries/useFamiliesQuery.test.js
@@ -1,3 +1,4 @@
+import { ref, nextTick } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { nanoid } from 'nanoid';
@@ -29,56 +30,105 @@ describe('useFamiliesQuery', () => {
});
it('should call query with correct parameters', () => {
- const familyIds = [nanoid()];
+ const mockFamilyIds = ref([nanoid()]);
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useFamiliesQuery(familyIds), {
+ withSetup(() => useFamiliesQuery(mockFamilyIds), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['families', familyIds],
+ queryKey: ['families', mockFamilyIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
}),
});
- expect(fetchDocumentsById).toHaveBeenCalledWith('families', familyIds);
+ expect(fetchDocumentsById).toHaveBeenCalledWith('families', mockFamilyIds);
});
it('should allow the query to be disabled via the passed query options', () => {
- const familyIds = [nanoid()];
+ const mockFamilyIds = ref([nanoid()]);
const queryOptions = { enabled: false };
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useFamiliesQuery(familyIds, queryOptions), {
+ withSetup(() => useFamiliesQuery(mockFamilyIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['families', familyIds],
+ queryKey: ['families', mockFamilyIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
}),
});
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
});
it('should keep the query disabled if not family IDs are specified', () => {
- const familyIds = [];
+ const mockFamilyIds = ref([]);
+ const queryOptions = { enabled: true };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useFamiliesQuery(mockFamilyIds, queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['families', mockFamilyIds],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
+ });
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
+ });
+
+ it('should only fetch data if the administration ID is available', async () => {
+ const mockFamilyIds = ref([]);
+ const queryOptions = { enabled: true };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useFamiliesQuery(mockFamilyIds, queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['families', mockFamilyIds],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
+ });
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
+
+ mockFamilyIds.value = [nanoid()];
+ await nextTick();
+
+ expect(fetchDocumentsById).toHaveBeenCalledWith('families', mockFamilyIds);
+ });
+
+ it('should not let queryOptions override the internally computed value', async () => {
+ const mockFamilyIds = ref([]);
const queryOptions = { enabled: true };
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useFamiliesQuery(familyIds, queryOptions), {
+ withSetup(() => useFamiliesQuery(mockFamilyIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['families', familyIds],
+ queryKey: ['families', mockFamilyIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
diff --git a/src/composables/queries/useGroupsListQuery.test.js b/src/composables/queries/useGroupsListQuery.test.js
index ec3dfd6c9..abb645533 100644
--- a/src/composables/queries/useGroupsListQuery.test.js
+++ b/src/composables/queries/useGroupsListQuery.test.js
@@ -1,4 +1,4 @@
-import { ref } from 'vue';
+import { nextTick, ref } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { withSetup } from '@/test-support/withSetup.js';
@@ -63,8 +63,11 @@ describe('useGroupsListQuery', () => {
);
});
- it('should only fetch groups only once user claims are loaded', async () => {
- vi.mocked(useUserClaimsQuery).mockReturnValue({ data: {}, isLoading: ref(true) });
+ it('should only fetch data once user claims are available', async () => {
+ const mockClaimsData = ref({});
+ const mockClaimsLoading = ref(true);
+
+ vi.mocked(useUserClaimsQuery).mockReturnValue({ data: mockClaimsData, isLoading: mockClaimsLoading });
vi.spyOn(VueQuery, 'useQuery');
@@ -81,6 +84,13 @@ describe('useGroupsListQuery', () => {
});
expect(orgFetcher).not.toHaveBeenCalled();
+
+ mockClaimsData.value = mockUserClaims.value;
+ mockClaimsLoading.value = false;
+
+ await nextTick();
+
+ expect(orgFetcher).toHaveBeenCalled();
});
it('should allow the query to be disabled via the passed query options', () => {
diff --git a/src/composables/queries/useGroupsQuery.js b/src/composables/queries/useGroupsQuery.js
index 4e49e3784..ff28fd95c 100644
--- a/src/composables/queries/useGroupsQuery.js
+++ b/src/composables/queries/useGroupsQuery.js
@@ -1,7 +1,6 @@
-import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
-import _isEmpty from 'lodash/isEmpty';
import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
+import { hasArrayEntries } from '@/helpers/hasArrayEntries';
import { fetchDocumentsById } from '@/helpers/query/utils';
import { GROUPS_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
@@ -15,12 +14,12 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
*/
const useGroupsQuery = (groupIds, queryOptions = undefined) => {
// Ensure all necessary data is loaded before enabling the query.
- const conditions = [() => !_isEmpty(groupIds)];
+ const conditions = [() => hasArrayEntries(groupIds)];
const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);
return useQuery({
queryKey: [GROUPS_QUERY_KEY, groupIds],
- queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.GROUPS, toValue(groupIds)),
+ queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.GROUPS, groupIds),
enabled: isQueryEnabled,
...options,
});
diff --git a/src/composables/queries/useGroupsQuery.test.js b/src/composables/queries/useGroupsQuery.test.js
index e142424a5..b9f248c7d 100644
--- a/src/composables/queries/useGroupsQuery.test.js
+++ b/src/composables/queries/useGroupsQuery.test.js
@@ -1,3 +1,4 @@
+import { ref, nextTick } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { nanoid } from 'nanoid';
@@ -29,37 +30,37 @@ describe('useGroupsQuery', () => {
});
it('should call query with correct parameters', () => {
- const groupIds = [nanoid(), nanoid()];
+ const mockGroupIds = ref([nanoid(), nanoid()]);
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useGroupsQuery(groupIds), {
+ withSetup(() => useGroupsQuery(mockGroupIds), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['groups', groupIds],
+ queryKey: ['groups', mockGroupIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
}),
});
- expect(fetchDocumentsById).toHaveBeenCalledWith('groups', groupIds);
+ expect(fetchDocumentsById).toHaveBeenCalledWith('groups', mockGroupIds);
});
it('should allow the query to be disabled via the passed query options', () => {
- const groupIds = [nanoid(), nanoid()];
+ const mockGroupIds = ref([nanoid(), nanoid()]);
const queryOptions = { enabled: false };
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useGroupsQuery(groupIds, queryOptions), {
+ withSetup(() => useGroupsQuery(mockGroupIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['groups', groupIds],
+ queryKey: ['groups', mockGroupIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -69,18 +70,44 @@ describe('useGroupsQuery', () => {
expect(fetchDocumentsById).not.toHaveBeenCalled();
});
- it('should keep the query disabled if not group IDs are specified', () => {
- const groupIds = [];
+ it('should only fetch data if the administration ID is available', async () => {
+ const mockGroupIds = ref([]);
const queryOptions = { enabled: true };
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useGroupsQuery(groupIds, queryOptions), {
+ withSetup(() => useGroupsQuery(mockGroupIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['groups', groupIds],
+ queryKey: ['groups', mockGroupIds],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
+ });
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
+
+ mockGroupIds.value = [nanoid()];
+ await nextTick();
+
+ expect(fetchDocumentsById).toHaveBeenCalledWith('groups', mockGroupIds);
+ });
+
+ it('should not let queryOptions override the internally computed value', async () => {
+ const mockGroupIds = ref([]);
+ const queryOptions = { enabled: true };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useGroupsQuery(mockGroupIds, queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['groups', mockGroupIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
diff --git a/src/composables/queries/useLegalDocsQuery.test.js b/src/composables/queries/useLegalDocsQuery.test.js
index 3289d7ab7..c7d02033c 100644
--- a/src/composables/queries/useLegalDocsQuery.test.js
+++ b/src/composables/queries/useLegalDocsQuery.test.js
@@ -41,4 +41,22 @@ describe('useLegalDocsQuery', () => {
expect(fetchLegalDocs).toHaveBeenCalledWith();
});
+
+ it('should allow the query to be disabled via the passed query options', () => {
+ const queryOptions = { enabled: false };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useLegalDocsQuery(queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['legal-docs'],
+ queryFn: expect.any(Function),
+ enabled: false,
+ });
+
+ expect(fetchLegalDocs).not.toHaveBeenCalled();
+ });
});
diff --git a/src/composables/queries/useOrgQuery.test.js b/src/composables/queries/useOrgQuery.test.js
index 76d4e3428..3ed7eb1d1 100644
--- a/src/composables/queries/useOrgQuery.test.js
+++ b/src/composables/queries/useOrgQuery.test.js
@@ -1,3 +1,4 @@
+import { ref } from 'vue';
import { describe, it, expect, vi } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { nanoid } from 'nanoid';
@@ -33,42 +34,42 @@ describe('useOrgQuery', () => {
it('should return useDistrictsQuery for districts as org type', () => {
const mockOrgType = SINGULAR_ORG_TYPES.DISTRICTS;
- const mockOrgIds = [nanoid(), nanoid()];
+ const mockOrgIds = ref([nanoid(), nanoid()]);
const result = useOrgQuery(mockOrgType, mockOrgIds);
expect(result).toBe('useDistrictsQuery');
});
it('should return useSchoolsQuery for schools as org type', () => {
const mockOrgType = SINGULAR_ORG_TYPES.SCHOOLS;
- const mockOrgIds = [nanoid(), nanoid()];
+ const mockOrgIds = ref([nanoid(), nanoid()]);
const result = useOrgQuery(mockOrgType, mockOrgIds);
expect(result).toBe('useSchoolsQuery');
});
it('should return useClassesQuery for classes as org type', () => {
const mockOrgType = SINGULAR_ORG_TYPES.CLASSES;
- const mockOrgIds = [nanoid(), nanoid()];
+ const mockOrgIds = ref([nanoid(), nanoid()]);
const result = useOrgQuery(mockOrgType, mockOrgIds);
expect(result).toBe('useClassesQuery');
});
it('should return useGroupsQuery for groups as org type', () => {
const mockOrgType = SINGULAR_ORG_TYPES.GROUPS;
- const mockOrgIds = [nanoid(), nanoid()];
+ const mockOrgIds = ref([nanoid(), nanoid()]);
const result = useOrgQuery(mockOrgType, mockOrgIds);
expect(result).toBe('useGroupsQuery');
});
it('should return useFamiliesQuery for families as org type', () => {
const mockOrgType = SINGULAR_ORG_TYPES.FAMILIES;
- const mockOrgIds = [nanoid(), nanoid()];
+ const mockOrgIds = ref([nanoid(), nanoid()]);
const result = useOrgQuery(mockOrgType, mockOrgIds);
expect(result).toBe('useFamiliesQuery');
});
it('should throw an error for unsupported org type', () => {
const mockOrgType = 'UNSUPPORTED';
- const mockOrgIds = [nanoid()];
+ const mockOrgIds = ref([nanoid(), nanoid()]);
expect(() => useOrgQuery(mockOrgType, mockOrgIds)).toThrow('Unsupported org type: UNSUPPORTED');
});
});
diff --git a/src/composables/queries/useOrgUsersQuery.js b/src/composables/queries/useOrgUsersQuery.js
index 5be0ee57f..4fd8e40d1 100644
--- a/src/composables/queries/useOrgUsersQuery.js
+++ b/src/composables/queries/useOrgUsersQuery.js
@@ -1,4 +1,3 @@
-import { ref } from 'vue';
import { useQuery } from '@tanstack/vue-query';
import { fetchUsersByOrg } from '@/helpers/query/users';
import { ORG_USERS_QUERY_KEY } from '@/constants/queryKeys';
@@ -9,7 +8,7 @@ import { ORG_USERS_QUERY_KEY } from '@/constants/queryKeys';
* @returns {UseQueryResult} The TanStack query result.
*/
const useOrgUsersQuery = (orgType, orgId, page, orderBy, queryOptions = undefined) => {
- const itemsPerPage = ref(1000000); // @TODO: Replace with a more reasonable value.
+ const itemsPerPage = 1000000; // @TODO: Replace with a more reasonable value.
return useQuery({
queryKey: [ORG_USERS_QUERY_KEY, orgType, orgId, page, orderBy],
diff --git a/src/composables/queries/useOrgUsersQuery.test.js b/src/composables/queries/useOrgUsersQuery.test.js
index ad05f7925..b928bf353 100644
--- a/src/composables/queries/useOrgUsersQuery.test.js
+++ b/src/composables/queries/useOrgUsersQuery.test.js
@@ -1,6 +1,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { withSetup } from '@/test-support/withSetup.js';
import * as VueQuery from '@tanstack/vue-query';
+import { nanoid } from 'nanoid';
import { fetchUsersByOrg } from '@/helpers/query/users';
import useOrgUsersQuery from './useOrgUsersQuery';
@@ -27,36 +28,53 @@ describe('useOrgUsersQuery', () => {
queryClient?.clear();
});
- it('should call useQuery with correct parameters', () => {
- const orgType = 'org';
- const orgId = '1';
- const page = 1;
- const orderBy = 'name';
- const queryOptions = { enabled: false };
+ it('should call query with correct parameters', () => {
+ const mockOrgType = 'org';
+ const mockOrgId = nanoid();
+ const mockPageNumber = 1;
+ const mockOrderBy = 'name';
+ const queryOptions = { enabled: true };
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useOrgUsersQuery(orgType, orgId, page, orderBy, queryOptions), {
+ withSetup(() => useOrgUsersQuery(mockOrgType, mockOrgId, mockPageNumber, mockOrderBy, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['org-users', orgType, orgId, page, orderBy],
+ queryKey: ['org-users', mockOrgType, mockOrgId, mockPageNumber, mockOrderBy],
queryFn: expect.any(Function),
- enabled: false,
+ enabled: true,
});
+
+ expect(fetchUsersByOrg).toHaveBeenCalledWith(
+ mockOrgType,
+ mockOrgId,
+ expect.anything(),
+ mockPageNumber,
+ mockOrderBy,
+ );
});
- it('should call fetchUsersByOrg with correct parameters', async () => {
- const orgType = 'school';
- const orgId = 'mock-school-uid';
- const page = 1;
- const orderBy = 'name';
+ it('should allow the query to be disabled via the passed query options', () => {
+ const mockOrgType = 'org';
+ const mockOrgId = nanoid();
+ const mockPageNumber = 1;
+ const mockOrderBy = 'name';
+ const queryOptions = { enabled: false };
+
+ vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useOrgUsersQuery(orgType, orgId, page, orderBy), {
+ withSetup(() => useOrgUsersQuery(mockOrgType, mockOrgId, mockPageNumber, mockOrderBy, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
- expect(fetchUsersByOrg).toHaveBeenCalledWith(orgType, orgId, expect.anything(), page, orderBy);
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['org-users', mockOrgType, mockOrgId, mockPageNumber, mockOrderBy],
+ queryFn: expect.any(Function),
+ enabled: false,
+ });
+
+ expect(fetchUsersByOrg).not.toHaveBeenCalled();
});
});
diff --git a/src/composables/queries/useSchoolsQuery.js b/src/composables/queries/useSchoolsQuery.js
index dd6a4f6d5..8efbe2a53 100644
--- a/src/composables/queries/useSchoolsQuery.js
+++ b/src/composables/queries/useSchoolsQuery.js
@@ -1,7 +1,6 @@
-import { toValue } from 'vue';
import { useQuery } from '@tanstack/vue-query';
-import _isEmpty from 'lodash/isEmpty';
import { computeQueryOverrides } from '@/helpers/computeQueryOverrides';
+import { hasArrayEntries } from '@/helpers/hasArrayEntries';
import { fetchDocumentsById } from '@/helpers/query/utils';
import { SCHOOLS_QUERY_KEY } from '@/constants/queryKeys';
import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
@@ -15,12 +14,12 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
*/
const useSchoolsQuery = (schoolIds, queryOptions = undefined) => {
// Ensure all necessary data is loaded before enabling the query.
- const conditions = [() => !_isEmpty(schoolIds)];
+ const conditions = [() => hasArrayEntries(schoolIds)];
const { isQueryEnabled, options } = computeQueryOverrides(conditions, queryOptions);
return useQuery({
queryKey: [SCHOOLS_QUERY_KEY, schoolIds],
- queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.SCHOOLS, toValue(schoolIds)),
+ queryFn: () => fetchDocumentsById(FIRESTORE_COLLECTIONS.SCHOOLS, schoolIds),
enabled: isQueryEnabled,
...options,
});
diff --git a/src/composables/queries/useSchoolsQuery.test.js b/src/composables/queries/useSchoolsQuery.test.js
index 76064c4bd..8fea5c776 100644
--- a/src/composables/queries/useSchoolsQuery.test.js
+++ b/src/composables/queries/useSchoolsQuery.test.js
@@ -1,3 +1,4 @@
+import { ref, nextTick } from 'vue';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as VueQuery from '@tanstack/vue-query';
import { nanoid } from 'nanoid';
@@ -29,37 +30,37 @@ describe('useSchoolsQuery', () => {
});
it('should call query with correct parameters when fetching a specific school', () => {
- const schoolIds = [nanoid(), nanoid()];
+ const mockSchoolIds = ref([nanoid(), nanoid()]);
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useSchoolsQuery(schoolIds), {
+ withSetup(() => useSchoolsQuery(mockSchoolIds), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['schools', schoolIds],
+ queryKey: ['schools', mockSchoolIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
}),
});
- expect(fetchDocumentsById).toHaveBeenCalledWith('schools', schoolIds);
+ expect(fetchDocumentsById).toHaveBeenCalledWith('schools', mockSchoolIds);
});
it('should allow the query to be disabled via the passed query options', () => {
- const schoolIds = [nanoid()];
+ const mockSchoolIds = ref([nanoid()]);
const queryOptions = { enabled: false };
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useSchoolsQuery(schoolIds, queryOptions), {
+ withSetup(() => useSchoolsQuery(mockSchoolIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['schools', schoolIds],
+ queryKey: ['schools', mockSchoolIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -69,18 +70,44 @@ describe('useSchoolsQuery', () => {
expect(fetchDocumentsById).not.toHaveBeenCalled();
});
- it('should keep the query disabled if not school IDs are specified', () => {
- const schoolIds = [];
+ it('should only fetch data once the school IDs are available', async () => {
+ const mockSchoolIds = ref([]);
const queryOptions = { enabled: true };
vi.spyOn(VueQuery, 'useQuery');
- withSetup(() => useSchoolsQuery(schoolIds, queryOptions), {
+ withSetup(() => useSchoolsQuery(mockSchoolIds, queryOptions), {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['schools', schoolIds],
+ queryKey: ['schools', mockSchoolIds],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: false,
+ }),
+ });
+
+ expect(fetchDocumentsById).not.toHaveBeenCalled();
+
+ mockSchoolIds.value = [nanoid(), nanoid()];
+ await nextTick();
+
+ expect(fetchDocumentsById).toHaveBeenCalledWith('schools', mockSchoolIds);
+ });
+
+ it('should not let queryOptions override the internally computed value', async () => {
+ const mockSchoolIds = ref([]);
+ const queryOptions = { enabled: true };
+
+ vi.spyOn(VueQuery, 'useQuery');
+
+ withSetup(() => useSchoolsQuery(mockSchoolIds, queryOptions), {
+ plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
+ });
+
+ expect(VueQuery.useQuery).toHaveBeenCalledWith({
+ queryKey: ['schools', mockSchoolIds],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
diff --git a/src/composables/queries/useUserAssignmentsQuery.js b/src/composables/queries/useUserAssignmentsQuery.js
index d67d82824..1b3c17e9f 100644
--- a/src/composables/queries/useUserAssignmentsQuery.js
+++ b/src/composables/queries/useUserAssignmentsQuery.js
@@ -19,8 +19,8 @@ const useUserAssignmentsQuery = (queryOptions = undefined) => {
const { isQueryEnabled, options } = computeQueryOverrides(queryConditions, queryOptions);
return useQuery({
- queryKey: [USER_ASSIGNMENTS_QUERY_KEY],
- queryFn: () => getUserAssignments(roarUid.value),
+ queryKey: [USER_ASSIGNMENTS_QUERY_KEY, roarUid],
+ queryFn: () => getUserAssignments(roarUid),
// Refetch on window focus for MEFS assessments as those are opened in a separate tab.
refetchOnWindowFocus: 'always',
enabled: isQueryEnabled,
diff --git a/src/composables/queries/useUserAssignmentsQuery.test.js b/src/composables/queries/useUserAssignmentsQuery.test.js
index 8e0c665f4..581a447b1 100644
--- a/src/composables/queries/useUserAssignmentsQuery.test.js
+++ b/src/composables/queries/useUserAssignmentsQuery.test.js
@@ -34,7 +34,7 @@ describe('useUserAssignmentsQuery', () => {
});
it('should call query with correct parameters', () => {
- const mockUserId = nanoid();
+ const mockUserId = ref(nanoid());
const authStore = useAuthStore(piniaInstance);
authStore.roarUid = mockUserId;
@@ -47,7 +47,7 @@ describe('useUserAssignmentsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-assignments'],
+ queryKey: ['user-assignments', mockUserId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
@@ -59,7 +59,7 @@ describe('useUserAssignmentsQuery', () => {
});
it('should correctly control the enabled state of the query', async () => {
- const mockUserId = nanoid();
+ const mockUserId = ref(nanoid());
const authStore = useAuthStore(piniaInstance);
authStore.roarUid = mockUserId;
@@ -76,7 +76,7 @@ describe('useUserAssignmentsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-assignments'],
+ queryKey: ['user-assignments', mockUserId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -93,11 +93,11 @@ describe('useUserAssignmentsQuery', () => {
expect(getUserAssignments).toHaveBeenCalledWith(mockUserId);
});
- it('should only fetch data if the uid is available', async () => {
- const mockUserId = nanoid();
+ it('should only fetch data once the uid is available', async () => {
+ const mockUserId = ref(null);
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = null;
+ authStore.roarUid = mockUserId;
authStore.userQueryKeyIndex = 1;
const queryOptions = { enabled: true };
@@ -107,7 +107,7 @@ describe('useUserAssignmentsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-assignments'],
+ queryKey: ['user-assignments', mockUserId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -118,15 +118,17 @@ describe('useUserAssignmentsQuery', () => {
expect(getUserAssignments).not.toHaveBeenCalled();
- authStore.roarUid = mockUserId;
+ mockUserId.value = nanoid();
await nextTick();
expect(getUserAssignments).toHaveBeenCalledWith(mockUserId);
});
it('should not let queryOptions override the internally computed value', async () => {
+ const mockUserId = ref(null);
+
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = null;
+ authStore.roarUid = mockUserId;
authStore.userQueryKeyIndex = 1;
const queryOptions = { enabled: true };
@@ -136,7 +138,7 @@ describe('useUserAssignmentsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-assignments'],
+ queryKey: ['user-assignments', mockUserId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
diff --git a/src/composables/queries/useUserClaimsQuery.js b/src/composables/queries/useUserClaimsQuery.js
index fb36506c8..214715053 100644
--- a/src/composables/queries/useUserClaimsQuery.js
+++ b/src/composables/queries/useUserClaimsQuery.js
@@ -19,8 +19,8 @@ const useUserClaimsQuery = (queryOptions = undefined) => {
const { isQueryEnabled, options } = computeQueryOverrides(queryConditions, queryOptions);
return useQuery({
- queryKey: [USER_CLAIMS_QUERY_KEY, uid.value, userQueryKeyIndex.value],
- queryFn: () => fetchDocById(FIRESTORE_COLLECTIONS.USER_CLAIMS, uid.value),
+ queryKey: [USER_CLAIMS_QUERY_KEY, uid, userQueryKeyIndex],
+ queryFn: () => fetchDocById(FIRESTORE_COLLECTIONS.USER_CLAIMS, uid),
enabled: isQueryEnabled,
...options,
});
diff --git a/src/composables/queries/useUserClaimsQuery.test.js b/src/composables/queries/useUserClaimsQuery.test.js
index 03b14ef4a..57d57a11c 100644
--- a/src/composables/queries/useUserClaimsQuery.test.js
+++ b/src/composables/queries/useUserClaimsQuery.test.js
@@ -34,11 +34,12 @@ describe('useUserClaimsQuery', () => {
});
it('should call query with correct parameters', () => {
- const mockUserId = nanoid();
+ const mockUserId = ref(nanoid());
+ const mockUserQueryKeyIndex = ref(1);
const authStore = useAuthStore(piniaInstance);
authStore.uid = mockUserId;
- authStore.userQueryKeyIndex = 1;
+ authStore.userQueryKeyIndex = mockUserQueryKeyIndex;
vi.spyOn(VueQuery, 'useQuery');
@@ -47,7 +48,7 @@ describe('useUserClaimsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-claims', mockUserId, 1],
+ queryKey: ['user-claims', mockUserId, mockUserQueryKeyIndex],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
@@ -58,11 +59,12 @@ describe('useUserClaimsQuery', () => {
});
it('should correctly control the enabled state of the query', async () => {
- const mockUserId = nanoid();
+ const mockUserId = ref(nanoid());
+ const mockUserQueryKeyIndex = ref(5);
const authStore = useAuthStore(piniaInstance);
authStore.uid = mockUserId;
- authStore.userQueryKeyIndex = 1;
+ authStore.userQueryKeyIndex = mockUserQueryKeyIndex;
const enableQuery = ref(false);
@@ -75,7 +77,7 @@ describe('useUserClaimsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-claims', mockUserId, 1],
+ queryKey: ['user-claims', mockUserId, mockUserQueryKeyIndex],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -91,12 +93,13 @@ describe('useUserClaimsQuery', () => {
expect(fetchDocById).toHaveBeenCalledWith('userClaims', mockUserId);
});
- it('should only fetch data if the uid is available', async () => {
- const mockUserId = nanoid();
+ it('should only fetch data if once uid is available', async () => {
+ const mockUserId = ref(null);
+ const mockUserQueryKeyIndex = ref(5);
const authStore = useAuthStore(piniaInstance);
- authStore.uid = null;
- authStore.userQueryKeyIndex = 1;
+ authStore.uid = mockUserId;
+ authStore.userQueryKeyIndex = mockUserQueryKeyIndex;
const queryOptions = { enabled: true };
@@ -105,7 +108,7 @@ describe('useUserClaimsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-claims', null, 1],
+ queryKey: ['user-claims', mockUserId, mockUserQueryKeyIndex],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -115,16 +118,19 @@ describe('useUserClaimsQuery', () => {
expect(fetchDocById).not.toHaveBeenCalled();
- authStore.uid = mockUserId;
+ mockUserId.value = nanoid();
await nextTick();
expect(fetchDocById).toHaveBeenCalledWith('userClaims', mockUserId);
});
it('should not let queryOptions override the internally computed value', async () => {
+ const mockUserId = ref(null);
+ const mockUserQueryKeyIndex = ref(5);
+
const authStore = useAuthStore(piniaInstance);
- authStore.uid = null;
- authStore.userQueryKeyIndex = 1;
+ authStore.uid = mockUserId;
+ authStore.userQueryKeyIndex = mockUserQueryKeyIndex;
const queryOptions = { enabled: true };
@@ -133,7 +139,7 @@ describe('useUserClaimsQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-claims', null, 1],
+ queryKey: ['user-claims', mockUserId, mockUserQueryKeyIndex],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
diff --git a/src/composables/queries/useUserDataQuery.js b/src/composables/queries/useUserDataQuery.js
index a91e36df2..ec801e7f0 100644
--- a/src/composables/queries/useUserDataQuery.js
+++ b/src/composables/queries/useUserDataQuery.js
@@ -23,8 +23,8 @@ const useUserDataQuery = (userId = undefined, queryOptions = undefined) => {
const { isQueryEnabled, options } = computeQueryOverrides(queryConditions, queryOptions);
return useQuery({
- queryKey: [USER_DATA_QUERY_KEY, uid.value, userQueryKeyIndex.value],
- queryFn: () => fetchDocById(FIRESTORE_COLLECTIONS.USERS, uid.value),
+ queryKey: [USER_DATA_QUERY_KEY, uid, userQueryKeyIndex],
+ queryFn: () => fetchDocById(FIRESTORE_COLLECTIONS.USERS, uid),
enabled: isQueryEnabled,
...options,
});
diff --git a/src/composables/queries/useUserDataQuery.test.js b/src/composables/queries/useUserDataQuery.test.js
index 667dde6b1..e4752ec99 100644
--- a/src/composables/queries/useUserDataQuery.test.js
+++ b/src/composables/queries/useUserDataQuery.test.js
@@ -34,11 +34,12 @@ describe('useUserDataQuery', () => {
});
it('should call query with correct parameters', () => {
- const mockUserId = nanoid();
+ const mockUserRoarUid = ref(nanoid());
+ const mockUserQueryKeyIndex = ref(1);
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = mockUserId;
- authStore.userQueryKeyIndex = 1;
+ authStore.roarUid = mockUserRoarUid;
+ authStore.userQueryKeyIndex = mockUserQueryKeyIndex;
vi.spyOn(VueQuery, 'useQuery');
@@ -46,24 +47,28 @@ describe('useUserDataQuery', () => {
plugins: [[VueQuery.VueQueryPlugin, { queryClient }]],
});
- expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user', mockUserId, 1],
- queryFn: expect.any(Function),
- enabled: expect.objectContaining({
- _value: true,
+ expect(VueQuery.useQuery).toHaveBeenCalledWith(
+ expect.objectContaining({
+ queryKey: ['user', expect.objectContaining({ _value: authStore.roarUid }), mockUserQueryKeyIndex],
+ queryFn: expect.any(Function),
+ enabled: expect.objectContaining({
+ _value: true,
+ }),
}),
- });
+ );
- expect(fetchDocById).toHaveBeenCalledWith('users', mockUserId);
+ expect(fetchDocById).toHaveBeenCalledWith('users', expect.objectContaining({ _value: authStore.roarUid }));
});
it('should allow the use of a manual user ID', async () => {
- const mockUserId = nanoid();
- const mockStudentUserId = nanoid();
+ const mockUserRoarUid = ref(nanoid());
+ const mockUserQueryKeyIndex = ref(1);
+
+ const mockStudentUserId = ref(nanoid());
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = mockUserId;
- authStore.userQueryKeyIndex = 1;
+ authStore.roarUid = mockUserRoarUid;
+ authStore.userQueryKeyIndex = mockUserQueryKeyIndex;
vi.spyOn(VueQuery, 'useQuery');
@@ -72,22 +77,23 @@ describe('useUserDataQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user', mockStudentUserId, 1],
+ queryKey: ['user', expect.objectContaining({ _value: mockStudentUserId }), mockUserQueryKeyIndex],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
}),
});
- expect(fetchDocById).toHaveBeenCalledWith('users', mockStudentUserId);
+ expect(fetchDocById).toHaveBeenCalledWith('users', expect.objectContaining({ _value: mockStudentUserId }));
});
it('should correctly control the enabled state of the query', async () => {
- const mockUserId = nanoid();
+ const mockUserRoarUid = ref(nanoid());
+ const mockUserQueryKeyIndex = ref(1);
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = mockUserId;
- authStore.userQueryKeyIndex = 1;
+ authStore.roarUid = mockUserRoarUid;
+ authStore.userQueryKeyIndex = mockUserQueryKeyIndex;
const enableQuery = ref(false);
@@ -100,7 +106,7 @@ describe('useUserDataQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user', mockUserId, 1],
+ queryKey: ['user', expect.objectContaining({ _value: authStore.roarUid }), mockUserQueryKeyIndex],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -113,15 +119,16 @@ describe('useUserDataQuery', () => {
enableQuery.value = true;
await nextTick();
- expect(fetchDocById).toHaveBeenCalledWith('users', mockUserId);
+ expect(fetchDocById).toHaveBeenCalledWith('users', expect.objectContaining({ _value: authStore.roarUid }));
});
- it('should only fetch data if the roarUid is available', async () => {
- const mockUserId = nanoid();
+ it('should only fetch data once the roarUid is available', async () => {
+ const mockUserRoarUid = ref(null);
+ const mockUserQueryKeyIndex = ref(1);
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = null;
- authStore.userQueryKeyIndex = 1;
+ authStore.roarUid = mockUserRoarUid;
+ authStore.userQueryKeyIndex = mockUserQueryKeyIndex;
const queryOptions = { enabled: true };
@@ -130,7 +137,7 @@ describe('useUserDataQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user', null, 1],
+ queryKey: ['user', expect.objectContaining({ _value: authStore.roarUid }), mockUserQueryKeyIndex],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -140,16 +147,19 @@ describe('useUserDataQuery', () => {
expect(fetchDocById).not.toHaveBeenCalled();
- authStore.roarUid = mockUserId;
+ mockUserRoarUid.value = nanoid();
await nextTick();
- expect(fetchDocById).toHaveBeenCalledWith('users', mockUserId);
+ expect(fetchDocById).toHaveBeenCalledWith('users', expect.objectContaining({ _value: authStore.roarUid }));
});
it('should not let queryOptions override the internally computed value', async () => {
+ const mockUserRoarUid = ref(null);
+ const mockUserQueryKeyIndex = ref(1);
+
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = null;
- authStore.userQueryKeyIndex = 1;
+ authStore.roarUid = mockUserRoarUid;
+ authStore.userQueryKeyIndex = mockUserQueryKeyIndex;
const queryOptions = { enabled: true };
@@ -158,7 +168,7 @@ describe('useUserDataQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user', null, 1],
+ queryKey: ['user', expect.objectContaining({ _value: authStore.roarUid }), mockUserQueryKeyIndex],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
diff --git a/src/composables/queries/useUserRunPageQuery.js b/src/composables/queries/useUserRunPageQuery.js
index f264c5b45..bb3a4671d 100644
--- a/src/composables/queries/useUserRunPageQuery.js
+++ b/src/composables/queries/useUserRunPageQuery.js
@@ -36,7 +36,7 @@ const useUserRunPageQuery = (userId, administrationId, orgType, orgId, queryOpti
const { isQueryEnabled, options } = computeQueryOverrides(queryConditions, queryOptions);
return useQuery({
- queryKey: [USER_RUN_PAGE_QUERY_KEY, toValue(userId), toValue(administrationId), toValue(orgType), toValue(orgId)],
+ queryKey: [USER_RUN_PAGE_QUERY_KEY, userId, administrationId, orgType, orgId],
queryFn: async () => {
const runPageData = await runPageFetcher({
administrationId: administrationId,
diff --git a/src/composables/queries/useUserStudentDataQuery.js b/src/composables/queries/useUserStudentDataQuery.js
index 32550a4b1..34d19b7f4 100644
--- a/src/composables/queries/useUserStudentDataQuery.js
+++ b/src/composables/queries/useUserStudentDataQuery.js
@@ -9,6 +9,8 @@ import { FIRESTORE_COLLECTIONS } from '@/constants/firebase';
/**
* User student data query.
*
+ * @TODO: Evaluate wether this query can be replaced by the existing useUserDataQuery composable.
+ *
* @param {QueryOptions|undefined} queryOptions – Optional TanStack query options.
* @returns {UseQueryResult} The TanStack query result.
*/
@@ -21,8 +23,8 @@ const useUserStudentDataQuery = (queryOptions = undefined) => {
const { isQueryEnabled, options } = computeQueryOverrides(queryConditions, queryOptions);
return useQuery({
- queryKey: [USER_STUDENT_DATA_QUERY_KEY],
- queryFn: () => fetchDocById(FIRESTORE_COLLECTIONS.USERS, roarUid.value, ['studentData']),
+ queryKey: [USER_STUDENT_DATA_QUERY_KEY, roarUid],
+ queryFn: () => fetchDocById(FIRESTORE_COLLECTIONS.USERS, roarUid, ['studentData']),
enabled: isQueryEnabled,
...options,
});
diff --git a/src/composables/queries/useUserStudentDataQuery.test.js b/src/composables/queries/useUserStudentDataQuery.test.js
index cdbbc50b1..771ed4404 100644
--- a/src/composables/queries/useUserStudentDataQuery.test.js
+++ b/src/composables/queries/useUserStudentDataQuery.test.js
@@ -34,11 +34,10 @@ describe('useUserStudentDataQuery', () => {
});
it('should call query with correct parameters', () => {
- const mockUserId = nanoid();
+ const mockUserRoarId = ref(nanoid());
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = mockUserId;
- authStore.userQueryKeyIndex = 1;
+ authStore.roarUid = mockUserRoarId;
vi.spyOn(VueQuery, 'useQuery');
@@ -47,21 +46,21 @@ describe('useUserStudentDataQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-student'],
+ queryKey: ['user-student', mockUserRoarId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: true,
}),
});
- expect(fetchDocById).toHaveBeenCalledWith('users', mockUserId, ['studentData']);
+ expect(fetchDocById).toHaveBeenCalledWith('users', mockUserRoarId, ['studentData']);
});
it('should correctly control the enabled state of the query', async () => {
- const mockUserId = nanoid();
+ const mockUserRoarId = ref(nanoid());
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = mockUserId;
+ authStore.roarUid = mockUserRoarId;
const enableQuery = ref(false);
@@ -74,7 +73,7 @@ describe('useUserStudentDataQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-student'],
+ queryKey: ['user-student', mockUserRoarId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -87,14 +86,14 @@ describe('useUserStudentDataQuery', () => {
enableQuery.value = true;
await nextTick();
- expect(fetchDocById).toHaveBeenCalledWith('users', mockUserId, ['studentData']);
+ expect(fetchDocById).toHaveBeenCalledWith('users', mockUserRoarId, ['studentData']);
});
- it('should only fetch data if the roarUid is available', async () => {
- const mockUserId = nanoid();
+ it('should only fetch data once the roarUid is available', async () => {
+ const mockUserRoarId = ref(null);
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = null;
+ authStore.roarUid = mockUserRoarId;
const queryOptions = { enabled: true };
@@ -103,7 +102,7 @@ describe('useUserStudentDataQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-student'],
+ queryKey: ['user-student', mockUserRoarId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
@@ -113,15 +112,23 @@ describe('useUserStudentDataQuery', () => {
expect(fetchDocById).not.toHaveBeenCalled();
- authStore.roarUid = mockUserId;
+ mockUserRoarId.value = nanoid();
await nextTick();
- expect(fetchDocById).toHaveBeenCalledWith('users', mockUserId, ['studentData']);
+ expect(VueQuery.useQuery).toHaveBeenCalledWith(
+ expect.objectContaining({
+ queryKey: ['user-student', mockUserRoarId],
+ }),
+ );
+
+ expect(fetchDocById).toHaveBeenCalledWith('users', mockUserRoarId, ['studentData']);
});
it('should not let queryOptions override the internally computed value', async () => {
+ const mockUserRoarId = ref(null);
+
const authStore = useAuthStore(piniaInstance);
- authStore.roarUid = null;
+ authStore.roarUid = mockUserRoarId;
const queryOptions = { enabled: true };
@@ -130,7 +137,7 @@ describe('useUserStudentDataQuery', () => {
});
expect(VueQuery.useQuery).toHaveBeenCalledWith({
- queryKey: ['user-student'],
+ queryKey: ['user-student', mockUserRoarId],
queryFn: expect.any(Function),
enabled: expect.objectContaining({
_value: false,
diff --git a/src/constants/mutationKeys.js b/src/constants/mutationKeys.js
index 00648609c..2bd7308cb 100644
--- a/src/constants/mutationKeys.js
+++ b/src/constants/mutationKeys.js
@@ -1,3 +1,4 @@
+export const ADMINISTRATION_UPSERT_MUTATION_KEY = 'administration-upsert';
export const TASK_ADD_MUTATION_KEY = 'task-add';
export const TASK_UPDATE_MUTATION_KEY = 'task-update';
export const TASK_VARIANT_ADD_MUTATION_KEY = 'task-variant-add';
diff --git a/src/constants/routes.js b/src/constants/routes.js
index 3577c905e..19d1deeb4 100644
--- a/src/constants/routes.js
+++ b/src/constants/routes.js
@@ -7,6 +7,7 @@
* @constant {Object} APP_ROUTES – The individual routes of the application.
*/
export const APP_ROUTES = {
+ HOME: '/',
SIGN_IN: '/signin',
PROGRESS_REPORT: '/administration/:administrationId/:orgType/:orgId',
SCORE_REPORT: '/scores/:administrationId/:orgType/:orgId',
diff --git a/src/helpers/hasArrayEntries.js b/src/helpers/hasArrayEntries.js
new file mode 100644
index 000000000..2b06e90de
--- /dev/null
+++ b/src/helpers/hasArrayEntries.js
@@ -0,0 +1,11 @@
+import { toValue } from 'vue';
+
+/**
+ * Test if an array has entries.
+ *
+ * @param {Array} array – The array to check for entries.
+ * @returns {boolean} Whether the array has entries.
+ */
+export const hasArrayEntries = (array) => {
+ return Array.isArray(toValue(array)) && toValue(array).length > 0;
+};
diff --git a/src/helpers/hasArrayEntries.test.js b/src/helpers/hasArrayEntries.test.js
new file mode 100644
index 000000000..398cd8940
--- /dev/null
+++ b/src/helpers/hasArrayEntries.test.js
@@ -0,0 +1,41 @@
+import { describe, it, expect } from 'vitest';
+import { ref } from 'vue';
+import { hasArrayEntries } from './hasArrayEntries';
+
+describe('hasArrayEntries', () => {
+ it('should return true for non-empty arrays', () => {
+ expect(hasArrayEntries([1, 2, 3])).toBe(true);
+ });
+
+ it('should return false for empty arrays', () => {
+ expect(hasArrayEntries([])).toBe(false);
+ });
+
+ it('should return false for null', () => {
+ expect(hasArrayEntries(null)).toBe(false);
+ });
+
+ it('should return false for undefined', () => {
+ expect(hasArrayEntries(undefined)).toBe(false);
+ });
+
+ it('should return true for non-empty Vue ref with arrays', () => {
+ const refWithArray = ref([1, 2, 3]);
+ expect(hasArrayEntries(refWithArray)).toBe(true);
+ });
+
+ it('should return false for empty Vue ref with arrays', () => {
+ const emptyRefWithArray = ref([]);
+ expect(hasArrayEntries(emptyRefWithArray)).toBe(false);
+ });
+
+ it('should return false for Vue ref with null', () => {
+ const refWithNull = ref(null);
+ expect(hasArrayEntries(refWithNull)).toBe(false);
+ });
+
+ it('should return false for Vue ref with undefined', () => {
+ const refWithUndefined = ref(undefined);
+ expect(hasArrayEntries(refWithUndefined)).toBe(false);
+ });
+});
diff --git a/src/helpers/query/runs.js b/src/helpers/query/runs.js
index 839042ebb..f41c46a33 100644
--- a/src/helpers/query/runs.js
+++ b/src/helpers/query/runs.js
@@ -1,3 +1,4 @@
+import { toValue } from 'vue';
import _pick from 'lodash/pick';
import _get from 'lodash/get';
import _mapValues from 'lodash/mapValues';
@@ -6,6 +7,23 @@ import _without from 'lodash/without';
import { convertValues, getAxiosInstance, mapFields } from './utils';
import { pluralizeFirestoreCollection } from '@/helpers';
+/**
+ * Constructs the request body for fetching runs based on the provided parameters.
+ *
+ * @param {Object} params - The parameters for constructing the request body.
+ * @param {string} params.administrationId - The administration ID.
+ * @param {string} params.orgType - The type of the organization.
+ * @param {string} params.orgId - The ID of the organization.
+ * @param {string} [params.taskId] - The task ID.
+ * @param {boolean} [params.aggregationQuery] - Whether to use aggregation query.
+ * @param {number} [params.pageLimit] - The page limit for pagination.
+ * @param {number} [params.page] - The page number for pagination.
+ * @param {boolean} [params.paginate=true] - Whether to paginate the results.
+ * @param {Array} [params.select=['scores.computed.composite']] - The fields to select.
+ * @param {boolean} [params.allDescendants=true] - Whether to include all descendants.
+ * @param {boolean} [params.requireCompleted=false] - Whether to require completed runs.
+ * @returns {Object} The constructed request body.
+ */
export const getRunsRequestBody = ({
administrationId,
orgType,
@@ -44,7 +62,6 @@ export const getRunsRequestBody = ({
];
if (administrationId && (orgId || !allDescendants)) {
- console.log('adding assignmentId and bestRun to structuredQuery');
requestBody.structuredQuery.where = {
compositeFilter: {
op: 'AND',
@@ -68,7 +85,6 @@ export const getRunsRequestBody = ({
};
if (orgId) {
- console.log('adding orgId to structuredQuery');
requestBody.structuredQuery.where.compositeFilter.filters.push({
fieldFilter: {
field: { fieldPath: `readOrgs.${pluralizeFirestoreCollection(orgType)}` },
@@ -131,12 +147,20 @@ export const getRunsRequestBody = ({
return requestBody;
};
-export const runCounter = (administrationId, orgType, orgId) => {
+/**
+ * Counts the number of runs for a given administration and organization.
+ *
+ * @param {string} administrationId - The administration ID.
+ * @param {string} orgType - The type of the organization.
+ * @param {string} orgId - The ID of the organization.
+ * @returns {Promise} The count of runs.
+ */
+export const runCounter = async (administrationId, orgType, orgId) => {
const axiosInstance = getAxiosInstance('app');
const requestBody = getRunsRequestBody({
- administrationId,
- orgType,
- orgId,
+ administrationId: toValue(administrationId),
+ orgType: toValue(orgType),
+ orgId: toValue(orgId),
aggregationQuery: true,
});
return axiosInstance.post(':runAggregationQuery', requestBody).then(({ data }) => {
@@ -144,6 +168,22 @@ export const runCounter = (administrationId, orgType, orgId) => {
});
};
+/**
+ * Fetches run page data for a given set of parameters.
+ *
+ * @param {Object} params - The parameters for fetching run page data.
+ * @param {string} params.administrationId - The administration ID.
+ * @param {string} [params.userId] - The user ID.
+ * @param {string} params.orgType - The organization type.
+ * @param {string} params.orgId - The organization ID.
+ * @param {string} [params.taskId] - The task ID.
+ * @param {number} [params.pageLimit] - The page limit for pagination.
+ * @param {number} [params.page] - The page number for pagination.
+ * @param {Array} [params.select=['scores.computed.composite']] - The fields to select.
+ * @param {string} [params.scoreKey='scores.computed.composite'] - The key for scores.
+ * @param {boolean} [params.paginate=true] - Whether to paginate the results.
+ * @returns {Promise>} The fetched run page data.
+ */
export const runPageFetcher = async ({
administrationId,
userId,
@@ -158,18 +198,18 @@ export const runPageFetcher = async ({
}) => {
const appAxiosInstance = getAxiosInstance('app');
const requestBody = getRunsRequestBody({
- administrationId,
- orgType,
- orgId,
- taskId,
- allDescendants: userId === undefined,
+ administrationId: toValue(administrationId),
+ orgType: toValue(orgType),
+ orgId: toValue(orgId),
+ taskId: toValue(taskId),
+ allDescendants: toValue(userId) === undefined,
aggregationQuery: false,
- pageLimit: paginate ? pageLimit.value : undefined,
- page: paginate ? page.value : undefined,
- paginate: paginate,
- select: select,
+ pageLimit: paginate ? toValue(pageLimit) : undefined,
+ page: paginate ? toValue(page) : undefined,
+ paginate: toValue(paginate),
+ select: toValue(select),
});
- const runQuery = userId === undefined ? ':runQuery' : `/users/${userId}:runQuery`;
+ const runQuery = toValue(userId) === undefined ? ':runQuery' : `/users/${toValue(userId)}:runQuery`;
return appAxiosInstance.post(runQuery, requestBody).then(async ({ data }) => {
const runData = mapFields(data, true);
diff --git a/src/helpers/query/users.js b/src/helpers/query/users.js
index 5ff0837d9..afeb62491 100644
--- a/src/helpers/query/users.js
+++ b/src/helpers/query/users.js
@@ -1,5 +1,22 @@
+import { toValue } from 'vue';
import { convertValues, getAxiosInstance, mapFields } from './utils';
+/**
+ * Constructs the request body for fetching users.
+ *
+ * @param {Object} params - The parameters for constructing the request body.
+ * @param {Array} [params.userIds=[]] - The IDs of the users to fetch.
+ * @param {string} params.orgType - The type of the organization (e.g., 'districts', 'schools').
+ * @param {string} params.orgId - The ID of the organization.
+ * @param {boolean} params.aggregationQuery - Whether to perform an aggregation query.
+ * @param {number} params.pageLimit - The maximum number of users to fetch per page.
+ * @param {number} params.page - The page number to fetch.
+ * @param {boolean} [params.paginate=true] - Whether to paginate the results.
+ * @param {Array} [params.select=['name']] - The fields to select in the query.
+ * @param {string} params.orderBy - The field to order the results by.
+ * @returns {Object} The constructed request body.
+ * @throws {Error} If neither userIds nor orgType and orgId are provided.
+ */
export const getUsersRequestBody = ({
userIds = [],
orgType,
@@ -81,45 +98,71 @@ export const getUsersRequestBody = ({
return requestBody;
};
+/**
+ * Fetches a page of users based on the provided user IDs.
+ *
+ * @param {Array} userIds - The IDs of the users to fetch.
+ * @param {number} pageLimit - The maximum number of users to fetch per page.
+ * @param {number} page - The page number to fetch.
+ * @returns {Promise