/*
 * Copyright (c) 2025 Mozilla Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package nu.validator.datatype.test;

import nu.validator.datatype.CustomElementName;
import nu.validator.datatype.Date;
import nu.validator.datatype.DateOrTime;
import nu.validator.datatype.Datetime;
import nu.validator.datatype.DatetimeLocal;
import nu.validator.datatype.DatetimeTz;
import nu.validator.datatype.FloatingPointExponent;
import nu.validator.datatype.HashName;
import nu.validator.datatype.Html5DatatypeLibrary;
import nu.validator.datatype.Id;
import nu.validator.datatype.Idref;
import nu.validator.datatype.Idrefs;
import nu.validator.datatype.IntNonNegative;
import nu.validator.datatype.IntPositive;
import nu.validator.datatype.Language;
import nu.validator.datatype.MetaCharset;
import nu.validator.datatype.MimeType;
import nu.validator.datatype.Month;
import nu.validator.datatype.NonEmptyString;
import nu.validator.datatype.SimpleColor;
import nu.validator.datatype.Time;
import nu.validator.datatype.TimeDatetime;
import nu.validator.datatype.Week;
import nu.validator.datatype.Zero;
import nu.validator.vendor.relaxng.datatype.Datatype;
import nu.validator.vendor.relaxng.datatype.DatatypeException;

/**
 * Unit tests for HTML5 datatype validators.
 *
 * Tests validation logic for various datatype classes including SimpleColor,
 * NonEmptyString, and other validators.
 */
public class DatatypeTest {

    private static int passed = 0;
    private static int failed = 0;

    public static void main(String[] args) {
        System.out.println("Testing SimpleColor...");
        testSimpleColorValid();
        testSimpleColorInvalid();

        System.out.println();
        System.out.println("Testing NonEmptyString...");
        testNonEmptyStringValid();
        testNonEmptyStringInvalid();

        System.out.println();
        System.out.println("Testing Time...");
        testTimeValid();
        testTimeInvalid();

        System.out.println();
        System.out.println("Testing Id...");
        testIdValid();
        testIdInvalid();

        System.out.println();
        System.out.println("Testing HashName...");
        testHashNameValid();
        testHashNameInvalid();

        System.out.println();
        System.out.println("Testing CustomElementName...");
        testCustomElementNameValid();
        testCustomElementNameInvalid();

        System.out.println();
        System.out.println("Testing MetaCharset...");
        testMetaCharsetValid();
        testMetaCharsetInvalid();
        testMetaCharsetLooping();

        System.out.println();
        System.out.println("Testing Date...");
        testDateValid();
        testDateInvalid();

        System.out.println();
        System.out.println("Testing Month...");
        testMonthValid();
        testMonthInvalid();

        System.out.println();
        System.out.println("Testing Week...");
        testWeekValid();
        testWeekInvalid();

        System.out.println();
        System.out.println("Testing MimeType...");
        testMimeTypeValid();
        testMimeTypeInvalid();

        System.out.println();
        System.out.println("Testing IriRef (URL)...");
        testIriRefValid();
        testIriRefInvalid();

        System.out.println();
        System.out.println("Testing Datetime (UTC)...");
        testDatetimeValid();
        testDatetimeInvalid();

        System.out.println();
        System.out.println("Testing DatetimeLocal...");
        testDatetimeLocalValid();
        testDatetimeLocalInvalid();

        System.out.println();
        System.out.println("Testing DatetimeTz...");
        testDatetimeTzValid();
        testDatetimeTzInvalid();

        System.out.println();
        System.out.println("Testing DateOrTime...");
        testDateOrTimeValid();
        testDateOrTimeInvalid();

        System.out.println();
        System.out.println("Testing TimeDatetime...");
        testTimeDatetimeValid();
        testTimeDatetimeInvalid();

        System.out.println();
        System.out.println("Testing IntNonNegative...");
        testIntNonNegativeValid();
        testIntNonNegativeInvalid();

        System.out.println();
        System.out.println("Testing IntPositive...");
        testIntPositiveValid();
        testIntPositiveInvalid();

        System.out.println();
        System.out.println("Testing Zero...");
        testZeroValid();
        testZeroInvalid();

        System.out.println();
        System.out.println("Testing FloatingPointExponent...");
        testFloatingPointExponentValid();
        testFloatingPointExponentInvalid();

        System.out.println();
        System.out.println("Testing Idref...");
        testIdrefValid();
        testIdrefInvalid();

        System.out.println();
        System.out.println("Testing Idrefs...");
        testIdrefsValid();
        testIdrefsInvalid();

        System.out.println();
        System.out.println("Testing Language...");
        testLanguageValid();
        testLanguageInvalid();

        System.out.println();
        System.out.println("Results: " + passed + " passed, " + failed + " failed");
        if (failed > 0) {
            System.exit(1);
        }
    }

    // SimpleColor tests

    private static void testSimpleColorValid() {
        SimpleColor validator = SimpleColor.THE_INSTANCE;

        assertValid("SimpleColor: lowercase hex", validator, "#abcdef");
        assertValid("SimpleColor: uppercase hex", validator, "#ABCDEF");
        assertValid("SimpleColor: mixed case hex", validator, "#AbCdEf");
        assertValid("SimpleColor: all digits", validator, "#123456");
        assertValid("SimpleColor: black", validator, "#000000");
        assertValid("SimpleColor: white", validator, "#ffffff");
        assertValid("SimpleColor: red", validator, "#ff0000");
        assertValid("SimpleColor: green", validator, "#00ff00");
        assertValid("SimpleColor: blue", validator, "#0000ff");
    }

