Refactor the participation section for an event

And add a test for this new section

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2020-12-04 15:12:00 +01:00
parent c94e431618
commit 96938a5511
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
3 changed files with 324 additions and 76 deletions

View File

@ -0,0 +1,167 @@
<template>
<div>
<div
class="event-participation has-text-right"
v-if="isEventNotAlreadyPassed"
>
<participation-button
v-if="shouldShowParticipationButton"
:participation="participation"
:event="event"
:current-actor="currentActor"
@join-event="(actor) => $emit('join-event', actor)"
@join-modal="$emit('join-modal')"
@join-event-with-confirmation="
(actor) => $emit('join-event-with-confirmation', actor)
"
@confirm-leave="$emit('confirm-leave')"
/>
<b-button
type="is-text"
v-if="!actorIsParticipant && anonymousParticipation !== null"
@click="$emit('cancel-anonymous-participation')"
>{{ $t("Cancel anonymous participation") }}</b-button
>
<small v-if="!actorIsParticipant && anonymousParticipation">
{{ $t("You are participating in this event anonymously") }}
<b-tooltip
:label="
$t(
'This information is saved only on your computer. Click for details'
)
"
>
<router-link :to="{ name: RouteName.TERMS }">
<b-icon size="is-small" icon="help-circle-outline" />
</router-link>
</b-tooltip>
</small>
<small
v-else-if="!actorIsParticipant && anonymousParticipation === false"
>
{{
$t(
"You are participating in this event anonymously but didn't confirm participation"
)
}}
<b-tooltip
:label="
$t(
'This information is saved only on your computer. Click for details'
)
"
>
<router-link :to="{ name: RouteName.TERMS }">
<b-icon size="is-small" icon="help-circle-outline" />
</router-link>
</b-tooltip>
</small>
</div>
<div v-else>
<button class="button is-primary" type="button" slot="trigger" disabled>
<template>
<span>{{ $t("Event already passed") }}</span>
</template>
<b-icon icon="menu-down" />
</button>
</div>
</div>
</template>
<script lang="ts">
import { EventStatus, ParticipantRole } from "@/types/enums";
import { IParticipant } from "@/types/participant.model";
import { Component, Prop, Vue } from "vue-property-decorator";
import RouteName from "@/router/name";
import { IEvent } from "@/types/event.model";
import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor";
import { IPerson } from "@/types/actor";
import { IConfig } from "@/types/config.model";
import { CONFIG } from "@/graphql/config";
import ParticipationButton from "../Event/ParticipationButton.vue";
@Component({
apollo: {
currentActor: CURRENT_ACTOR_CLIENT,
config: CONFIG,
},
components: {
ParticipationButton,
},
})
export default class ParticipationSection extends Vue {
@Prop({ required: true }) participation!: IParticipant;
@Prop({ required: true }) event!: IEvent;
@Prop({ required: true, default: null }) anonymousParticipation!:
| boolean
| null;
currentActor!: IPerson;
config!: IConfig;
RouteName = RouteName;
get actorIsParticipant(): boolean {
if (this.actorIsOrganizer) return true;
return (
this.participation &&
this.participation.role === ParticipantRole.PARTICIPANT
);
}
get actorIsOrganizer(): boolean {
return (
this.participation && this.participation.role === ParticipantRole.CREATOR
);
}
get shouldShowParticipationButton(): boolean {
// If we have an anonymous participation, don't show the participation button
if (
this.config &&
this.config.anonymous.participation.allowed &&
this.anonymousParticipation
) {
return false;
}
// So that people can cancel their participation
if (this.actorIsParticipant) return true;
// You can participate to draft or cancelled events
if (this.event.draft || this.event.status === EventStatus.CANCELLED)
return false;
// Organizer can't participate
if (this.actorIsOrganizer) return false;
// If capacity is OK
if (this.eventCapacityOK) return true;
// Else
return false;
}
get eventCapacityOK(): boolean {
if (this.event.draft) return true;
if (!this.event.options.maximumAttendeeCapacity) return true;
return (
this.event.options.maximumAttendeeCapacity >
this.event.participantStats.participant
);
}
get isEventNotAlreadyPassed(): boolean {
return new Date(this.endDate) > new Date();
}
get endDate(): Date {
return this.event.endsOn !== null && this.event.endsOn > this.event.beginsOn
? this.event.endsOn
: this.event.beginsOn;
}
}
</script>

View File

@ -104,80 +104,16 @@
</span> </span>
</div> </div>
<div class="column is-3-tablet"> <div class="column is-3-tablet">
<div> <participation-section
<div :participation="participations[0]"
class="event-participation has-text-right" :event="event"
v-if="new Date(endDate) > new Date()" :anonymousParticipation="anonymousParticipation"
> @join-event="joinEvent"
<participation-button @join-modal="isJoinModalActive = true"
v-if="shouldShowParticipationButton" @join-event-with-confirmation="joinEventWithConfirmation"
:participation="participations[0]" @confirm-leave="confirmLeave"
:event="event" @cancel-anonymous-participation="cancelAnonymousParticipation"
:current-actor="currentActor" />
@join-event="joinEvent"
@join-modal="isJoinModalActive = true"
@join-event-with-confirmation="joinEventWithConfirmation"
@confirm-leave="confirmLeave"
/>
<b-button
type="is-text"
v-if="
!actorIsParticipant && anonymousParticipation !== null
"
@click="cancelAnonymousParticipation"
>{{ $t("Cancel anonymous participation") }}</b-button
>
<small v-if="!actorIsParticipant && anonymousParticipation">
{{ $t("You are participating in this event anonymously") }}
<b-tooltip
:label="
$t(
'This information is saved only on your computer. Click for details'
)
"
>
<router-link :to="{ name: RouteName.TERMS }">
<b-icon size="is-small" icon="help-circle-outline" />
</router-link>
</b-tooltip>
</small>
<small
v-else-if="
!actorIsParticipant && anonymousParticipation === false
"
>
{{
$t(
"You are participating in this event anonymously but didn't confirm participation"
)
}}
<b-tooltip
:label="
$t(
'This information is saved only on your computer. Click for details'
)
"
>
<router-link :to="{ name: RouteName.TERMS }">
<b-icon size="is-small" icon="help-circle-outline" />
</router-link>
</b-tooltip>
</small>
</div>
<div v-else>
<button
class="button is-primary"
type="button"
slot="trigger"
disabled
>
<template>
<span>{{ $t("Event already passed") }}</span>
</template>
<b-icon icon="menu-down" />
</button>
</div>
</div>
<div class="has-text-right"> <div class="has-text-right">
<template class="visibility" v-if="!event.draft"> <template class="visibility" v-if="!event.draft">
<p v-if="event.visibility === EventVisibility.PUBLIC"> <p v-if="event.visibility === EventVisibility.PUBLIC">
@ -646,7 +582,7 @@ import { IReport } from "../../types/report.model";
import { CREATE_REPORT } from "../../graphql/report"; import { CREATE_REPORT } from "../../graphql/report";
import EventMixin from "../../mixins/event"; import EventMixin from "../../mixins/event";
import IdentityPicker from "../Account/IdentityPicker.vue"; import IdentityPicker from "../Account/IdentityPicker.vue";
import ParticipationButton from "../../components/Event/ParticipationButton.vue"; import ParticipationSection from "../../components/Participation/ParticipationSection.vue";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { Address } from "../../types/address.model"; import { Address } from "../../types/address.model";
import CommentTree from "../../components/Comment/CommentTree.vue"; import CommentTree from "../../components/Comment/CommentTree.vue";
@ -676,7 +612,7 @@ import { IParticipant } from "../../types/participant.model";
DateCalendarIcon, DateCalendarIcon,
ReportModal, ReportModal,
IdentityPicker, IdentityPicker,
ParticipationButton, ParticipationSection,
CommentTree, CommentTree,
Tag, Tag,
ActorCard, ActorCard,

View File

@ -0,0 +1,145 @@
import { config, createLocalVue, mount, Wrapper } from "@vue/test-utils";
import ParticipationSection from "@/components/Participation/ParticipationSection.vue";
import Buefy from "buefy";
import VueRouter from "vue-router";
import { routes } from "@/router";
import { CommentModeration } from "@/types/enums";
import {
createMockClient,
MockApolloClient,
RequestHandler,
} from "mock-apollo-client";
import buildCurrentUserResolver from "@/apollo/user";
import { InMemoryCache } from "apollo-cache-inmemory";
import { CONFIG } from "@/graphql/config";
import VueApollo from "vue-apollo";
import { configMock } from "../../mocks/config";
const localVue = createLocalVue();
localVue.use(Buefy);
localVue.use(VueRouter);
const router = new VueRouter({ routes, mode: "history" });
config.mocks.$t = (key: string): string => key;
const eventData = {
id: "1",
uuid: "e37910ea-fd5a-4756-7634-00971f3f4107",
options: {
commentModeration: CommentModeration.ALLOW_ALL,
},
beginsOn: new Date("2089-12-04T09:21:25Z"),
endsOn: new Date("2089-12-04T11:21:25Z"),
};
describe("ParticipationSection", () => {
let wrapper: Wrapper<Vue>;
let mockClient: MockApolloClient;
let apolloProvider;
let requestHandlers: Record<string, RequestHandler>;
const generateWrapper = (
handlers: Record<string, unknown> = {},
customProps: Record<string, unknown> = {},
baseData: Record<string, unknown> = {}
) => {
const cache = new InMemoryCache({ addTypename: false });
mockClient = createMockClient({
cache,
resolvers: buildCurrentUserResolver(cache),
});
requestHandlers = {
configQueryHandler: jest.fn().mockResolvedValue(configMock),
...handlers,
};
mockClient.setRequestHandler(CONFIG, requestHandlers.configQueryHandler);
apolloProvider = new VueApollo({
defaultClient: mockClient,
});
wrapper = mount(ParticipationSection, {
localVue,
router,
apolloProvider,
propsData: {
participation: null,
event: eventData,
anonymousParticipation: null,
...customProps,
},
data() {
return {
currentActor: {
id: "76",
},
...baseData,
};
},
});
};
it("renders the participation section with minimal data", async () => {
generateWrapper();
await wrapper.vm.$nextTick();
expect(wrapper.exists()).toBe(true);
expect(requestHandlers.configQueryHandler).toHaveBeenCalled();
expect(wrapper.vm.$apollo.queries.config).toBeTruthy();
expect(wrapper.find(".event-participation").exists()).toBeTruthy();
const participationButton = wrapper.find(
".event-participation .participation-button a.button.is-large.is-primary"
);
expect(participationButton.attributes("href")).toBe(
`/events/${eventData.uuid}/participate/with-account`
);
expect(participationButton.text()).toBe("Participate");
});
it("renders the participation section with existing confimed anonymous participation", async () => {
generateWrapper({}, { anonymousParticipation: true });
expect(wrapper.find(".event-participation > small").text()).toContain(
"You are participating in this event anonymously"
);
const cancelAnonymousParticipationButton = wrapper.find(
".event-participation > button.button.is-text"
);
expect(cancelAnonymousParticipationButton.text()).toBe(
"Cancel anonymous participation"
);
cancelAnonymousParticipationButton.trigger("click");
await wrapper.vm.$nextTick();
expect(wrapper.emitted("cancel-anonymous-participation")).toBeTruthy();
});
it("renders the participation section with existing unconfirmed anonymous participation", async () => {
generateWrapper({}, { anonymousParticipation: false });
expect(wrapper.find(".event-participation > small").text()).toContain(
"You are participating in this event anonymously but didn't confirm participation"
);
});
it("renders the participation section but the event is already passed", async () => {
generateWrapper(
{},
{
event: {
...eventData,
beginsOn: "2020-12-02T10:52:56Z",
endsOn: "2020-12-03T10:52:56Z",
},
}
);
expect(wrapper.find(".event-participation").exists()).toBeFalsy();
expect(wrapper.find("button.button.is-primary").text()).toBe(
"Event already passed"
);
});
});