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
6 changes: 6 additions & 0 deletions client-v3/e2e/tests/03-system-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ test('switches to the System tab', async () => {
});
});

test('System tab shows server info rows', async () => {
await expect(page.locator('strong:has-text("Hostname")')).toBeVisible();
await expect(page.locator('strong:has-text("IP Address")')).toBeVisible();
await expect(page.locator('strong:has-text("Port")')).toBeVisible();
});

test('can open the View Clients modal', async () => {
await page.click('button:has-text("View Clients")');
await waitForModal(page, 'Connected Clients');
Expand Down
51 changes: 51 additions & 0 deletions client-v3/e2e/tests/06-show-config-characters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
waitForAppReady,
waitForModal,
confirmModal,
cancelModal,
waitForModalClosed,
confirmDialog,
} from '../helpers.js';
Expand Down Expand Up @@ -82,6 +83,56 @@ test('deletes a character', async () => {
});
});

// ── Character Merge ────────────────────────────────────────────────────────

test('Merge button is visible for each character row', async () => {
const row = page.locator('tr', { has: page.locator('td:has-text("Hamlet")') });
await expect(row.locator('button:has-text("Merge")')).toBeVisible();
});

test('creates a character for merge testing', async () => {
await page.getByRole('button', { name: 'New Character', exact: true }).click();
await waitForModal(page, 'New Character');
await page.fill('.modal.show input[type="text"]', 'Horatio');
await confirmModal(page);
await waitForModalClosed(page);
await expect(page.locator('td:has-text("Horatio")').first()).toBeVisible();
});

test('merge modal opens with correct title', async () => {
const row = page.locator('tr', { has: page.locator('td:has-text("Horatio")') });
await row.locator('button:has-text("Merge")').click();
await waitForModal(page, /Merge Horatio/);
await cancelModal(page);
await waitForModalClosed(page);
});

test('merge OK button is disabled with no destination selected', async () => {
const row = page.locator('tr', { has: page.locator('td:has-text("Horatio")') });
await row.locator('button:has-text("Merge")').click();
await waitForModal(page, /Merge Horatio/);
const okBtn = page.locator('.modal.show .modal-footer button.btn-primary');
await expect(okBtn).toBeDisabled();
await cancelModal(page);
await waitForModalClosed(page);
});

test('merges a character into another', async () => {
const row = page.locator('tr', { has: page.locator('td:has-text("Horatio")') });
await row.locator('button:has-text("Merge")').click();
await waitForModal(page, /Merge Horatio/);
// Open the dropdown by clicking the multiselect container, then select the option.
await page.locator('.modal.show .multiselect').click();
await page.locator('.modal.show .multiselect__option', { hasText: 'Hamlet' }).click();
await confirmModal(page);
await waitForModalClosed(page);
// Scope to the character table to avoid matching cells in the Line Counts tab (always in DOM).
await expect(page.locator('#character-table td:has-text("Horatio")')).not.toBeVisible({
timeout: 5_000,
});
await expect(page.locator('#character-table td:has-text("Hamlet")')).toBeVisible();
});

// ── Character Groups ──────────────────────────────────────────────────────

test('switches to Character Groups sub-tab', async () => {
Expand Down
79 changes: 41 additions & 38 deletions client-v3/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions client-v3/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "client-v3",
"version": "0.30.5",
"version": "0.30.6",
"description": "DigiScript front end (Vue 3)",
"author": "DreamTeamProd",
"private": true,
Expand Down Expand Up @@ -33,19 +33,19 @@
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"bootstrap": "^5.3.8",
"bootstrap-vue-next": "^0.45.4",
"bootstrap-vue-next": "^0.45.5",
"bootswatch": "^5.3.8",
"contrast-color": "1.0.1",
"core-js": "^3.49.0",
"d3-hierarchy": "^3.1.2",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"deep-object-diff": "1.1.9",
"dompurify": "^3.4.7",
"dompurify": "^3.4.8",
"fuse.js": "^7.4.1",
"lodash": "^4.18.1",
"loglevel": "^1.9.2",
"marked": "^18.0.4",
"marked": "^18.0.5",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"splitpanes": "^4.1.2",
Expand All @@ -65,11 +65,11 @@
"@typescript-eslint/parser": "^8.60.1",
"@vitejs/plugin-vue": "^6.0.7",
"@vitest/ui": "^4.1.8",
"@vue/test-utils": "^2.4.10",
"@vue/test-utils": "^2.4.11",
"eslint": "^10.4.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-vue": "^10.9.1",
"eslint-plugin-vue": "^10.9.2",
"globals": "^17.6.0",
"jiti": "^2.7.0",
"jsdom": "^29.1.1",
Expand Down
21 changes: 6 additions & 15 deletions client-v3/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,21 +162,17 @@
:hide-footer="changingPage"
:no-close-on-backdrop="changingPage"
:no-close-on-esc="changingPage"
@ok="goToLivePage"
@ok.prevent="goToLivePage"
>
<BForm @submit.stop.prevent="">
<BFormGroup id="page-input-group" label="Page" label-for="page-input" label-cols="auto">
<BFormInput
id="page-input"
v-model="pageInputState.pageNo"
v-model.number="pageInputNo"
name="page-input"
type="number"
:state="v$.pageNo.$dirty ? !v$.pageNo.$error : null"
aria-describedby="page-feedback"
:min="1"
/>
<BFormInvalidFeedback id="page-feedback">
This is a required field, and must be greater than 0.
</BFormInvalidFeedback>
</BFormGroup>
</BForm>
</BModal>
Expand All @@ -188,8 +184,6 @@ import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import type { BModal } from 'bootstrap-vue-next';
import { useVuelidate } from '@vuelidate/core';
import { required, minValue } from '@vuelidate/validators';
import log from 'loglevel';
import { toast } from '@/js/toast';
import { useConfirm } from '@/composables/useConfirm';
Expand Down Expand Up @@ -231,9 +225,7 @@ const goToPageModal = ref<InstanceType<typeof BModal>>();
const currentShowSession = computed(() => showStore.currentSession);

// Jump-to-page form
const pageInputState = ref({ pageNo: 1 });
const pageRules = { pageNo: { required, minValue: minValue(1) } };
const v$ = useVuelidate(pageRules, pageInputState);
const pageInputNo = ref(1);

let loadTimer: ReturnType<typeof setTimeout> | null = null;

Expand Down Expand Up @@ -404,9 +396,8 @@ async function handleLogout(): Promise<void> {
}

async function goToLivePage(): Promise<void> {
const valid = await v$.value.$validate();
if (!valid) return;
sendObj({ OP: 'LIVE_SHOW_JUMP_TO_PAGE', DATA: { page: pageInputState.value.pageNo } });
if (!pageInputNo.value || pageInputNo.value < 1) return;
sendObj({ OP: 'LIVE_SHOW_JUMP_TO_PAGE', DATA: { page: pageInputNo.value } });
goToPageModal.value?.hide();
}
</script>
Expand Down
Loading
Loading