Skip to content

Commit

Permalink
Merge pull request #445 from wwahammy/move-post-success-into-own-file
Browse files Browse the repository at this point in the history
Move postSuccess into own file
  • Loading branch information
wwahammy authored Oct 6, 2023
2 parents 39ff29a + d31f2d7 commit 300e8ca
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// License: LGPL-3.0-or-later
import PlausibleCallback from './PlausibleCallback';


describe('PlausibleCallback', () => {
describe('.canRun', () => {
it('false when getPlausible is undefined', () => {
const c = new PlausibleCallback({ props: {}} as any);
expect(c.canRun()).toEqual(false)
})

it('false when getPlausible returns undefined', () => {
const c = new PlausibleCallback({ props: {getPlausible: ():any => undefined}} as any);
expect(c.canRun()).toEqual(false)
})

it('true when returns plausible function', () => {
const realPlausibleFunc = jest.fn();
const c = new PlausibleCallback({ props: {getPlausible: ():any => realPlausibleFunc}} as any);
expect(c.canRun()).toEqual(true);
})
})

describe('.run', () => {
function build(result?:{charge?:{amount?:number}}) {
const realPlausibleFunc = jest.fn();
return {
plausible: realPlausibleFunc,
obj: new PlausibleCallback({ props: {getPlausible: ():any => realPlausibleFunc}, result} as any)
};

}

it('calls plausible with no amount when result is undefined', async () => {
const {plausible, obj} = build();
await obj.run();
expect(plausible).toHaveBeenCalledWith('payment_succeeded', {
props: {
amount: undefined,
}
});
})

it('calls plausible with no amount when charge is undefined', async () => {
const {plausible, obj} = build({});
await obj.run();
expect(plausible).toHaveBeenCalledWith('payment_succeeded', {
props: {
amount: undefined,
}
});
})

it('calls plausible with no amount when charge.amount is undefined', async () => {
const {plausible, obj} = build({charge:{}});
await obj.run();
expect(plausible).toHaveBeenCalledWith('payment_succeeded', {
props: {
amount: undefined,
}
});
})

it('calls plausible with amount/100 when charge.amount is defined', async () => {
const {plausible, obj} = build({charge:{amount: 1000}});
await obj.run();
expect(plausible).toHaveBeenCalledWith('payment_succeeded', {
props: {
amount: 10,
}
});
})
});

describe('.catchError', () => {
it('does not rethrow errors', () => {
const c = new PlausibleCallback({} as any);
expect(() => c.catchError(new Error())).not.toThrow();
})
})
});
36 changes: 36 additions & 0 deletions client/js/nonprofits/donate/DonationSubmitter/PlausibleCallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Callback } from "../../../../../app/javascript/common/Callbacks";
import DonationSubmitter from './';

// License: LGPL-3.0-or-later
export interface PlausibleFunction {
(eventType: string, val: any): void
}

export interface GetPlausible {

(): PlausibleFunction | undefined
}


export default class PlausibleCallback extends Callback<DonationSubmitter> {

private get plausibleFunction(): PlausibleFunction {
return this.props.props.getPlausible()
}
canRun(): boolean {
return !!(this.props.props.getPlausible && this.props.props.getPlausible())
}

run(): void {
this.plausibleFunction('payment_succeeded', {
props: {
amount: this.props.result?.charge?.amount && (this.props.result.charge.amount / 100)
}
});
}

catchError(e: unknown): void {
console.log(e);
}

}
81 changes: 72 additions & 9 deletions client/js/nonprofits/donate/DonationSubmitter/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
// License: LGPL-3.0-or-later
import DonationSubmitter from '.';
import run from '../../../../../app/javascript/common/Callbacks/run';
import PlausibleCallback from './PlausibleCallback';
import {waitFor} from '@testing-library/dom';

jest.mock('../../../../../app/javascript/common/Callbacks/run', () => jest.fn());