    private static void testSimpleColorInvalid() {
        SimpleColor validator = SimpleColor.THE_INSTANCE;

        assertInvalid("SimpleColor: empty string", validator, "");
        assertInvalid("SimpleColor: missing hash", validator, "abcdef");
        assertInvalid("SimpleColor: too short (3 digits)", validator, "#fff");
        assertInvalid("SimpleColor: too short (5 digits)", validator, "#12345");
        assertInvalid("SimpleColor: too long (7 digits)", validator, "#1234567");
        assertInvalid("SimpleColor: too long (8 digits)", validator, "#12345678");
        assertInvalid("SimpleColor: invalid hex digit 'g'", validator, "#gggggg");
        assertInvalid("SimpleColor: invalid hex digit 'z'", validator, "#12345z");
        assertInvalid("SimpleColor: spaces not allowed", validator, "# abcde");
        assertInvalid("SimpleColor: leading space", validator, " #abcdef");
        assertInvalid("SimpleColor: trailing space", validator, "#abcdef ");
        assertInvalid("SimpleColor: wrong start char", validator, "@abcdef");
        assertInvalid("SimpleColor: named color not allowed", validator, "red");
        assertInvalid("SimpleColor: rgb() not allowed", validator, "rgb(255,0,0)");
    }

    // NonEmptyString tests

    private static void testNonEmptyStringValid() {
        NonEmptyString validator = NonEmptyString.THE_INSTANCE;

        assertValid("NonEmptyString: single char", validator, "a");
        assertValid("NonEmptyString: word", validator, "hello");
        assertValid("NonEmptyString: sentence", validator, "Hello, World!");
        assertValid("NonEmptyString: whitespace only", validator, " ");
        assertValid("NonEmptyString: tab only", validator, "\t");
        assertValid("NonEmptyString: newline only", validator, "\n");
        assertValid("NonEmptyString: multiple spaces", validator, "   ");
        assertValid("NonEmptyString: unicode", validator, "\u00e9");
        assertValid("NonEmptyString: emoji", validator, "\uD83D\uDE00");
        assertValid("NonEmptyString: very long string", validator,
                "a".repeat(10000));
    }

    private static void testNonEmptyStringInvalid() {
        NonEmptyString validator = NonEmptyString.THE_INSTANCE;

        assertInvalid("NonEmptyString: empty string", validator, "");
    }

    // Time tests

    private static void testTimeValid() {
        Time validator = Time.THE_INSTANCE;

        // Basic valid times (HH:MM)
        assertValid("Time: midnight", validator, "00:00");
        assertValid("Time: noon", validator, "12:00");
        assertValid("Time: end of day", validator, "23:59");
        assertValid("Time: early morning", validator, "06:30");
        assertValid("Time: evening", validator, "18:45");

        // With seconds (HH:MM:SS)
        assertValid("Time: with seconds", validator, "12:30:45");
        assertValid("Time: midnight with seconds", validator, "00:00:00");
        assertValid("Time: max seconds", validator, "23:59:59");

        // With milliseconds (HH:MM:SS.mmm)
        assertValid("Time: with 1 decimal", validator, "12:30:45.1");
        assertValid("Time: with 2 decimals", validator, "12:30:45.12");
        assertValid("Time: with 3 decimals", validator, "12:30:45.123");
    }

    private static void testTimeInvalid() {
        Time validator = Time.THE_INSTANCE;

        assertInvalid("Time: empty string", validator, "");
        assertInvalid("Time: invalid hour 24", validator, "24:00");
        assertInvalid("Time: invalid hour 25", validator, "25:00");
        assertInvalid("Time: invalid minute 60", validator, "12:60");
        assertInvalid("Time: invalid second 60", validator, "12:30:60");
        assertInvalid("Time: single digit hour", validator, "1:00");
        assertInvalid("Time: single digit minute", validator, "12:0");
        assertInvalid("Time: missing colon", validator, "1200");
        assertInvalid("Time: extra colon", validator, "12:00:");
        assertInvalid("Time: AM/PM not allowed", validator, "12:00 PM");
        assertInvalid("Time: text not allowed", validator, "noon");
        assertInvalid("Time: negative hour", validator, "-01:00");
    }

    // Id tests

    private static void testIdValid() {
        Id validator = Id.THE_INSTANCE;

        assertValid("Id: single char", validator, "a");
        assertValid("Id: simple id", validator, "myId");
        assertValid("Id: with hyphen", validator, "my-id");
        assertValid("Id: with underscore", validator, "my_id");
        assertValid("Id: with numbers", validator, "id123");
        assertValid("Id: starting with number", validator, "123");
        assertValid("Id: special chars", validator, "id!@#$%");
        assertValid("Id: unicode", validator, "\u00e9l\u00e8ve");
        assertValid("Id: emoji", validator, "\uD83D\uDE00");
        assertValid("Id: long id", validator, "a".repeat(1000));
    }

    private static void testIdInvalid() {
        Id validator = Id.THE_INSTANCE;

        assertInvalid("Id: empty string", validator, "");
        assertInvalid("Id: single space", validator, " ");
        assertInvalid("Id: leading space", validator, " id");
        assertInvalid("Id: trailing space", validator, "id ");
        assertInvalid("Id: space in middle", validator, "my id");
        assertInvalid("Id: tab", validator, "my\tid");
        assertInvalid("Id: newline", validator, "my\nid");
        assertInvalid("Id: carriage return", validator, "my\rid");
    }

    // HashName tests

    private static void testHashNameValid() {
        HashName validator = HashName.THE_INSTANCE;

        assertValid("HashName: simple", validator, "#foo");
        assertValid("HashName: single char after hash", validator, "#a");
        assertValid("HashName: with hyphen", validator, "#my-name");
        assertValid("HashName: with numbers", validator, "#id123");
        assertValid("HashName: only numbers", validator, "#123");
        assertValid("HashName: unicode", validator, "#caf\u00e9");
        assertValid("HashName: with spaces after hash", validator, "# space");
    }

    private static void testHashNameInvalid() {
        HashName validator = HashName.THE_INSTANCE;

        assertInvalid("HashName: empty string", validator, "");
        assertInvalid("HashName: just hash", validator, "#");
        assertInvalid("HashName: no hash", validator, "foo");
        assertInvalid("HashName: wrong prefix", validator, "@foo");
    }

    // CustomElementName tests

