Examples suite: one backend, four apps, and a CLI, with tests to match - #785
Examples suite: one backend, four apps, and a CLI, with tests to match#785Decipher wants to merge 17 commits into
Conversation
docs/drupal becomes the one backend every example runs against: SQLite + Tome provisioning via .devtools (assemble/provision/start), committed Umami config and content with Spanish translations, form displays trimmed to the fields editors need, OAuth consumer wiring for the authenticated examples, and local patches (decoupled_router #3111456, druxt #3273228) that make language switching resolve from the requested path. See docs/drupal/patches/README.md for why each is held locally.
Small fixes to druxt, druxt-entity and druxt-router found while exercising them across the suite: DruxtModule wrapper resolution, DruxtEntityForm network-failure handling (with test), and router store route handling. Each was reproduced in an example before being fixed.
The examples get their own CI surface: per-app lint/test jobs, a structure check, cspell/knip/markdownlint config covering the new trees, and a manual examples:preview job that boots the backend and all four apps behind Cloudflare tunnels with a code-server (with recommended extensions) for live review. Root docs updated to describe the suite.
The _deprecated/* standalone examples (debug, entity-explorer, entity-queries, entity-form, menu-edit, router-modes, schema, wrappers, custom-module) fold into examples/druxt-site as /examples/* pattern pages on one vanilla DruxtSite app: the Umami site with a minimal wireframe theme, working language switching pinned by a Cypress multilingual battery, and shared Cypress helpers under examples/shared. scripts/check-examples.js enforces the suite's structure; the docs guides point at the consolidated example.
Three apps that prove Druxt powers things that aren't brochure sites, each on the same Umami backend: Recipe Box (druxt-daisyui), a swipeable recipe deck; Meal Planner (druxt-tailwind), a no-Nuxt Vite app planning a week of meals via druxt/getCollection alone; and Content Ops Console (druxt-bootstrapvue), an editorial console with optimistic inline editing and full-record editing through DruxtEntityForm over a real OAuth2 write path. Each carries its own Cypress battery.
A framework-agnostic CLI (no Vue, no Nuxt) for inspecting a Drupal JSON:API backend through the same classes every other example uses under the hood: resource types, view/form schemas, entity samples, views, and generated Druxt Entity wrapper stubs. Jest tests run against recorded fixtures, so the suite needs no live backend.
|
|
Important Review skippedToo many files! This PR contains 323 files, which is 223 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (12)
📒 Files selected for processing (323)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #785 +/- ##
===========================================
+ Coverage 81.49% 81.50% +0.01%
===========================================
Files 112 112
Lines 2853 2855 +2
Branches 576 618 +42
===========================================
+ Hits 2325 2327 +2
Misses 436 436
Partials 92 92 🚀 New features to boost your workflow:
|
CodeQL flagged the single-pass tag stripping in Recipe Box's summary (js/incomplete-multi-character-sanitization): nested tags can reform after one replacement and an unterminated trailing tag survives entirely. Both strippers now loop until the text stops changing and match unterminated tags too. Content Ops' word counter had the same pattern, fixed identically. The rendered output was already Vue-escaped interpolation in both apps, so this hardens display text rather than closing a live injection.
…ow CI All four e2e jobs failed on GitHub runners with 'database is locked' during tome:install: the parent drush process holds a connection while its child processes write, SQLite allows one writer, and without a busy timeout a blocked write fails instead of waiting. PDO::ATTR_TIMEOUT is pdo_sqlite's busy timeout; 60 seconds outlasts any cache write. The same provisioning passed on faster disks, which is why it never showed before this run.
…mode The busy timeout was not enough: the lock that kills tome:install on GitHub runners is a deadlock, not slow contention. The parent drush process holds read locks while its child processes run, rollback-journal SQLite lets readers block the one writer, and the parent releases nothing until the child exits - so the child's end-of-request cache write waits out its timeout and dies as 'database is locked' (the stack points at DrupalKernel->terminate()). WAL lets readers and the writer coexist, and the mode persists in the database file, so provision sets it once right after creating the database and fails loudly if the pragma does not take.
tome:install spawns child drush processes while the parent stays bootstrapped, and SQLite allows one writer. On GitHub runners that combination deadlocks - a child's end-of-request cache write waits out its busy timeout against the parent's locks and dies as 'database is locked'. WAL and a 60 second timeout were tried first and verified insufficient: core enables WAL on every connection already, and the lock holder outlives any timeout because the parent will not release until the child it is waiting on exits. Provision now runs the same steps itself with no process overlap: site-install, enable tome_sync, clear the profile's own content and align the site UUID with sync (what tome's prepareConfigForImport does, and why config:import otherwise refuses to start), config:import twice (translation collections land on the second pass), then a serial content import through tome_sync's own importer service inside one bootstrapped process, ending with the same import-complete event. Verified locally end to end; the one failing step (consumer cleanup) fails only against a pre-OAuth config tree that ships no consumers module, which the branch's config does.
The serial provisioning still deadlocked in CI, one level deeper: when core.extension changes, drush config:import hands the module install batch to a nested drush process (drush_backend_batch_process), and that child dies against its parent's SQLite locks the same way tome:install's children did. Core's ConfigImporter runs every step synchronously when driven directly, module installs included, so config now imports through a php-script and provisioning holds one process end to end. Verified locally: full provision passes, the language.es collection lands (frontpage title reads Inicio), and content imports completely.
… run
The e2e specs were written against live-preview verification and CI is
the first environment to execute them. Provisioning now passes and
druxt-daisyui runs green; the rest failed on genuine spec and page bugs:
- druxt-tailwind asserted exact heading text against whitespace-padded
markup; assert the trimmed text.
- Content Ops called should('have.value') with no argument, which
compares the value against undefined.
- router-modes demoed /node/1, but nids depend on import order and the
node does not exist, so two 404 debug blocks rendered their own h1s
into the page-wide heading assertion. The demo now uses a stable
alias, and the assertion scopes to the page's own heading.
- menu-edit reused a cy chain stored in a const, so the second loop
iteration searched for list items inside the first iteration's
results. Re-query per label.
- reactive-filtering clicked every active chip in one command while
each toggle re-renders the chip list, failing on detached elements.
The default pantry is deterministic, so toggle each chip by name.
- druxt-devel-template runs first and absorbs the dev server's initial
page compile, which exceeds the 4 second default timeout on CI
runners. The opening query now waits for the compile.
Meal Planner's templates pad their text nodes, so every exact-text assertion in its spec now compares the trimmed text - the previous fix caught the heading and this catches the chips and counters. The start script judged the site on one HEAD request with a 5 second window, but the first request bootstraps a cold Drupal and a slow CI runner can take longer - three matrix jobs served fine while test-e2e lost the coin flip. The check now retries for up to 90 seconds.
The Cypress failure screenshots told both stories directly. The devel-template spec drove the DruxtDevelTemplate tool through the search block's missing-template debug UI, but the theming pass added SearchFormBlockUmami.vue, so that block resolves its wrapper and the debug UI can never appear again. The spec now pins the resolution itself - the themed form renders, no debug details - and the tool demo moves to the pattern examples overhaul with a purpose-built unthemed block. router-modes asserted the first h1 on the page, but example routes render inside the site chrome, whose page_title region contributes an empty h1 ahead of the page's own. Target the heading by content.
…ounts The storybook spec's first CI execution failed on an exact option count (expected 28 blocks, the backend has 24) - counts that pin the demo data, not the behaviour, and drift with every backend change. The control assertions now check the list is populated and contains the option the test selects next, and the unexecuted assertions later in the same test get the same treatment before they fail the next run. Counts inside tests that already pass stay exact.
The failure screenshot showed the id control's 24 options render the block ids as text with no value attributes, so the option[value=...] existence checks from the previous fix could never match. Assert the ids appear in the option text instead, which is also what select() matches on.
There are plenty of Druxt examples scattered around, but until now there
hasn't been one place where you can boot a real backend and click
through every core module working against it, with tests to prove it.
This PR consolidates the standalone examples into a suite built around
that idea.
What's in the suite
druxt-site/examples/*pattern pages, and working language switchingdruxt-daisyui(Recipe Box)druxt-tailwind(Meal Planner)druxt/getCollectiondirectlydruxt-bootstrapvue(Content Ops Console)DruxtEntityForm, over a real OAuth2 write pathnode-client(druxt-inspect)DruxtClient/DruxtSchemaalone, running in plain Node. Inspect types, schemas, views, and generate wrapper stubsEach concept app answers a fair question: can Druxt power something that
isn't a brochure site? They share the same Umami backend and each uses a
different slice of the module set.
The backend
docs/drupalis now a self-contained backend for the whole suite:SQLite plus Tome provisioning (no Docker), committed Umami config and
content including Spanish translations, and an OAuth consumer for the
authenticated examples.
Language switching works end to end, which took two local patches
(
docs/drupal/patches/README.mdexplains each): one fordecoupled_router #3111456
so the language resolves from the requested path, and a rework of
druxt #3273228
(MR9) so view routes carry a langcode. Both are intended to become the
replacement patches on their issues. A Cypress multilingual battery pins
the behaviour, including the one known gap: field labels don't translate
yet because schemas have no language dimension.
Framework fixes
Building the suite surfaced three small bugs in the packages themselves,
fixed here with tests: DruxtModule wrapper resolution, how
DruxtEntityForm reports network failures, and how the router store
handles routes.
CI
Each app gets lint and test jobs, plus a manual
examples:previewjobthat boots the backend and all four frontends for live review. The
GitHub workflow is untouched. The added pipeline config is additive.
Trying it
Sign in with
admin / druxt123(demo credentials) to try the writepaths in the Content Ops Console.