HasThemeElement.java

package org.vaadin.addons.dramafinder.element.shared;

import java.util.regex.Pattern;

import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;

/**
 * Mixin for components that support the {@code theme} attribute.
 * <p>
 * Vaadin combines every applied theme variant into a single space-separated
 * {@code theme} attribute, in an order that is neither stable nor the order the
 * variants were added. {@link #assertTheme(String)} therefore compares the
 * variants as an unordered set: it still requires the attribute to hold exactly
 * the variants given, but {@code "small primary"} and {@code "primary small"}
 * are interchangeable. Use {@link #assertHasThemeVariant(String)} to assert a
 * single variant regardless of the others.
 */
public interface HasThemeElement extends HasLocatorElement {

    /** Get the current {@code theme} attribute value. */
    default String getTheme() {
        return getLocator().getAttribute("theme");
    }

    /**
     * Assert that the {@code theme} attribute holds exactly the given variants,
     * in any order, or is absent when {@code null}.
     *
     * @param theme space-separated theme variants, or {@code null} to assert
     *              that no {@code theme} attribute is present
     */
    default void assertTheme(String theme) {
        if (theme != null) {
            assertThat(getLocator()).hasAttribute("theme", themeSetPattern(theme));
        } else {
            assertThat(getLocator()).not().hasAttribute("theme", Pattern.compile(".*"));
        }
    }

    /**
     * Assert that the {@code theme} attribute contains the given variant,
     * ignoring any other variants that are also applied.
     *
     * @param variant the theme variant name, e.g. {@code success}
     */
    default void assertHasThemeVariant(String variant) {
        assertThat(getLocator()).hasAttribute("theme", themeVariantPattern(variant));
    }

    /**
     * Assert that the {@code theme} attribute does not contain the given
     * variant. Also passes when the attribute is absent altogether.
     *
     * @param variant the theme variant name, e.g. {@code success}
     */
    default void assertHasNoThemeVariant(String variant) {
        assertThat(getLocator()).not().hasAttribute("theme", themeVariantPattern(variant));
    }

    /**
     * Build a pattern matching an attribute value that holds exactly the given
     * variants, in any order: one lookahead per variant, plus a token count so
     * that extra variants still fail the assertion.
     */
    private static Pattern themeSetPattern(String theme) {
        String trimmed = theme.trim();
        if (trimmed.isEmpty()) {
            return Pattern.compile("^\\s*$");
        }
        String[] variants = trimmed.split("\\s+");
        StringBuilder pattern = new StringBuilder("^");
        for (String variant : variants) {
            pattern.append("(?=(?:.*\\s)?").append(escape(variant)).append("(?:\\s.*)?$)");
        }
        return Pattern.compile(pattern.append("(?:\\s*\\S+){").append(variants.length).append("}\\s*$").toString());
    }

    /**
     * Build a pattern matching {@code variant} as a whole token inside a
     * space-separated attribute value.
     */
    private static Pattern themeVariantPattern(String variant) {
        return Pattern.compile("(^|\\s)" + escape(variant) + "($|\\s)");
    }

    /**
     * Escape regex metacharacters by hand, because Playwright compiles the
     * pattern into a JavaScript {@code RegExp}, which does not understand
     * {@link Pattern#quote(String)}'s {@code \Q...\E}.
     */
    private static String escape(String literal) {
        return literal.replaceAll("[\\\\^$.|?*+()\\[\\]{}]", "\\\\$0");
    }

}