@dcrackel/hematournamentui 1.0.713 → 1.0.714
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/HemaTournamentUI-lib.es.js +6856 -6243
- package/dist/HemaTournamentUI-lib.umd.js +27 -27
- package/dist/hematournamentui-tailwind.css +79 -9
- package/package.json +1 -1
- package/src/index.js +43 -42
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.stories.js +49 -0
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.test.js +129 -0
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.vue +165 -0
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.stories.js +10 -1
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.test.js +31 -0
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue +20 -1
- package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js +22 -0
- package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue +27 -10
- package/src/stories/Templates/EventManagement/Bracket/Bracket.stories.js +33 -4
- package/src/stories/Templates/EventManagement/Bracket/Bracket.test.js +218 -178
- package/src/stories/Templates/EventManagement/Bracket/Bracket.vue +42 -1
- package/src/stories/Templates/EventManagement/Bracket/BracketPlannedSuggestions.js +8 -0
- package/src/stories/Templates/EventManagement/Bracket/BracketStories.test.js +121 -0
- package/src/stories/Util/bracketDirectors.js +86 -0
|
@@ -23,7 +23,16 @@ export default {
|
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export const Assigned = {
|
|
26
|
+
export const Assigned = {
|
|
27
|
+
parameters: {
|
|
28
|
+
docs: {
|
|
29
|
+
description: {
|
|
30
|
+
story:
|
|
31
|
+
'The remove control emits action:remove with a detached station payload. The parent updates planned coverage.'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
27
36
|
|
|
28
37
|
export const AddDirector = {
|
|
29
38
|
args: {
|
|
@@ -42,4 +42,35 @@ describe('BracketDirectorAssignmentCard', () => {
|
|
|
42
42
|
expect(wrapper.emitted('action:add')).toEqual([[]]);
|
|
43
43
|
expect(wrapper.find('base-text-stub').attributes('text')).toBe('Click here to add director');
|
|
44
44
|
});
|
|
45
|
+
|
|
46
|
+
it('emits a detached station when removing a director and waits for updated props', async () => {
|
|
47
|
+
const selectedStation = structuredClone(station);
|
|
48
|
+
const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { station: selectedStation } });
|
|
49
|
+
const removeButton = wrapper.get('[data-testid="remove-bracket-director"]');
|
|
50
|
+
expect(removeButton.attributes('aria-label')).toBe('Remove Sam Rivera from Ring 3');
|
|
51
|
+
await removeButton.trigger('click');
|
|
52
|
+
const payload = wrapper.emitted('action:remove')[0][0];
|
|
53
|
+
expect(payload).toEqual(selectedStation);
|
|
54
|
+
expect(payload).not.toBe(selectedStation);
|
|
55
|
+
payload.Director.DisplayName = 'Changed';
|
|
56
|
+
expect(selectedStation.Director.DisplayName).toBe('Sam Rivera');
|
|
57
|
+
expect(wrapper.find('[data-testid="bracket-director-assignment"]').exists()).toBe(true);
|
|
58
|
+
await wrapper.setProps({ station: null });
|
|
59
|
+
expect(wrapper.find('[data-testid="bracket-director-assignment"]').exists()).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('has no remove control on the add card or an empty card', () => {
|
|
63
|
+
const addCard = shallowMount(BracketDirectorAssignmentCard, { props: { variant: 'add' } });
|
|
64
|
+
const emptyCard = shallowMount(BracketDirectorAssignmentCard);
|
|
65
|
+
expect(addCard.find('[data-testid="remove-bracket-director"]').exists()).toBe(false);
|
|
66
|
+
expect(emptyCard.find('[data-testid="remove-bracket-director"]').exists()).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('displays the ring as text and reflects parent updates', async () => {
|
|
70
|
+
const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { station } });
|
|
71
|
+
expect(wrapper.find('select').exists()).toBe(false);
|
|
72
|
+
expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Ring 3');
|
|
73
|
+
await wrapper.setProps({ station: { ...station, RingId: 1, RingName: 'Ring 1' } });
|
|
74
|
+
expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Ring 1');
|
|
75
|
+
});
|
|
45
76
|
});
|
package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue
CHANGED
|
@@ -45,6 +45,16 @@
|
|
|
45
45
|
<BaseText :text="ringLabel" color="quaternary" size="xs" />
|
|
46
46
|
<BaseText class="truncate" :text="directorName" color="primaryHighlight" size="sm" weight="bold" />
|
|
47
47
|
</div>
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
data-testid="remove-bracket-director"
|
|
51
|
+
:aria-label="removeLabel"
|
|
52
|
+
:title="removeLabel"
|
|
53
|
+
class="flex h-8 w-8 flex-none items-center justify-center rounded text-quaternary transition hover:bg-dropdownSelect hover:text-alarm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
|
|
54
|
+
@click.stop="removeDirector"
|
|
55
|
+
>
|
|
56
|
+
<BaseIcon iconName="fa-xmark" color="none" size="sm" />
|
|
57
|
+
</button>
|
|
48
58
|
</article>
|
|
49
59
|
</template>
|
|
50
60
|
|
|
@@ -63,7 +73,7 @@ const getInitials = displayName =>
|
|
|
63
73
|
export default {
|
|
64
74
|
name: 'BracketDirectorAssignmentCard',
|
|
65
75
|
components: { BaseIcon, BaseText },
|
|
66
|
-
emits: ['action:add'],
|
|
76
|
+
emits: ['action:add', 'action:remove'],
|
|
67
77
|
props: {
|
|
68
78
|
station: {
|
|
69
79
|
type: Object,
|
|
@@ -79,7 +89,16 @@ export default {
|
|
|
79
89
|
default: 'Click here to add director'
|
|
80
90
|
}
|
|
81
91
|
},
|
|
92
|
+
methods: {
|
|
93
|
+
removeDirector() {
|
|
94
|
+
if (!this.station) return;
|
|
95
|
+
this.$emit('action:remove', JSON.parse(JSON.stringify(this.station)));
|
|
96
|
+
}
|
|
97
|
+
},
|
|
82
98
|
computed: {
|
|
99
|
+
removeLabel() {
|
|
100
|
+
return 'Remove ' + this.directorName + ' from ' + this.ringLabel;
|
|
101
|
+
},
|
|
83
102
|
assignmentLabel() {
|
|
84
103
|
return `${this.ringLabel}, Director ${this.directorName}`;
|
|
85
104
|
},
|
package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js
CHANGED
|
@@ -26,4 +26,26 @@ describe('BracketDirectorAssignmentPanel', () => {
|
|
|
26
26
|
|
|
27
27
|
expect(wrapper.emitted('request:add-planned-director')).toEqual([[]]);
|
|
28
28
|
});
|
|
29
|
+
|
|
30
|
+
it('forwards remove intent and reconciles the parent station list', async () => {
|
|
31
|
+
const wrapper = shallowMount(BracketDirectorAssignmentPanel, { props: { stations } });
|
|
32
|
+
wrapper.findComponent(BracketDirectorAssignmentCard).vm.$emit('action:remove', stations[0]);
|
|
33
|
+
expect(wrapper.emitted('request:remove-planned-director')).toEqual([[stations[0]]]);
|
|
34
|
+
expect(wrapper.props('stations')).toHaveLength(2);
|
|
35
|
+
await wrapper.setProps({ stations: stations.slice(1) });
|
|
36
|
+
expect(
|
|
37
|
+
wrapper
|
|
38
|
+
.findAllComponents(BracketDirectorAssignmentCard)
|
|
39
|
+
.filter(card => card.props('variant') !== 'add')
|
|
40
|
+
.map(card => card.props('station'))
|
|
41
|
+
).toEqual([stations[1]]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('opens director management', async () => {
|
|
45
|
+
const wrapper = shallowMount(BracketDirectorAssignmentPanel, {
|
|
46
|
+
props: { stations, canManageDirectors: true }
|
|
47
|
+
});
|
|
48
|
+
await wrapper.get('[data-testid="manage-bracket-directors"]').trigger('click');
|
|
49
|
+
expect(wrapper.emitted('request:manage-planned-directors')).toEqual([[]]);
|
|
50
|
+
});
|
|
29
51
|
});
|
package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue
CHANGED
|
@@ -3,18 +3,34 @@
|
|
|
3
3
|
data-testid="bracket-director-assignment-panel"
|
|
4
4
|
class="w-full rounded-xl border border-dropdownSelect bg-bannerBackground p-4 shadow-lg xl:sticky xl:top-4 xl:w-72"
|
|
5
5
|
>
|
|
6
|
-
<div class="mb-4">
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
<div class="mb-4 flex items-start justify-between gap-3">
|
|
7
|
+
<div>
|
|
8
|
+
<BaseText color="primaryHighlight" size="md" text="Director Assignments" weight="bold" />
|
|
9
|
+
<BaseText
|
|
10
|
+
class="mt-1"
|
|
11
|
+
color="quaternary"
|
|
12
|
+
size="xs"
|
|
13
|
+
text="Plan ring coverage. Scheduled bouts may still be moved between rings."
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
<button
|
|
17
|
+
v-if="canManageDirectors"
|
|
18
|
+
type="button"
|
|
19
|
+
data-testid="manage-bracket-directors"
|
|
20
|
+
class="rounded border border-dropdownSelect bg-white px-2 py-1 text-xs text-primaryHighlight hover:bg-dropdownSelect focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
|
|
21
|
+
@click="$emit('request:manage-planned-directors')"
|
|
22
|
+
>
|
|
23
|
+
Manage
|
|
24
|
+
</button>
|
|
14
25
|
</div>
|
|
15
26
|
|
|
16
27
|
<div class="space-y-3">
|
|
17
|
-
<BracketDirectorAssignmentCard
|
|
28
|
+
<BracketDirectorAssignmentCard
|
|
29
|
+
v-for="station in stations"
|
|
30
|
+
:key="station.RingId"
|
|
31
|
+
:station="station"
|
|
32
|
+
@action:remove="$emit('request:remove-planned-director', $event)"
|
|
33
|
+
/>
|
|
18
34
|
<BracketDirectorAssignmentCard
|
|
19
35
|
v-if="showAddCard"
|
|
20
36
|
variant="add"
|
|
@@ -31,8 +47,9 @@ import BracketDirectorAssignmentCard from '../../../Cards/BracketDirectorAssignm
|
|
|
31
47
|
export default {
|
|
32
48
|
name: 'BracketDirectorAssignmentPanel',
|
|
33
49
|
components: { BaseText, BracketDirectorAssignmentCard },
|
|
34
|
-
emits: ['request:add-planned-director'],
|
|
50
|
+
emits: ['request:add-planned-director', 'request:remove-planned-director', 'request:manage-planned-directors'],
|
|
35
51
|
props: {
|
|
52
|
+
canManageDirectors: { type: Boolean, default: false },
|
|
36
53
|
stations: {
|
|
37
54
|
type: Array,
|
|
38
55
|
default: () => []
|
|
@@ -9,7 +9,12 @@ import {
|
|
|
9
9
|
getStoryDirectorId,
|
|
10
10
|
replaceStoryBout
|
|
11
11
|
} from './BracketStoryHarness.js';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
availableDirectors,
|
|
14
|
+
availableRings,
|
|
15
|
+
plannedDirectingStations,
|
|
16
|
+
plannedSuggestionViewerId
|
|
17
|
+
} from './BracketPlannedSuggestions.js';
|
|
13
18
|
//import getDEWithBouts from '../../../../mocks/getDEWithBouts.js';
|
|
14
19
|
import getDEWithBouts6Fencers from '../../../../mocks/getDeBouts-6fencers.js';
|
|
15
20
|
import getDEWithBouts12Fencers from '../../../../mocks/getDeBouts-12fencers.js';
|
|
@@ -29,8 +34,9 @@ const renderInteractiveBracket = args => ({
|
|
|
29
34
|
const storyStations = ref(cloneStations(args.plannedDirectingStations));
|
|
30
35
|
const storyAssignments = ref([]);
|
|
31
36
|
const canAddDirector = computed(() =>
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
(args.availableDirectors || availableDirectors).some(
|
|
38
|
+
director =>
|
|
39
|
+
!storyStations.value.some(station => String(station.Director?.PersonId) === String(director.PersonId))
|
|
34
40
|
)
|
|
35
41
|
);
|
|
36
42
|
const redistributeAssignments = () => {
|
|
@@ -93,7 +99,11 @@ const renderInteractiveBracket = args => ({
|
|
|
93
99
|
};
|
|
94
100
|
|
|
95
101
|
const getViewerStation = () =>
|
|
96
|
-
|
|
102
|
+
[
|
|
103
|
+
...storyStations.value,
|
|
104
|
+
...plannedDirectingStations,
|
|
105
|
+
...(args.availableDirectors || []).map(Director => ({ Director }))
|
|
106
|
+
].find(station => String(station.Director?.PersonId) === String(args.viewerPersonId));
|
|
97
107
|
|
|
98
108
|
const handleAddPlannedDirector = () => {
|
|
99
109
|
const assignedRingIds = new Set(storyStations.value.map(station => String(station.RingId)));
|
|
@@ -125,6 +135,16 @@ const renderInteractiveBracket = args => ({
|
|
|
125
135
|
requestBoutDirector,
|
|
126
136
|
assignPlannedDirector,
|
|
127
137
|
handleAddPlannedDirector,
|
|
138
|
+
handleRemovePlannedDirector(station) {
|
|
139
|
+
const remainingStations = storyStations.value.filter(item => String(item.RingId) !== String(station?.RingId));
|
|
140
|
+
if (remainingStations.length === storyStations.value.length) return;
|
|
141
|
+
storyStations.value = remainingStations;
|
|
142
|
+
redistributeAssignments();
|
|
143
|
+
},
|
|
144
|
+
handlePlannedDirectorsUpdate(stations) {
|
|
145
|
+
storyStations.value = cloneStations(stations);
|
|
146
|
+
redistributeAssignments();
|
|
147
|
+
},
|
|
128
148
|
handleCloseDirectingBout,
|
|
129
149
|
handleDirectingBout,
|
|
130
150
|
persistStoryBout
|
|
@@ -156,6 +176,8 @@ const renderInteractiveBracket = args => ({
|
|
|
156
176
|
@request:bout-director="requestBoutDirector"
|
|
157
177
|
@close:directing-bout="handleCloseDirectingBout"
|
|
158
178
|
@request:add-planned-director="handleAddPlannedDirector"
|
|
179
|
+
@request:remove-planned-director="handleRemovePlannedDirector"
|
|
180
|
+
@update:planned-directing-stations="handlePlannedDirectorsUpdate"
|
|
159
181
|
@update:directing-bout="handleDirectingBout"
|
|
160
182
|
@request:bout-active="handleDirectingBout"
|
|
161
183
|
@update:bout="persistStoryBout"
|
|
@@ -171,6 +193,8 @@ export default {
|
|
|
171
193
|
tags: ['Bracket'],
|
|
172
194
|
render: renderInteractiveBracket,
|
|
173
195
|
args: {
|
|
196
|
+
availableDirectors,
|
|
197
|
+
rings: availableRings,
|
|
174
198
|
bouts: getDEWithBouts.pools[0].Bouts,
|
|
175
199
|
eventRules: getDEWithBouts.eventRules,
|
|
176
200
|
hostingClubColors: getDEWithBouts.hostingClubColors,
|
|
@@ -184,6 +208,11 @@ export default {
|
|
|
184
208
|
showBoutDirectorAssignment: true
|
|
185
209
|
},
|
|
186
210
|
argTypes: {
|
|
211
|
+
availableDirectors: {
|
|
212
|
+
control: 'object',
|
|
213
|
+
description: 'Eligible staff, including DE-only directors, supplied by the parent.'
|
|
214
|
+
},
|
|
215
|
+
rings: { control: 'object', description: 'Available rings supplied by the parent.' },
|
|
187
216
|
showBoutDirectorAssignment: {
|
|
188
217
|
control: 'boolean',
|
|
189
218
|
description: 'Include Assign director in the pairing hover menu. The parent supplies staff selection and saving.'
|
|
@@ -1,178 +1,218 @@
|
|
|
1
|
-
import { shallowMount } from '@vue/test-utils';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import DirectorModal from '../../../Molecules/Modals/DirectorModal/DirectorModal.vue';
|
|
4
|
-
import EditBoutModal from '../../../Molecules/Modals/EditBoutModal/EditBoutModal.vue';
|
|
5
|
-
import Tableau from '../../../Organisms/Containers/Tableau/Tableau.vue';
|
|
6
|
-
import BracketDirectorAssignmentPanel from '../../../Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue';
|
|
7
|
-
import Bracket from './Bracket.vue';
|
|
8
|
-
|
|
9
|
-
const createBout = (overrides = {}) => ({
|
|
10
|
-
BoutId: 42,
|
|
11
|
-
DEBoutId: 1,
|
|
12
|
-
RoundLabel: 'Final',
|
|
13
|
-
Status: 'Scheduled',
|
|
14
|
-
Score1: 4,
|
|
15
|
-
Score2: 3,
|
|
16
|
-
Person1: { PersonId: 1, DisplayName: 'Fencer One' },
|
|
17
|
-
Person2: { PersonId: 2, DisplayName: 'Fencer Two' },
|
|
18
|
-
...overrides
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const mountBracket = (bouts = [createBout()], optionalProps = {}) =>
|
|
22
|
-
shallowMount(Bracket, {
|
|
23
|
-
props: {
|
|
24
|
-
bouts,
|
|
25
|
-
eventRules: [],
|
|
26
|
-
hostingClubColors: {},
|
|
27
|
-
connectedToServer: true,
|
|
28
|
-
...optionalProps
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('Bracket bout interaction', () => {
|
|
33
|
-
it('emits directing intent for a scheduled bout and waits for Active props', async () => {
|
|
34
|
-
const bout = createBout();
|
|
35
|
-
const wrapper = mountBracket([bout]);
|
|
36
|
-
|
|
37
|
-
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
38
|
-
await wrapper.vm.$nextTick();
|
|
39
|
-
|
|
40
|
-
expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
|
|
41
|
-
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
|
|
42
|
-
|
|
43
|
-
await wrapper.setProps({ bouts: [{ ...bout, Status: 'Active' }] });
|
|
44
|
-
|
|
45
|
-
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(true);
|
|
46
|
-
expect(wrapper.findComponent(DirectorModal).props('bout').Status).toBe('Active');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('resumes an active bout through the directing path', async () => {
|
|
50
|
-
const bout = createBout({ Status: 'Active' });
|
|
51
|
-
const wrapper = mountBracket([bout]);
|
|
52
|
-
|
|
53
|
-
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
54
|
-
await wrapper.vm.$nextTick();
|
|
55
|
-
|
|
56
|
-
expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
|
|
57
|
-
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('opens completed bouts for editing instead of directing', async () => {
|
|
61
|
-
const bout = createBout({ Status: 'Completed' });
|
|
62
|
-
const wrapper = mountBracket([bout]);
|
|
63
|
-
|
|
64
|
-
wrapper.findComponent(Tableau).vm.$emit('action:editBout', bout);
|
|
65
|
-
await wrapper.vm.$nextTick();
|
|
66
|
-
|
|
67
|
-
expect(wrapper.findComponent(EditBoutModal).props('show')).toBe(true);
|
|
68
|
-
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
|
|
69
|
-
expect(wrapper.emitted('update:directing-bout')).toBeUndefined();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('does not direct a BYE bout', async () => {
|
|
73
|
-
const bout = createBout({
|
|
74
|
-
Person2: { PersonId: 0, DisplayName: 'BYE' }
|
|
75
|
-
});
|
|
76
|
-
const wrapper = mountBracket([bout]);
|
|
77
|
-
|
|
78
|
-
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
79
|
-
await wrapper.vm.$nextTick();
|
|
80
|
-
|
|
81
|
-
expect(wrapper.emitted('update:directing-bout')).toBeUndefined();
|
|
82
|
-
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('closes the scoring modal without changing status or scores', async () => {
|
|
86
|
-
const bout = createBout({ Status: 'Active', Score1: 6, Score2: 5 });
|
|
87
|
-
const wrapper = mountBracket([bout]);
|
|
88
|
-
|
|
89
|
-
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
90
|
-
await wrapper.vm.$nextTick();
|
|
91
|
-
wrapper.findComponent(DirectorModal).vm.$emit('update:closeModal', bout);
|
|
92
|
-
await wrapper.vm.$nextTick();
|
|
93
|
-
|
|
94
|
-
expect(wrapper.vm.boutsData[0]).toMatchObject({ Status: 'Active', Score1: 6, Score2: 5 });
|
|
95
|
-
expect(wrapper.emitted('update:bout')).toBeUndefined();
|
|
96
|
-
expect(wrapper.emitted('close:directing-bout')).toEqual([[bout]]);
|
|
97
|
-
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('emits submitted bout data without assigning completion fields', async () => {
|
|
101
|
-
const bout = createBout({ Status: 'Active' });
|
|
102
|
-
const submittedBout = { ...bout, Score1: 7 };
|
|
103
|
-
const wrapper = mountBracket([bout]);
|
|
104
|
-
|
|
105
|
-
wrapper.findComponent(DirectorModal).vm.$emit('submit:bout', submittedBout);
|
|
106
|
-
await wrapper.vm.$nextTick();
|
|
107
|
-
|
|
108
|
-
const emittedBout = wrapper.emitted('submit:bout')[0][0];
|
|
109
|
-
expect(emittedBout).toEqual(submittedBout);
|
|
110
|
-
expect(emittedBout.Status).toBe('Active');
|
|
111
|
-
expect(emittedBout.Ended).toBeUndefined();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('does not render a director-selection modal', () => {
|
|
115
|
-
const wrapper = mountBracket();
|
|
116
|
-
|
|
117
|
-
expect(wrapper.findComponent({ name: 'AssignDirectorModal' }).exists()).toBe(false);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('renders optional planned suggestions without changing the directing contract', async () => {
|
|
121
|
-
const bout = createBout();
|
|
122
|
-
const plannedDirectingStations = [
|
|
123
|
-
{
|
|
124
|
-
RingId: 1,
|
|
125
|
-
RingName: 'Ring 1',
|
|
126
|
-
Color: '#2563eb',
|
|
127
|
-
Director: { PersonId: 1001, DisplayName: 'Alex Morgan' }
|
|
128
|
-
}
|
|
129
|
-
];
|
|
130
|
-
const plannedBoutAssignments = [{ DEBoutId: bout.DEBoutId, RingId: 1 }];
|
|
131
|
-
const wrapper = mountBracket([bout], {
|
|
132
|
-
plannedDirectingStations,
|
|
133
|
-
plannedBoutAssignments,
|
|
134
|
-
viewerPersonId: 1001
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
expect(wrapper.findComponent(BracketDirectorAssignmentPanel).props('stations')).toEqual(plannedDirectingStations);
|
|
138
|
-
expect(wrapper.findComponent(Tableau).props()).toMatchObject({
|
|
139
|
-
plannedDirectingStations,
|
|
140
|
-
plannedBoutAssignments,
|
|
141
|
-
viewerPersonId: 1001
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
145
|
-
await wrapper.vm.$nextTick();
|
|
146
|
-
|
|
147
|
-
expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
|
|
148
|
-
expect(wrapper.emitted('assign:director')).toBeUndefined();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('emits add-planned-director intent from the assignment panel', async () => {
|
|
152
|
-
const wrapper = mountBracket([], { showPlannedDirectorAssignments: true });
|
|
153
|
-
|
|
154
|
-
wrapper.findComponent(BracketDirectorAssignmentPanel).vm.$emit('request:add-planned-director');
|
|
155
|
-
await wrapper.vm.$nextTick();
|
|
156
|
-
|
|
157
|
-
expect(wrapper.emitted('request:add-planned-director')).toEqual([[]]);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('does not render planned directing controls when explicitly disabled', () => {
|
|
161
|
-
const wrapper = mountBracket(undefined, {
|
|
162
|
-
showPlannedDirectorAssignments: false,
|
|
163
|
-
showBoutDirectorAssignment: false
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
expect(wrapper.findComponent(BracketDirectorAssignmentPanel).exists()).toBe(false);
|
|
167
|
-
expect(wrapper.findComponent(Tableau).props('plannedDirectingStations')).toEqual([]);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe('Bracket assignment defaults', () => {
|
|
172
|
-
it('shows assignment actions and the director panel without opt-in props', () => {
|
|
173
|
-
const wrapper = mountBracket();
|
|
174
|
-
expect(wrapper.findComponent(Tableau).props('showBoutDirectorAssignment')).toBe(true);
|
|
175
|
-
expect(wrapper.findComponent(BracketDirectorAssignmentPanel).exists()).toBe(true);
|
|
176
|
-
wrapper.unmount();
|
|
177
|
-
});
|
|
178
|
-
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import DirectorModal from '../../../Molecules/Modals/DirectorModal/DirectorModal.vue';
|
|
4
|
+
import EditBoutModal from '../../../Molecules/Modals/EditBoutModal/EditBoutModal.vue';
|
|
5
|
+
import Tableau from '../../../Organisms/Containers/Tableau/Tableau.vue';
|
|
6
|
+
import BracketDirectorAssignmentPanel from '../../../Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue';
|
|
7
|
+
import Bracket from './Bracket.vue';
|
|
8
|
+
|
|
9
|
+
const createBout = (overrides = {}) => ({
|
|
10
|
+
BoutId: 42,
|
|
11
|
+
DEBoutId: 1,
|
|
12
|
+
RoundLabel: 'Final',
|
|
13
|
+
Status: 'Scheduled',
|
|
14
|
+
Score1: 4,
|
|
15
|
+
Score2: 3,
|
|
16
|
+
Person1: { PersonId: 1, DisplayName: 'Fencer One' },
|
|
17
|
+
Person2: { PersonId: 2, DisplayName: 'Fencer Two' },
|
|
18
|
+
...overrides
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const mountBracket = (bouts = [createBout()], optionalProps = {}) =>
|
|
22
|
+
shallowMount(Bracket, {
|
|
23
|
+
props: {
|
|
24
|
+
bouts,
|
|
25
|
+
eventRules: [],
|
|
26
|
+
hostingClubColors: {},
|
|
27
|
+
connectedToServer: true,
|
|
28
|
+
...optionalProps
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('Bracket bout interaction', () => {
|
|
33
|
+
it('emits directing intent for a scheduled bout and waits for Active props', async () => {
|
|
34
|
+
const bout = createBout();
|
|
35
|
+
const wrapper = mountBracket([bout]);
|
|
36
|
+
|
|
37
|
+
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
38
|
+
await wrapper.vm.$nextTick();
|
|
39
|
+
|
|
40
|
+
expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
|
|
41
|
+
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
|
|
42
|
+
|
|
43
|
+
await wrapper.setProps({ bouts: [{ ...bout, Status: 'Active' }] });
|
|
44
|
+
|
|
45
|
+
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(true);
|
|
46
|
+
expect(wrapper.findComponent(DirectorModal).props('bout').Status).toBe('Active');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('resumes an active bout through the directing path', async () => {
|
|
50
|
+
const bout = createBout({ Status: 'Active' });
|
|
51
|
+
const wrapper = mountBracket([bout]);
|
|
52
|
+
|
|
53
|
+
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
54
|
+
await wrapper.vm.$nextTick();
|
|
55
|
+
|
|
56
|
+
expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
|
|
57
|
+
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('opens completed bouts for editing instead of directing', async () => {
|
|
61
|
+
const bout = createBout({ Status: 'Completed' });
|
|
62
|
+
const wrapper = mountBracket([bout]);
|
|
63
|
+
|
|
64
|
+
wrapper.findComponent(Tableau).vm.$emit('action:editBout', bout);
|
|
65
|
+
await wrapper.vm.$nextTick();
|
|
66
|
+
|
|
67
|
+
expect(wrapper.findComponent(EditBoutModal).props('show')).toBe(true);
|
|
68
|
+
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
|
|
69
|
+
expect(wrapper.emitted('update:directing-bout')).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('does not direct a BYE bout', async () => {
|
|
73
|
+
const bout = createBout({
|
|
74
|
+
Person2: { PersonId: 0, DisplayName: 'BYE' }
|
|
75
|
+
});
|
|
76
|
+
const wrapper = mountBracket([bout]);
|
|
77
|
+
|
|
78
|
+
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
79
|
+
await wrapper.vm.$nextTick();
|
|
80
|
+
|
|
81
|
+
expect(wrapper.emitted('update:directing-bout')).toBeUndefined();
|
|
82
|
+
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('closes the scoring modal without changing status or scores', async () => {
|
|
86
|
+
const bout = createBout({ Status: 'Active', Score1: 6, Score2: 5 });
|
|
87
|
+
const wrapper = mountBracket([bout]);
|
|
88
|
+
|
|
89
|
+
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
90
|
+
await wrapper.vm.$nextTick();
|
|
91
|
+
wrapper.findComponent(DirectorModal).vm.$emit('update:closeModal', bout);
|
|
92
|
+
await wrapper.vm.$nextTick();
|
|
93
|
+
|
|
94
|
+
expect(wrapper.vm.boutsData[0]).toMatchObject({ Status: 'Active', Score1: 6, Score2: 5 });
|
|
95
|
+
expect(wrapper.emitted('update:bout')).toBeUndefined();
|
|
96
|
+
expect(wrapper.emitted('close:directing-bout')).toEqual([[bout]]);
|
|
97
|
+
expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('emits submitted bout data without assigning completion fields', async () => {
|
|
101
|
+
const bout = createBout({ Status: 'Active' });
|
|
102
|
+
const submittedBout = { ...bout, Score1: 7 };
|
|
103
|
+
const wrapper = mountBracket([bout]);
|
|
104
|
+
|
|
105
|
+
wrapper.findComponent(DirectorModal).vm.$emit('submit:bout', submittedBout);
|
|
106
|
+
await wrapper.vm.$nextTick();
|
|
107
|
+
|
|
108
|
+
const emittedBout = wrapper.emitted('submit:bout')[0][0];
|
|
109
|
+
expect(emittedBout).toEqual(submittedBout);
|
|
110
|
+
expect(emittedBout.Status).toBe('Active');
|
|
111
|
+
expect(emittedBout.Ended).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('does not render a director-selection modal', () => {
|
|
115
|
+
const wrapper = mountBracket();
|
|
116
|
+
|
|
117
|
+
expect(wrapper.findComponent({ name: 'AssignDirectorModal' }).exists()).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('renders optional planned suggestions without changing the directing contract', async () => {
|
|
121
|
+
const bout = createBout();
|
|
122
|
+
const plannedDirectingStations = [
|
|
123
|
+
{
|
|
124
|
+
RingId: 1,
|
|
125
|
+
RingName: 'Ring 1',
|
|
126
|
+
Color: '#2563eb',
|
|
127
|
+
Director: { PersonId: 1001, DisplayName: 'Alex Morgan' }
|
|
128
|
+
}
|
|
129
|
+
];
|
|
130
|
+
const plannedBoutAssignments = [{ DEBoutId: bout.DEBoutId, RingId: 1 }];
|
|
131
|
+
const wrapper = mountBracket([bout], {
|
|
132
|
+
plannedDirectingStations,
|
|
133
|
+
plannedBoutAssignments,
|
|
134
|
+
viewerPersonId: 1001
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(wrapper.findComponent(BracketDirectorAssignmentPanel).props('stations')).toEqual(plannedDirectingStations);
|
|
138
|
+
expect(wrapper.findComponent(Tableau).props()).toMatchObject({
|
|
139
|
+
plannedDirectingStations,
|
|
140
|
+
plannedBoutAssignments,
|
|
141
|
+
viewerPersonId: 1001
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
|
|
145
|
+
await wrapper.vm.$nextTick();
|
|
146
|
+
|
|
147
|
+
expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
|
|
148
|
+
expect(wrapper.emitted('assign:director')).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('emits add-planned-director intent from the assignment panel', async () => {
|
|
152
|
+
const wrapper = mountBracket([], { showPlannedDirectorAssignments: true });
|
|
153
|
+
|
|
154
|
+
wrapper.findComponent(BracketDirectorAssignmentPanel).vm.$emit('request:add-planned-director');
|
|
155
|
+
await wrapper.vm.$nextTick();
|
|
156
|
+
|
|
157
|
+
expect(wrapper.emitted('request:add-planned-director')).toEqual([[]]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('does not render planned directing controls when explicitly disabled', () => {
|
|
161
|
+
const wrapper = mountBracket(undefined, {
|
|
162
|
+
showPlannedDirectorAssignments: false,
|
|
163
|
+
showBoutDirectorAssignment: false
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(wrapper.findComponent(BracketDirectorAssignmentPanel).exists()).toBe(false);
|
|
167
|
+
expect(wrapper.findComponent(Tableau).props('plannedDirectingStations')).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('Bracket assignment defaults', () => {
|
|
172
|
+
it('shows assignment actions and the director panel without opt-in props', () => {
|
|
173
|
+
const wrapper = mountBracket();
|
|
174
|
+
expect(wrapper.findComponent(Tableau).props('showBoutDirectorAssignment')).toBe(true);
|
|
175
|
+
expect(wrapper.findComponent(BracketDirectorAssignmentPanel).exists()).toBe(true);
|
|
176
|
+
wrapper.unmount();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('forwards removal of a right-column director without changing authoritative bout fields', async () => {
|
|
180
|
+
const station = { RingId: 1, RingName: 'Ring 1', Director: { PersonId: 99, DisplayName: 'Dave Smith' } };
|
|
181
|
+
const bout = createBout({ Status: 'Active', RefereeId: 99, RefName: 'Dave Smith' });
|
|
182
|
+
const wrapper = mountBracket([bout], { plannedDirectingStations: [station] });
|
|
183
|
+
wrapper.findComponent(BracketDirectorAssignmentPanel).vm.$emit('request:remove-planned-director', station);
|
|
184
|
+
expect(wrapper.emitted('request:remove-planned-director')).toEqual([[station]]);
|
|
185
|
+
expect(wrapper.emitted('update:bout')).toBeUndefined();
|
|
186
|
+
expect(wrapper.vm.boutsData).toEqual([bout]);
|
|
187
|
+
expect(wrapper.props('plannedDirectingStations')).toEqual([station]);
|
|
188
|
+
await wrapper.setProps({ plannedDirectingStations: [] });
|
|
189
|
+
expect(wrapper.findComponent(BracketDirectorAssignmentPanel).props('stations')).toEqual([]);
|
|
190
|
+
wrapper.unmount();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('opens the staff-fed modal and forwards detached roster save intent without changing bout or station props', async () => {
|
|
194
|
+
const selected = {
|
|
195
|
+
RingId: 1,
|
|
196
|
+
RingName: 'Ring 1',
|
|
197
|
+
Director: { PersonId: 99, DisplayName: 'Dave Smith' },
|
|
198
|
+
Color: '#2563eb'
|
|
199
|
+
};
|
|
200
|
+
const wrapper = mountBracket(undefined, {
|
|
201
|
+
availableDirectors: [selected.Director],
|
|
202
|
+
rings: [{ RingId: 1, RingName: 'Ring 1' }],
|
|
203
|
+
plannedDirectingStations: [selected]
|
|
204
|
+
});
|
|
205
|
+
wrapper.findComponent(BracketDirectorAssignmentPanel).vm.$emit('request:add-planned-director');
|
|
206
|
+
await wrapper.vm.$nextTick();
|
|
207
|
+
const modal = wrapper.findComponent({ name: 'BracketDirectorsModal' });
|
|
208
|
+
expect(modal.props('show')).toBe(true);
|
|
209
|
+
expect(wrapper.emitted('request:add-planned-director')).toBeUndefined();
|
|
210
|
+
modal.vm.$emit('submit:stations', []);
|
|
211
|
+
await wrapper.vm.$nextTick();
|
|
212
|
+
expect(wrapper.emitted('update:planned-directing-stations')).toEqual([[[]]]);
|
|
213
|
+
expect(wrapper.props('plannedDirectingStations')).toEqual([selected]);
|
|
214
|
+
expect(wrapper.emitted('update:bout')).toBeUndefined();
|
|
215
|
+
expect(modal.props('show')).toBe(false);
|
|
216
|
+
wrapper.unmount();
|
|
217
|
+
});
|
|
218
|
+
});
|