describe('DonationSubmitter', () => {


function SetupDonationSubmitter(updated=jest.fn()) {
beforeEach(() => {
jest.clearAllMocks();
})

function SetupDonationSubmitter(updated=jest.fn(), getPlausible=jest.fn()) {
const runCallbacks = run as jest.Mock;

const ret = {
submitter: new DonationSubmitter(),
submitter: new DonationSubmitter({getPlausible}),
updated,
getPlausible,
runCallbacks,
};

ret.submitter.addEventListener('updated', ret.updated)


return ret;

}

it('has only one postSuccess callback', () => {
const ret = SetupDonationSubmitter()
expect(Array.from(ret.submitter.callbacks().keys())).toStrictEqual(['success'])

expect(ret.submitter.callbacks('success')).toStrictEqual({before: [], after: [PlausibleCallback]})
})

describe("before anything happens", () => {

function prepare(): ReturnType<typeof SetupDonationSubmitter> {
Expand Down Expand Up @@ -52,6 +72,12 @@ describe('DonationSubmitter', () => {
const {updated} = prepare()
expect(updated).not.toHaveBeenCalled()
})

it('has not ran callbacks', () => {
const {runCallbacks} = prepare();
expect(runCallbacks).not.toHaveBeenCalled();

});
})

describe("when beginSubmit and then savedCard", () => {
Expand Down Expand Up @@ -106,10 +132,16 @@ describe('DonationSubmitter', () => {

expect(updated).toHaveBeenCalledTimes(2);
})

it('has not ran callbacks', () => {
const {runCallbacks} = prepare();
expect(runCallbacks).not.toHaveBeenCalled();

});
})

describe("when beginSubmit and then completed", () => {

const donationResult = { };
function prepare(): ReturnType<typeof SetupDonationSubmitter> {
const mocked = SetupDonationSubmitter();
Expand Down Expand Up @@ -156,13 +188,19 @@ describe('DonationSubmitter', () => {
expect(updated).toHaveBeenCalledTimes(3);
})

it('calling completed twice only fires it once', () => {
it('calling completed twice only fires it once', async () => {
const {submitter: state, updated} = prepare();
state.reportCompleted(donationResult);

expect(updated).toHaveBeenCalledTimes(3)
})

it('has ran callbacks', async () => {
const {runCallbacks, submitter:state} = prepare();
expect(runCallbacks).toHaveBeenCalledWith(state, []);

await waitFor(() => expect(runCallbacks).toHaveBeenCalledWith(state, [PlausibleCallback]))
});
})

describe("when beginSubmit and then errored", () => {
Expand Down Expand Up @@ -221,6 +259,12 @@ describe('DonationSubmitter', () => {
expect(updated).toHaveBeenCalledTimes(2);

})

it('has not ran callbacks', () => {
const {runCallbacks} = prepare();
expect(runCallbacks).not.toHaveBeenCalled();

});
})

describe("when savedCard and then errored", () => {
Expand Down Expand Up @@ -278,6 +322,12 @@ describe('DonationSubmitter', () => {

expect(updated).toHaveBeenCalledTimes(3);
})

it('has not ran callbacks', () => {
const {runCallbacks} = prepare();
expect(runCallbacks).not.toHaveBeenCalled();

});
});


Expand Down Expand Up @@ -329,13 +379,18 @@ describe('DonationSubmitter', () => {
expect(updated).toHaveBeenCalledTimes(4);
});

it('has not ran callbacks', () => {
const {runCallbacks} = prepare();
expect(runCallbacks).not.toHaveBeenCalled();

});
})

describe("when errored and then re-attempted", () => {
describe("when errored and then succeeded", () => {
const error = "Error message";
const donationResult:any = { charge: undefined };
function prepare(): ReturnType<typeof SetupDonationSubmitter> {
const mocked = SetupDonationSubmitter(jest.fn());
const mocked = SetupDonationSubmitter(jest.fn(), jest.fn());
mocked.submitter.reportBeginSubmit();
mocked.submitter.reportSavedCard();
mocked.submitter.reportError(error);
Expand Down Expand Up @@ -380,6 +435,14 @@ describe('DonationSubmitter', () => {

expect(updated).toHaveBeenCalledTimes(6);
});

it('has ran callbacks', async () => {
const {runCallbacks, submitter:state} = prepare();
expect(runCallbacks).toHaveBeenCalledWith(state, []);
await waitFor(() => expect(runCallbacks).toHaveBeenCalledWith(state, [PlausibleCallback]))


});
})


Expand Down
35 changes: 31 additions & 4 deletions client/js/nonprofits/donate/DonationSubmitter/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
// License: LGPL-3.0-or-later

import StateManager, {DonationResult} from "./StateManager";
import noop from 'lodash/noop';

export default class DonationSubmitter implements EventTarget {
import StateManager, { DonationResult } from "./StateManager";
import { CallbackControllerBuilder } from '../../../../../app/javascript/common/Callbacks';


import PlausibleCallback, { GetPlausible } from './PlausibleCallback';
import type { CallbackAccessor, CallbackFilters, CallbackMap, CallbackClass } from "../../../../../app/javascript/common/Callbacks/types";

interface DonationSubmitterProps {
getPlausible?: GetPlausible,
}

type ActionNames = 'success'

export default class DonationSubmitter implements EventTarget, CallbackAccessor<DonationSubmitter, ActionNames> {


private stateManager = new StateManager();

private eventTarget = new EventTarget();

constructor() {
private callbackController = new CallbackControllerBuilder('success').withInputType<DonationSubmitter>();

constructor(public readonly props: DonationSubmitterProps) {
this.callbackController.addAfterCallback('success', PlausibleCallback);

this.stateManager.addEventListener('beginSubmit', this.handleBeginSubmit);
this.stateManager.addEventListener('savedCard', this.handleSavedCard);
Expand Down Expand Up @@ -40,7 +56,17 @@ export default class DonationSubmitter implements EventTarget {
return this.stateManager.result;
}

public reportBeginSubmit():void {
private async postSuccess(): Promise<void> {
await this.callbackController.run('success', this, noop);
}

callbacks(): CallbackMap<DonationSubmitter, ActionNames>;
callbacks(actionName: ActionNames): CallbackFilters<CallbackClass<DonationSubmitter>> | undefined;
callbacks(actionName?: ActionNames): CallbackMap<DonationSubmitter, ActionNames> | CallbackFilters<CallbackClass<DonationSubmitter>> | undefined {
return this.callbackController.callbacks(actionName);
}

public reportBeginSubmit(): void {
this.stateManager.reportBeginSubmit();
}

Expand All @@ -67,6 +93,7 @@ export default class DonationSubmitter implements EventTarget {
}

private handleCompleted = (_evt: Event) => {
this.postSuccess();
this.dispatchEvent(new Event('updated'));
}

Expand Down
Loading

0 comments on commit 300e8ca

Please sign in to comment.