test: add additional E2E tests for tooltip

This commit is contained in:
JackUait 2025-11-11 06:27:05 +03:00
commit 1bd18fe258

View file

@ -230,6 +230,48 @@ test.describe('Tooltip API', () => {
await expect(tooltip).toContainText('Second tooltip');
await expect(tooltip).not.toContainText('First tooltip');
});
test('should throw when content is neither string nor Node', async ({ page }) => {
const testElement = await page.evaluate(({ holderId }) => {
const container = document.getElementById(holderId);
const element = document.createElement('div');
element.textContent = 'Invalid content test';
element.id = 'invalid-content';
container?.appendChild(element);
return {
id: element.id,
};
}, { holderId: HOLDER_ID });
const errorMessage = await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (!element || !editor?.tooltip) {
return null;
}
try {
const invalidContent = { text: 'invalid' } as unknown as Node;
editor.tooltip.show(element, invalidContent);
return 'no error';
} catch (error) {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
}, { elementId: testElement.id });
expect(errorMessage).not.toBeNull();
expect(errorMessage).not.toBe('no error');
expect(errorMessage).toContain('[CodeX Tooltip] Wrong type');
});
});
test.describe('hide()', () => {
@ -316,6 +358,46 @@ test.describe('Tooltip API', () => {
await waitForTooltipToHide(page);
});
test('should bypass hiding delay when hide(true) is called', async ({ page }) => {
const testElement = await page.evaluate(({ holderId }) => {
const container = document.getElementById(holderId);
const element = document.createElement('span');
element.textContent = 'Skip delay element';
element.id = 'skip-delay';
container?.appendChild(element);
return {
id: element.id,
};
}, { holderId: HOLDER_ID });
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Delayed tooltip', {
hidingDelay: 1000,
});
}
}, { elementId: testElement.id });
const tooltipBeforeHide = await waitForTooltip(page);
await expect(tooltipBeforeHide).toBeVisible();
await page.evaluate(() => {
const editor = window.editorInstance;
if (editor?.tooltip) {
(editor.tooltip as unknown as { hide(skipDelay?: boolean): void }).hide(true);
}
});
await waitForTooltipToHide(page);
});
test('should handle hide() when no tooltip is visible', async ({ page }) => {
// Calling hide() when no tooltip is visible should not throw
await page.evaluate(() => {
@ -686,6 +768,87 @@ test.describe('Tooltip API', () => {
await expect(tooltip).toContainText('After recreate');
});
test('should reuse existing tooltip stylesheet across reinitializations', async ({ page }) => {
const testElement = await page.evaluate(({ holderId }) => {
const container = document.getElementById(holderId);
const element = document.createElement('button');
element.textContent = 'Stylesheet check';
element.id = 'stylesheet-button';
container?.appendChild(element);
return {
id: element.id,
};
}, { holderId: HOLDER_ID });
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Stylesheet tooltip', {
delay: 0,
});
}
}, { elementId: testElement.id });
await waitForTooltip(page);
const initialStyleCount = await page.evaluate(() => {
return document.querySelectorAll('#codex-tooltips-style').length;
});
expect(initialStyleCount).toBe(1);
await page.evaluate(async () => {
if (window.editorInstance) {
await window.editorInstance.destroy();
window.editorInstance = undefined;
}
});
const afterDestroyStyleCount = await page.evaluate(() => {
return document.querySelectorAll('#codex-tooltips-style').length;
});
expect(afterDestroyStyleCount).toBe(1);
await createEditor(page);
await page.evaluate(({ holderId, elementId }) => {
const container = document.getElementById(holderId);
let element = document.getElementById(elementId);
if (!element && container) {
element = document.createElement('button');
element.textContent = 'Stylesheet check';
element.id = elementId;
container.appendChild(element);
}
}, { holderId: HOLDER_ID,
elementId: testElement.id });
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Stylesheet tooltip reinit', {
delay: 0,
});
}
}, { elementId: testElement.id });
await waitForTooltip(page);
const afterReinitStyleCount = await page.evaluate(() => {
return document.querySelectorAll('#codex-tooltips-style').length;
});
expect(afterReinitStyleCount).toBe(1);
});
});
test.describe('Edge cases', () => {
@ -970,6 +1133,323 @@ test.describe('Tooltip API', () => {
// Should still be hidden (no errors)
await waitForTooltipToHide(page);
});
test('should hide tooltip when window scrolls', async ({ page }) => {
const testElement = await page.evaluate(({ holderId }) => {
const container = document.getElementById(holderId);
const element = document.createElement('button');
element.textContent = 'Scroll target';
element.id = 'scroll-target';
element.style.marginTop = '800px';
container?.appendChild(element);
return {
id: element.id,
};
}, { holderId: HOLDER_ID });
await page.evaluate(() => {
document.body.style.height = '2000px';
});
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Scroll tooltip', {
delay: 0,
});
}
}, { elementId: testElement.id });
const tooltipBeforeScroll = await waitForTooltip(page);
await expect(tooltipBeforeScroll).toBeVisible();
await page.evaluate(() => {
window.scrollTo(0, 100);
window.dispatchEvent(new Event('scroll'));
});
await waitForTooltipToHide(page);
await page.evaluate(() => {
document.body.style.height = '';
window.scrollTo(0, 0);
});
});
test('should sync aria-hidden and visibility with tooltip state', async ({ page }) => {
const testElement = await page.evaluate(({ holderId }) => {
const container = document.getElementById(holderId);
const element = document.createElement('button');
element.textContent = 'Accessibility target';
element.id = 'accessibility-target';
container?.appendChild(element);
return {
id: element.id,
};
}, { holderId: HOLDER_ID });
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Accessible tooltip', {
delay: 0,
});
}
}, { elementId: testElement.id });
const tooltip = await waitForTooltip(page);
await expect(tooltip).toHaveAttribute('role', 'tooltip');
await expect(tooltip).toHaveAttribute('aria-hidden', 'false');
const shownState = await page.evaluate((selector) => {
const tooltipElement = document.querySelector(selector) as HTMLElement | null;
if (!tooltipElement) {
return null;
}
return {
ariaHidden: tooltipElement.getAttribute('aria-hidden'),
visibility: tooltipElement.style.visibility,
};
}, TOOLTIP_INTERFACE_SELECTOR);
expect(shownState).not.toBeNull();
expect(shownState?.ariaHidden).toBe('false');
expect(shownState?.visibility).toBe('visible');
await page.evaluate(() => {
const editor = window.editorInstance;
if (editor?.tooltip) {
editor.tooltip.hide();
}
});
await waitForTooltipToHide(page);
const hiddenState = await page.evaluate((selector) => {
const tooltipElement = document.querySelector(selector) as HTMLElement | null;
if (!tooltipElement) {
return null;
}
return {
ariaHidden: tooltipElement.getAttribute('aria-hidden'),
visibility: tooltipElement.style.visibility,
};
}, TOOLTIP_INTERFACE_SELECTOR);
expect(hiddenState).not.toBeNull();
expect(hiddenState?.ariaHidden).toBe('true');
expect(hiddenState?.visibility).toBe('hidden');
});
test('should update aria-hidden when tooltip class changes', async ({ page }) => {
const testElement = await page.evaluate(({ holderId }) => {
const container = document.getElementById(holderId);
const element = document.createElement('span');
element.textContent = 'Mutation target';
element.id = 'mutation-target';
container?.appendChild(element);
return {
id: element.id,
};
}, { holderId: HOLDER_ID });
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Mutation tooltip', {
delay: 0,
});
}
}, { elementId: testElement.id });
await waitForTooltip(page);
await page.evaluate((selector) => {
const tooltipElement = document.querySelector(selector) as HTMLElement | null;
tooltipElement?.classList.remove('ct--shown');
}, TOOLTIP_INTERFACE_SELECTOR);
await page.waitForFunction((selector) => {
const tooltipElement = document.querySelector(selector);
return tooltipElement?.getAttribute('aria-hidden') === 'true';
}, TOOLTIP_INTERFACE_SELECTOR);
const state = await page.evaluate((selector) => {
const tooltipElement = document.querySelector(selector) as HTMLElement | null;
if (!tooltipElement) {
return null;
}
return {
ariaHidden: tooltipElement.getAttribute('aria-hidden'),
visibility: tooltipElement.style.visibility,
};
}, TOOLTIP_INTERFACE_SELECTOR);
expect(state).not.toBeNull();
expect(state?.ariaHidden).toBe('true');
expect(state?.visibility).toBe('hidden');
});
test('should apply placement offsets for left and right positions', async ({ page }) => {
const testElement = await page.evaluate(({ holderId }) => {
const container = document.getElementById(holderId);
const element = document.createElement('div');
element.textContent = 'Placement target';
element.id = 'placement-target';
element.style.margin = '200px';
element.style.display = 'inline-block';
element.style.padding = '16px';
container?.appendChild(element);
return {
id: element.id,
};
}, { holderId: HOLDER_ID });
// Left placement without margin
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Left placement', {
placement: 'left',
delay: 0,
});
}
}, { elementId: testElement.id });
await waitForTooltip(page);
const leftPlacement = await page.evaluate((selector) => {
const tooltipElement = document.querySelector(selector) as HTMLElement | null;
if (!tooltipElement) {
return null;
}
return {
left: parseFloat(tooltipElement.style.left),
classes: Array.from(tooltipElement.classList),
};
}, TOOLTIP_INTERFACE_SELECTOR);
expect(leftPlacement).not.toBeNull();
expect(leftPlacement?.classes).toContain('ct--left');
// Left placement with margin
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Left margin placement', {
placement: 'left',
delay: 0,
marginLeft: 40,
});
}
}, { elementId: testElement.id });
await waitForTooltip(page);
const leftPlacementWithMargin = await page.evaluate((selector) => {
const tooltipElement = document.querySelector(selector) as HTMLElement | null;
if (!tooltipElement) {
return null;
}
return parseFloat(tooltipElement.style.left);
}, TOOLTIP_INTERFACE_SELECTOR);
expect(leftPlacementWithMargin).not.toBeNull();
expect(leftPlacementWithMargin as number).toBeLessThan(leftPlacement?.left ?? Number.POSITIVE_INFINITY);
// Right placement without margin
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Right placement', {
placement: 'right',
delay: 0,
});
}
}, { elementId: testElement.id });
await waitForTooltip(page);
const rightPlacement = await page.evaluate((selector) => {
const tooltipElement = document.querySelector(selector) as HTMLElement | null;
if (!tooltipElement) {
return null;
}
return {
left: parseFloat(tooltipElement.style.left),
classes: Array.from(tooltipElement.classList),
};
}, TOOLTIP_INTERFACE_SELECTOR);
expect(rightPlacement).not.toBeNull();
expect(rightPlacement?.classes).toContain('ct--right');
// Right placement with margin
await page.evaluate(({ elementId }) => {
const editor = window.editorInstance;
const element = document.getElementById(elementId);
if (element && editor?.tooltip) {
editor.tooltip.show(element, 'Right margin placement', {
placement: 'right',
delay: 0,
marginRight: 40,
});
}
}, { elementId: testElement.id });
await waitForTooltip(page);
const rightPlacementWithMargin = await page.evaluate((selector) => {
const tooltipElement = document.querySelector(selector) as HTMLElement | null;
if (!tooltipElement) {
return null;
}
return parseFloat(tooltipElement.style.left);
}, TOOLTIP_INTERFACE_SELECTOR);
expect(rightPlacementWithMargin).not.toBeNull();
expect(rightPlacementWithMargin as number).toBeGreaterThan(rightPlacement?.left ?? Number.NEGATIVE_INFINITY);
});
});
});