Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions client-v3/e2e/tests/08-show-config-cues.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ test('can open the Go to Page dialog in cue editor', async () => {
await waitForModalClosed(page);
});

test('Go to Page submits and navigates to the requested page', async () => {
await page.click('button:has-text("Go to Page")');
await waitForModal(page, 'Go to Page');
await page.fill('.modal.show input[type="number"]', '1');
await confirmModal(page);
// If the fix is working, clicking OK closes the modal; previously Vuelidate prevented this
await waitForModalClosed(page);
await expect(page.locator('p.mb-0:has-text("Current Page: 1")')).toBeVisible();
});

// ── Cue Counts ────────────────────────────────────────────────────────────

test('switches to Cue Counts sub-tab', async () => {
Expand Down
49 changes: 49 additions & 0 deletions client-v3/e2e/tests/10-show-config-script.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,55 @@ test('edits the cue identifier', async () => {
});
});

test('can add a cue using Enter key in Add New Cue modal', async () => {
await page.locator('.add-cue-btn').first().click();
await waitForModal(page, 'Add New Cue');
await page.locator('.modal.show select#new-cue-type').selectOption({ index: 1 });
await page.fill('#new-cue-ident', '003');
// Enter key submits the form (fix: BForm @submit bound to onSubmitNew)
await page.locator('#new-cue-ident').press('Enter');
await waitForModalClosed(page);
await expect(page.locator('.cue-button:not(.add-cue-btn)')).toHaveCount(2, { timeout: 5_000 });
});

test('can edit a cue identifier using Enter key in Edit Cue modal', async () => {
// Click the second cue button (003)
await page.locator('.cue-button:not(.add-cue-btn)').last().click();
await waitForModal(page, 'Edit Cue');
await page.fill('#edit-cue-ident', '004');
// Enter key submits the form (fix: BForm @submit bound to onSubmitEdit)
await page.locator('#edit-cue-ident').press('Enter');
await waitForModalClosed(page);
await expect(page.locator('.cue-button:not(.add-cue-btn)')).toHaveCount(2, { timeout: 5_000 });
});

test('Jump to Cue navigates and auto-closes the modal', async () => {
await page.click('button:has-text("Go to Cue")');
await waitForModal(page, 'Jump to Cue');
await page.locator('.modal.show select').selectOption({ index: 1 }); // LX type
await page.locator('.modal.show input').fill('004');
await confirmModal(page); // Click Search
// Single exact match → navigateToMatch() now calls modal.hide() (fix)
await waitForModalClosed(page);
await expect(page.locator('.v-toast__text').filter({ hasText: /Jumped to/ })).toBeVisible({
timeout: 5_000,
});
});

test('deletes the Enter-key-added cue', async () => {
// Removes the LX 004 cue created in the Enter key test, leaving only LX 002
await page.locator('.cue-button:not(.add-cue-btn)').last().click();
await waitForModal(page, 'Edit Cue');
await page.locator('.modal.show button:has-text("Delete")').click();
await waitForModal(page, 'Delete Cue');
await page
.locator('.modal.show')
.filter({ has: page.locator('.modal-title:has-text("Delete Cue")') })
.locator('.modal-footer button.btn-danger')
.click();
await expect(page.locator('.cue-button:not(.add-cue-btn)')).toHaveCount(1, { timeout: 5_000 });
});

