mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-05-06 08:26:45 -03:00
fix(trigger-words): support stable inline editing
This commit is contained in:
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "Bearbeitung abbrechen",
|
||||
"save": "Änderungen speichern",
|
||||
"addPlaceholder": "Tippen zum Hinzufügen oder klicken Sie auf Vorschläge unten",
|
||||
"editWord": "Trigger Word bearbeiten",
|
||||
"editPlaceholder": "Trigger Word bearbeiten",
|
||||
"copyWord": "Trigger Word kopieren",
|
||||
"deleteWord": "Trigger Word löschen",
|
||||
"suggestions": {
|
||||
|
||||
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "Cancel editing",
|
||||
"save": "Save changes",
|
||||
"addPlaceholder": "Type to add or click suggestions below",
|
||||
"editWord": "Edit trigger word",
|
||||
"editPlaceholder": "Edit trigger word",
|
||||
"copyWord": "Copy trigger word",
|
||||
"deleteWord": "Delete trigger word",
|
||||
"suggestions": {
|
||||
|
||||
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "Cancelar edición",
|
||||
"save": "Guardar cambios",
|
||||
"addPlaceholder": "Escribe para añadir o haz clic en sugerencias de abajo",
|
||||
"editWord": "Editar palabra de activación",
|
||||
"editPlaceholder": "Editar palabra de activación",
|
||||
"copyWord": "Copiar palabra clave",
|
||||
"deleteWord": "Eliminar palabra clave",
|
||||
"suggestions": {
|
||||
|
||||
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "Annuler la modification",
|
||||
"save": "Sauvegarder les modifications",
|
||||
"addPlaceholder": "Tapez pour ajouter ou cliquez sur les suggestions ci-dessous",
|
||||
"editWord": "Modifier le mot déclencheur",
|
||||
"editPlaceholder": "Modifier le mot déclencheur",
|
||||
"copyWord": "Copier le mot-clé",
|
||||
"deleteWord": "Supprimer le mot-clé",
|
||||
"suggestions": {
|
||||
|
||||
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "בטל עריכה",
|
||||
"save": "שמור שינויים",
|
||||
"addPlaceholder": "הקלד להוספה או לחץ על הצעות למטה",
|
||||
"editWord": "עריכת מילת טריגר",
|
||||
"editPlaceholder": "עריכת מילת טריגר",
|
||||
"copyWord": "העתק מילת טריגר",
|
||||
"deleteWord": "מחק מילת טריגר",
|
||||
"suggestions": {
|
||||
|
||||
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "編集をキャンセル",
|
||||
"save": "変更を保存",
|
||||
"addPlaceholder": "入力して追加するか、下の提案をクリック",
|
||||
"editWord": "トリガーワードを編集",
|
||||
"editPlaceholder": "トリガーワードを編集",
|
||||
"copyWord": "トリガーワードをコピー",
|
||||
"deleteWord": "トリガーワードを削除",
|
||||
"suggestions": {
|
||||
|
||||
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "편집 취소",
|
||||
"save": "변경사항 저장",
|
||||
"addPlaceholder": "입력하거나 아래 제안을 클릭하세요",
|
||||
"editWord": "트리거 단어 편집",
|
||||
"editPlaceholder": "트리거 단어 편집",
|
||||
"copyWord": "트리거 단어 복사",
|
||||
"deleteWord": "트리거 단어 삭제",
|
||||
"suggestions": {
|
||||
|
||||
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "Отменить редактирование",
|
||||
"save": "Сохранить изменения",
|
||||
"addPlaceholder": "Введите для добавления или нажмите на предложения ниже",
|
||||
"editWord": "Редактировать триггерное слово",
|
||||
"editPlaceholder": "Редактировать триггерное слово",
|
||||
"copyWord": "Копировать триггерное слово",
|
||||
"deleteWord": "Удалить триггерное слово",
|
||||
"suggestions": {
|
||||
|
||||
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "取消编辑",
|
||||
"save": "保存更改",
|
||||
"addPlaceholder": "输入或点击下方建议添加",
|
||||
"editWord": "编辑触发词",
|
||||
"editPlaceholder": "编辑触发词",
|
||||
"copyWord": "复制触发词",
|
||||
"deleteWord": "删除触发词",
|
||||
"suggestions": {
|
||||
|
||||
@@ -1190,6 +1190,8 @@
|
||||
"cancel": "取消編輯",
|
||||
"save": "儲存變更",
|
||||
"addPlaceholder": "輸入或點擊下方建議",
|
||||
"editWord": "編輯觸發詞",
|
||||
"editPlaceholder": "編輯觸發詞",
|
||||
"copyWord": "複製觸發詞",
|
||||
"deleteWord": "刪除觸發詞",
|
||||
"suggestions": {
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.trigger-word-tag:not(.is-editing) {
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.trigger-word-content {
|
||||
color: var(--lora-accent) !important;
|
||||
font-size: 0.85em;
|
||||
@@ -65,6 +69,38 @@
|
||||
border-color: var(--lora-accent);
|
||||
}
|
||||
|
||||
.trigger-words.edit-mode .trigger-word-tag {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.trigger-word-tag.is-editing {
|
||||
align-items: center;
|
||||
flex: 0 1 min(var(--trigger-word-edit-width, 48ch), 100%);
|
||||
width: min(var(--trigger-word-edit-width, 48ch), 100%);
|
||||
height: var(--trigger-word-edit-height, auto);
|
||||
border-color: var(--lora-accent);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.trigger-word-edit-input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 1px 2px;
|
||||
border: none;
|
||||
resize: none;
|
||||
overflow: auto;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: var(--lora-accent);
|
||||
font: inherit;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.trigger-word-copy {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -297,6 +297,8 @@ export function setupTriggerWordsEditMode() {
|
||||
// Disable click-to-copy and show delete buttons
|
||||
triggerWordTags.forEach(tag => {
|
||||
tag.onclick = null;
|
||||
tag.addEventListener('click', startEditTriggerWord);
|
||||
tag.title = translate('modals.model.triggerWords.editWord');
|
||||
const copyIcon = tag.querySelector('.trigger-word-copy');
|
||||
const deleteBtn = tag.querySelector('.metadata-delete-btn');
|
||||
|
||||
@@ -353,6 +355,7 @@ export function setupTriggerWordsEditMode() {
|
||||
// If canceling, restore original trigger words
|
||||
restoreOriginalTriggerWords(triggerWordsSection, originalTriggerWords);
|
||||
} else {
|
||||
commitActiveTriggerWordEdit(triggerWordsSection);
|
||||
// If saving, reset UI state on current trigger words
|
||||
resetTriggerWordsUIState(triggerWordsSection);
|
||||
// Reset the skip restore flag
|
||||
@@ -432,15 +435,18 @@ function deleteTriggerWord(e) {
|
||||
* @param {HTMLElement} section - The trigger words section
|
||||
*/
|
||||
function resetTriggerWordsUIState(section) {
|
||||
commitActiveTriggerWordEdit(section);
|
||||
|
||||
const triggerWordTags = section.querySelectorAll('.trigger-word-tag');
|
||||
|
||||
triggerWordTags.forEach(tag => {
|
||||
const word = tag.dataset.word;
|
||||
const copyIcon = tag.querySelector('.trigger-word-copy');
|
||||
const deleteBtn = tag.querySelector('.metadata-delete-btn');
|
||||
|
||||
// Restore click-to-copy functionality
|
||||
tag.removeEventListener('click', startEditTriggerWord);
|
||||
tag.onclick = () => copyTriggerWord(tag.dataset.word);
|
||||
tag.title = translate('modals.model.triggerWords.copyWord');
|
||||
|
||||
// Show copy icon, hide delete button
|
||||
if (copyIcon) copyIcon.style.display = '';
|
||||
@@ -474,23 +480,156 @@ function restoreOriginalTriggerWords(section, originalWords) {
|
||||
|
||||
// Recreate original tags
|
||||
originalWords.forEach(word => {
|
||||
tagsContainer.appendChild(createTriggerWordTag(word, false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a trigger word tag element
|
||||
* @param {string} word - Trigger word
|
||||
* @param {boolean} isEditMode - Whether the tag should be editable
|
||||
* @returns {HTMLElement} Tag element
|
||||
*/
|
||||
function createTriggerWordTag(word, isEditMode = false) {
|
||||
const tag = document.createElement('div');
|
||||
tag.className = 'trigger-word-tag';
|
||||
tag.dataset.word = word;
|
||||
tag.onclick = () => copyTriggerWord(tag.dataset.word);
|
||||
tag.title = translate(isEditMode ? 'modals.model.triggerWords.editWord' : 'modals.model.triggerWords.copyWord');
|
||||
|
||||
const escapedWord = escapeHtml(word);
|
||||
tag.innerHTML = `
|
||||
<span class="trigger-word-content">${escapedWord}</span>
|
||||
<span class="trigger-word-copy">
|
||||
<span class="trigger-word-copy" style="${isEditMode ? 'display:none;' : ''}">
|
||||
<i class="fas fa-copy"></i>
|
||||
</span>
|
||||
<button class="metadata-delete-btn" style="display:none;" onclick="event.stopPropagation();">
|
||||
<button class="metadata-delete-btn" style="${isEditMode ? '' : 'display:none;'}" onclick="event.stopPropagation();" title="${translate('modals.model.triggerWords.deleteWord')}">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
tagsContainer.appendChild(tag);
|
||||
|
||||
const deleteBtn = tag.querySelector('.metadata-delete-btn');
|
||||
deleteBtn.addEventListener('click', deleteTriggerWord);
|
||||
|
||||
if (isEditMode) {
|
||||
tag.addEventListener('click', startEditTriggerWord);
|
||||
} else {
|
||||
tag.onclick = () => copyTriggerWord(tag.dataset.word);
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a trigger word against existing tags
|
||||
* @param {string} word - Trigger word
|
||||
* @param {HTMLElement} tagsContainer - Tags container
|
||||
* @param {HTMLElement|null} currentTag - Tag being edited, if any
|
||||
* @returns {boolean} Whether the word is valid
|
||||
*/
|
||||
function validateTriggerWord(word, tagsContainer, currentTag = null) {
|
||||
if (word.split(/\s+/).length > MAX_WORDS_PER_TRIGGER_GROUP) {
|
||||
showToast('toast.triggerWords.tooLong', {}, 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentTags = tagsContainer.querySelectorAll('.trigger-word-tag');
|
||||
const existingWords = Array.from(currentTags)
|
||||
.filter(tag => tag !== currentTag)
|
||||
.map(tag => tag.dataset.word);
|
||||
|
||||
if (existingWords.includes(word)) {
|
||||
showToast('toast.triggerWords.alreadyExists', {}, 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start inline editing for a trigger word tag
|
||||
* @param {Event} e - Click event
|
||||
*/
|
||||
function startEditTriggerWord(e) {
|
||||
if (e.target.closest('.metadata-delete-btn') || e.target.closest('.trigger-word-edit-input')) return;
|
||||
|
||||
const tag = this.closest('.trigger-word-tag');
|
||||
const section = tag?.closest('.trigger-words');
|
||||
if (!tag || !section?.classList.contains('edit-mode') || tag.classList.contains('is-editing')) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
commitActiveTriggerWordEdit(section);
|
||||
|
||||
const content = tag.querySelector('.trigger-word-content');
|
||||
const originalWord = tag.dataset.word;
|
||||
const originalRect = tag.getBoundingClientRect();
|
||||
if (originalRect.width > 0) {
|
||||
tag.style.setProperty('--trigger-word-edit-width', `${Math.ceil(originalRect.width)}px`);
|
||||
}
|
||||
if (originalRect.height > 0) {
|
||||
tag.style.setProperty('--trigger-word-edit-height', `${Math.ceil(originalRect.height)}px`);
|
||||
}
|
||||
|
||||
const editor = document.createElement('textarea');
|
||||
editor.className = 'trigger-word-edit-input';
|
||||
editor.rows = 1;
|
||||
editor.value = originalWord;
|
||||
editor.setAttribute('aria-label', translate('modals.model.triggerWords.editWord'));
|
||||
editor.placeholder = translate('modals.model.triggerWords.editPlaceholder');
|
||||
|
||||
let finished = false;
|
||||
const finish = (shouldCommit) => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
|
||||
const nextWord = editor.value.trim().replace(/\s*\n+\s*/g, ' ');
|
||||
if (shouldCommit && nextWord && nextWord !== originalWord) {
|
||||
const tagsContainer = tag.closest('.trigger-words-tags');
|
||||
if (tagsContainer && validateTriggerWord(nextWord, tagsContainer, tag)) {
|
||||
tag.dataset.word = nextWord;
|
||||
content.textContent = nextWord;
|
||||
}
|
||||
}
|
||||
|
||||
editor.remove();
|
||||
content.style.display = '';
|
||||
tag.classList.remove('is-editing');
|
||||
tag.style.removeProperty('--trigger-word-edit-width');
|
||||
tag.style.removeProperty('--trigger-word-edit-height');
|
||||
updateTrainedWordsDropdown();
|
||||
};
|
||||
|
||||
editor.addEventListener('click', event => event.stopPropagation());
|
||||
editor.addEventListener('keydown', event => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
finish(true);
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
finish(false);
|
||||
}
|
||||
});
|
||||
editor.addEventListener('blur', () => finish(true));
|
||||
|
||||
editor.style.visibility = 'hidden';
|
||||
content.after(editor);
|
||||
tag.classList.add('is-editing');
|
||||
content.style.display = 'none';
|
||||
editor.style.visibility = '';
|
||||
editor.focus();
|
||||
editor.select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit an active inline trigger word edit if one exists
|
||||
* @param {HTMLElement} section - Trigger words section
|
||||
*/
|
||||
function commitActiveTriggerWordEdit(section) {
|
||||
const input = section.querySelector('.trigger-word-edit-input');
|
||||
if (input) {
|
||||
input.dispatchEvent(new FocusEvent('blur'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,12 +664,6 @@ function addNewTriggerWord(word) {
|
||||
noTriggerWordsMsg.style.display = 'none';
|
||||
}
|
||||
|
||||
// Validation: Check length
|
||||
if (word.split(/\s+/).length > MAX_WORDS_PER_TRIGGER_GROUP) {
|
||||
showToast('toast.triggerWords.tooLong', {}, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validation: Check total number
|
||||
const currentTags = tagsContainer.querySelectorAll('.trigger-word-tag');
|
||||
if (currentTags.length >= MAX_TRIGGER_WORD_GROUPS) {
|
||||
@@ -538,33 +671,9 @@ function addNewTriggerWord(word) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validation: Check for duplicates
|
||||
const existingWords = Array.from(currentTags).map(tag => tag.dataset.word);
|
||||
if (existingWords.includes(word)) {
|
||||
showToast('toast.triggerWords.alreadyExists', {}, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new tag
|
||||
const newTag = document.createElement('div');
|
||||
newTag.className = 'trigger-word-tag';
|
||||
newTag.dataset.word = word;
|
||||
|
||||
const escapedWord = escapeHtml(word);
|
||||
newTag.innerHTML = `
|
||||
<span class="trigger-word-content">${escapedWord}</span>
|
||||
<span class="trigger-word-copy" style="display:none;">
|
||||
<i class="fas fa-copy"></i>
|
||||
</span>
|
||||
<button class="metadata-delete-btn" onclick="event.stopPropagation();">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
// Add event listener to delete button
|
||||
const deleteBtn = newTag.querySelector('.metadata-delete-btn');
|
||||
deleteBtn.addEventListener('click', deleteTriggerWord);
|
||||
if (!validateTriggerWord(word, tagsContainer)) return;
|
||||
|
||||
const newTag = createTriggerWordTag(word, triggerWordsSection.classList.contains('edit-mode'));
|
||||
tagsContainer.appendChild(newTag);
|
||||
|
||||
// Update status of items in the trained words dropdown
|
||||
@@ -636,6 +745,8 @@ async function saveTriggerWords() {
|
||||
const filePath = editBtn.dataset.filePath;
|
||||
const triggerWordsSection = editBtn.closest('.trigger-words');
|
||||
|
||||
commitActiveTriggerWordEdit(triggerWordsSection);
|
||||
|
||||
// Auto-commit any pending input to prevent data loss
|
||||
const input = triggerWordsSection.querySelector('.metadata-input');
|
||||
if (input && input.value.trim()) {
|
||||
|
||||
138
tests/frontend/components/triggerWords.inlineEdit.test.js
Normal file
138
tests/frontend/components/triggerWords.inlineEdit.test.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const {
|
||||
TRIGGER_WORDS_MODULE,
|
||||
I18N_HELPERS_MODULE,
|
||||
UI_HELPERS_MODULE,
|
||||
} = vi.hoisted(() => ({
|
||||
TRIGGER_WORDS_MODULE: new URL('../../../static/js/components/shared/TriggerWords.js', import.meta.url).pathname,
|
||||
I18N_HELPERS_MODULE: new URL('../../../static/js/utils/i18nHelpers.js', import.meta.url).pathname,
|
||||
UI_HELPERS_MODULE: new URL('../../../static/js/utils/uiHelpers.js', import.meta.url).pathname,
|
||||
}));
|
||||
|
||||
vi.mock(I18N_HELPERS_MODULE, () => ({
|
||||
translate: vi.fn((key, params, fallback) => fallback || key),
|
||||
}));
|
||||
|
||||
vi.mock(UI_HELPERS_MODULE, () => ({
|
||||
showToast: vi.fn(),
|
||||
copyToClipboard: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../../static/js/api/modelApiFactory.js', () => ({
|
||||
getModelApiClient: vi.fn(() => ({
|
||||
saveModelMetadata: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe("TriggerWords inline editing", () => {
|
||||
let renderTriggerWords;
|
||||
let setupTriggerWordsEditMode;
|
||||
let showToast;
|
||||
|
||||
beforeEach(async () => {
|
||||
document.body.innerHTML = '';
|
||||
vi.clearAllMocks();
|
||||
global.fetch = vi.fn(async () => ({
|
||||
json: async () => ({
|
||||
success: true,
|
||||
trained_words: [],
|
||||
class_tokens: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
const module = await import(TRIGGER_WORDS_MODULE);
|
||||
const uiHelpers = await import(UI_HELPERS_MODULE);
|
||||
renderTriggerWords = module.renderTriggerWords;
|
||||
setupTriggerWordsEditMode = module.setupTriggerWordsEditMode;
|
||||
showToast = uiHelpers.showToast;
|
||||
});
|
||||
|
||||
async function enterEditMode(words = ["alpha", "beta"]) {
|
||||
document.body.innerHTML = renderTriggerWords(words, "test.safetensors");
|
||||
setupTriggerWordsEditMode();
|
||||
|
||||
document.querySelector('.edit-trigger-words-btn')
|
||||
.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(document.querySelector('.metadata-suggestions-dropdown')).toBeTruthy();
|
||||
});
|
||||
}
|
||||
|
||||
function editFirstTag(nextValue, key = 'Enter') {
|
||||
const firstTag = document.querySelector('.trigger-word-tag');
|
||||
firstTag.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||
|
||||
const input = firstTag.querySelector('.trigger-word-edit-input');
|
||||
input.value = nextValue;
|
||||
input.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true, cancelable: true }));
|
||||
|
||||
return firstTag;
|
||||
}
|
||||
|
||||
it("updates an existing trigger word in place", async () => {
|
||||
await enterEditMode();
|
||||
|
||||
const firstTag = editFirstTag("gamma");
|
||||
|
||||
expect(firstTag.dataset.word).toBe("gamma");
|
||||
expect(firstTag.querySelector('.trigger-word-content').textContent).toBe("gamma");
|
||||
expect(document.querySelector('.trigger-word-edit-input')).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps the original word and shows a toast when editing to a duplicate", async () => {
|
||||
await enterEditMode();
|
||||
|
||||
const firstTag = editFirstTag("beta");
|
||||
|
||||
expect(firstTag.dataset.word).toBe("alpha");
|
||||
expect(firstTag.querySelector('.trigger-word-content').textContent).toBe("alpha");
|
||||
expect(showToast).toHaveBeenCalledWith('toast.triggerWords.alreadyExists', {}, 'error');
|
||||
});
|
||||
|
||||
it("restores the original value when Escape is pressed", async () => {
|
||||
await enterEditMode();
|
||||
|
||||
const firstTag = editFirstTag("gamma", "Escape");
|
||||
|
||||
expect(firstTag.dataset.word).toBe("alpha");
|
||||
expect(firstTag.querySelector('.trigger-word-content').textContent).toBe("alpha");
|
||||
});
|
||||
|
||||
it("preserves the current tag dimensions while editing long trigger words", async () => {
|
||||
await enterEditMode(["alpha beta gamma delta epsilon zeta eta theta"]);
|
||||
|
||||
const firstTag = document.querySelector('.trigger-word-tag');
|
||||
vi.spyOn(firstTag, 'getBoundingClientRect').mockReturnValue({
|
||||
width: 320,
|
||||
height: 44,
|
||||
top: 0,
|
||||
right: 320,
|
||||
bottom: 44,
|
||||
left: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
toJSON: () => ({}),
|
||||
});
|
||||
|
||||
firstTag.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||
|
||||
const editor = firstTag.querySelector('.trigger-word-edit-input');
|
||||
expect(editor.tagName).toBe('TEXTAREA');
|
||||
expect(firstTag.style.getPropertyValue('--trigger-word-edit-width')).toBe('320px');
|
||||
expect(firstTag.style.getPropertyValue('--trigger-word-edit-height')).toBe('44px');
|
||||
});
|
||||
|
||||
it("restores all original trigger words when edit mode is canceled", async () => {
|
||||
await enterEditMode();
|
||||
editFirstTag("gamma");
|
||||
|
||||
document.querySelector('.edit-trigger-words-btn')
|
||||
.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
||||
|
||||
const words = Array.from(document.querySelectorAll('.trigger-word-tag'))
|
||||
.map(tag => tag.dataset.word);
|
||||
expect(words).toEqual(["alpha", "beta"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user