@dcrackel/hematournamentui 1.0.711 → 1.0.713

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.
Files changed (109) hide show
  1. package/.prettierignore +7 -7
  2. package/.storybook/main.js +3 -1
  3. package/README.md +182 -148
  4. package/dist/HemaTournamentUI-lib.es.js +4150 -4039
  5. package/dist/HemaTournamentUI-lib.umd.js +27 -27
  6. package/dist/hematournamentui-tailwind.css +11 -0
  7. package/package.json +96 -96
  8. package/prettier.config.js +16 -16
  9. package/src/main.js +1 -1
  10. package/src/mocks/getDEBouts-TableOf128.js +37 -37
  11. package/src/mocks/getDEBouts-TableOf64.js +35 -35
  12. package/src/stories/Atoms/Input/BaseInput.test.js +97 -97
  13. package/src/stories/Atoms/Input/BaseInput.vue +149 -149
  14. package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.stories.js +23 -23
  15. package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.vue +35 -35
  16. package/src/stories/Molecules/Indicators/RegistrationStatus/RegistrationStatus.vue +30 -0
  17. package/src/stories/Molecules/Menus/HoverActions/HoverActions.test.js +138 -0
  18. package/src/stories/Molecules/Menus/HoverActions/HoverActions.vue +212 -0
  19. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.stories.js +6 -0
  20. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.test.js +18 -18
  21. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.vue +1 -1
  22. package/src/stories/Organisms/Cards/BoutCard/BoutCard.vue +5 -1
  23. package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardActive/BoutCardActive.vue +19 -10
  24. package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardInactive/BoutCardInactive.vue +11 -7
  25. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.stories.js +33 -33
  26. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.test.js +45 -45
  27. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue +103 -103
  28. package/src/stories/Organisms/Cards/Director/DirectorCard.test.js +82 -0
  29. package/src/stories/Organisms/Cards/Director/DirectorCard.vue +32 -51
  30. package/src/stories/Organisms/Cards/EditBout/EditBout.test.js +51 -51
  31. package/src/stories/Organisms/Cards/EventCard/EventCard.stories.js +41 -1
  32. package/src/stories/Organisms/Cards/EventCard/EventCard.test.js +106 -0
  33. package/src/stories/Organisms/Cards/EventCard/EventCard.vue +62 -30
  34. package/src/stories/Organisms/Cards/FencerCard/FencerCard.vue +298 -298
  35. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.stories.js +77 -60
  36. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.test.js +144 -143
  37. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.vue +289 -256
  38. package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.stories.js +69 -69
  39. package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.vue +80 -80
  40. package/src/stories/Organisms/Cards/WaitlistFencerCard/WaitlistFencerCard.vue +153 -153
  41. package/src/stories/Organisms/ComplexInputs/DropDown/DropDownMenu.vue +4 -0
  42. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.stories.js +31 -31
  43. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.test.js +109 -109
  44. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.vue +192 -192
  45. package/src/stories/Organisms/Containers/Tableau/Tableau.stories.js +1 -0
  46. package/src/stories/Organisms/Containers/Tableau/Tableau.test.js +247 -204
  47. package/src/stories/Organisms/Containers/Tableau/Tableau.vue +89 -15
  48. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js +29 -29
  49. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue +46 -46
  50. package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.test.js +21 -21
  51. package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.vue +39 -39
  52. package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.js +145 -145
  53. package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.test.js +64 -64
  54. package/src/stories/Organisms/Containers/Tableau/plannedAssignments.js +36 -57
  55. package/src/stories/Organisms/Containers/Tableau/plannedAssignments.test.js +101 -65
  56. package/src/stories/Organisms/Containers/TableauColumn/TableauColumn.vue +14 -1
  57. package/src/stories/Organisms/Headers/AttendanceSummary/AttendanceSummary.vue +12 -5
  58. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.stories.js +52 -48
  59. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.vue +256 -227
  60. package/src/stories/Organisms/Wizards/EditEventWizard/DirectElimination/DirectElimination.vue +226 -226
  61. package/src/stories/Organisms/Wizards/EditEventWizard/DirectEliminationPromotion/DirectEliminationPromotion.vue +204 -204
  62. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.stories.js +137 -133
  63. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.test.js +221 -191
  64. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.vue +372 -366
  65. package/src/stories/Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue +172 -172
  66. package/src/stories/Organisms/Wizards/EditEventWizard/PoolConfiguration/PoolConfiguration.vue +216 -216
  67. package/src/stories/Organisms/Wizards/EditEventWizard/PriceFields.test.js +79 -79
  68. package/src/stories/Organisms/Wizards/EditEventWizard/RuleSerialization.test.js +29 -0
  69. package/src/stories/Organisms/Wizards/EditEventWizard/WeaponSelection.test.js +138 -0
  70. package/src/stories/Templates/EventManagement/Bracket/Bracket.stories.js +123 -12
  71. package/src/stories/Templates/EventManagement/Bracket/Bracket.test.js +178 -166
  72. package/src/stories/Templates/EventManagement/Bracket/Bracket.vue +35 -7
  73. package/src/stories/Templates/EventManagement/Bracket/BracketDirectorAssignment.test.js +242 -0
  74. package/src/stories/Templates/EventManagement/Bracket/BracketMobile.test.js +131 -0
  75. package/src/stories/Templates/EventManagement/Bracket/BracketPlannedSuggestions.js +42 -39
  76. package/src/stories/Templates/EventManagement/Bracket/BracketStories.test.js +80 -0
  77. package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.js +93 -0
  78. package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.test.js +152 -0
  79. package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.js +39 -39
  80. package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.test.js +79 -79
  81. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.stories.js +93 -93
  82. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.vue +91 -91
  83. package/src/stories/Templates/EventManagement/EventAttendance/EventAttendance.stories.js +31 -1
  84. package/src/stories/Templates/EventManagement/PoolLive/PoolLive.test.js +121 -121
  85. package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.stories.js +9 -2
  86. package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.vue +5 -4
  87. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.stories.js +81 -81
  88. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.test.js +39 -39
  89. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.vue +101 -101
  90. package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.stories.js +71 -71
  91. package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.vue +86 -86
  92. package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.stories.js +86 -86
  93. package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.vue +247 -247
  94. package/src/stories/Templates/PersonManagement/Basic/BasicEdit.vue +170 -170
  95. package/src/stories/Templates/PersonManagement/PersonManagement.stories.js +156 -156
  96. package/src/stories/Templates/PersonManagement/PersonManagement.test.js +329 -329
  97. package/src/stories/Templates/PersonManagement/PersonManagement.vue +329 -329
  98. package/src/stories/Templates/PersonManagement/PersonManagementDraft.js +42 -42
  99. package/src/stories/Templates/TournamentManagement/ListEvents/ListEvents.stories.js +58 -45
  100. package/src/stories/Templates/TournamentManagement/TournamentManagement.stories.js +84 -84
  101. package/src/stories/Templates/TournamentManagement/TournamentManagement.test.js +114 -114
  102. package/src/stories/Templates/TournamentManagement/TournamentManagement.vue +363 -363
  103. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.stories.js +116 -116
  104. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.vue +307 -307
  105. package/src/stories/Util/Validations/PersonManagementValidations.js +45 -45
  106. package/src/stories/Util/Validations/PriceValidation.js +6 -6
  107. package/src/stories/Util/boutDirectorAssignment.js +46 -0
  108. package/src/stories/Util/registrationStatus.js +11 -0
  109. package/src/stories/Util/tabs.js +19 -47
