aboutsummaryrefslogtreecommitdiff
path: root/tests/library/algebra.test.py
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-07-17 14:43:56 +0530
committerGitHub <[email protected]>2026-07-17 14:43:56 +0530
commitd27e5f4145972990544975a033d03abdd3a5f596 (patch)
treeb0d069809b22ee040e3bd294b6b7a868d926fde8 /tests/library/algebra.test.py
parent8bff9a0ec3c7b7207ecd994ace4c696b71fd84f5 (diff)
parent5d71c2f3b7218895a10bf485ddd3a04df2c4f66e (diff)
downloadedify-main.tar.xz
edify-main.zip
Multi-locale phone validator, expanded corpora, and all-validator round-trip + operator-algebra coverage (#288)HEADmain
Completes the remaining non-docs library items on the v1.0.0 milestone: a genuinely multi-locale `phone` validator, real per-locale corpus hardening, and an end-to-end coverage layer that exercises every shipped validator and the operator algebra. ## Multi-locale phone (#154) `phone` is rebuilt as a table-driven multi-locale model: an optional `+` or `00` prefix, two to eight digit groups of one to four digits each, single-character space/dot/dash separators, and any group optionally parenthesised. It now accepts the display forms it previously missed — leading and inline parenthesised area codes such as `(555) 123-4567` and `+44 (0)20 7946 0958`, and variable national grouping from three-group North American through five-group French — plus two-to-six-digit service and short codes. Doubled separators, bare prefixes, and letters are still rejected. The corpus grows from a seven-string smoke test to twenty-one real numbers across eleven locales with ten adversarial rejects. ## Postal locale coverage (#155) Multi-locale postal codes are delivered by the existing `postal` validator; this pins it down with a twenty-code corpus spanning North America, Europe, Asia, and Oceania (including alphanumeric UK, Irish, Canadian, and Dutch shapes) with ten adversarial rejects. `zip_code` stays deliberately US-scoped, with `postal` as its locale-complete counterpart. ## Validator snapshots (#191) Every per-validator migration to a callable `Pattern` has landed, so regenerating the built-in snapshot corpus is a no-op apart from `phone`'s new emitted shape, which is refreshed here. ## End-to-end coverage of every construct (#258) - Every registered library validator now round-trips through both dict and JSON serialization with its emitted regex preserved — a property test over the full validator set, alongside the existing compiled-source invariant. - The operator algebra (`+`, `|`, and `.use()`) is asserted identical to its fluent-chain equivalent across every character-class constant, at both the emitted-string and compiled-matcher level, so the two surfaces can never drift. - Hardening corpora expand beyond the smoke-test handful to `phone`, `postal`, `semver`, `slug`, `hostname`, `port`, `isbn`, and `vin`, each with real-world accepts and adversarial rejects. Closes #154, Closes #155, Closes #191, Closes #258
Diffstat (limited to 'tests/library/algebra.test.py')
-rw-r--r--tests/library/algebra.test.py77
1 files changed, 77 insertions, 0 deletions
diff --git a/tests/library/algebra.test.py b/tests/library/algebra.test.py
new file mode 100644
index 0000000..ee4005d
--- /dev/null
+++ b/tests/library/algebra.test.py
@@ -0,0 +1,77 @@
+"""Chain <-> operator-algebra equivalence: ``+``, ``|`` and ``.use()`` emit the fluent-chain regex.
+
+Every character-class module constant is driven through both surfaces and the two
+emitted regex strings are asserted identical, so the operator algebra can never
+drift from the fluent builder it stands in for.
+"""
+
+import pytest
+
+from edify import (
+ ALPHANUMERIC,
+ ANY_CHAR,
+ CARRIAGE_RETURN,
+ DIGIT,
+ LETTER,
+ LOWERCASE,
+ NEW_LINE,
+ NON_DIGIT,
+ NON_WHITESPACE,
+ NON_WORD,
+ NULL_BYTE,
+ TAB,
+ UPPERCASE,
+ WHITESPACE,
+ WORD,
+ Pattern,
+)
+
+_CONSTANTS: dict[str, Pattern] = {
+ "ANY_CHAR": ANY_CHAR,
+ "WHITESPACE": WHITESPACE,
+ "NON_WHITESPACE": NON_WHITESPACE,
+ "DIGIT": DIGIT,
+ "NON_DIGIT": NON_DIGIT,
+ "WORD": WORD,
+ "NON_WORD": NON_WORD,
+ "NEW_LINE": NEW_LINE,
+ "CARRIAGE_RETURN": CARRIAGE_RETURN,
+ "TAB": TAB,
+ "NULL_BYTE": NULL_BYTE,
+ "LETTER": LETTER,
+ "UPPERCASE": UPPERCASE,
+ "LOWERCASE": LOWERCASE,
+ "ALPHANUMERIC": ALPHANUMERIC,
+}
+_IDS = list(_CONSTANTS)
+_VALUES = list(_CONSTANTS.values())
+
+
[email protected]("constant", _VALUES, ids=_IDS)
+def test_plus_matches_chain_subexpression(constant: Pattern):
+ operator_form = (constant + DIGIT).to_regex_string()
+ chain_form = Pattern().subexpression(constant).subexpression(DIGIT).to_regex_string()
+ assert operator_form == chain_form
+
+
[email protected]("constant", _VALUES, ids=_IDS)
+def test_or_matches_chain_any_of(constant: Pattern):
+ operator_form = (constant | DIGIT).to_regex_string()
+ chain_form = (
+ Pattern().any_of().subexpression(constant).subexpression(DIGIT).end().to_regex_string()
+ )
+ assert operator_form == chain_form
+
+
[email protected]("constant", _VALUES, ids=_IDS)
+def test_use_matches_chain_subexpression(constant: Pattern):
+ use_form = Pattern().use(constant).to_regex_string()
+ chain_form = Pattern().subexpression(constant).to_regex_string()
+ assert use_form == chain_form
+
+
[email protected]("constant", _VALUES, ids=_IDS)
+def test_operator_and_chain_compile_to_the_same_matcher(constant: Pattern):
+ operator_compiled = (constant + DIGIT).to_regex().source
+ chain_compiled = Pattern().subexpression(constant).subexpression(DIGIT).to_regex().source
+ assert operator_compiled == chain_compiled