Raiju/tests/dropdown-check.js
2026-10-25 06:05:42 +03:00

92 lines
5.1 KiB
JavaScript

// Every custom dropdown (rj-select) of the dashboard: opened at several window sizes, no option may be squeezed or clipped,
// long lists scroll, and the list stays inside the window. Guards against the flex-shrink bug that cut the text of long lists.
const { chromium } = require('playwright');
const base = process.env.REVIEW_URL;
const password = process.env.REVIEW_PASSWORD;
const errors = [];
let opened = 0;
const seen = new Set();
function assert(condition, message) {
if (!condition) throw new Error(message);
}
async function inspectOpenList(page, label) {
return page.evaluate(() => {
const list = Array.from(document.querySelectorAll('.rj-select__list')).find(el => !el.hidden);
if (!list) return { error: 'the list did not open' };
const rect = list.getBoundingClientRect();
const options = Array.from(list.querySelectorAll('.rj-select__option, .rj-select__group'));
const squeezed = options.filter(el => {
const style = getComputedStyle(el);
const needed = parseFloat(style.lineHeight === 'normal' ? parseFloat(style.fontSize) * 1.2 : style.lineHeight) + parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
return el.getBoundingClientRect().height + 0.5 < needed || el.scrollHeight > el.clientHeight + 1;
}).map(el => el.textContent.trim() + ' [h=' + el.getBoundingClientRect().height + ' ch=' + el.clientHeight + ' sh=' + el.scrollHeight + ' lh=' + getComputedStyle(el).lineHeight + ']');
return {
count: options.length, squeezed,
scrolls: list.scrollHeight > list.clientHeight + 1,
fits: list.scrollHeight <= list.clientHeight + 1,
inside: rect.top >= -1 && rect.bottom <= window.innerHeight + 1 && rect.left >= -1 && rect.right <= window.innerWidth + 1,
height: Math.round(rect.height)
};
});
}
async function checkVisibleDropdowns(page, where) {
const buttons = await page.locator('.rj-select__button').all();
for (const button of buttons) {
if (!(await button.isVisible())) continue;
const id = (await button.getAttribute('data-rj-select-for')) || '(no id)';
const key = where + ' / ' + id;
if (seen.has(key)) continue;
seen.add(key);
await button.scrollIntoViewIfNeeded();
await button.click();
const result = await inspectOpenList(page, key);
assert(!result.error, key + ': ' + result.error);
assert(result.squeezed.length === 0, key + ': options are squeezed or clipped: ' + result.squeezed.join(', '));
assert(result.fits || result.scrolls, key + ': the list neither fits nor scrolls');
assert(result.inside, key + ': the list leaves the window (' + result.height + 'px high)');
opened++;
await page.keyboard.press('Escape');
await page.waitForTimeout(60);
if (await page.locator('.rj-select__list:not([hidden])').count()) await page.mouse.click(2, 2);
}
}
async function walk(page, sizeName) {
const topTabs = await page.evaluate(() => Array.from(document.querySelectorAll('.sidebar-nav-item[data-tab]')).map(el => el.getAttribute('data-tab')));
for (const tab of topTabs) {
await page.evaluate(name => switchTab(name), tab);
await page.waitForTimeout(150);
if (tab !== 'settings') { await checkVisibleDropdowns(page, sizeName + ' ' + tab); continue; }
const settingsTabs = await page.evaluate(() => Array.from(document.querySelectorAll('.settings-tab-btn[data-settings-tab]')).map(el => el.getAttribute('data-settings-tab')));
for (const settingsTab of Array.from(new Set(settingsTabs))) {
await page.evaluate(name => { const button = document.querySelector('.settings-tab-btn[data-settings-tab="' + name + '"]'); if (button) button.click(); }, settingsTab);
await page.waitForTimeout(200);
await checkVisibleDropdowns(page, sizeName + ' settings/' + settingsTab);
}
}
}
(async () => {
const browser = await chromium.launch();
for (const [name, viewport] of [['desktop', { width: 1280, height: 900 }], ['short', { width: 1280, height: 520 }], ['tablet', { width: 820, height: 700 }], ['phone', { width: 390, height: 640 }]]) {
const context = await browser.newContext({ viewport });
const page = await context.newPage();
page.on('pageerror', error => errors.push(name + ': ' + error.message));
await page.goto(base + '/admin/index.php');
await page.locator('#username').fill('admin');
await page.locator('#password').fill(password);
await Promise.all([page.waitForURL('**/admin/dashboard.php'), page.locator('form').first().locator('button[type=submit]').click()]);
await page.waitForTimeout(400);
await walk(page, name);
await context.close();
}
await browser.close();
assert(opened > 20, 'only ' + opened + ' dropdowns were checked');
if (errors.length) throw new Error(errors.join('\n'));
console.log('dropdown-check ok: ' + opened + ' dropdowns opened and inspected');
})().catch(error => { console.error(error.message); process.exit(1); });