    private static void testCustomElementNameValid() {
        CustomElementName validator = CustomElementName.THE_INSTANCE;

        assertValid("CustomElementName: simple", validator, "my-element");
        assertValid("CustomElementName: with numbers", validator, "my-element-123");
        assertValid("CustomElementName: multiple hyphens", validator, "my-custom-element");
        assertValid("CustomElementName: hyphen at end", validator, "element-");
        assertValid("CustomElementName: underscore", validator, "my_element-test");
        assertValid("CustomElementName: dot", validator, "my.element-test");
        assertValid("CustomElementName: unicode", validator, "my-\u00e9l\u00e8ment");
        assertValid("CustomElementName: single letter then hyphen", validator, "x-foo");
        assertValid("CustomElementName: with colon", validator, "andy-custom:menu");
        assertValid("CustomElementName: colon after hyphen", validator, "my-element:sub");
        assertValid("CustomElementName: with tilde", validator, "my-element~test");
        assertValid("CustomElementName: with hash", validator, "my-#element");
        assertValid("CustomElementName: with at sign", validator, "my-elem@ent");
        assertValid("CustomElementName: with exclamation", validator, "my-element!");
    }

    private static void testCustomElementNameInvalid() {
        CustomElementName validator = CustomElementName.THE_INSTANCE;

        assertInvalid("CustomElementName: empty string", validator, "");
        assertInvalid("CustomElementName: no hyphen", validator, "myelement");
        assertInvalid("CustomElementName: starts with number", validator, "1-element");
        assertInvalid("CustomElementName: starts with hyphen", validator, "-element");
        assertInvalid("CustomElementName: starts with uppercase", validator, "My-element");
        assertInvalid("CustomElementName: contains uppercase", validator, "my-Element");
        assertInvalid("CustomElementName: prohibited annotation-xml", validator, "annotation-xml");
        assertInvalid("CustomElementName: prohibited color-profile", validator, "color-profile");
        assertInvalid("CustomElementName: prohibited font-face", validator, "font-face");
        assertInvalid("CustomElementName: prohibited font-face-format", validator, "font-face-format");
        assertInvalid("CustomElementName: prohibited font-face-name", validator, "font-face-name");
        assertInvalid("CustomElementName: prohibited font-face-src", validator, "font-face-src");
        assertInvalid("CustomElementName: prohibited font-face-uri", validator, "font-face-uri");
        assertInvalid("CustomElementName: prohibited missing-glyph", validator, "missing-glyph");
    }

    // MetaCharset tests

    private static void testMetaCharsetValid() {
        MetaCharset validator = MetaCharset.THE_INSTANCE;

        assertValid("MetaCharset: simple valid",
                validator, "text/html; charset=utf-8");
        assertValid("MetaCharset: no space after semicolon",
                validator, "text/html;charset=utf-8");
        assertValid("MetaCharset: multiple spaces",
                validator, "text/html;   charset=utf-8");
        assertValid("MetaCharset: tab after semicolon",
                validator, "text/html;\tcharset=utf-8");
        assertValid("MetaCharset: uppercase CHARSET",
                validator, "text/html; CHARSET=utf-8");
        assertValid("MetaCharset: mixed case",
                validator, "text/html; ChArSeT=utf-8");
    }

    private static void testMetaCharsetInvalid() {
        MetaCharset validator = MetaCharset.THE_INSTANCE;

        assertInvalid("MetaCharset: empty string",
                validator, "");
        assertInvalid("MetaCharset: missing text/html",
                validator, "charset=utf-8");
        assertInvalid("MetaCharset: wrong content type",
                validator, "text/plain; charset=utf-8");
        assertInvalid("MetaCharset: missing charset",
                validator, "text/html;");
        assertInvalid("MetaCharset: wrong encoding",
                validator, "text/html; charset=iso-8859-1");
        assertInvalid("MetaCharset: empty encoding",
                validator, "text/html; charset=");
    }

    /**
     * Tests for issue #877: The algorithm should loop to find a valid
     * "charset=" pattern when the first occurrence is not followed by "=".
     * https://github.com/validator/validator/issues/877
     */
    private static void testMetaCharsetLooping() {
        MetaCharset validator = MetaCharset.THE_INSTANCE;

        // These are the test cases from issue #877
        // All should extract "utf-8" (we test with utf-8 since that's
        // the only valid encoding for HTML5)
        assertValid("MetaCharset: charset space charset=",
                validator, "text/html; charset charset=utf-8");
        assertValid("MetaCharset: charsetxxxxxcharset=",
                validator, "text/html; charsetxxxxxcharset=utf-8");
        assertValid("MetaCharset: charsetcharset=",
                validator, "text/html; charsetcharset=utf-8");

        // Additional edge cases
        assertValid("MetaCharset: charset without = then valid charset=",
                validator, "text/html; charsetfoo charset=utf-8");
        assertValid("MetaCharset: multiple invalid charsets then valid",
                validator, "text/html; charset; charset; charset=utf-8");
    }

    // Date tests

    private static void testDateValid() {
        Date validator = Date.THE_INSTANCE;

        assertValid("Date: basic date", validator, "2024-01-15");
        assertValid("Date: first day of year", validator, "2024-01-01");
        assertValid("Date: last day of year", validator, "2024-12-31");
        assertValid("Date: leap year Feb 29", validator, "2024-02-29");
        assertValid("Date: year 1", validator, "0001-01-01");
        assertValid("Date: 5-digit year", validator, "10000-06-15");
        assertValid("Date: end of February non-leap", validator, "2023-02-28");
        assertValid("Date: April 30", validator, "2024-04-30");
        assertValid("Date: June 30", validator, "2024-06-30");
    }

    private static void testDateInvalid() {
        Date validator = Date.THE_INSTANCE;

        assertInvalid("Date: empty string", validator, "");
        assertInvalid("Date: invalid month 00", validator, "2024-00-15");
        assertInvalid("Date: invalid month 13", validator, "2024-13-15");
        assertInvalid("Date: invalid day 00", validator, "2024-01-00");
        assertInvalid("Date: invalid day 32", validator, "2024-01-32");
        assertInvalid("Date: Feb 30", validator, "2024-02-30");
        assertInvalid("Date: Feb 29 non-leap", validator, "2023-02-29");
        assertInvalid("Date: April 31", validator, "2024-04-31");
        assertInvalid("Date: June 31", validator, "2024-06-31");
        assertInvalid("Date: year 0", validator, "0000-01-01");
        assertInvalid("Date: 2-digit year", validator, "24-01-15");
        assertInvalid("Date: 3-digit year", validator, "124-01-15");
        assertInvalid("Date: single digit month", validator, "2024-1-15");
        assertInvalid("Date: single digit day", validator, "2024-01-5");
        assertInvalid("Date: slashes instead of dashes", validator, "2024/01/15");
        assertInvalid("Date: no separators", validator, "20240115");
        assertInvalid("Date: with time", validator, "2024-01-15T12:00");
    }

