• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kiva / ui / 19147367510

06 Nov 2025 07:26PM UTC coverage: 91.443% (+41.5%) from 49.902%
19147367510

push

github

emuvente
test: refactor category-row-arrows-visible-mixin test with runner method

3722 of 3979 branches covered (93.54%)

Branch coverage included in aggregate %.

18923 of 20785 relevant lines covered (91.04%)

78.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.31
/src/composables/useGivingFund.js
1
import logFormatter from '#src/util/logFormatter';
1✔
2
// eslint-disable-next-line max-len
1✔
3
import myGivingFundParticipationDonations from '#src/graphql/query/portfolio/myGivingFundParticipationDonations.graphql';
1✔
4
import myGivingFundParticipationFull from '#src/graphql/query/portfolio/myGivingFundParticipationFull.graphql';
1✔
5
import myGivingFundsQuery from '#src/graphql/query/portfolio/myGivingFunds.graphql';
1✔
6
import myGivingFundsCountQuery from '#src/graphql/query/portfolio/myGivingFundsCount.graphql';
1✔
7

8
export default function useGivingFund(apollo) {
1✔
9
        const DEFAULT_LIMIT = 20;
40✔
10

11
        /**
40✔
12
         * Util method to generate offsets for paginated fetching
40✔
13
         */
40✔
14
        const generateOffsets = (totalDonationEntryCount, limit) => {
40✔
15
                // return empty array if total count is less than or equal to limit
2✔
16
                if (totalDonationEntryCount <= limit) {
2!
17
                        return [];
×
18
                }
×
19
                // figure out how many more donations to fetch
2✔
20
                const totalToFetch = totalDonationEntryCount - limit;
2✔
21
                // calculate how many more fetches we need to do
2✔
22
                const fetchesNeeded = Math.ceil(totalToFetch / limit);
2✔
23
                // create an array of offsets to fetch
2✔
24
                const offsets = Array.from({ length: fetchesNeeded }, (_, i) => (i + 1) * limit);
2✔
25
                return offsets;
2✔
26
        };
40✔
27

28
        /**
40✔
29
         * Get a users Giving Fund Data
40✔
30
         */
40✔
31
        const fetchMyGivingFundsData = async () => {
40✔
32
                try {
3✔
33
                        const response = await apollo.query({
3✔
34
                                query: myGivingFundsQuery,
3✔
35
                                fetchPolicy: 'network-only',
3✔
36
                        });
3✔
37
                        return response?.data?.my ?? {};
3✔
38
                } catch (error) {
3✔
39
                        logFormatter(`Error fetching giving fund data: ${error}`, 'error');
1✔
40
                }
1✔
41
        };
40✔
42

43
        /**
40✔
44
         * Get the number of Giving Funds a user has
40✔
45
         */
40✔
46
        const fetchMyGivingFundsCount = async () => {
40✔
47
                try {
2✔
48
                        const response = await apollo.query({
2✔
49
                                query: myGivingFundsCountQuery,
2✔
50
                                fetchPolicy: 'network-only',
2✔
51
                        });
2✔
52
                        return response?.data?.my ?? {};
2!
53
                } catch (error) {
2✔
54
                        logFormatter(`Error fetching giving fund data: ${error}`, 'error');
1✔
55
                }
1✔
56
        };
40✔
57

58
        /**
40✔
59
         * Generic Fetch method for Donation Participation
40✔
60
         * Note: the myGivingFundParticipationDonations is abbreviated to focus on amount donated and the fund id
40✔
61
         */
40✔
62
        const fetchGivingFundDonationData = async (fundIds = [], limit = DEFAULT_LIMIT, offset = 0) => {
40✔
63
                const variables = {
17✔
64
                        // default is 10, increasing to 20 to reduce need to fetch more
17✔
65
                        limit,
17✔
66
                        offset,
17✔
67
                };
17✔
68
                // if we have fundIds, add to variables
17✔
69
                if (fundIds.length) {
17✔
70
                        variables.filter = {
6✔
71
                                fundIds,
6✔
72
                        };
6✔
73
                }
6✔
74

75
                try {
17✔
76
                        const response = await apollo.query({
17✔
77
                                query: myGivingFundParticipationDonations,
17✔
78
                                fetchPolicy: 'network-only',
17✔
79
                                variables,
17✔
80
                        });
17✔
81

82
                        // return query result
16✔
83
                        return response?.data?.my ?? {};
17✔
84
                } catch (error) {
17✔
85
                        logFormatter(`Error fetching giving fund donation data: ${error}`, 'error');
1✔
86
                }
1✔
87
        };
40✔
88

89
        const getFundsContributedToIds = async (ownerId = null) => {
40✔
90
                const fundIds = [];
7✔
91
                const donationEntries = [];
7✔
92
                await fetchGivingFundDonationData().then(data => {
7✔
93
                        const totalDonationEntryCount = data?.givingFundParticipation?.totalCount || 0;
7✔
94
                        // extract unique fund ids from donation data
7✔
95
                        if (totalDonationEntryCount && data?.givingFundParticipation?.values.length) {
7✔
96
                                // push initial donation entry to fund entries
5✔
97
                                donationEntries.push(...data.givingFundParticipation.values);
5✔
98
                                // if our totalCount is greater than our default limit, fetch the rest
5✔
99
                                if (totalDonationEntryCount > DEFAULT_LIMIT) {
5✔
100
                                        const offsets = generateOffsets(totalDonationEntryCount, DEFAULT_LIMIT);
1✔
101
                                        // fetch all offsets in parallel
1✔
102
                                        // eslint-disable-next-line max-len
1✔
103
                                        const fetchPromises = offsets.map(offset => fetchGivingFundDonationData(fundIds, DEFAULT_LIMIT, offset));
1✔
104
                                        // wait for all fetches to complete
1✔
105
                                        Promise.all(fetchPromises).then(results => {
1✔
106
                                                // extract donation entries from each result
1✔
107
                                                results.forEach(result => {
1✔
108
                                                        if (result?.givingFundParticipation?.values.length) {
1✔
109
                                                                donationEntries.push(...result.givingFundParticipation.values);
1✔
110
                                                        }
1✔
111
                                                });
1✔
112
                                        });
1✔
113
                                }
1✔
114
                                // filter out funds without owner or owned by current user
5✔
115
                                const filteredDonations = donationEntries?.filter(donation => {
5✔
116
                                        return donation?.givingFund?.owner?.id && donation?.givingFund?.owner?.id !== parseInt(ownerId, 10);
30✔
117
                                });
5✔
118
                                // extract unique fund ids
5✔
119
                                filteredDonations.forEach(donation => {
5✔
120
                                        if (!fundIds.includes(donation.givingFund?.id)) {
27✔
121
                                                fundIds.push(donation.givingFund?.id);
26✔
122
                                        }
26✔
123
                                });
5✔
124
                        }
5✔
125
                });
7✔
126
                return fundIds;
7✔
127
        };
40✔
128

129
        const getTotalDonatedForSingleOffset = async (fundId, offset) => {
40✔
130
                let totalDonated = 0;
2✔
131
                await fetchGivingFundDonationData([fundId], DEFAULT_LIMIT, offset).then(data => {
2✔
132
                        // return total donated amount
2✔
133
                        totalDonated = data?.givingFundParticipation?.totalCount
2✔
134
                                ? data.givingFundParticipation.values
2✔
135
                                        .reduce((sum, donation) => sum + donation.amountDonated, 0)
1✔
136
                                : 0;
2✔
137
                });
2✔
138
                return totalDonated;
2✔
139
        };
40✔
140

141
        const getDonationTotalsForFund = async fundId => {
40✔
142
                let totalDonated = 0;
7✔
143
                if (!fundId) {
7✔
144
                        return totalDonated;
4✔
145
                }
4✔
146

147
                // fetch donation data for fundId
3✔
148
                await fetchGivingFundDonationData([fundId]).then(data => {
3✔
149
                        // if totalCount is greater than default limit
3✔
150
                        // fetch with a series of offsets to get all donation data
3✔
151
                        // then sum all donation amounts
3✔
152
                        // else return single fetch total amount
3✔
153
                        // if no donations, return 0
3✔
154
                        if (data?.givingFundParticipation?.totalCount > DEFAULT_LIMIT) {
3✔
155
                                // fetch the rest of the donation data
1✔
156
                                // figure out how many more donations to fetch
1✔
157
                                const totalToFetch = data.givingFundParticipation.totalCount - 1;
1✔
158
                                // calculate how many more fetches we need to do
1✔
159
                                const fetchesNeeded = Math.ceil(totalToFetch / DEFAULT_LIMIT);
1✔
160
                                // create an array of offsets to fetch
1✔
161
                                const offsets = Array.from({ length: fetchesNeeded }, (_, i) => (i + 1) * DEFAULT_LIMIT);
1✔
162
                                // fetch all offsets in parallel
1✔
163
                                const fetchPromises = offsets.map(offset => getTotalDonatedForSingleOffset(fundId, offset));
1✔
164
                                // wait for all fetches to complete
1✔
165
                                return Promise.all(fetchPromises).then(results => {
1✔
166
                                        // sum all results
1✔
167
                                        totalDonated = results.reduce((sum, amount) => sum + amount, 0);
1✔
168
                                        // add the initial donation amount
1✔
169
                                        totalDonated += data.givingFundParticipation.values
1✔
170
                                                .reduce((sum, donation) => sum + donation.amountDonated, 0);
1✔
171
                                });
1✔
172
                        }
1✔
173
                        // return single total donated amount
2✔
174
                        totalDonated = data?.givingFundParticipation?.totalCount
2✔
175
                                ? data.givingFundParticipation.values
3✔
176
                                        .reduce((sum, donation) => sum + donation.amountDonated, 0)
1✔
177
                                : 0;
3✔
178
                });
3✔
179

180
                return totalDonated;
3✔
181
        };
40✔
182

183
        /**
40✔
184
         * Generic Fetch method for Full Donation Participation Data
40✔
185
         */
40✔
186
        const fetchFullGivingFundDonationData = async (fundIds = [], limit = DEFAULT_LIMIT, offset = 0) => {
40✔
187
                const variables = {
9✔
188
                        limit,
9✔
189
                        offset,
9✔
190
                };
9✔
191
                // if we have fundIds, add to variables
9✔
192
                if (fundIds.length) {
9✔
193
                        variables.filter = {
7✔
194
                                fundIds,
7✔
195
                        };
7✔
196
                }
7✔
197

198
                try {
9✔
199
                        const response = await apollo.query({
9✔
200
                                query: myGivingFundParticipationFull,
9✔
201
                                fetchPolicy: 'network-only',
9✔
202
                                variables,
9✔
203
                        });
9✔
204

205
                        // return query result
8✔
206
                        return response?.data?.my ?? {};
9!
207
                } catch (error) {
9✔
208
                        logFormatter(`Error fetching giving fund donation data: ${error}`, 'error');
1✔
209
                }
1✔
210
        };
40✔
211

212
        const getDedupedFundsContributedToEntries = async (fundIds = []) => {
40✔
213
                const retrievedFundIds = [];
5✔
214
                const dedupedFunds = [];
5✔
215
                let donationEntries = [];
5✔
216
                await fetchFullGivingFundDonationData(fundIds).then(data => {
5✔
217
                        const totalDonationEntryCount = data?.givingFundParticipation?.totalCount || 0;
5✔
218
                        // operate on donation data to extract unique fund entries
5✔
219
                        if (totalDonationEntryCount && data?.givingFundParticipation?.values.length) {
5✔
220
                                // push initial donation entry to fund entries
2✔
221
                                donationEntries.push(...data.givingFundParticipation.values);
2✔
222
                                // if our totalCount is greater than our default limit, fetch the rest
2✔
223
                                if (totalDonationEntryCount > DEFAULT_LIMIT) {
2✔
224
                                        const offsets = generateOffsets(totalDonationEntryCount, DEFAULT_LIMIT);
1✔
225
                                        // fetch all offsets in parallel
1✔
226
                                        // eslint-disable-next-line max-len
1✔
227
                                        const fetchPromises = offsets.map(offset => fetchFullGivingFundDonationData(fundIds, DEFAULT_LIMIT, offset));
1✔
228
                                        // wait for all fetches to complete
1✔
229
                                        Promise.all(fetchPromises).then(results => {
1✔
230
                                                // extract donation entries from each result
1✔
231
                                                results.forEach(result => {
1✔
232
                                                        if (result?.givingFundParticipation?.values.length) {
1✔
233
                                                                donationEntries.push(...result.givingFundParticipation.values);
1✔
234
                                                        }
1✔
235
                                                });
1✔
236
                                        });
1✔
237
                                }
1✔
238

239
                                // map donation entry to fund entries
2✔
240
                                donationEntries = data?.givingFundParticipation?.values?.map(entry => {
2✔
241
                                        return entry?.givingFund;
23✔
242
                                }) ?? [];
2!
243
                                // extract unique funds
2✔
244
                                donationEntries.forEach(givingFund => {
2✔
245
                                        if (!retrievedFundIds.includes(givingFund?.id)) {
23✔
246
                                                retrievedFundIds.push(givingFund?.id);
4✔
247
                                                dedupedFunds.push(givingFund);
4✔
248
                                        }
4✔
249
                                });
2✔
250
                        }
2✔
251
                });
5✔
252
                return dedupedFunds;
5✔
253
        };
40✔
254

255
        const getFundTargetDisplayNounFromName = categoryName => {
40✔
256
                if (!categoryName) return null;
7✔
257
                switch (categoryName) {
5✔
258
                        case 'climate-threatened people':
7✔
259
                                return 'climate action';
1✔
260
                        case 'U.S. entrepreneurs':
7✔
261
                                return 'U.S. small businesses';
1✔
262
                        default:
7✔
263
                                return categoryName;
3✔
264
                }
7✔
265
        };
40✔
266

267
        const getFundTargetSupportedPeoplePhraseFromName = categoryName => {
40✔
268
                if (!categoryName) return null;
7✔
269
                switch (categoryName) {
5✔
270
                        case 'women':
7✔
271
                        case 'refugees':
7✔
272
                        case 'U.S. entrepreneurs':
7✔
273
                                return categoryName;
3✔
274
                        default:
7✔
275
                                return 'people';
2✔
276
                }
7✔
277
        };
40✔
278

279
        return {
40✔
280
                fetchMyGivingFundsCount,
40✔
281
                fetchMyGivingFundsData,
40✔
282
                fetchFullGivingFundDonationData,
40✔
283
                fetchGivingFundDonationData,
40✔
284
                getDedupedFundsContributedToEntries,
40✔
285
                getDonationTotalsForFund,
40✔
286
                getFundsContributedToIds,
40✔
287
                getFundTargetDisplayNounFromName,
40✔
288
                getFundTargetSupportedPeoplePhraseFromName,
40✔
289
        };
40✔
290
}
40✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc