From 3eb6951d1d11a49d2b56414032a6671c897b1472 Mon Sep 17 00:00:00 2001 From: Eddie Ferrer Date: Wed, 29 Nov 2023 12:29:31 -0700 Subject: [PATCH] refactor: remove mixin in favor of composition api --- .../components/KvClassicLoanCard.spec.js | 63 +++++ .../specs/components/KvWideLoanCard.spec.js | 63 +++++ @kiva/kv-components/utils/loanCard.js | 189 +++++++++++++ @kiva/kv-components/utils/loanCardMixin.js | 252 ------------------ @kiva/kv-components/vue/KvClassicLoanCard.vue | 138 +++++++++- @kiva/kv-components/vue/KvWideLoanCard.vue | 139 +++++++++- .../vue/stories/KvClassicLoanCard.stories.js | 33 +++ .../vue/stories/KvWideLoanCard.stories.js | 41 +-- 8 files changed, 642 insertions(+), 276 deletions(-) create mode 100644 @kiva/kv-components/tests/unit/specs/components/KvClassicLoanCard.spec.js create mode 100644 @kiva/kv-components/tests/unit/specs/components/KvWideLoanCard.spec.js create mode 100644 @kiva/kv-components/utils/loanCard.js delete mode 100644 @kiva/kv-components/utils/loanCardMixin.js diff --git a/@kiva/kv-components/tests/unit/specs/components/KvClassicLoanCard.spec.js b/@kiva/kv-components/tests/unit/specs/components/KvClassicLoanCard.spec.js new file mode 100644 index 00000000..cc465cf8 --- /dev/null +++ b/@kiva/kv-components/tests/unit/specs/components/KvClassicLoanCard.spec.js @@ -0,0 +1,63 @@ +import { render } from '@testing-library/vue'; +import { axe } from 'jest-axe'; +import KvClassicLoanCard from '../../../../vue/KvClassicLoanCard.vue'; + +const nextWeek = new Date(); +nextWeek.setDate(new Date().getDate() + 7); +const kvTrackFunction = () => { }; + +const photoPath = 'https://www-kiva-org.freetls.fastly.net/img/'; +const loan = { + id: 1, + name: 'Alan', + geocode: { + city: 'Lyantonde', + state: 'Central Region', + country: { + isoCode: 'UG', + name: 'Uganda', + region: 'Africa', + __typename: 'Country', + }, + __typename: 'Geocode', + }, + image: { hash: '9673d0722a7675b9b8d11f90849d9b44' }, + fundraisingPercent: 0.5, + unreservedAmount: '500.00', + use: 'to purchase heifers to increase headcount of cattle and sales of organic milk.', + status: 'fundraising', + loanAmount: '1000.00', + borrowerCount: 1, + activity: { + id: 61, + name: 'Dairy', + __typename: 'Activity', + }, + sector: { + id: 1, + name: 'Agriculture', + __typename: 'Sector', + }, + plannedExpirationDate: nextWeek.toISOString(), +}; + +describe('KvClassicLoanCard', () => { + it('has no automated accessibility violations', async () => { + const { container } = render(KvClassicLoanCard, + { + props: { + loanId: loan.id, + loan: { + ...loan, + unreservedAmount: undefined, + fundraisingPercent: undefined, + }, + kvTrackFunction, + photoPath, + externalLinks: true, + }, + }); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); diff --git a/@kiva/kv-components/tests/unit/specs/components/KvWideLoanCard.spec.js b/@kiva/kv-components/tests/unit/specs/components/KvWideLoanCard.spec.js new file mode 100644 index 00000000..3456353b --- /dev/null +++ b/@kiva/kv-components/tests/unit/specs/components/KvWideLoanCard.spec.js @@ -0,0 +1,63 @@ +import { render } from '@testing-library/vue'; +import { axe } from 'jest-axe'; +import KvWideLoanCard from '../../../../vue/KvWideLoanCard.vue'; + +const nextWeek = new Date(); +nextWeek.setDate(new Date().getDate() + 7); +const kvTrackFunction = () => { }; + +const photoPath = 'https://www-kiva-org.freetls.fastly.net/img/'; +const loan = { + id: 1, + name: 'Alan', + geocode: { + city: 'Lyantonde', + state: 'Central Region', + country: { + isoCode: 'UG', + name: 'Uganda', + region: 'Africa', + __typename: 'Country', + }, + __typename: 'Geocode', + }, + image: { hash: '9673d0722a7675b9b8d11f90849d9b44' }, + fundraisingPercent: 0.5, + unreservedAmount: '500.00', + use: 'to purchase heifers to increase headcount of cattle and sales of organic milk.', + status: 'fundraising', + loanAmount: '1000.00', + borrowerCount: 1, + activity: { + id: 61, + name: 'Dairy', + __typename: 'Activity', + }, + sector: { + id: 1, + name: 'Agriculture', + __typename: 'Sector', + }, + plannedExpirationDate: nextWeek.toISOString(), +}; + +describe('KvWideLoanCard', () => { + it('has no automated accessibility violations', async () => { + const { container } = render(KvWideLoanCard, + { + props: { + loanId: loan.id, + loan: { + ...loan, + unreservedAmount: undefined, + fundraisingPercent: undefined, + }, + kvTrackFunction, + photoPath, + externalLinks: true, + }, + }); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); diff --git a/@kiva/kv-components/utils/loanCard.js b/@kiva/kv-components/utils/loanCard.js new file mode 100644 index 00000000..216e7e66 --- /dev/null +++ b/@kiva/kv-components/utils/loanCard.js @@ -0,0 +1,189 @@ +import { + computed, + toRefs, +} from 'vue-demi'; + +import { mdiMapMarker } from '@mdi/js'; + +const LSE_LOAN_KEY = 'N/A'; +const ECO_FRIENDLY_KEY = 'ECO-FRIENDLY'; +const SUSTAINABLE_AG_KEY = 'SUSTAINABLE AG'; +const SINGLE_PARENT_KEY = 'SINGLE PARENT'; +const REFUGEE_KEY = 'REFUGEES/DISPLACED'; + +export function loanCardComputedProperties(props) { + const { + externalLinks, + customLoanDetails, + loanId, + loan, + categoryPageName, + customCallouts, + } = toRefs(props); + + const tag = computed(() => (externalLinks.value ? 'a' : 'router-link')); + const readMorePath = computed(() => (customLoanDetails.value ? '' : `/lend/${loanId.value}`)); + const isLoading = computed(() => !loanId.value || !loan.value); + const borrowerName = computed(() => loan.value?.name || ''); + const countryName = computed(() => loan.value?.geocode?.country?.name || ''); + const city = computed(() => loan.value?.geocode?.city || ''); + const state = computed(() => loan.value?.geocode?.state || ''); + const distributionModel = computed(() => loan.value?.distributionModel || ''); + const imageHash = computed(() => loan.value?.image?.hash ?? ''); + const hasProgressData = computed(() => { + return typeof loan.value?.unreservedAmount !== 'undefined' + && typeof loan.value?.fundraisingPercent !== 'undefined'; + }); + + const allDataLoaded = computed(() => !isLoading.value && hasProgressData.value); + + const fundraisingPercent = computed(() => loan.value?.fundraisingPercent ?? 0); + + const unreservedAmount = computed(() => { + const stringAmount = loan.value?.unreservedAmount ?? '0'; + return Number(stringAmount); + }); + + const formattedLocation = computed(() => { + if (distributionModel.value === 'direct') { + return `${city.value}, ${state.value}, ${countryName.value}`; + } + if (countryName.value === 'Puerto Rico') { + return `${city.value}, PR`; + } + return countryName.value; + }); + + const loanUse = computed(() => loan.value?.use ?? ''); + + const loanStatus = computed(() => loan.value?.status ?? ''); + + const loanAmount = computed(() => loan.value?.loanAmount ?? '0'); + + const loanBorrowerCount = computed(() => loan.value?.borrowerCount ?? 0); + + const loanCallouts = computed(() => { + const callouts = []; + const activityName = loan.value?.activity?.name ?? ''; + const sectorName = loan.value?.sector?.name ?? ''; + const tags = loan.value?.tags?.filter((loantag) => loantag.charAt(0) === '#') + .map((loantag) => loantag.substring(1)) ?? []; + const themes = loan.value?.themes ?? []; + const categories = { + ecoFriendly: !!tags + .filter((t) => t.toUpperCase() === ECO_FRIENDLY_KEY || t.toUpperCase() === SUSTAINABLE_AG_KEY).length, + refugeesIdps: !!themes.filter((t) => t.toUpperCase() === REFUGEE_KEY).length, + singleParents: !!tags.filter((t) => t.toUpperCase() === SINGLE_PARENT_KEY).length, + }; + + const isLseLoan = loan.value?.partnerName?.toUpperCase().includes(LSE_LOAN_KEY); + + // P1 Category + // Exp limited to: Eco-friendly, Refugees and IDPs, Single Parents + // Tag as first option for LSE loans + if (isLseLoan && tags.length) { + const position = Math.floor(Math.random() * tags.length); + const atag = tags[position]; + callouts.push(atag); + } + + if (!categoryPageName.value) { + if (categories.ecoFriendly + // eslint-disable-next-line max-len + && !callouts.find((c) => c.toUpperCase() === ECO_FRIENDLY_KEY || c.toUpperCase() === SUSTAINABLE_AG_KEY)) { + callouts.push('Eco-friendly'); + } else if (categories.refugeesIdps) { + callouts.push('Refugees and IDPs'); + } else if (categories.singleParents + && !callouts.find((c) => c.toUpperCase() === SINGLE_PARENT_KEY)) { + callouts.push('Single Parent'); + } + } + + // P2 Activity + if (activityName && categoryPageName.value?.toUpperCase() !== activityName.toUpperCase()) { + callouts.push(activityName); + } + + // P3 Sector + if (sectorName + && (activityName.toUpperCase() !== sectorName.toUpperCase()) + && (sectorName.toUpperCase() !== categoryPageName.value?.toUpperCase()) + && callouts.length < 2) { + callouts.push(sectorName); + } + + // P4 Tag + if (!!tags.length && callouts.length < 2) { + const position = Math.floor(Math.random() * tags.length); + const atag = tags[position]; + if (!callouts.filter((c) => c.toUpperCase() === tag.toUpperCase()).length) { + callouts.push(atag); + } + } + + // P5 Theme + if (!!themes.length && callouts.length < 2) { + const position = Math.floor(Math.random() * themes.length); + const theme = themes[position]; + if (!callouts.filter((c) => c.toUpperCase() === theme.toUpperCase()).length + && theme.toUpperCase() !== categoryPageName.value?.toUpperCase()) { + callouts.push(theme); + } + } + + // Only show one callout for LSE loans + if (isLseLoan && callouts.length > 1) return [callouts.shift()]; + + // Add all custom callouts if available + callouts.push(...customCallouts?.value); + return callouts; + }); + + return { + tag, + readMorePath, + isLoading, + borrowerName, + countryName, + city, + state, + distributionModel, + imageHash, + hasProgressData, + allDataLoaded, + fundraisingPercent, + unreservedAmount, + formattedLocation, + loanUse, + loanStatus, + loanAmount, + loanBorrowerCount, + mdiMapMarker, + loanCallouts, + }; +} + +export function loanCardMethods(props) { + const { + loanId, + customLoanDetails, + kvTrackFunction, + } = toRefs(props); + + function showLoanDetails(e) { + if (customLoanDetails.value) { + e.preventDefault(); + this.$emit('show-loan-details'); + } + } + + function clickReadMore(target) { + kvTrackFunction.value('Lending', 'click-Read more', target, loanId.value); + } + + return { + showLoanDetails, + clickReadMore, + }; +} diff --git a/@kiva/kv-components/utils/loanCardMixin.js b/@kiva/kv-components/utils/loanCardMixin.js deleted file mode 100644 index 4c1c4698..00000000 --- a/@kiva/kv-components/utils/loanCardMixin.js +++ /dev/null @@ -1,252 +0,0 @@ -import { mdiMapMarker } from '@mdi/js'; - -const LSE_LOAN_KEY = 'N/A'; -const ECO_FRIENDLY_KEY = 'ECO-FRIENDLY'; -const SUSTAINABLE_AG_KEY = 'SUSTAINABLE AG'; -const SINGLE_PARENT_KEY = 'SINGLE PARENT'; -const REFUGEE_KEY = 'REFUGEES/DISPLACED'; - -export default { - props: { - loanId: { - type: Number, - default: undefined, - }, - loan: { - type: Object, - default: null, - }, - customLoanDetails: { - type: Boolean, - default: false, - }, - showTags: { - type: Boolean, - default: false, - }, - categoryPageName: { - type: String, - default: '', - }, - enableFiveDollarsNotes: { - type: Boolean, - default: false, - }, - isAdding: { - type: Boolean, - default: false, - }, - isVisitor: { - type: Boolean, - default: true, - }, - basketItems: { - type: Array, - default: () => ([]), - }, - isBookmarked: { - type: Boolean, - default: false, - }, - kvTrackFunction: { - type: Function, - required: true, - }, - photoPath: { - type: String, - required: true, - }, - showViewLoan: { - type: Boolean, - default: false, - }, - externalLinks: { - type: Boolean, - default: false, - }, - route: { - type: Object, - default: undefined, - }, - userBalance: { - type: String, - default: undefined, - }, - getCookie: { - type: Function, - default: undefined, - }, - setCookie: { - type: Function, - default: undefined, - }, - fiveDollarsSelected: { - type: Boolean, - default: false, - }, - customCallouts: { - type: Array, - default: () => ([]), - }, - }, - data() { - return { - mdiMapMarker, - }; - }, - computed: { - tag() { - return this.externalLinks ? 'a' : 'router-link'; - }, - readMorePath() { - return this.customLoanDetails ? '' : `/lend/${this.loanId}`; - }, - isLoading() { - return !this.loanId || !this.loan; - }, - borrowerName() { - return this.loan?.name || ''; - }, - countryName() { - return this.loan?.geocode?.country?.name || ''; - }, - city() { - return this.loan?.geocode?.city || ''; - }, - state() { - return this.loan?.geocode?.state || ''; - }, - distributionModel() { - return this.loan?.distributionModel || ''; - }, - imageHash() { - return this.loan?.image?.hash ?? ''; - }, - hasProgressData() { - // Local resolver values for the progress bar load client-side - return typeof this.loan?.unreservedAmount !== 'undefined' - && typeof this.loan?.fundraisingPercent !== 'undefined'; - }, - allDataLoaded() { - return !this.isLoading && this.hasProgressData; - }, - fundraisingPercent() { - return this.loan?.fundraisingPercent ?? 0; - }, - unreservedAmount() { - const stringAmount = this.loan?.unreservedAmount ?? '0'; - // convert stringAmount to a number - return Number(stringAmount); - }, - formattedLocation() { - if (this.distributionModel === 'direct') { - const formattedString = `${this.city}, ${this.state}, ${this.countryName}`; - return formattedString; - } - if (this.countryName === 'Puerto Rico') { - const formattedString = `${this.city}, PR`; - return formattedString; - } - return this.countryName; - }, - loanUse() { - return this.loan?.use ?? ''; - }, - loanStatus() { - return this.loan?.status ?? ''; - }, - loanAmount() { - return this.loan?.loanAmount ?? '0'; - }, - loanBorrowerCount() { - return this.loan?.borrowerCount ?? 0; - }, - loanCallouts() { - const callouts = []; - const activityName = this.loan?.activity?.name ?? ''; - const sectorName = this.loan?.sector?.name ?? ''; - const tags = this.loan?.tags?.filter((tag) => tag.charAt(0) === '#') - .map((tag) => tag.substring(1)) ?? []; - const themes = this.loan?.themes ?? []; - const categories = { - ecoFriendly: !!tags // eslint-disable-next-line max-len - .filter((t) => t.toUpperCase() === ECO_FRIENDLY_KEY || t.toUpperCase() === SUSTAINABLE_AG_KEY).length, - refugeesIdps: !!themes.filter((t) => t.toUpperCase() === REFUGEE_KEY).length, - singleParents: !!tags.filter((t) => t.toUpperCase() === SINGLE_PARENT_KEY).length, - }; - - const isLseLoan = this.loan?.partnerName?.toUpperCase().includes(LSE_LOAN_KEY); - - // P1 Category - // Exp limited to: Eco-friendly, Refugees and IDPs, Single Parents - // Tag as first option for LSE loans - if (isLseLoan && tags.length) { - const position = Math.floor(Math.random() * tags.length); - const tag = tags[position]; - callouts.push(tag); - } - - if (!this.categoryPageName) { - if (categories.ecoFriendly // eslint-disable-next-line max-len - && !callouts.find((c) => c.toUpperCase() === ECO_FRIENDLY_KEY || c.toUpperCase() === SUSTAINABLE_AG_KEY)) { - callouts.push('Eco-friendly'); - } else if (categories.refugeesIdps) { - callouts.push('Refugees and IDPs'); - } else if (categories.singleParents - && !callouts.find((c) => c.toUpperCase() === SINGLE_PARENT_KEY)) { - callouts.push('Single Parent'); - } - } - - // P2 Activity - if (activityName && this.categoryPageName?.toUpperCase() !== activityName.toUpperCase()) { - callouts.push(activityName); - } - - // P3 Sector - if (sectorName - && (activityName.toUpperCase() !== sectorName.toUpperCase()) - && (sectorName.toUpperCase() !== this.categoryPageName?.toUpperCase()) - && callouts.length < 2) { - callouts.push(sectorName); - } - - // P4 Tag - if (!!tags.length && callouts.length < 2) { - const position = Math.floor(Math.random() * tags.length); - const tag = tags[position]; - if (!callouts.filter((c) => c.toUpperCase() === tag.toUpperCase()).length) { - callouts.push(tag); - } - } - - // P5 Theme - if (!!themes.length && callouts.length < 2) { - const position = Math.floor(Math.random() * themes.length); - const theme = themes[position]; - if (!callouts.filter((c) => c.toUpperCase() === theme.toUpperCase()).length - && theme.toUpperCase() !== this.categoryPageName?.toUpperCase()) { - callouts.push(theme); - } - } - - // Only show one callout for LSE loans - if (isLseLoan && callouts.length > 1) return [callouts.shift()]; - - // Add all custom callouts if available - callouts.push(...this.customCallouts); - return callouts; - }, - }, - methods: { - showLoanDetails(e) { - if (this.customLoanDetails) { - e.preventDefault(); - this.$emit('show-loan-details'); - } - }, - clickReadMore(target) { - this.kvTrackFunction('Lending', 'click-Read more', target, this.loanId); - }, - }, -}; diff --git a/@kiva/kv-components/vue/KvClassicLoanCard.vue b/@kiva/kv-components/vue/KvClassicLoanCard.vue index 014eacd4..97530b05 100644 --- a/@kiva/kv-components/vue/KvClassicLoanCard.vue +++ b/@kiva/kv-components/vue/KvClassicLoanCard.vue @@ -220,7 +220,8 @@