    // Month tests

    private static void testMonthValid() {
        Month validator = Month.THE_INSTANCE;

        assertValid("Month: basic month", validator, "2024-01");
        assertValid("Month: first month", validator, "2024-01");
        assertValid("Month: last month", validator, "2024-12");
        assertValid("Month: year 1", validator, "0001-06");
        assertValid("Month: 5-digit year", validator, "10000-06");
    }

    private static void testMonthInvalid() {
        Month validator = Month.THE_INSTANCE;

        assertInvalid("Month: empty string", validator, "");
        assertInvalid("Month: invalid month 00", validator, "2024-00");
        assertInvalid("Month: invalid month 13", validator, "2024-13");
        assertInvalid("Month: year 0", validator, "0000-01");
        assertInvalid("Month: 2-digit year", validator, "24-01");
        assertInvalid("Month: single digit month", validator, "2024-1");
        assertInvalid("Month: with day", validator, "2024-01-15");
        assertInvalid("Month: no separator", validator, "202401");
    }

    // Week tests

    private static void testWeekValid() {
        Week validator = Week.THE_INSTANCE;

        assertValid("Week: basic week", validator, "2024-W01");
        assertValid("Week: first week", validator, "2024-W01");
        assertValid("Week: week 52", validator, "2024-W52");
        assertValid("Week: week 53 in year with 53 weeks", validator, "2004-W53");
        assertValid("Week: week 53 in 2020", validator, "2020-W53");
        assertValid("Week: year 1", validator, "0001-W01");
        assertValid("Week: 5-digit year", validator, "10000-W26");
    }

    private static void testWeekInvalid() {
        Week validator = Week.THE_INSTANCE;

        assertInvalid("Week: empty string", validator, "");
        assertInvalid("Week: invalid week 00", validator, "2024-W00");
        assertInvalid("Week: invalid week 54", validator, "2024-W54");
        assertInvalid("Week: week 53 in year without 53 weeks", validator, "2023-W53");
        assertInvalid("Week: year 0", validator, "0000-W01");
        assertInvalid("Week: missing W prefix", validator, "2024-01");
        assertInvalid("Week: lowercase w", validator, "2024-w01");
        assertInvalid("Week: single digit week", validator, "2024-W1");
    }

    // MimeType tests

    private static void testMimeTypeValid() {
        MimeType validator = MimeType.THE_INSTANCE;

        assertValid("MimeType: text/html", validator, "text/html");
        assertValid("MimeType: text/plain", validator, "text/plain");
        assertValid("MimeType: application/json", validator, "application/json");
        assertValid("MimeType: image/png", validator, "image/png");
        assertValid("MimeType: with charset param", validator, "text/html; charset=utf-8");
        assertValid("MimeType: with quoted param", validator, "text/html; charset=\"utf-8\"");
        assertValid("MimeType: multiple params", validator, "text/html; charset=utf-8; boundary=something");
        assertValid("MimeType: param with spaces", validator, "text/html ; charset=utf-8");
        assertValid("MimeType: vendor type", validator, "application/vnd.ms-excel");
        assertValid("MimeType: x- prefix", validator, "application/x-custom");
        assertValid("MimeType: with + suffix", validator, "application/atom+xml");
        assertValid("MimeType: multipart/form-data", validator, "multipart/form-data");
    }

    private static void testMimeTypeInvalid() {
        MimeType validator = MimeType.THE_INSTANCE;

        assertInvalid("MimeType: empty string", validator, "");
        assertInvalid("MimeType: no subtype", validator, "text");
        assertInvalid("MimeType: no subtype with slash", validator, "text/");
        assertInvalid("MimeType: no supertype", validator, "/html");
        assertInvalid("MimeType: just slash", validator, "/");
        assertInvalid("MimeType: trailing semicolon", validator, "text/html;");
        assertInvalid("MimeType: param without value", validator, "text/html; charset");
        assertInvalid("MimeType: param with = but no value", validator, "text/html; charset=");
        assertInvalid("MimeType: unclosed quote", validator, "text/html; charset=\"utf-8");
        assertInvalid("MimeType: trailing whitespace", validator, "text/html ");
    }

    // IriRef (URL) tests

    private static void testIriRefValid() {
        Html5DatatypeLibrary library = new Html5DatatypeLibrary();
        Datatype validator;
        try {
            validator = library.createDatatype("iri-ref");
        } catch (DatatypeException e) {
            System.out.println("FAIL: Could not create iri-ref datatype: " + e.getMessage());
            failed++;
            return;
        }

        // Absolute URLs
        assertValid("IriRef: http URL", validator, "http://example.com/");
        assertValid("IriRef: https URL", validator, "https://example.com/");
        assertValid("IriRef: http with port", validator, "http://example.com:8080/");
        assertValid("IriRef: http with path", validator, "http://example.com/path/to/file");
        assertValid("IriRef: http with query", validator, "http://example.com/?foo=bar");
        assertValid("IriRef: http with fragment", validator, "http://example.com/#section");
        assertInvalid("IriRef: http with credentials", validator, "http://user:pass@example.com:8080/path?q=1#frag");
        assertValid("IriRef: http with all parts", validator, "http://example.com:8080/path?q=1#frag");
        assertValid("IriRef: ftp URL", validator, "ftp://ftp.example.com/file");
        assertValid("IriRef: mailto URL", validator, "mailto:user@example.com");
        assertValid("IriRef: file URL", validator, "file:///path/to/file");

        // Relative URLs
        assertValid("IriRef: relative path", validator, "path/to/file");
        assertValid("IriRef: absolute path", validator, "/path/to/file");
        assertValid("IriRef: fragment only", validator, "#section");
        assertValid("IriRef: query only", validator, "?query=value");
        assertValid("IriRef: relative with query", validator, "file.html?q=1");
        assertValid("IriRef: parent directory", validator, "../file.html");
        assertValid("IriRef: current directory", validator, "./file.html");

        // Data URLs
        assertValid("IriRef: data URL text", validator, "data:text/plain,Hello");
        // Minimal valid base64 PNG (1x1 transparent pixel)
        assertValid("IriRef: data URL base64", validator,
                "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==");

        // Special schemes
        assertValid("IriRef: javascript (allowed)", validator, "javascript:void(0)");
        assertValid("IriRef: webcal (http alias)", validator, "webcal://example.com/cal");
        assertValid("IriRef: feed (http alias)", validator, "feed://example.com/rss");

        // Unicode in URLs
        assertValid("IriRef: unicode in path", validator, "http://example.com/caf%C3%A9");
        assertValid("IriRef: IDN domain", validator, "http://xn--nxasmq5b.com/");
    }

