Skip to content

feat: add a Laravel-style Support layer (Str, Number, Arr, Cast, Env.filled, shortDiffForHumans, validateRequest, view mixins) - #202

Merged
anilcancakir merged 5 commits into
masterfrom
feature/support-helpers
Sep 25, 2026
Merged

anilcancakir merged 5 commits into
masterfrom
feature/support-helpers

Conversation

@anilcancakir

Copy link
Copy Markdown
Member

What

A Laravel-style Support layer for magic, extracted from helpers that apps built on magic kept writing for themselves:

  • Number (lib/src/support/number.dart): format, currency, percentage, fileSize, abbreviate, locale-aware through intl, locale from Lang.current with an en fallback for an unknown locale. Mirrors Laravel's Number: percentage takes a 0-100 value, currency is built on NumberFormat.simpleCurrency (tr ₺1.234,50, not TRY1.234,50).
  • Str (lib/src/support/str.dart): upper, lower and initials with the Turkish/Azerbaijani dotted-i rule (istanbul -> İSTANBUL in tr), accepting tr, tr_TR or tr-TR; lower maps İ to a plain i in every locale because the JS engine adds a combining dot on web.
  • Cast and Arr (lib/src/support/{cast,arr}.dart): Cast reads a loosely typed wire value with a fallback instead of a hard cast (Cast.stringOr, Cast.intOrNull, ...; only intOr parses a numeric string). Arr is dot-path access with Laravel's get/has/set/dot semantics and deliberately no typed accessors; typing composes with Cast.
  • Env: KEY="" and KEY='' now resolve to '' (flutter_dotenv leaves the literal quotes), matching Laravel; blank still stays blank. New Env.filled(key, fallback) for a value a blank would corrupt, and Env.getOrFail(key).
  • Carbon.shortDiffForHumans([other]): a compact ladder (14m ago, 1w ago, 5m from now, Just now) through time.* catalogue keys with an English fallback; diffForHumans is unchanged.
  • Validation: ValidatesRequests.validateRequest / validateRequestAsync run a FormRequest through the controller's own error bag and return the full prepared map; setErrorsFromMap plus an errorFieldFor hook, and the opt-in CollapsesIndexedErrorKeys mixin (items.0.name -> name). The default keeps raw keys.
  • View mixins: RefetchesOnMount and SubmitsOnce (lib/src/ui/).
  • Testing: MagicTest.init() also resets the Gate and the Translator between tests; MagicTest.loadTranslations(locale, {directory}) loads a real catalogue for a widget test (JsonAssetLoader.flatten is now public).
  • Docs (doc/digging-deeper/helpers.md plus sections in carbon, validation, configuration, views and testing), CHANGELOG, skill, and the time group in the install lang stub.

Why

uptizm, watchools and depools each hand-rolled the same things: three apps format decimal separators through three different catalogue keys, two re-implement Turkish casing, and uptizm carried its own env cleaning, wire reads, request validation bridge, 422 key collapsing and view mixins. These belong at framework level the way Laravel ships Str, Number and Arr.

Testing

  • flutter analyze: no issues. flutter test: 1774 passing (was 1674 on master). dart format --set-exit-if-changed .: clean.
  • Every new surface has red-first tests; Str also runs on --platform chrome because the dotted-i case only discriminates on web.
  • Measured intl output on the VM backs the Number tests (tr 1.234.567,891, %99,95, compact 1,5 Mn).
  • uptizm was moved onto this branch in a companion PR and passes its full gate against it; a browser walk in Turkish and English at desktop and 500px showed the new casing, relative times and router behaviour.

No version bump; publishing is a separate step.

@codecov

codecov Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.83568% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/src/concerns/validates_requests.dart 91.66% 3 Missing ⚠️
lib/src/support/number.dart 91.89% 3 Missing ⚠️
lib/src/support/str.dart 90.47% 2 Missing ⚠️
lib/src/foundation/env.dart 93.33% 1 Missing ⚠️
lib/src/support/carbon.dart 96.42% 1 Missing ⚠️
lib/src/ui/submits_once.dart 83.33% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@kodizm

kodizm Bot commented Sep 25, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

Looks correct and well tested. Nothing here should block the merge. There is one testing-harness side effect worth fixing, plus two repo-rule gaps.

Minor

