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:
parent
c94e431618
commit
96938a5511
167
js/src/components/Participation/ParticipationSection.vue
Normal file
167
js/src/components/Participation/ParticipationSection.vue
Normal 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>
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user