    private static void testIriRefInvalid() {
        Html5DatatypeLibrary library = new Html5DatatypeLibrary();
        Datatype validator;
        try {
            validator = library.createDatatype("iri-ref");
        } catch (DatatypeException e) {
            System.out.println("FAIL: Could not create iri-ref datatype: " + e.getMessage());
            failed++;
            return;
        }

        assertInvalid("IriRef: empty string", validator, "");
        assertInvalid("IriRef: whitespace only", validator, "   ");
        assertInvalid("IriRef: tabs only", validator, "\t\t");
    }

    // Datetime tests (UTC - requires trailing Z)

    private static void testDatetimeValid() {
        Datetime validator = Datetime.THE_INSTANCE;

        // Basic valid formats with Z
        assertValid("Datetime: basic with T and Z", validator, "2024-01-15T12:30Z");
        assertValid("Datetime: with seconds", validator, "2024-01-15T12:30:45Z");
        assertValid("Datetime: with milliseconds", validator, "2024-01-15T12:30:45.123Z");
        assertValid("Datetime: with 1 decimal", validator, "2024-01-15T12:30:45.1Z");
        assertValid("Datetime: with 2 decimals", validator, "2024-01-15T12:30:45.12Z");

        // Space separator instead of T
        assertValid("Datetime: space instead of T", validator, "2024-01-15 12:30Z");
        assertValid("Datetime: space with seconds", validator, "2024-01-15 12:30:45Z");

        // Edge times
        assertValid("Datetime: midnight", validator, "2024-01-15T00:00Z");
        assertValid("Datetime: end of day", validator, "2024-01-15T23:59Z");
        assertValid("Datetime: max seconds", validator, "2024-01-15T23:59:59Z");

        // Year boundaries
        assertValid("Datetime: year 1", validator, "0001-01-01T00:00Z");
        assertValid("Datetime: 5-digit year", validator, "10000-06-15T12:00Z");
        assertValid("Datetime: leap year Feb 29", validator, "2024-02-29T12:00Z");
    }

    private static void testDatetimeInvalid() {
        Datetime validator = Datetime.THE_INSTANCE;

        assertInvalid("Datetime: empty string", validator, "");
        assertInvalid("Datetime: missing Z", validator, "2024-01-15T12:30");
        assertInvalid("Datetime: lowercase z", validator, "2024-01-15T12:30z");
        assertInvalid("Datetime: with timezone offset", validator, "2024-01-15T12:30+05:00");
        assertInvalid("Datetime: missing time", validator, "2024-01-15Z");
        assertInvalid("Datetime: date only", validator, "2024-01-15");
        assertInvalid("Datetime: invalid month 13", validator, "2024-13-15T12:00Z");
        assertInvalid("Datetime: year 0", validator, "0000-01-15T12:00Z");
        // These now work after fixing regex group alignment:
        assertInvalid("Datetime: invalid hour 24", validator, "2024-01-15T24:00Z");
        assertInvalid("Datetime: invalid minute 60", validator, "2024-01-15T12:60Z");
        assertInvalid("Datetime: invalid second 60", validator, "2024-01-15T12:30:60Z");
        assertInvalid("Datetime: Feb 29 non-leap year", validator, "2023-02-29T12:00Z");
        assertInvalid("Datetime: invalid day 32", validator, "2024-01-32T12:00Z");
    }

    // DatetimeLocal tests (no timezone)

    private static void testDatetimeLocalValid() {
        DatetimeLocal validator = DatetimeLocal.THE_INSTANCE;

        // Basic valid formats without timezone
        assertValid("DatetimeLocal: basic with T", validator, "2024-01-15T12:30");
        assertValid("DatetimeLocal: with seconds", validator, "2024-01-15T12:30:45");
        assertValid("DatetimeLocal: with milliseconds", validator, "2024-01-15T12:30:45.123");
        assertValid("DatetimeLocal: with 1 decimal", validator, "2024-01-15T12:30:45.1");
        assertValid("DatetimeLocal: with 2 decimals", validator, "2024-01-15T12:30:45.12");

        // Space separator
        assertValid("DatetimeLocal: space instead of T", validator, "2024-01-15 12:30");
        assertValid("DatetimeLocal: space with seconds", validator, "2024-01-15 12:30:45");

        // Edge times
        assertValid("DatetimeLocal: midnight", validator, "2024-01-15T00:00");
        assertValid("DatetimeLocal: end of day", validator, "2024-01-15T23:59:59");

        // Year boundaries
        assertValid("DatetimeLocal: year 1", validator, "0001-01-01T00:00");
        assertValid("DatetimeLocal: 5-digit year", validator, "10000-06-15T12:00");
    }

    private static void testDatetimeLocalInvalid() {
        DatetimeLocal validator = DatetimeLocal.THE_INSTANCE;

        assertInvalid("DatetimeLocal: empty string", validator, "");
        assertInvalid("DatetimeLocal: with Z", validator, "2024-01-15T12:30Z");
        assertInvalid("DatetimeLocal: with timezone", validator, "2024-01-15T12:30+05:00");
        assertInvalid("DatetimeLocal: date only", validator, "2024-01-15");
        assertInvalid("DatetimeLocal: time only", validator, "12:30:45");
        assertInvalid("DatetimeLocal: year 0", validator, "0000-01-15T12:00");
        // These now work after fixing regex group alignment:
        assertInvalid("DatetimeLocal: invalid hour 24", validator, "2024-01-15T24:00");
        assertInvalid("DatetimeLocal: invalid minute 60", validator, "2024-01-15T12:60");
        assertInvalid("DatetimeLocal: invalid second 60", validator, "2024-01-15T12:30:60");
        assertInvalid("DatetimeLocal: Feb 29 non-leap", validator, "2023-02-29T12:00");
        assertInvalid("DatetimeLocal: invalid day 32", validator, "2024-01-32T12:00");
    }