lib/src/testing/magic_test.dart:45 (correctness, test harness only): Translator.reset() in tearDown disposes the translator singleton. DateManager is a static singleton that MagicTest never resets, and it subscribes to Translator.instance once during boot() (date_manager.dart:117), then returns early on every later boot (_booted, line 76). So in a file where more than one test boots Magic with localisation on, every test after the first keeps a listener on the disposed translator. In those tests, Lang.setLocale(...) stops updating the Carbon/Jiffy locale. Resetting DateManager in the same tearDown would keep the two in step.

CHANGELOG.md:22 (maintainability): The MagicTest.init() change can break existing suites. The entry itself says abilities defined in setUpAll must move to setUp, and translations loaded in setUpAll are also dropped after the first test. The entry sits under Added, but CLAUDE.md gives a BREAKING heading for this kind of change.

example/ (maintainability): This PR adds no example usage of the new helpers or mixins. That diverges from CLAUDE.md Post-change sync step 5 ("update or add usage for changed/new features").

Tests

Every new surface has its own test file: Number, Str, Arr, Cast, Env, the short Carbon diff, validateRequest/CollapsesIndexedErrorKeys, RefetchesOnMount, SubmitsOnce, MagicTest and the exports. The DateManager interaction above has no test.

CI

  • Lint & Test: success
  • codecov/patch: success (94.81% of the diff covered)
  • Internal Links & Anchors: success
  • External Links: skipped
  • Auto-merge low-risk Dependabot PRs: skipped

I read all the lib/ changes, the lang stub, the CHANGELOG and SKILL.md. I did not read the docs pages, references/secondary-systems.md or the test files line by line.

@anilcancakir

Copy link
Copy Markdown
Member Author

@kodizm review

Addressed the three points from the last pass:

  • MagicTest.init() tearDown now calls DateManager.reset() before Translator.reset(), with a red-first test (a DateManager booted in one test was still booted in the next).
  • The MagicTest.init() reset change is filed under ### BREAKING, naming the setUpAll migration.
  • example/test/support_helpers_test.dart shows Number, Str, Arr with Cast, shortDiffForHumans and SubmitsOnce in use.

@kodizm

kodizm Bot commented Sep 25, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

All three earlier points are fixed, and the new commit looks correct. Nothing blocks the merge.

What changed since the last review: 1cc070b makes MagicTest.init() reset DateManager before Translator, moves the reset entry under ### BREAKING, and adds example/test/support_helpers_test.dart.

Minor

example/test/support_helpers_test.dart:5 (maintainability): The doc comment says the file is "kept runnable so the examples cannot drift from the API", but CI never runs it. .github/workflows/ci.yml:33 runs flutter test --coverage from the repo root only, so nothing under example/test/ executes, and the existing widget_test.dart there doesn't either. I checked each call in the file by hand against the current signatures (Str.initials(capitalize:), Number.percentage(precision:, locale:), Cast.intOr/stringOrNull, Carbon.setTestNow([Carbon?]), SubmitsOnce.submitOnce/isSubmitting), and they all match today. Either add an example/ test step to CI or soften the comment.

Tests

  • magic_test_test.dart group 9 is a red-first pair of tests. It shows that a DateManager booted in one test is not booted in the next.
  • DateManager.reset() only nulls _instance. The old listener goes away because Translator.reset() disposes the translator right after it, so the order in the tearDown is correct.

CI

  • Lint & Test: success
  • codecov/patch: success (94.83% of the diff covered)
  • Internal Links & Anchors: success
  • External Links: skipped
  • Auto-merge low-risk Dependabot PRs: skipped

For this pass I read only the diff of 1cc070b plus the DateManager, Str, Cast, Carbon and SubmitsOnce sources the example calls. Everything else stands as reviewed before.

@kodizm

kodizm Bot commented Sep 25, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

The last open point is fixed and nothing new is introduced. Nothing blocks the merge.

What changed since the last review: aab15cc changes only the doc comment in example/test/support_helpers_test.dart. It no longer claims CI keeps the examples from drifting. It now says the file runs with flutter test from example/ and is checked by hand, which matches .github/workflows/ci.yml. There are no code changes.

Tests

Nothing to cover, because this commit only changes a comment.

CI

  • Lint & Test: success
  • codecov/patch: success (94.83% of the diff covered)
  • Internal Links & Anchors: success
  • External Links: skipped
  • Auto-merge low-risk Dependabot PRs: skipped

@anilcancakir
anilcancakir merged commit b90afc9 into master Sep 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant