"""Exercise Manager's search overrides with the shipped TurboGrid in Chromium. Browser cases require Playwright and its Chromium install. All requests are intercepted locally; no ComfyUI server or external services are used. """ import os from pathlib import Path import pytest JS = Path(os.environ.get("MANAGER_JS_DIR") or (Path(__file__).resolve().parent.parent / "comfyui_manager" / "js")) @pytest.mark.parametrize("filename", ["custom-nodes-manager.js", "model-manager.js"]) def test_searching_managers_use_the_safe_grid(filename): source = (JS / filename).read_text() assert 'import ManagerGrid from "./manager-grid.js"' in source assert "new ManagerGrid(container)" in source assert 'from "./turbogrid.esm.js"' not in source @pytest.fixture(scope="module") def browser(): playwright = pytest.importorskip("playwright.sync_api") with playwright.sync_playwright() as driver: if not Path(driver.chromium.executable_path).exists(): pytest.skip("install Chromium with: playwright install chromium") instance = driver.chromium.launch() yield instance instance.close() @pytest.fixture def page(browser): page = browser.new_page() def route(request): name = request.request.url.rsplit("/", 1)[-1] if name in {"manager-grid.js", "turbogrid.esm.js"}: request.fulfill(path=str(JS / name), content_type="text/javascript") elif request.request.url == "http://manager.test/": request.fulfill( body='
', content_type="text/html", ) else: request.abort() page.route("**/*", route) page.goto("http://manager.test/") page.evaluate("""async () => { const {default: ManagerGrid} = await import('/manager-grid.js'); window.grid = new ManagerGrid(document.getElementById('grid')); grid.setData({columns: [{id: 'name', name: 'Name'}], rows: []}); grid.render(); }""") page.wait_for_function("window.grid.options !== undefined") yield page page.close() @pytest.mark.parametrize("keyword", ["before", "img", "after"]) def test_highlighting_keeps_all_text_inert(page, keyword): text = 'before
& after'
result = page.evaluate("""({text, keyword}) => {
const cell = document.createElement('div');
cell.id = 'cell';
cell.textContent = text;
document.body.append(cell);
grid.highlightKeywordsSync([cell], [keyword]);
return {text: cell.textContent, marks: [...cell.querySelectorAll('mark')].map(x => x.textContent),
images: cell.querySelectorAll('img').length};
}""", {"text": text, "keyword": keyword})
assert result == {"text": text, "marks": [keyword], "images": 0}
assert page.evaluate("window.__probe === undefined")
def test_keyword_order_continues_across_text_nodes(page):
result = page.evaluate("""() => {
const cell = document.createElement('div');
cell.innerHTML = 'Alpha beta Alpha beta ';
grid.highlightKeywordsSync([cell], ['alpha', 'beta']);
return [...cell.querySelectorAll('mark')].map(x => x.textContent);
}""")
assert result == ["Alpha", "beta", "Alpha", "beta"]
def test_search_cache_does_not_execute_html(page):
result = page.evaluate("""() => {
const row = {description: '
Alpha & Beta'};
const matched = grid.highlightKeywordsFilter(row, ['description'], 'alpha & beta');
return {matched, text: row.tg_text_description, highlight: row.tg_highlight_description};
}""")
assert result == {"matched": True, "text": "Alpha & Beta", "highlight": True}
page.wait_for_timeout(50)
assert page.evaluate("window.__probe === undefined")
def test_search_preserves_order_columns_and_empty_query(page):
result = page.evaluate("""() => {
const row = {name: 'Alpha Beta', description: 'Gamma', missing: null, count: 0};
const columns = ['name', 'description', 'missing', 'count'];
const ordered = grid.highlightKeywordsFilter(row, columns, ' ALPHA beta ');
const reversed = grid.highlightKeywordsFilter(row, columns, 'beta alpha');
const acrossColumns = grid.highlightKeywordsFilter(row, columns, 'alpha gamma');
const zero = grid.highlightKeywordsFilter(row, columns, '0');
const empty = grid.highlightKeywordsFilter(row, columns, '');
return {ordered, reversed, acrossColumns, zero, empty,
flags: columns.map(column => row['tg_highlight_' + column])};
}""")
assert result == {"ordered": True, "reversed": False, "acrossColumns": False,
"zero": True, "empty": True, "flags": [None] * 4}
def test_custom_text_generator_and_cache_keys(page):
result = page.evaluate("""() => {
Object.assign(grid.options.highlightKeywords, {
textKey: 'text_', highlightKey: 'mark_',
textGenerator: (row, column) => row[column].label
});
const row = {name: {label: 'Model <Flux>'}};
const matched = grid.highlightKeywordsFilter(row, ['name'], 'model