    // DatetimeTz tests (with timezone offset - Z or ±HH:MM)

    private static void testDatetimeTzValid() {
        DatetimeTz validator = DatetimeTz.THE_INSTANCE;

        // With Z timezone
        assertValid("DatetimeTz: with Z", validator, "2024-01-15T12:30:00Z");
        assertValid("DatetimeTz: with Z and milliseconds", validator, "2024-01-15T12:30:45.123Z");
        assertValid("DatetimeTz: without seconds", validator, "2024-01-15T12:30Z");

        // With positive offset
        assertValid("DatetimeTz: +00:00", validator, "2024-01-15T12:30:00+00:00");
        assertValid("DatetimeTz: +05:30", validator, "2024-01-15T12:30:00+05:30");
        assertValid("DatetimeTz: +12:00", validator, "2024-01-15T12:30:00+12:00");
        assertValid("DatetimeTz: +14:00", validator, "2024-01-15T12:30:00+14:00");

        // With negative offset
        assertValid("DatetimeTz: -05:00", validator, "2024-01-15T12:30:00-05:00");
        assertValid("DatetimeTz: -12:00", validator, "2024-01-15T12:30:00-12:00");

        // Space separator
        assertValid("DatetimeTz: space instead of T", validator, "2024-01-15 12:30:00Z");
        assertValid("DatetimeTz: space with offset", validator, "2024-01-15 12:30:00+05:00");

        // Without colon in offset
        assertValid("DatetimeTz: offset without colon", validator, "2024-01-15T12:30:00+0530");
    }

    private static void testDatetimeTzInvalid() {
        DatetimeTz validator = DatetimeTz.THE_INSTANCE;

        assertInvalid("DatetimeTz: empty string", validator, "");
        assertInvalid("DatetimeTz: no timezone", validator, "2024-01-15T12:30:00");
        assertInvalid("DatetimeTz: date only", validator, "2024-01-15");
        assertInvalid("DatetimeTz: lowercase z", validator, "2024-01-15T12:30:00z");
        // -00:00 is specifically prohibited - must use +00:00
        assertInvalid("DatetimeTz: -00:00 not allowed", validator, "2024-01-15T12:30:00-00:00");
        assertInvalid("DatetimeTz: year 0", validator, "0000-01-15T12:00:00Z");
        // Note: Offsets like +13:00 are valid per the checker
    }

    // DateOrTime tests (flexible date and/or time)

    private static void testDateOrTimeValid() {
        DateOrTime validator = DateOrTime.THE_INSTANCE;

        // Date only
        assertValid("DateOrTime: date only", validator, "2024-01-15");
        assertValid("DateOrTime: leap year Feb 29", validator, "2024-02-29");
        assertValid("DateOrTime: year 1", validator, "0001-01-01");
        assertValid("DateOrTime: 5-digit year", validator, "10000-06-15");

        // Time only (now works after fixing regex groups)
        assertValid("DateOrTime: time HH:MM", validator, "12:30");
        assertValid("DateOrTime: time with seconds", validator, "12:30:45");
        assertValid("DateOrTime: time with ms", validator, "12:30:45.123");
        assertValid("DateOrTime: midnight", validator, "00:00");
        assertValid("DateOrTime: end of day", validator, "23:59:59");
    }

    private static void testDateOrTimeInvalid() {
        DateOrTime validator = DateOrTime.THE_INSTANCE;

        assertInvalid("DateOrTime: empty string", validator, "");
        assertInvalid("DateOrTime: text", validator, "today");

        // Date validation (now works after fixing regex groups)
        assertInvalid("DateOrTime: invalid month 13", validator, "2024-13-15");
        assertInvalid("DateOrTime: invalid day 32", validator, "2024-01-32");
        assertInvalid("DateOrTime: Feb 29 non-leap", validator, "2023-02-29");
        assertInvalid("DateOrTime: year 0", validator, "0000-01-15");

        // Time validation (now works after fixing regex groups)
        assertInvalid("DateOrTime: invalid hour 24", validator, "24:00");
        assertInvalid("DateOrTime: invalid minute 60", validator, "12:60");
        assertInvalid("DateOrTime: invalid second 60", validator, "12:30:60");
    }

    // TimeDatetime tests (very flexible - accepts many formats)

    private static void testTimeDatetimeValid() {
        TimeDatetime validator = TimeDatetime.THE_INSTANCE;

        // Month string
        assertValid("TimeDatetime: month string", validator, "2024-06");

        // Date string
        assertValid("TimeDatetime: date string", validator, "2024-06-15");

        // Yearless date string
        assertValid("TimeDatetime: yearless date", validator, "06-15");

        // Time string
        assertValid("TimeDatetime: time HH:MM", validator, "12:30");
        assertValid("TimeDatetime: time with seconds", validator, "12:30:45");
        assertValid("TimeDatetime: time with ms", validator, "12:30:45.123");

        // Local date and time string
        assertValid("TimeDatetime: local datetime", validator, "2024-06-15T12:30");
        assertValid("TimeDatetime: local datetime space", validator, "2024-06-15 12:30");

        // Time-zone offset string
        assertValid("TimeDatetime: timezone Z", validator, "Z");
        assertValid("TimeDatetime: timezone +05:30", validator, "+05:30");
        assertValid("TimeDatetime: timezone -08:00", validator, "-08:00");

        // Global date and time string
        assertValid("TimeDatetime: global datetime Z", validator, "2024-06-15T12:30:00Z");
        assertValid("TimeDatetime: global datetime offset", validator, "2024-06-15T12:30:00+05:00");

        // Week string
        assertValid("TimeDatetime: week string", validator, "2024-W26");

        // Year string
        assertValid("TimeDatetime: year string", validator, "2024");

        // Duration strings (ISO 8601 format)
        assertValid("TimeDatetime: duration days", validator, "P5D");
        assertValid("TimeDatetime: duration hours", validator, "PT2H");
        assertValid("TimeDatetime: duration minutes", validator, "PT30M");
        assertValid("TimeDatetime: duration seconds", validator, "PT45S");
        assertValid("TimeDatetime: duration complex", validator, "P1DT2H30M45S");
        assertValid("TimeDatetime: duration with ms", validator, "PT45.5S");

        // Duration in short format
        assertValid("TimeDatetime: duration 5d", validator, "5d");
        assertValid("TimeDatetime: duration 2h", validator, "2h");
        assertValid("TimeDatetime: duration 30m", validator, "30m");
        assertValid("TimeDatetime: duration 45s", validator, "45s");
        assertValid("TimeDatetime: duration 5w", validator, "5w");

        // With leading/trailing whitespace
        assertValid("TimeDatetime: leading space", validator, "  2024-06-15");
        assertValid("TimeDatetime: trailing space", validator, "2024-06-15  ");
        assertValid("TimeDatetime: both whitespace", validator, "  12:30  ");
    }