@@ -1,191 +1,221 @@
1
- import { afterEach, describe, expect, it } from 'vitest';
2
- import { mount, shallowMount } from '@vue/test-utils';
3
- import EditEventWizard from './EditEventWizard.vue';
4
- import EditEventInfo from '../../../Templates/EventManagement/EditEventInfo/EditEventInfo.vue';
5
- import EventCustomization from './EventCustomization/EventCustomization.vue';
6
- import BasicEventInfo from './BasicEventInfo/BasicEventInfo.vue';
7
-
8
- const wrappers = [];
9
- afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount()));
10
-
11
- function createEvent() {
12
- return {
13
- EventId: 12,
14
- EventName: 'Longsword Open',
15
- Date: '2026-10-17',
16
- StartTime: '12:00',
17
- NumberOfRings: 2,
18
- WeaponId: 1,
19
- IncludeInRatings: false,
20
- rules: [
21
- { RuleId: 1, RuleValue: '90' },
22
- { RuleId: 2, RuleValue: '7' },
23
- { RuleId: 4, RuleValue: '12' },
24
- { RuleId: 9, RuleValue: 'Win Percent,Indicator' },
25
- { RuleId: 17, RuleValue: 'Count' },
26
- { RuleId: 18, RuleValue: 'true' }
27
- ]
28
- };
29
- }
30
-
31
- function mountWizard(props = {}) {
32
- const wrapper = shallowMount(EditEventWizard, { props: { event: createEvent(), weapons: [], ...props } });
33
- wrappers.push(wrapper);
34
- return wrapper;
35
- }
36
-
37
- describe('event editing with step saves', () => {
38
- it('requests a save on each step and waits for the parent before advancing', async () => {
39
- const wrapper = mountWizard({ editMode: true });
40
- expect(wrapper.findComponent(BasicEventInfo).props('nextButtonLabel')).toBe('Save and Continue');
41
- wrapper.vm.handleValueChange({ EventName: 'Changed Event' });
42
- wrapper.vm.handleUpdate();
43
- const request = wrapper.emitted('save:step')[0][0];
44
- expect(request.step).toBe(1);
45
- expect(request.event.EventName).toBe('Changed Event');
46
- expect(wrapper.vm.currentStep).toBe(1);
47
- expect(wrapper.emitted('update:submit')).toBeUndefined();
48
- await wrapper.setProps({ event: request.event, step: 2, saving: false, saveNotice: 'Changes saved.' });
49
- expect(wrapper.vm.currentStep).toBe(2);
50
- expect(wrapper.findComponent(EventCustomization).props('nextButtonLabel')).toBe('Save and Continue');
51
- expect(wrapper.text()).toContain('Changes saved.');
52
- expect(wrapper.emitted('update:dirty').at(-1)).toEqual([false]);
53
- });
54
-
55
- it('blocks duplicate saves, navigation and edits while saving', async () => {
56
- const wrapper = mountWizard({ editMode: true });
57
- wrapper.vm.handleValueChange({ EventName: 'Changed Event' });
58
- wrapper.vm.handleUpdate();
59
- await wrapper.setProps({ saving: true });
60
- expect(wrapper.find('fieldset').attributes()).toHaveProperty('disabled');
61
- expect(wrapper.text()).toContain('Saving changes');
62
- wrapper.vm.handleUpdate();
63
- wrapper.vm.handleBack();
64
- wrapper.vm.handleValueChange({ EventName: 'Blocked Change' });
65
- expect(wrapper.emitted('save:step')).toHaveLength(1);
66
- expect(wrapper.vm.eventConfig.EventName).toBe('Changed Event');
67
- });
68
-
69
- it('retains page two edits on failure and saves the latest toggle even with a stale Next payload', async () => {
70
- const wrapper = mountWizard({ editMode: true, step: 2 });
71
- const oldConfig = { ...wrapper.vm.eventConfig };
72
- wrapper.vm.handleValueChange({ IncludeInRatings: true, CountDoubles: true });
73
- wrapper.vm.handleUpdate(oldConfig);
74
- expect(wrapper.emitted('save:step')[0][0].event.IncludeInRatings).toBe(true);
75
- expect(wrapper.emitted('save:step')[0][0].event.rules.find(rule => rule.RuleId === 20).RuleValue).toBe('true');
76
- await wrapper.setProps({ saving: false, saveError: 'Changes could not be saved.' });
77
- expect(wrapper.vm.currentStep).toBe(2);
78
- expect(wrapper.vm.eventConfig.IncludeInRatings).toBe(true);
79
- expect(wrapper.find('[role="alert"]').text()).toBe('Changes could not be saved.');
80
- wrapper.vm.handleUpdate();
81
- expect(wrapper.emitted('save:step')).toHaveLength(2);
82
- });
83
-
84
- it('uses Save on the last step and stays on it until the app chooses otherwise', () => {
85
- const wrapper = mountWizard({ editMode: true, step: 5 });
86
- expect(wrapper.findComponent({ name: 'DirectElimination' }).props('nextButtonLabel')).toBe('Save');
87
- wrapper.vm.handleSubmit();
88
- expect(wrapper.emitted('save:step')[0][0].step).toBe(5);
89
- expect(wrapper.vm.currentStep).toBe(5);
90
- });
91
-
92
- it('tracks reverted changes, requests Back navigation and never mutates the incoming event', () => {
93
- const event = createEvent();
94
- const original = structuredClone(event);
95
- const wrapper = mountWizard({ editMode: true, step: 3, event });
96
- wrapper.vm.handleValueChange({ EventName: 'Changed' });
97
- expect(wrapper.emitted('update:dirty').at(-1)).toEqual([true]);
98
- wrapper.vm.handleValueChange({ EventName: event.EventName });
99
- expect(wrapper.emitted('update:dirty').at(-1)).toEqual([false]);
100
- wrapper.vm.handleBack();
101
- expect(wrapper.emitted('navigate:step')[0]).toEqual([2]);
102
- expect(wrapper.vm.currentStep).toBe(3);
103
- expect(event).toEqual(original);
104
- });
105
-
106
- it('loads ratings membership from the event column and mirrors a legacy rule only if present', () => {
107
- const wrapper = mountWizard({ editMode: true, step: 2 });
108
- expect(wrapper.vm.eventConfig.IncludeInRatings).toBe(false);
109
- expect(wrapper.vm.buildEventPayload().rules.some(rule => rule.RuleId === 22)).toBe(false);
110
- const event = createEvent();
111
- event.rules.push({ RuleId: 22, RuleValue: 'true' });
112
- const legacy = mountWizard({ editMode: true, event });
113
- expect(legacy.vm.buildEventPayload().rules.find(rule => rule.RuleId === 22).RuleValue).toBe('false');
114
- });
115
-
116
- it('omits disabled max-point rules instead of reenabling them after saving', () => {
117
- const wrapper = mountWizard({ editMode: true });
118
- wrapper.vm.handleValueChange({ poolMax: false, DEMax: false });
119
- const rules = wrapper.vm.buildEventPayload().rules;
120
- expect(rules.some(rule => rule.RuleId === 2 || rule.RuleId === 4)).toBe(false);
121
- });
122
- });
123
-
124
- it('keeps creation as a wizard with one submit at the end', () => {
125
- const wrapper = mountWizard({ event: { ...createEvent(), EventId: 0 } });
126
- expect(wrapper.findComponent(BasicEventInfo).props('nextButtonLabel')).toBe('Next');
127
- for (let step = 1; step < 5; step += 1) wrapper.vm.handleUpdate();
128
- expect(wrapper.vm.currentStep).toBe(5);
129
- expect(wrapper.emitted('save:step')).toBeUndefined();
130
- expect(wrapper.emitted('update:submit')).toBeUndefined();
131
- wrapper.vm.handleSubmit();
132
- expect(wrapper.emitted('update:submit')).toHaveLength(1);
133
- });
134
-
135
- it('forwards controlled edit state and save, dirty, navigation and cancel events through EditEventInfo', () => {
136
- const wrapper = shallowMount(EditEventInfo, {
137
- props: { event: createEvent(), rules: [], weapons: [], editMode: true, step: 2, saving: false, saveError: 'Retry' }
138
- });
139
- wrappers.push(wrapper);
140
- const wizard = wrapper.findComponent(EditEventWizard);
141
- expect(wizard.props('editMode')).toBe(true);
142
- expect(wizard.props('step')).toBe(2);
143
- expect(wizard.props('saveError')).toBe('Retry');
144
- const request = { event: createEvent(), step: 2 };
145
- wizard.vm.$emit('save:step', request);
146
- wizard.vm.$emit('navigate:step', 1);
147
- wizard.vm.$emit('update:dirty', true);
148
- wizard.vm.$emit('update:cancel');
149
- expect(wrapper.emitted('save:step')[0][0]).toEqual(request);
150
- expect(wrapper.emitted('navigate:step')[0]).toEqual([1]);
151
- expect(wrapper.emitted('update:dirty')[0]).toEqual([true]);
152
- expect(wrapper.emitted('update:cancel')).toHaveLength(1);
153
- });
154
-
155
- it('renders a saved boolean Double Counter value as checked on page two', () => {
156
- const wrapper = shallowMount(EventCustomization, {
157
- props: { eventConfig: { CountDoubles: true }, steps: [], currentStep: 1 }
158
- });
159
- wrappers.push(wrapper);
160
- const toggle = wrapper
161
- .findAllComponents({ name: 'Toggle' })
162
- .find(control => control.props('label') === 'Double Counter Added & Will Be Tracked');
163
- expect(toggle.props('checked')).toBe(true);
164
- });
165
-
166
- it('renders page two controls and emits a save from the actual Save and Continue button', async () => {
167
- const wrapper = mount(EditEventWizard, { props: { event: createEvent(), weapons: [], editMode: true, step: 2 } });
168
- wrappers.push(wrapper);
169
- await wrapper.vm.$nextTick();
170
- expect(wrapper.emitted('update:dirty').at(-1)).toEqual([false]);
171
- await wrapper.findAll('input[type="checkbox"]')[0].setValue(true);
172
- const save = wrapper.findAll('button').find(button => button.text() === 'Save and Continue');
173
- await save.trigger('click');
174
- expect(wrapper.emitted('save:step')[0][0].event.IncludeInRatings).toBe(true);
175
- expect(wrapper.vm.currentStep).toBe(2);
176
- expect(wrapper.emitted('update:dirty').at(-1)).toEqual([true]);
177
- });
178
-
179
- it('reconciles authoritative saved data in final-step controls without leaving the editor', async () => {
180
- const event = createEvent();
181
- event.rules.push({ RuleId: 3, RuleValue: '180' });
182
- const wrapper = mount(EditEventWizard, { props: { event, weapons: [], editMode: true, step: 5 } });
183
- wrappers.push(wrapper);
184
- await wrapper.vm.$nextTick();
185
- expect(wrapper.findAll('input[type="checkbox"]')[0].element.checked).toBe(true);
186
- await wrapper.setProps({ event: { ...event, rules: [] }, saveNotice: 'Changes saved.' });
187
- expect(wrapper.vm.currentStep).toBe(5);
188
- expect(wrapper.findAll('input[type="checkbox"]')[0].element.checked).toBe(false);
189
- expect(wrapper.text()).toContain('Changes saved.');
190
- expect(wrapper.findAll('button').some(button => button.text() === 'Save')).toBe(true);
191
- });
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import { mount, shallowMount } from '@vue/test-utils';
3
+ import EditEventWizard from './EditEventWizard.vue';
4
+ import EditEventInfo from '../../../Templates/EventManagement/EditEventInfo/EditEventInfo.vue';
5
+ import EventCustomization from './EventCustomization/EventCustomization.vue';
6
+ import BasicEventInfo from './BasicEventInfo/BasicEventInfo.vue';
7
+
8
+ const wrappers = [];
9
+ afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount()));
10
+
11
+ function createEvent() {
12
+ return {
13
+ EventId: 12,
14
+ EventName: 'Longsword Open',
15
+ Date: '2026-10-17',
16
+ StartTime: '12:00',
17
+ NumberOfRings: 2,
18
+ WeaponId: 1,
19
+ IncludeInRatings: false,
20
+ rules: [
21
+ { RuleId: 1, RuleValue: '90' },
22
+ { RuleId: 2, RuleValue: '7' },
23
+ { RuleId: 4, RuleValue: '12' },
24
+ { RuleId: 9, RuleValue: 'Win Percent,Indicator' },
25
+ { RuleId: 17, RuleValue: 'Count' },
26
+ { RuleId: 18, RuleValue: 'true' }
27
+ ]
28
+ };
29
+ }
30
+
31
+ function mountWizard(props = {}) {
32
+ const wrapper = shallowMount(EditEventWizard, { props: { event: createEvent(), weapons: [], ...props } });
33
+ wrappers.push(wrapper);
34
+ return wrapper;
35
+ }
36
+
37
+ describe('event editing with step saves', () => {
38
+ it('requests a save on each step and waits for the parent before advancing', async () => {
39
+ const wrapper = mountWizard({ editMode: true });
40
+ expect(wrapper.findComponent(BasicEventInfo).props('nextButtonLabel')).toBe('Save and Continue');
41
+ wrapper.vm.handleValueChange({ EventName: 'Changed Event' });
42
+ wrapper.vm.handleUpdate();
43
+ const request = wrapper.emitted('save:step')[0][0];
44
+ expect(request.step).toBe(1);
45
+ expect(request.event.EventName).toBe('Changed Event');
46
+ expect(wrapper.vm.currentStep).toBe(1);
47
+ expect(wrapper.emitted('update:submit')).toBeUndefined();
48
+ await wrapper.setProps({ event: request.event, step: 2, saving: false, saveNotice: 'Changes saved.' });
49
+ expect(wrapper.vm.currentStep).toBe(2);
50
+ expect(wrapper.findComponent(EventCustomization).props('nextButtonLabel')).toBe('Save and Continue');
51
+ expect(wrapper.text()).toContain('Changes saved.');
52
+ expect(wrapper.emitted('update:dirty').at(-1)).toEqual([false]);
53
+ });
54
+
55
+ it('blocks duplicate saves, navigation and edits while saving', async () => {
56
+ const wrapper = mountWizard({ editMode: true });
57
+ wrapper.vm.handleValueChange({ EventName: 'Changed Event' });
58
+ wrapper.vm.handleUpdate();
59
+ await wrapper.setProps({ saving: true });
60
+ expect(wrapper.find('fieldset').attributes()).toHaveProperty('disabled');
61
+ expect(wrapper.text()).toContain('Saving changes');
62
+ wrapper.vm.handleUpdate();
63
+ wrapper.vm.handleBack();
64
+ wrapper.vm.handleValueChange({ EventName: 'Blocked Change' });
65
+ expect(wrapper.emitted('save:step')).toHaveLength(1);
66
+ expect(wrapper.vm.eventConfig.EventName).toBe('Changed Event');
67
+ });
68
+
69
+ it('retains page two edits on failure and saves the latest toggle even with a stale Next payload', async () => {
70
+ const wrapper = mountWizard({ editMode: true, step: 2 });
71
+ const oldConfig = { ...wrapper.vm.eventConfig };
72
+ wrapper.vm.handleValueChange({ IncludeInRatings: true, CountDoubles: true });
73
+ wrapper.vm.handleUpdate(oldConfig);
74
+ expect(wrapper.emitted('save:step')[0][0].event.IncludeInRatings).toBe(true);
75
+ expect(wrapper.emitted('save:step')[0][0].event.rules.find(rule => rule.RuleId === 20).RuleValue).toBe('true');
76
+ await wrapper.setProps({ saving: false, saveError: 'Changes could not be saved.' });
77
+ expect(wrapper.vm.currentStep).toBe(2);
78
+ expect(wrapper.vm.eventConfig.IncludeInRatings).toBe(true);
79
+ expect(wrapper.find('[role="alert"]').text()).toBe('Changes could not be saved.');
80
+ wrapper.vm.handleUpdate();
81
+ expect(wrapper.emitted('save:step')).toHaveLength(2);
82
+ });
83
+
84
+ it('uses Save on the last step and stays on it until the app chooses otherwise', () => {
85
+ const wrapper = mountWizard({ editMode: true, step: 5 });
86
+ expect(wrapper.findComponent({ name: 'DirectElimination' }).props('nextButtonLabel')).toBe('Save');
87
+ wrapper.vm.handleSubmit();
88
+ expect(wrapper.emitted('save:step')[0][0].step).toBe(5);
89
+ expect(wrapper.vm.currentStep).toBe(5);
90
+ });
91
+
92
+ it('tracks reverted changes, requests Back navigation and never mutates the incoming event', () => {
93
+ const event = createEvent();
94
+ const original = structuredClone(event);
95
+ const wrapper = mountWizard({ editMode: true, step: 3, event });
96
+ wrapper.vm.handleValueChange({ EventName: 'Changed' });
97
+ expect(wrapper.emitted('update:dirty').at(-1)).toEqual([true]);
98
+ wrapper.vm.handleValueChange({ EventName: event.EventName });
99
+ expect(wrapper.emitted('update:dirty').at(-1)).toEqual([false]);
100
+ wrapper.vm.handleBack();
101
+ expect(wrapper.emitted('navigate:step')[0]).toEqual([2]);
102
+ expect(wrapper.vm.currentStep).toBe(3);
103
+ expect(event).toEqual(original);
104
+ });
105
+
106
+ it('loads ratings membership from the event column and mirrors a legacy rule only if present', () => {
107
+ const wrapper = mountWizard({ editMode: true, step: 2 });
108
+ expect(wrapper.vm.eventConfig.IncludeInRatings).toBe(false);
109
+ expect(wrapper.vm.buildEventPayload().rules.some(rule => rule.RuleId === 22)).toBe(false);
110
+ const event = createEvent();
111
+ event.rules.push({ RuleId: 22, RuleValue: 'true' });
112
+ const legacy = mountWizard({ editMode: true, event });
113
+ expect(legacy.vm.buildEventPayload().rules.find(rule => rule.RuleId === 22).RuleValue).toBe('false');
114
+ });
115
+
116
+ it('omits disabled max-point rules instead of reenabling them after saving', () => {
117
+ const wrapper = mountWizard({ editMode: true });
118
+ wrapper.vm.handleValueChange({ poolMax: false, DEMax: false });
119
+ const rules = wrapper.vm.buildEventPayload().rules;
120
+ expect(rules.some(rule => rule.RuleId === 2 || rule.RuleId === 4)).toBe(false);
121
+ });
122
+ });
123
+
124
+ it('keeps creation as a wizard with one submit at the end', () => {
125
+ const wrapper = mountWizard({ event: { ...createEvent(), EventId: 0 } });
126
+ expect(wrapper.findComponent(BasicEventInfo).props('nextButtonLabel')).toBe('Next');
127
+ expect(wrapper.vm.eventConfig.WaitlistEnabled).toBe(true);
128
+ wrapper.findComponent(BasicEventInfo).vm.$emit('update', { WaitlistEnabled: false });
129
+ for (let step = 1; step < 5; step += 1) wrapper.vm.handleUpdate();
130
+ expect(wrapper.vm.currentStep).toBe(5);
131
+ expect(wrapper.emitted('save:step')).toBeUndefined();
132
+ expect(wrapper.emitted('update:submit')).toBeUndefined();
133
+ wrapper.vm.handleSubmit();
134
+ expect(wrapper.emitted('update:submit')).toHaveLength(1);
135
+ expect(wrapper.emitted('update:submit')[0][0].WaitlistEnabled).toBe(false);
136
+ });
137
+
138
+ it('forwards controlled edit state and save, dirty, navigation and cancel events through EditEventInfo', () => {
139
+ const wrapper = shallowMount(EditEventInfo, {
140
+ props: { event: createEvent(), rules: [], weapons: [], editMode: true, step: 2, saving: false, saveError: 'Retry' }
141
+ });
142
+ wrappers.push(wrapper);
143
+ const wizard = wrapper.findComponent(EditEventWizard);
144
+ expect(wizard.props('editMode')).toBe(true);
145
+ expect(wizard.props('step')).toBe(2);
146
+ expect(wizard.props('saveError')).toBe('Retry');
147
+ const request = { event: createEvent(), step: 2 };
148
+ wizard.vm.$emit('save:step', request);
149
+ wizard.vm.$emit('navigate:step', 1);
150
+ wizard.vm.$emit('update:dirty', true);
151
+ wizard.vm.$emit('update:cancel');
152
+ expect(wrapper.emitted('save:step')[0][0]).toEqual(request);
153
+ expect(wrapper.emitted('navigate:step')[0]).toEqual([1]);
154
+ expect(wrapper.emitted('update:dirty')[0]).toEqual([true]);
155
+ expect(wrapper.emitted('update:cancel')).toHaveLength(1);
156
+ });
157
+
158
+ it('renders a saved boolean Double Counter value as checked on page two', () => {
159
+ const wrapper = shallowMount(EventCustomization, {
160
+ props: { eventConfig: { CountDoubles: true }, steps: [], currentStep: 1 }
161
+ });
162
+ wrappers.push(wrapper);
163
+ const toggle = wrapper
164
+ .findAllComponents({ name: 'Toggle' })
165
+ .find(control => control.props('label') === 'Double Counter Added & Will Be Tracked');
166
+ expect(toggle.props('checked')).toBe(true);
167
+ });
168
+
169
+ it('renders page two controls and emits a save from the actual Save and Continue button', async () => {
170
+ const wrapper = mount(EditEventWizard, { props: { event: createEvent(), weapons: [], editMode: true, step: 2 } });
171
+ wrappers.push(wrapper);
172
+ await wrapper.vm.$nextTick();
173
+ expect(wrapper.emitted('update:dirty').at(-1)).toEqual([false]);
174
+ await wrapper.findAll('input[type="checkbox"]')[0].setValue(true);
175
+ const save = wrapper.findAll('button').find(button => button.text() === 'Save and Continue');
176
+ await save.trigger('click');
177
+ expect(wrapper.emitted('save:step')[0][0].event.IncludeInRatings).toBe(true);
178
+ expect(wrapper.vm.currentStep).toBe(2);
179
+ expect(wrapper.emitted('update:dirty').at(-1)).toEqual([true]);
180
+ });
181
+
182
+ it('reconciles authoritative saved data in final-step controls without leaving the editor', async () => {
183
+ const event = createEvent();
184
+ event.rules.push({ RuleId: 3, RuleValue: '180' });
185
+ const wrapper = mount(EditEventWizard, { props: { event, weapons: [], editMode: true, step: 5 } });
186
+ wrappers.push(wrapper);
187
+ await wrapper.vm.$nextTick();
188
+ expect(wrapper.findAll('input[type="checkbox"]')[0].element.checked).toBe(true);
189
+ await wrapper.setProps({ event: { ...event, rules: [] }, saveNotice: 'Changes saved.' });
190
+ expect(wrapper.vm.currentStep).toBe(5);
191
+ expect(wrapper.findAll('input[type="checkbox"]')[0].element.checked).toBe(false);
192
+ expect(wrapper.text()).toContain('Changes saved.');
193
+ expect(wrapper.findAll('button').some(button => button.text() === 'Save')).toBe(true);
194
+ });
195
+
196
+ it('saves waitlist edits from the checkbox and reconciles the saved preference without mutating the event', async () => {
197
+ const event = createEvent();
198
+ const original = structuredClone(event);
199
+ const wrapper = mount(EditEventWizard, {
200
+ props: { event, weapons: [], editMode: true },
201
+ global: { stubs: { DatePicker: true, DropDownMenu: true, StepIndicator: true, TitledInput: true } }
202
+ });
203
+ wrappers.push(wrapper);
204
+ const checkbox = () => wrapper.find('input[type="checkbox"]');
205
+ expect(checkbox().element.checked).toBe(true);
206
+ await checkbox().setValue(false);
207
+ expect(wrapper.emitted('update:dirty').at(-1)).toEqual([true]);
208
+ await wrapper
209
+ .findAll('button')
210
+ .find(button => button.text() === 'Save and Continue')
211
+ .trigger('click');
212
+ const savedEvent = wrapper.emitted('save:step')[0][0].event;
213
+ expect(savedEvent.WaitlistEnabled).toBe(false);
214
+ expect(event).toEqual(original);
215
+ await wrapper.setProps({ event: savedEvent });
216
+ expect(checkbox().element.checked).toBe(false);
217
+ expect(wrapper.emitted('update:dirty').at(-1)).toEqual([false]);
218
+ await checkbox().setValue(true);
219
+ await wrapper.setProps({ event: { ...savedEvent } });
220
+ expect(checkbox().element.checked).toBe(false);
221
+ });