Skip to content

Commit

Permalink
Move comment form ajax post into resource
Browse files Browse the repository at this point in the history
- Pull axios out of the DonationCommentPopUp component
  and wraps it in a resource.
- Add tests for post response handling

Ticket: https://phabricator.wikimedia.org/T367387
  • Loading branch information
Abban committed Sep 10, 2024
1 parent 6859ba7 commit 0622d91
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 17 deletions.
35 changes: 35 additions & 0 deletions src/api/CommentResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import axios, { AxiosResponse } from 'axios';

export interface CommentRequest {
donationId: number;
updateToken: string;
comment: string;
withName: boolean;
isPublic: boolean;
}

interface CommentResponse {
status: string;
message: string;
}

export interface CommentResource {
post: ( data: CommentRequest ) => Promise<string>;
}

export class ApiCommentResource implements CommentResource {
postEndpoint: string;

constructor( postEndpoint: string ) {
this.postEndpoint = postEndpoint;
}

post( data: CommentRequest ): Promise<string> {
return axios.post( this.postEndpoint, data ).then( ( validationResult: AxiosResponse<CommentResponse> ) => {
if ( validationResult.data.status === 'OK' ) {
return Promise.reject();
}
return validationResult.data.message;
} );
}
}
39 changes: 22 additions & 17 deletions src/components/pages/donation_confirmation/DonationCommentPopUp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
<input type="hidden" name="donationId" :value="donation.id"/>
<input type="hidden" name="updateToken" :value="donation.updateToken">
<div v-if="commentHasBeenSubmitted">
<p v-html="$t( serverResponse )"></p>
<p class="donation-comment-server-response" v-html="$t( serverResponse )"></p>
<FormButton
button-type="button"
class="donation-comment-return-button"
:is-outlined="true"
@click="$emit( 'close' )"
>
Expand Down Expand Up @@ -66,24 +67,24 @@
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import axios, { AxiosResponse } from 'axios';
import { computed, inject, onMounted, ref } from 'vue';
import { trackDynamicForm, trackFormSubmission } from '@src/util/tracking';
import { addressTypeFromName, AddressTypeModel } from '@src/view_models/AddressTypeModel';
import { Donation } from '@src/view_models/Donation';
import FormButton from '@src/components/shared/form_elements/FormButton.vue';
import FormSummary from '@src/components/shared/FormSummary.vue';
import TextField from '@src/components/shared/form_fields/TextField.vue';
import CheckboxField from '@src/components/shared/form_fields/CheckboxField.vue';
import { CommentResource } from '@src/api/CommentResource';
interface Props {
donation: Donation;
addressType: string;
postCommentUrl: string;
}
const props = defineProps<Props>();
const emit = defineEmits( [ 'disable-comment-link' ] );
const emit = defineEmits( [ 'disable-comment-link', 'close' ] );
const commentResource = inject<CommentResource>( 'commentResource' );
const commentForm = ref<HTMLFormElement>( null );
const comment = ref<string>( '' );
Expand All @@ -95,19 +96,23 @@ const serverResponse = ref<string>( '' );
const showPublishAuthor = computed<boolean>( () => addressTypeFromName( props.addressType ) !== AddressTypeModel.ANON );
const postComment = (): void => {
const postComment = async (): Promise<void> => {
trackFormSubmission( commentForm.value );
const jsonForm = new FormData( commentForm.value );
axios.post( props.postCommentUrl, jsonForm ).then( ( validationResult: AxiosResponse<any> ) => {
if ( validationResult.data.status === 'OK' ) {
commentErrored.value = false;
commentHasBeenSubmitted.value = true;
serverResponse.value = validationResult.data.message;
emit( 'disable-comment-link' );
} else {
commentErrored.value = true;
}
} );
try {
const message = await commentResource.post( {
donationId: props.donation.id,
updateToken: props.donation.updateToken,
comment: comment.value,
withName: commentHasPublicAuthorName.value,
isPublic: commentIsPublic.value,
} );
commentErrored.value = false;
commentHasBeenSubmitted.value = true;
serverResponse.value = message;
emit( 'disable-comment-link' );
} catch ( e ) {
commentErrored.value = true;
}
};
onMounted( trackDynamicForm );
Expand Down
2 changes: 2 additions & 0 deletions src/pages/donation_confirmation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import DonationConfirmation from '@src/components/pages/DonationConfirmation.vue
import { createFeatureFetcher } from '@src/util/FeatureFetcher';
import { bucketIdToCssClass } from '@src/util/bucket_id_to_css_class';
import { ApiCityAutocompleteResource } from '@src/api/CityAutocompleteResource';
import { ApiCommentResource } from '@src/api/CommentResource';

interface DonationConfirmationModel {
urls: { [ key: string ]: string },
Expand Down Expand Up @@ -93,5 +94,6 @@ store.dispatch(
} );
app.use( store );
app.provide( 'cityAutocompleteResource', new ApiCityAutocompleteResource() );
app.provide( 'commentResource', new ApiCommentResource( pageData.applicationVars.urls.postComment ) );
app.mount( '#app' );
} );
15 changes: 15 additions & 0 deletions tests/unit/TestDoubles/FakeCommentResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { CommentResource } from '@src/api/CommentResource';