test('deletes the cue', async () => {
await page.locator('.cue-button:not(.add-cue-btn)').first().click();
await waitForModal(page, 'Edit Cue');
Expand Down
39 changes: 5 additions & 34 deletions client-v3/src/components/show/config/cues/CueEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,11 @@
:no-footer="changingPage"
:no-close-on-backdrop="changingPage"
:no-close-on-esc="changingPage"
:ok-disabled="pageV$.pageInputFormState.$invalid"
@ok.prevent="goToPage"
>
<BForm @submit.stop.prevent="goToPage">
<BForm @submit.stop.prevent="">
<BFormGroup label="Page" label-for="page-input" label-cols="auto">
<BFormInput
id="page-input"
v-model="v$.pageInputFormState.pageNo.$model"
name="page-input"
type="number"
:state="validatePageState('pageNo')"
aria-describedby="page-feedback"
/>
<BFormInvalidFeedback id="page-feedback">
This is a required field, and must be greater than 0.
</BFormInvalidFeedback>
<BFormInput id="page-input" v-model.number="pageInputNo" type="number" :min="1" />
</BFormGroup>
</BForm>
</BModal>
Expand All @@ -83,10 +72,7 @@

<script setup lang="ts">
import { ref, watch, onBeforeMount } from 'vue';
import { useVuelidate } from '@vuelidate/core';
import { required, minValue } from '@vuelidate/validators';
import type { BModal } from 'bootstrap-vue-next';
import { notNull, notNullAndGreaterThanZero } from '@/js/customValidators';
import { useScriptStore } from '@/stores/script';
import { useShowStore } from '@/stores/show';
import { useUserStore } from '@/stores/user';
Expand All @@ -104,16 +90,7 @@ const changingPage = ref(false);
const goToPageModal = ref<InstanceType<typeof BModal> | null>(null);
const jumpToCueModal = ref<InstanceType<typeof JumpToCueModal> | null>(null);

const pageInputFormState = ref({ pageNo: 1 });

const rules = {
pageInputFormState: {
pageNo: { required, notNull, notNullAndGreaterThanZero, minValue: minValue(1) },
},
};
const v$ = useVuelidate(rules, { pageInputFormState });
// Alias for template ok-disabled binding
const pageV$ = v$;
const pageInputNo = ref(1);

watch(currentEditPage, (val) => {
localStorage.setItem('cueEditPage', String(val));
Expand Down Expand Up @@ -149,11 +126,6 @@ onBeforeMount(async () => {
await goToPageInner(currentEditPage.value);
});

function validatePageState(name: 'pageNo'): boolean | null {
const field = v$.value.pageInputFormState[name];
return field.$dirty ? !field.$error : null;
}

async function goToPageInner(pageNo: number): Promise<void> {
if (pageNo > 1) {
await scriptStore.loadScriptPage(pageNo - 1);
Expand All @@ -164,10 +136,9 @@ async function goToPageInner(pageNo: number): Promise<void> {
}

async function goToPage(): Promise<void> {
const valid = await v$.value.$validate();
if (!valid) return;
if (!pageInputNo.value || pageInputNo.value < 1) return;
changingPage.value = true;
await goToPageInner(pageInputFormState.value.pageNo);
await goToPageInner(pageInputNo.value);
changingPage.value = false;
goToPageModal.value?.hide();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ async function performCueSearch(): Promise<void> {
cueSearchResults.value = result;
if (result.exact_matches.length === 1) {
navigateToMatch(result.exact_matches[0]);
modal.value?.hide();
} else {
showResults.value = true;
}
Expand All @@ -185,6 +184,7 @@ function navigateToMatch(match: CueMatch): void {
toast.success(
`Jumped to ${match.cue_type.prefix} ${match.cue.ident} on page ${match.location.page}`
);
modal.value?.hide();
}

function resetCueSearch(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
@hidden="resetNewForm"
@ok="onSubmitNew"
>
<BForm @submit.stop.prevent="">
<BForm @submit.stop.prevent="onSubmitNew">
<BFormGroup label="Cue Type" label-for="new-cue-type">
<BFormSelect
id="new-cue-type"
Expand Down Expand Up @@ -188,7 +188,7 @@
Save
</BButton>
</template>
<BForm @submit.stop.prevent="">
<BForm @submit.stop.prevent="onSubmitEdit">
<BFormGroup label="Cue Type" label-for="edit-cue-type">
<BFormSelect
id="edit-cue-type"
Expand Down
Loading