    private static void testTimeDatetimeInvalid() {
        TimeDatetime validator = TimeDatetime.THE_INSTANCE;

        assertInvalid("TimeDatetime: empty string", validator, "");
        assertInvalid("TimeDatetime: whitespace only", validator, "   ");
        assertInvalid("TimeDatetime: invalid month 13", validator, "2024-13");
        assertInvalid("TimeDatetime: invalid month 00", validator, "2024-00");
        assertInvalid("TimeDatetime: invalid day 32", validator, "2024-01-32");
        assertInvalid("TimeDatetime: invalid hour 24", validator, "24:00");
        assertInvalid("TimeDatetime: invalid minute 60", validator, "12:60");
        assertInvalid("TimeDatetime: invalid week 00", validator, "2024-W00");
        assertInvalid("TimeDatetime: invalid week 54", validator, "2024-W54");
        assertInvalid("TimeDatetime: text", validator, "yesterday");
        assertInvalid("TimeDatetime: invalid duration", validator, "P");
        assertInvalid("TimeDatetime: year 0", validator, "0000");
    }

    // IntNonNegative tests

    private static void testIntNonNegativeValid() {
        IntNonNegative validator = IntNonNegative.THE_INSTANCE;

        assertValid("IntNonNegative: zero", validator, "0");
        assertValid("IntNonNegative: single digit", validator, "5");
        assertValid("IntNonNegative: double digit", validator, "42");
        assertValid("IntNonNegative: large number", validator, "123456789");
        assertValid("IntNonNegative: leading zeros", validator, "007");
        assertValid("IntNonNegative: all zeros", validator, "000");
    }

    private static void testIntNonNegativeInvalid() {
        IntNonNegative validator = IntNonNegative.THE_INSTANCE;

        assertInvalid("IntNonNegative: empty string", validator, "");
        assertInvalid("IntNonNegative: negative", validator, "-1");
        assertInvalid("IntNonNegative: negative zero", validator, "-0");
        assertInvalid("IntNonNegative: decimal", validator, "1.5");
        assertInvalid("IntNonNegative: letter", validator, "a");
        assertInvalid("IntNonNegative: mixed", validator, "12a");
        assertInvalid("IntNonNegative: whitespace", validator, " 1");
        assertInvalid("IntNonNegative: plus sign", validator, "+1");
    }

    // IntPositive tests

    private static void testIntPositiveValid() {
        IntPositive validator = IntPositive.THE_INSTANCE;

        assertValid("IntPositive: single digit", validator, "1");
        assertValid("IntPositive: double digit", validator, "42");
        assertValid("IntPositive: large number", validator, "123456789");
        assertValid("IntPositive: leading zeros then digit", validator, "007");
        assertValid("IntPositive: leading zeros then non-zero", validator, "001");
    }

    private static void testIntPositiveInvalid() {
        IntPositive validator = IntPositive.THE_INSTANCE;

        assertInvalid("IntPositive: empty string", validator, "");
        assertInvalid("IntPositive: zero", validator, "0");
        assertInvalid("IntPositive: all zeros", validator, "000");
        assertInvalid("IntPositive: negative", validator, "-1");
        assertInvalid("IntPositive: decimal", validator, "1.5");
        assertInvalid("IntPositive: letter", validator, "a");
        assertInvalid("IntPositive: plus sign", validator, "+1");
    }

    // Zero tests

    private static void testZeroValid() {
        Zero validator = Zero.THE_INSTANCE;

        assertValid("Zero: just zero", validator, "0");
    }

    private static void testZeroInvalid() {
        Zero validator = Zero.THE_INSTANCE;

        assertInvalid("Zero: empty string", validator, "");
        assertInvalid("Zero: one", validator, "1");
        assertInvalid("Zero: double zero", validator, "00");
        assertInvalid("Zero: negative zero", validator, "-0");
        assertInvalid("Zero: zero with space", validator, " 0");
        assertInvalid("Zero: letter o", validator, "o");
        assertInvalid("Zero: O letter", validator, "O");
    }

    // FloatingPointExponent tests

    private static void testFloatingPointExponentValid() {
        FloatingPointExponent validator = FloatingPointExponent.THE_INSTANCE;

        // Integer-like values
        assertValid("FloatingPoint: zero", validator, "0");
        assertValid("FloatingPoint: positive integer", validator, "123");
        assertValid("FloatingPoint: negative integer", validator, "-456");

        // Decimal values
        assertValid("FloatingPoint: decimal", validator, "3.14");
        assertValid("FloatingPoint: negative decimal", validator, "-3.14");
        assertValid("FloatingPoint: leading zero decimal", validator, "0.5");
        assertValid("FloatingPoint: small decimal", validator, ".5");

        // With exponent
        assertValid("FloatingPoint: with e", validator, "1e10");
        assertValid("FloatingPoint: with E", validator, "1E10");
        assertValid("FloatingPoint: with positive exponent", validator, "1e+10");
        assertValid("FloatingPoint: with negative exponent", validator, "1e-10");
        assertValid("FloatingPoint: decimal with exponent", validator, "3.14e5");
        assertValid("FloatingPoint: negative with exponent", validator, "-2.5e3");

        // Complex examples
        assertValid("FloatingPoint: scientific notation", validator, "6.022e23");
        assertValid("FloatingPoint: very small", validator, "1e-100");
    }

