Repository navigation
0.8.0-beta: contract tests, validator docs, Amount::toWords() - #28
Merged
Merged
Conversation
Ignore the random fixture generators (*::fake, BillId::fakePaymentId) via mutators.global-ignore and add tests for the real survivors: - anchored length checks: NationalId, LegalId, PostalCode and BillId payment IDs with a stray leading/trailing letter - middle-zero substr window and checksum codes asserted by ErrorCode - LegalId: one valid ID per check digit, including the sum-10 fold - WordsToNumber: outer trim, a second ممیز, empty tokens in the fraction, bare صد after هزار, the quadrillion key and the PHP_INT_MAX boundary Currency::convert() drops a redundant int branch: integer `/` already stays int on exact division, so its mutants were all equivalent. Full-repo MSI is 88% (was 81%). minMsi / minCoveredMsi and the mutate script now enforce 88, and the CI job reads the thresholds from the config instead of overriding them with 0.
…ting and text Extract the fixtures-present skip into an abstract PersianToolsFixtureTestCase and add one contract class per domain, with vectors hand-lifted from the vendored specs (each citing spec.ts:line): - LegalIdContractTest: verifyIranianLegalId, all 9 vectors match - NumberWordsContractTest: NumberToWords, addOrdinalSuffix, wordsToNumber, including upstream's UNITS / TEN / MAGNITUDE tables - FormattingContractTest: addCommas, removeCommas, digits - TextContractTest: isPersian, isArabic, toPersianChars, halfSpace Each class has a divergences() registry of [api, input, upstream, abzar, reason] that asserts abzar's output and that it still differs from upstream's. docs/en/persian-tools-parity.md lists every entry. Code changes driven by the specs: - WordsToNumber reads شیش, چارصد, بیلیون and کوآدریلیون, so it parses everything upstream's numberToWords() writes. - HalfSpaceFixer ends a suffix at a closing bracket, quote or colon, so `(آبی تر)` is bound like `آبی تر.` already was.
Add national-id, card-number, iban, legal-id, phone-number and plate-number pages, following the postal-code / bill-id template: usage, rules, warnings, error codes, details and accessors, plus extractAll / masked / fake where the class has them. docs/en/error-codes.md lists every ErrorCode case with its Persian message, emitting class and kind (error, warning or thrown). The new ErrorCodeDocsTest asserts each case has a row carrying its current message, mirroring the completeness check in ErrorCodeMessageSnapshotTest. Everything is linked from docs/en/README.md and the README's "Further reading" section. Every documented output was checked against real output.
Amount::toWords(Unit $unit = Unit::TOMAN) reuses NumberToWords::convert() and Unit::persianName(): 1,200,000 rials, RIAL → یک میلیون و دویست هزار ریال 1,200,000 rials, TOMAN → یکصد و بیست هزار تومان 12,345 rials, TOMAN → یک هزار و دویست و سی و چهار تومان و پنج ریال 5 rials, TOMAN → پنج ریال 0, TOMAN → صفر تومان It defaults to Toman like Currency::format(), spells a sub-toman remainder out instead of truncating it, and covers the full int range without throwing. Documented in docs/en/currency.md and the README's Money section.
- composer.json: description names every validator, money and text tools; keywords add card-number, sheba, postal-code, license-plate, bill-id, toman, rial, money, words-to-number, keyboard-layout, persian-digits, zwnj and ordinal. - README.fa.md: Persian landing page (what abzar is, install, ten short examples, links to the English docs and UPGRADE.md), linked from the top of the README. - CHANGELOG: 0.8.0-beta section. Install pins in README, installation and api-stability move to ^0.8@beta. UPGRADE.md gains a "0.7 → 0.8: no breaking changes" section noting the two widened outputs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
0.8.0-beta: no breaking changes. It adds a cleanup pass, contract tests built from more of the vendored persian-tools specs, reference docs for every validator,
Amount::toWords(), and better discoverability. Each commit covers one part, so you can review them one at a time.1. Cleanup and mutation testing (
23cd171)::fake()generators (mutators.global-ignore). That alone took full-repo MSI from 81% to 86%.NationalId/LegalId/PostalCode/BillIdpayment IDs;LegalIdper check digit. Before, every fixture had check digit 0, so deleting the checksum loop passed the suite;WordsToNumberedge cases, including thePHP_INT_MAXboundary.Currency::convert()that did nothing (integer/already stays int on exact division). It's removed and output is unchanged.minMsi/minCoveredMsiandcomposer mutateenforce 88, and the CI job reads the thresholds from the config instead of overriding them with 0. The job is still advisory (continue-on-error).2. persian-tools contract tests (
720b676)PersianToolsFixtureTestCaseholds the shared fixtures-present skip.LegalIdContractTest,NumberWordsContractTest,FormattingContractTestandTextContractTest.divergences()registry of[api, input, upstream, abzar, reason]. Every entry asserts abzar's output and that it still differs from upstream's.docs/en/persian-tools-parity.mdlists them all.WordsToNumberreadsشیش,چارصد,بیلیونandکوآدریلیون, so it parses every cardinal upstream writes.HalfSpaceFixerignored a suffix followed by),],», a quote or a colon.(آبی تر)now becomes(آبیتر), likeآبی تر.already did.3. Docs (
e4bf08d)docs/en/error-codes.mdlists everyErrorCodewith its Persian message, emitting class and kind (error, warning or thrown).ErrorCodeDocsTestchecks that each code has a row carrying its current message.4.
Amount::toWords(Unit $unit = Unit::TOMAN)(1d726cd)12,345rials →یک هزار و دویست و سی و چهار تومان و پنج ریال. The rial remainder is spelled out, never truncated.intrange and never throws.5. Release prep (
d8c0469)composer.jsonhas a new description and keywords.README.fa.mdis a Persian landing page, linked from the top of the README.^0.8@beta.UPGRADE.mdgets a "0.7 → 0.8" section.Checklist
composer testpasses: 982 tests, also run on PHP 8.1 in Docker.composer phpstanis clean at level 8.composer cs-check(php-cs-fixer) is clean.CHANGELOG.md. The entries are already under[0.8.0-beta] — 2026-10-01, since this PR is the release.README.md/docs/en/*) updated. Every documented example output was checked against what the code actually returns.php:8.2-cliwith pcov).composer validate --strictpasses, andgit archive HEADincludesREADME.fa.md.Type of change
HalfSpaceFixerbefore closing brackets)Amount::toWords(), the newWordsToNumberspellings)Notes for reviewers
Two outputs widen.
WordsToNumbernow parses spellings that returnednullbefore, andHalfSpaceFixerbinds suffixes in more places. Both are noted inUPGRADE.mdin case anyone asserts on the old output.Divergences beyond the planned list:
removeCommas;withSeparators($s, '')returns a string;DigitConverterfolds every digit set into its target;HalfSpaceFixerhas no بی/هم prefix rules, no fixed compounds, and doesn't normalise whitespace.Each is recorded with its reason.
LegalIdContractTesthas nodivergences()registry. All 9 vectors match, and an empty data provider would fail underfailOnWarning.Still to do after merge: tag
0.8.0-beta. The release workflow finds the changelog section by that name.