Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"@graphql-tools/load": "^7.7.0",
"@graphql-tools/url-loader": "^7.17.18",
"@kiva/kv-analytics": "^1.4.0",
"@kiva/kv-components": "^10.3.0",
"@kiva/kv-components": "^10.4.1",
"@kiva/kv-shop": "^3.8.28",
"@kiva/kv-tokens": "^5.0.0",
"@mdi/js": "^7.4.47",
Expand Down
8 changes: 3 additions & 5 deletions src/components/Checkout/DonationItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -385,11 +385,9 @@ export default {
return !this.isCampaignDonation && !this.orderTotalVariant;
},
showTipFromBalanceVariant() {
// The compressed one-line layout exists to make room for the switch below it. With no tip
// there is no switch, so the row keeps the layout the repayments prompt was designed against
return this.tipFromBalanceEligible
&& numeral(this.donation.price).value() > 0
&& this.canHostTipFromBalanceToggle;
// Ignores the tip amount: at zero the switch and its label go, but the copy and layout
// stay so the row does not flip back to the control mid-checkout
return this.tipFromBalanceEligible && this.canHostTipFromBalanceToggle;
},
donationTitle() {
return 'Donation to Kiva';
Expand Down
22 changes: 17 additions & 5 deletions src/components/Checkout/KivaCreditTipToggle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,38 @@ const AUDIENCE_DEPOSIT_LIMIT = 1000;
export const TIP_FROM_BALANCE_SEEDED_COOKIE = 'kvtipseeded';

/**
* Whether the lender is in the experiment audience, ignoring which arm they are in. The checkout
* page reads this too, so the variant treatment only appears where the switch itself can.
* Whether the lender gets the variant treatment, ignoring the tip. The checkout page renders
* the variant copy and layout on this alone, so zeroing the tip removes the switch without
* flipping the page back to the control mid-checkout.
*
* The balance has to exceed everything in the basket but the tip, not merely be positive: below
* that the amount due is the raw shortfall either way, so the switch cannot change what is
* charged. Unknown deposits read as ineligible until the basket query lands.
*
* @param {Object} state The basket state provided by the checkout page
* @returns {boolean} Whether the lender is in the experiment audience
* @returns {boolean} Whether the lender gets the variant treatment
*/
export function meetsTipFromBalanceCriteria(state = {}) {
export function meetsTipFromBalanceTreatmentCriteria(state = {}) {
return !!state.myId
&& state.balance > state.nonTipTotal
&& state.hasLoans
&& state.tipAmount > 0
&& !state.onTeam
&& typeof state.lifetimeDeposits === 'number'
&& state.lifetimeDeposits < AUDIENCE_DEPOSIT_LIMIT;
}

/**
* Whether the lender is in the experiment audience, ignoring which arm they are in. On top of
* the treatment criteria this needs a tip for the balance to cover: with none there is no
* decision to make, so neither arm renders the switch or fires exposure.
*
* @param {Object} state The basket state provided by the checkout page
* @returns {boolean} Whether the lender is in the experiment audience
*/
export function meetsTipFromBalanceCriteria(state = {}) {
Comment thread
dyersituations marked this conversation as resolved.
return meetsTipFromBalanceTreatmentCriteria(state) && state.tipAmount > 0;
}

export default {
name: 'KivaCreditTipToggle',
components: {
Expand Down
9 changes: 5 additions & 4 deletions src/pages/Checkout/CheckoutPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ import { CUSTOM_TIP_DEFAULT_EXP_KEY } from '#src/components/Checkout/DonationNud
import {
TIP_FROM_BALANCE_EXP_KEY,
TIP_FROM_BALANCE_SEEDED_COOKIE,
meetsTipFromBalanceCriteria,
meetsTipFromBalanceTreatmentCriteria,
} from '#src/components/Checkout/KivaCreditTipToggle';
import updateKivaCreditDonationPreference from '#src/graphql/mutation/updateKivaCreditDonationPreference.graphql';
import experimentAssignmentQuery from '#src/graphql/query/experimentAssignment.graphql';
Expand Down Expand Up @@ -977,10 +977,11 @@ export default {
&& this.applyKivaCreditToDonation === false);
},
showTipFromBalanceVariant() {
// The variant treatment only appears where the switch can, so a lender without a
// balance, a loan or a tip sees the original checkout untouched
// Ignores the tip amount on purpose: a lender who zeroes the tip loses the switch but
// keeps the copy and layout, instead of watching the page flip back to the control
// mid-checkout. Exposure still requires a tip, through the toggle's own criteria
return this.tipFromBalanceVersion === 'b'
&& meetsTipFromBalanceCriteria(this.tipToggleBasketState);
&& meetsTipFromBalanceTreatmentCriteria(this.tipToggleBasketState);
},
isKivaCreditText() {
return this.isKivaCreditReplacementExpEnabled ? 'Account balance' : 'Kiva Credit';
Expand Down
8 changes: 4 additions & 4 deletions test/unit/specs/components/Checkout/DonationItem.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,13 @@ describe('DonationItem showTipFromBalanceVariant', () => {
})).toBe(false);
});

// The compressed layout only earns its place when there is a switch to make room for, and
// at a zero tip the row shares space with the donate-repayments prompt instead
it('stays off at a zero tip, where there is no switch to make room for', () => {
// A zeroed tip keeps the treatment: the switch and its label go, but the copy and layout
// staying put means the page never flips back to the control mid-checkout
it('keeps the variant styling at a zero tip, where only the switch goes', () => {
expect(showVariant({
tipFromBalanceEligible: true,
canHostTipFromBalanceToggle: true,
tip: '0.00',
})).toBe(false);
})).toBe(true);
});
});
43 changes: 43 additions & 0 deletions test/unit/specs/pages/Checkout/CheckoutPage.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -620,3 +620,46 @@ describe('CheckoutPage tipToggleBasketState', () => {
expect(meetsTipFromBalanceCriteria(tipToggleBasketState({ ...context, myBalance: '40.00' }))).toBe(true);
});
});

describe('CheckoutPage showTipFromBalanceVariant', () => {
const showVariant = context => CheckoutPage.computed.showTipFromBalanceVariant.call(context);

const basketState = (overrides = {}) => ({
myId: 1234,
balance: 40,
hasLoans: true,
tipAmount: 5,
nonTipTotal: 25,
onTeam: false,
lifetimeDeposits: 0,
...overrides,
});

it('shows the treatment for an eligible variant lender', () => {
expect(showVariant({
tipFromBalanceVersion: 'b',
tipToggleBasketState: basketState(),
})).toBe(true);
});

it('keeps the treatment at a zero tip, so only the switch disappears', () => {
expect(showVariant({
tipFromBalanceVersion: 'b',
tipToggleBasketState: basketState({ tipAmount: 0 }),
})).toBe(true);
});

it('never shows the treatment for control', () => {
expect(showVariant({
tipFromBalanceVersion: 'a',
tipToggleBasketState: basketState(),
})).toBe(false);
});

it('never shows the treatment outside the audience, zero tip or not', () => {
expect(showVariant({
tipFromBalanceVersion: 'b',
tipToggleBasketState: basketState({ tipAmount: 0, onTeam: true }),
})).toBe(false);
});
});
Loading