    private static void testFloatingPointExponentInvalid() {
        FloatingPointExponent validator = FloatingPointExponent.THE_INSTANCE;

        assertInvalid("FloatingPoint: empty string", validator, "");
        assertInvalid("FloatingPoint: just minus", validator, "-");
        assertInvalid("FloatingPoint: just dot", validator, ".");
        assertInvalid("FloatingPoint: trailing dot", validator, "1.");
        assertInvalid("FloatingPoint: just e", validator, "e");
        assertInvalid("FloatingPoint: e at start", validator, "e5");
        assertInvalid("FloatingPoint: trailing e", validator, "1e");
        assertInvalid("FloatingPoint: e with just sign", validator, "1e+");
        assertInvalid("FloatingPoint: double dot", validator, "1..2");
        assertInvalid("FloatingPoint: double e", validator, "1e2e3");
        assertInvalid("FloatingPoint: letter", validator, "abc");
        assertInvalid("FloatingPoint: hex", validator, "0x1f");
        assertInvalid("FloatingPoint: comma decimal", validator, "1,5");
    }

    // Idref tests (extends Id, same validation)

    private static void testIdrefValid() {
        Idref validator = Idref.THE_INSTANCE;

        assertValid("Idref: single char", validator, "a");
        assertValid("Idref: simple id", validator, "myId");
        assertValid("Idref: with hyphen", validator, "my-id");
        assertValid("Idref: with numbers", validator, "id123");
        assertValid("Idref: starting with number", validator, "123");
        assertValid("Idref: unicode", validator, "\u00e9l\u00e8ve");
    }

    private static void testIdrefInvalid() {
        Idref validator = Idref.THE_INSTANCE;

        assertInvalid("Idref: empty string", validator, "");
        assertInvalid("Idref: single space", validator, " ");
        assertInvalid("Idref: leading space", validator, " id");
        assertInvalid("Idref: trailing space", validator, "id ");
        assertInvalid("Idref: space in middle", validator, "my id");
        assertInvalid("Idref: tab", validator, "my\tid");
        assertInvalid("Idref: newline", validator, "my\nid");
    }

    // Idrefs tests

    private static void testIdrefsValid() {
        Idrefs validator = Idrefs.THE_INSTANCE;

        assertValid("Idrefs: single id", validator, "myId");
        assertValid("Idrefs: multiple ids", validator, "id1 id2 id3");
        assertValid("Idrefs: single non-whitespace char", validator, "a");
        assertValid("Idrefs: with leading whitespace", validator, " id");
        assertValid("Idrefs: with trailing whitespace", validator, "id ");
        assertValid("Idrefs: mixed whitespace and ids", validator, "  id1  id2  ");
    }

    private static void testIdrefsInvalid() {
        Idrefs validator = Idrefs.THE_INSTANCE;

        assertInvalid("Idrefs: empty string", validator, "");
        assertInvalid("Idrefs: just space", validator, " ");
        assertInvalid("Idrefs: multiple spaces", validator, "   ");
        assertInvalid("Idrefs: just tab", validator, "\t");
        assertInvalid("Idrefs: just newline", validator, "\n");
        assertInvalid("Idrefs: mixed whitespace only", validator, " \t\n ");
    }

    // Language tests

    private static void testLanguageValid() {
        Language validator = Language.THE_INSTANCE;

        // ISO 639-1 codes (2-letter)
        assertValid("Language: English", validator, "en");
        assertValid("Language: French", validator, "fr");
        assertValid("Language: German", validator, "de");
        assertValid("Language: Japanese", validator, "ja");
        assertValid("Language: Chinese", validator, "zh");

        // With region (ISO 3166-1)
        assertValid("Language: US English", validator, "en-US");
        assertValid("Language: British English", validator, "en-GB");
        assertValid("Language: Brazilian Portuguese", validator, "pt-BR");
        assertValid("Language: Swiss German", validator, "de-CH");

        // With script
        assertValid("Language: Simplified Chinese", validator, "zh-Hans");
        assertValid("Language: Traditional Chinese", validator, "zh-Hant");
        assertValid("Language: Serbian Latin", validator, "sr-Latn");

        // Complex tags
        assertValid("Language: Chinese Taiwan Traditional", validator, "zh-Hant-TW");

        // Private use
        assertValid("Language: private use", validator, "x-private");
        assertValid("Language: with private extension", validator, "en-x-custom");

        // Case insensitivity (should be normalized)
        assertValid("Language: uppercase", validator, "EN");
        assertValid("Language: mixed case", validator, "En-Us");
    }

    private static void testLanguageInvalid() {
        Language validator = Language.THE_INSTANCE;

        assertInvalid("Language: empty string", validator, "");
        assertInvalid("Language: leading hyphen", validator, "-en");
        assertInvalid("Language: trailing hyphen", validator, "en-");
        assertInvalid("Language: double hyphen", validator, "en--US");
        assertInvalid("Language: single letter", validator, "e");
        assertInvalid("Language: too long subtag", validator, "en-abcdefghi");
        assertInvalid("Language: invalid language", validator, "xx");
        assertInvalid("Language: reserved 4-letter", validator, "abcd");
        assertInvalid("Language: just x", validator, "x");
        assertInvalid("Language: x- too short", validator, "x-a");
    }

    // Test helpers

    private static void assertValid(String testName,
            nu.validator.vendor.relaxng.datatype.Datatype validator, String value) {
        try {
            validator.checkValid(value, null);
            pass(testName);
        } catch (DatatypeException e) {
            System.out.println("FAIL: " + testName);
            System.out.println("  Expected: valid");
            System.out.println("  Got exception: " + e.getMessage());
            failed++;
        }
    }

    private static void assertInvalid(String testName,
            nu.validator.vendor.relaxng.datatype.Datatype validator, String value) {
        try {
            validator.checkValid(value, null);
            System.out.println("FAIL: " + testName);
            System.out.println("  Expected: invalid (exception)");
            System.out.println("  Got: valid (no exception)");
            failed++;
        } catch (DatatypeException e) {
            pass(testName);
        }
    }

    private static void pass(String testName) {
        System.out.println("PASS: " + testName);
        passed++;
    }
}
