feat(intent): AppTestIntentGenerator emits <name>.test + graduate the runner to @aerokit/test#6289
Merged
Merged
Conversation
… runner to @aerokit/test Phase 3 of the generated-app integration-test feature (PROPOSAL_APPTEST.md): - npm/test = @aerokit/test — the generic Playwright runner graduated from the KeyFolders pilot (@keyfolders/kf-test). Reads a module's <name>.test manifest and drives list/crud/rest/multilingual/(opt-in)shell flows against a running instance. Distinct from the in-instance @aerokit/sdk/junit unit-test surface. - AppTestIntentGenerator (@order(900), engine-intent) emits one <name>.test JSON manifest per module — module id, standalone shell + sanitized REST base, id property, languages, and per entity: label/plural/layout/route/nav-group/api/ table (read back from the .model the EDM generator wrote at @order(200), the same source the Harmonia templates consume) + fields/relations (logical data from the intent). Composition detail children and cross-model projections are excluded; the multilingual sample is derived from inline seeds. - '.test' registered as an intent-owned (scrub-managed) extension — it is regenerated every Generate, never hand-edited (a free extension: JS *.test.js carry extension 'js'). Tests: AppTestIntentGeneratorTest (5, pure manifest builder) + an IntentEngineIT assertion (the full pipeline emits orders.test with the right coordinates, the Order document layout, Customer manage-list, the multilingual Country, and the excluded OrderItem detail). formatter:validate clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| await cfg.beforeCreate?.(page, record); | ||
| await fillForm(page, manifest, entity, record, relationSamples, opts); | ||
| await page.getByRole('button', { name: 'Create' }).click(); | ||
| await expect(page).toHaveURL(new RegExp(entity.route.replace(/[#/]/g, '\\$&') + '$')); |
| await expect(page).toHaveURL(/\/edit$/); | ||
| await fillField(page, handle, updated, opts); | ||
| await page.getByRole('button', { name: 'Save' }).click(); | ||
| await expect(page).toHaveURL(new RegExp(entity.route.replace(/[#/]/g, '\\$&') + '$')); |
| const manifest = typeof manifestRef === 'string' ? JSON.parse(fs.readFileSync(manifestRef, 'utf8')) : manifestRef; | ||
| for (const entity of manifest.entities ?? []) { | ||
| test.describe(`${manifest.module} / ${entity.name}`, () => { | ||
| listFlow(manifest, entity, opts); |
| listFlow(manifest, entity, opts); | ||
| crudFlow(manifest, entity, opts); | ||
| restFlow(manifest, entity, opts); | ||
| multilingualFlow(manifest, entity, opts); |
| crudFlow(manifest, entity, opts); | ||
| restFlow(manifest, entity, opts); | ||
| multilingualFlow(manifest, entity, opts); | ||
| shellFlow(manifest, entity, opts); |
| String baseName = IntentNaming.baseName(context); | ||
| Map<String, Map<String, Object>> edmEntities = readModelEntities(context, baseName); | ||
| if (edmEntities.isEmpty()) { | ||
| LOGGER.debug("Skipping app-test manifest for [{}] — no .model entities to describe", baseName); |
|
|
||
| Map<String, Object> manifest = buildManifest(baseName, context.getProjectName(), model, edmEntities); | ||
| context.writeModelFile(baseName + ".test", GSON.toJson(manifest) + "\n"); | ||
| LOGGER.debug("Generated app-test manifest [{}.test]", baseName); |
| } | ||
| } | ||
| } catch (RuntimeException e) { | ||
| LOGGER.warn("Failed to read the .model for the app-test manifest of [{}]; skipping", baseName, e); |
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
Phase 3 of the generated-app integration-test feature (
PROPOSAL_APPTEST.md, piloted in KeyFolders as@keyfolders/kf-test+ a hand-writtenkf-mod-countries.test). Nothing today verifies that a generated app actually does what its.intentsays; this lands the upstream generator + graduates the runner.Pieces
1.
npm/test=@aerokit/test(the runner). The generic Playwright runner graduated from the pilot. It reads a module's<name>.testmanifest (WHAT the app promises) and knows HOW to verify each promise against the generated Harmonia UI + reused REST controllers — flows: list, crud, rest (same contract, no browser), multilingual, and opt-in shell. Test records carry anAPPTEST-prefix and are cleaned up; seed data is never mutated. This is the app-integration surface (browser + REST from outside), distinct from the in-instance@aerokit/sdk/junitunit-test facade.2.
AppTestIntentGenerator(@Order(900), engine-intent). Emits one<name>.testJSON manifest per module. It runs afterEdmIntentGenerator(200) and reads the.modelback for the resolved per-entity metadata (perspective → sanitized REST path,dataName→ table,menuLabel→ plural, layout type, nav group,multilingual) — the same source the Harmonia templates consume, so paths never drift — while taking the logical field/relation data (type/required/unique/length/major, dropdown relations) straight from the intent. Composition detail children and cross-model projections are excluded (no standalone list); the multilingual sample is derived from inline base +language:seeds when available.3.
.testregistered as an intent-owned, scrub-managed extension. Regenerated on every Generate, never hand-edited (a free extension — JS*.test.jsfiles carry extensionjs, no clash). The per-moduletest/harness at the repo root references it but does not own it.Manifest shape (generated)
{ "module": "orders", "standaloneShell": "/services/web/<project>/gen/orders/index.html", "restBase": "/services/java/<project>/gen/orders/api", "idProperty": "Id", "languages": ["en", "bg"], "entities": [ { "name": "Customer", "label": "Customer", "labelPlural": "Customers", "layout": "manage-list", "route": "#/Customer", "navGroup": "...", "api": "/.../CustomerController", "table": "...", "fields": [...], "relations": [...] } ] }Tests
AppTestIntentGeneratorTest— 5 unit tests over the pure manifest builder (module coordinates, entity metadata + fields, multilingual sample from seeds, dropdown relations, detail/projection exclusion).IntentEngineIT— the full parse → generate pipeline now also assertsorders.testis written with the sanitized REST base, theOrderdocument layout, theCustomermanage-list + controller/route, the multilingualCountry, and the excludedOrderItemcomposition detail.mvn formatter:validateclean on both touched modules.Follow-ups (Phase 4, per PROPOSAL_APPTEST.md / kf-catalog BACKLOG)
The
kf-inframodule-testCI workflow and rolling the 4-filetest/harness across modules (mirroring the countries pilot) land next; document/roll-up/calendar/report runner flows (Phase 2) extend the manifest schema + runner beyond the list/crud/rest/multilingual set.🤖 Generated with Claude Code