export const successMessage = 'Success';

export class FakeSucceedingCommentResource implements CommentResource {
post(): Promise<string> {
return Promise.resolve( successMessage );
}
}

export class FakeFailingCommentResource implements CommentResource {
post(): Promise<string> {
return Promise.reject();
}
}
48 changes: 48 additions & 0 deletions tests/unit/components/DonationCommentPopUp.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { mount } from '@vue/test-utils';
import DonationCommentPopUp from '@src/components/pages/donation_confirmation/DonationCommentPopUp.vue';
import { AddressTypeModel, addressTypeName } from '@src/view_models/AddressTypeModel';
import {
FakeFailingCommentResource,
FakeSucceedingCommentResource,
successMessage
} from '@test/unit/TestDoubles/FakeCommentResource';

describe( 'DonationCommentPopUp.vue', () => {
function getDefaultConfirmationData( isAnonymous: boolean ): any {
Expand Down Expand Up @@ -34,6 +39,11 @@ describe( 'DonationCommentPopUp.vue', () => {
it( 'displays anyonmous comment toggle for private / company donations', () => {
const wrapper = mount( DonationCommentPopUp, {
props: getDefaultConfirmationData( false ),
global: {
provide: {
commentResource: new FakeSucceedingCommentResource(),
},
},
} );

expect( wrapper.find( '#withName' ).exists() ).toBeTruthy();
Expand All @@ -42,8 +52,46 @@ describe( 'DonationCommentPopUp.vue', () => {
it( 'hides anyonmous comment toggle for anonymous donations', () => {
const wrapper = mount( DonationCommentPopUp, {
props: getDefaultConfirmationData( true ),
global: {
provide: {
commentResource: new FakeSucceedingCommentResource(),
},
},
} );

expect( wrapper.find( '#withName' ).exists() ).toBeFalsy();
} );

it( 'shows error when API response is rejected', async () => {
const wrapper = mount( DonationCommentPopUp, {
props: getDefaultConfirmationData( true ),
global: {
provide: {
commentResource: new FakeFailingCommentResource(),
},
},
} );

await wrapper.trigger( 'submit' );

expect( wrapper.find( '#comment-error' ).exists() ).toBeTruthy();
expect( wrapper.find( '#comment-error' ).text() ).toStrictEqual( 'donation_comment_popup_error' );
} );

it( 'shows message returned from API', async () => {
const wrapper = mount( DonationCommentPopUp, {
props: getDefaultConfirmationData( true ),
global: {
provide: {
commentResource: new FakeSucceedingCommentResource(),
},
},
} );

await wrapper.find( '#comment' ).setValue( 'My super great comment' );
await wrapper.trigger( 'submit' );

expect( wrapper.find( '.donation-comment-server-response' ).text() ).toStrictEqual( successMessage );
expect( wrapper.find( '.donation-comment-return-button' ).exists() ).toBeTruthy();
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '@test/data/confirmationData';
import { addressValidationPatterns } from '@test/data/validation';
import { DonorResource } from '@src/api/DonorResource';
import { FakeSucceedingCommentResource } from '@test/unit/TestDoubles/FakeCommentResource';

describe( 'DonationConfirmation.vue', () => {
const getWrapper = ( bankData: ConfirmationData, translateMock: ( key: string ) => string = ( key: string ) => key ): VueWrapper<any> => {
Expand All @@ -39,6 +40,9 @@ describe( 'DonationConfirmation.vue', () => {
$t: translateMock,
$n: () => {},
},
provide: {
commentResource: new FakeSucceedingCommentResource(),
},
},
} );
};
Expand Down

0 comments on commit 0622d91

Please sign in to comment.