From e8c64d7ef932ccc94022ca79591d2d349854948b Mon Sep 17 00:00:00 2001 From: abap2UI5 protocol Date: Sat, 3 Oct 2026 15:58:24 +0000 Subject: [PATCH 1/3] Specification, schemas and backend conformance suite of protocol 2 The first version of the abap2UI5 protocol repository: - spec/: the core protocol (transport, request, response, actions, sessions, navigation, errors, versioning), derived from abap2UI5 1.146.0 with every normative statement traced to its source file and method; accidental behaviour recorded as implementation notes - profiles/: the UI5 profile, the portable profile v1 (61 controls + 4 layout-data elements from the census of 247 core apps, machine-readable as portable-v1.json, coverage curve, Web Components mapping) and the semantic profile (agent snapshot v1, moved here from mcp-server) - schema/: JSON Schemas 2020-12 for request, response, snapshot and the portable profile - conformance/backend: abap2ui5-conformance backend --url ... with 75 checks (core + ui5 profile) and a library API; conformance/frontend reserved and described - conformance/apps: the Z2UI5_CL_CONF_* apps in ABAP (abapGit, abaplint-clean) and as cap2UI5 apps; hosts for @abap2ui5/node-runtime and cap2UI5 - traffic/: recorded traffic of both backends (suite, the UI5 frontend's request code, the agent client) - every message validates - node:test gate and CI on Node 22/24 running the suite against both reference backends: node-runtime 75/75, cap2ui5 74/75 (one SHOULD warning, filed in conformance/RESULTS.md) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Kz1J78phfNKbqUaq3VcP4U --- .github/workflows/ci.yml | 68 + .gitignore | 8 + AGENTS.md | 74 + CHANGELOG.md | 22 + CLAUDE.md | 8 + README.md | 103 +- conformance/README.md | 21 + conformance/RESULTS.md | 86 + conformance/apps/README.md | 131 + .../apps/abap/z2ui5_cl_conf_actions.clas.abap | 88 + .../apps/abap/z2ui5_cl_conf_actions.clas.xml | 16 + .../apps/abap/z2ui5_cl_conf_bind.clas.abap | 159 + .../apps/abap/z2ui5_cl_conf_bind.clas.xml | 16 + .../apps/abap/z2ui5_cl_conf_echo.clas.abap | 92 + .../apps/abap/z2ui5_cl_conf_echo.clas.xml | 16 + .../apps/abap/z2ui5_cl_conf_error.clas.abap | 68 + .../apps/abap/z2ui5_cl_conf_error.clas.xml | 16 + .../apps/abap/z2ui5_cl_conf_msg.clas.abap | 80 + .../apps/abap/z2ui5_cl_conf_msg.clas.xml | 16 + .../apps/abap/z2ui5_cl_conf_nav.clas.abap | 69 + .../apps/abap/z2ui5_cl_conf_nav.clas.xml | 16 + .../apps/abap/z2ui5_cl_conf_nav_tgt.clas.abap | 66 + .../apps/abap/z2ui5_cl_conf_nav_tgt.clas.xml | 16 + .../apps/abap/z2ui5_cl_conf_route.clas.abap | 70 + .../apps/abap/z2ui5_cl_conf_route.clas.xml | 16 + .../apps/abap/z2ui5_cl_conf_slots.clas.abap | 181 + .../apps/abap/z2ui5_cl_conf_slots.clas.xml | 16 + conformance/apps/abaplint.jsonc | 388 + conformance/apps/cap2ui5/actions.js | 41 + conformance/apps/cap2ui5/bind.js | 76 + conformance/apps/cap2ui5/echo.js | 50 + conformance/apps/cap2ui5/error.js | 30 + conformance/apps/cap2ui5/msg.js | 39 + conformance/apps/cap2ui5/nav.js | 59 + conformance/apps/cap2ui5/route.js | 33 + conformance/apps/cap2ui5/slots.js | 98 + conformance/backend/README.md | 163 + .../backend/bin/abap2ui5-conformance.mjs | 100 + conformance/backend/index.mjs | 18 + conformance/backend/lib/checks/actions.mjs | 42 + conformance/backend/lib/checks/errors.mjs | 60 + conformance/backend/lib/checks/events.mjs | 67 + conformance/backend/lib/checks/index.mjs | 17 + conformance/backend/lib/checks/messages.mjs | 51 + conformance/backend/lib/checks/model.mjs | 138 + conformance/backend/lib/checks/navigation.mjs | 192 + conformance/backend/lib/checks/response.mjs | 111 + conformance/backend/lib/checks/sessions.mjs | 74 + conformance/backend/lib/checks/slots.mjs | 103 + conformance/backend/lib/checks/transport.mjs | 108 + conformance/backend/lib/checks/ui5.mjs | 99 + conformance/backend/lib/client.mjs | 112 + conformance/backend/lib/report.mjs | 19 + conformance/backend/lib/runner.mjs | 186 + conformance/backend/lib/schema.mjs | 162 + conformance/frontend/README.md | 52 + conformance/hosts/cap2ui5/package-lock.json | 1111 +++ conformance/hosts/cap2ui5/package.json | 27 + conformance/hosts/cap2ui5/serve.mjs | 91 + conformance/hosts/node-runtime/build.mjs | 107 + conformance/hosts/node-runtime/serve.mjs | 44 + package-lock.json | 1135 +++ package.json | 63 + profiles/README.md | 26 + profiles/portable-coverage.md | 274 + profiles/portable-v1.json | 5010 +++++++++++ profiles/portable.md | 602 ++ profiles/semantic.md | 562 ++ profiles/ui5.md | 96 + schema/portable-profile.schema.json | 380 + schema/request.schema.json | 273 + schema/response.schema.json | 197 + schema/snapshot.schema.json | 108 + scripts/gen-check-list.mjs | 27 + scripts/gen-portable-profile.mjs | 203 + scripts/lib/ui5-frontend.mjs | 150 + scripts/record-traffic.mjs | 202 + scripts/render-portable.mjs | 71 + scripts/run-conformance.mjs | 42 + spec/README.md | 80 + spec/actions.md | 84 + spec/core.md | 112 + spec/errors.md | 81 + spec/navigation.md | 175 + spec/request.md | 176 + spec/response.md | 202 + spec/sessions.md | 107 + spec/transport.md | 203 + spec/versioning.md | 57 + test/apps.test.mjs | 54 + test/backends.test.mjs | 40 + test/docs.test.mjs | 92 + test/lib/traffic.mjs | 49 + test/portable.test.mjs | 47 + test/runner.test.mjs | 80 + test/schema.test.mjs | 89 + test/traffic.test.mjs | 43 + tools/portable-census/README.md | 28 + tools/portable-census/build.mjs | 88 + tools/portable-census/census.mjs | 431 + tools/portable-census/coverage.mjs | 98 + tools/portable-census/features.mjs | 96 + tools/portable-census/gen-coverage.mjs | 105 + tools/portable-census/gen-proposal.mjs | 110 + tools/portable-census/merge.mjs | 22 + tools/portable-census/proposal-head.md | 36 + tools/portable-census/proposal-tail.md | 113 + tools/portable-census/v1.json | 1 + tools/portable-census/v11.json | 1 + traffic/cap2ui5/agent-client.json | 1134 +++ traffic/cap2ui5/suite.json | 7979 +++++++++++++++++ traffic/cap2ui5/ui5-frontend.json | 941 ++ traffic/node-runtime/agent-client.json | 1125 +++ traffic/node-runtime/suite.json | 7835 ++++++++++++++++ traffic/node-runtime/ui5-frontend.json | 927 ++ 115 files changed, 37866 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 conformance/README.md create mode 100644 conformance/RESULTS.md create mode 100644 conformance/apps/README.md create mode 100644 conformance/apps/abap/z2ui5_cl_conf_actions.clas.abap create mode 100644 conformance/apps/abap/z2ui5_cl_conf_actions.clas.xml create mode 100644 conformance/apps/abap/z2ui5_cl_conf_bind.clas.abap create mode 100644 conformance/apps/abap/z2ui5_cl_conf_bind.clas.xml create mode 100644 conformance/apps/abap/z2ui5_cl_conf_echo.clas.abap create mode 100644 conformance/apps/abap/z2ui5_cl_conf_echo.clas.xml create mode 100644 conformance/apps/abap/z2ui5_cl_conf_error.clas.abap create mode 100644 conformance/apps/abap/z2ui5_cl_conf_error.clas.xml create mode 100644 conformance/apps/abap/z2ui5_cl_conf_msg.clas.abap create mode 100644 conformance/apps/abap/z2ui5_cl_conf_msg.clas.xml create mode 100644 conformance/apps/abap/z2ui5_cl_conf_nav.clas.abap create mode 100644 conformance/apps/abap/z2ui5_cl_conf_nav.clas.xml create mode 100644 conformance/apps/abap/z2ui5_cl_conf_nav_tgt.clas.abap create mode 100644 conformance/apps/abap/z2ui5_cl_conf_nav_tgt.clas.xml create mode 100644 conformance/apps/abap/z2ui5_cl_conf_route.clas.abap create mode 100644 conformance/apps/abap/z2ui5_cl_conf_route.clas.xml create mode 100644 conformance/apps/abap/z2ui5_cl_conf_slots.clas.abap create mode 100644 conformance/apps/abap/z2ui5_cl_conf_slots.clas.xml create mode 100644 conformance/apps/abaplint.jsonc create mode 100644 conformance/apps/cap2ui5/actions.js create mode 100644 conformance/apps/cap2ui5/bind.js create mode 100644 conformance/apps/cap2ui5/echo.js create mode 100644 conformance/apps/cap2ui5/error.js create mode 100644 conformance/apps/cap2ui5/msg.js create mode 100644 conformance/apps/cap2ui5/nav.js create mode 100644 conformance/apps/cap2ui5/route.js create mode 100644 conformance/apps/cap2ui5/slots.js create mode 100644 conformance/backend/README.md create mode 100755 conformance/backend/bin/abap2ui5-conformance.mjs create mode 100644 conformance/backend/index.mjs create mode 100644 conformance/backend/lib/checks/actions.mjs create mode 100644 conformance/backend/lib/checks/errors.mjs create mode 100644 conformance/backend/lib/checks/events.mjs create mode 100644 conformance/backend/lib/checks/index.mjs create mode 100644 conformance/backend/lib/checks/messages.mjs create mode 100644 conformance/backend/lib/checks/model.mjs create mode 100644 conformance/backend/lib/checks/navigation.mjs create mode 100644 conformance/backend/lib/checks/response.mjs create mode 100644 conformance/backend/lib/checks/sessions.mjs create mode 100644 conformance/backend/lib/checks/slots.mjs create mode 100644 conformance/backend/lib/checks/transport.mjs create mode 100644 conformance/backend/lib/checks/ui5.mjs create mode 100644 conformance/backend/lib/client.mjs create mode 100644 conformance/backend/lib/report.mjs create mode 100644 conformance/backend/lib/runner.mjs create mode 100644 conformance/backend/lib/schema.mjs create mode 100644 conformance/frontend/README.md create mode 100644 conformance/hosts/cap2ui5/package-lock.json create mode 100644 conformance/hosts/cap2ui5/package.json create mode 100644 conformance/hosts/cap2ui5/serve.mjs create mode 100644 conformance/hosts/node-runtime/build.mjs create mode 100644 conformance/hosts/node-runtime/serve.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 profiles/README.md create mode 100644 profiles/portable-coverage.md create mode 100644 profiles/portable-v1.json create mode 100644 profiles/portable.md create mode 100644 profiles/semantic.md create mode 100644 profiles/ui5.md create mode 100644 schema/portable-profile.schema.json create mode 100644 schema/request.schema.json create mode 100644 schema/response.schema.json create mode 100644 schema/snapshot.schema.json create mode 100644 scripts/gen-check-list.mjs create mode 100644 scripts/gen-portable-profile.mjs create mode 100644 scripts/lib/ui5-frontend.mjs create mode 100644 scripts/record-traffic.mjs create mode 100644 scripts/render-portable.mjs create mode 100644 scripts/run-conformance.mjs create mode 100644 spec/README.md create mode 100644 spec/actions.md create mode 100644 spec/core.md create mode 100644 spec/errors.md create mode 100644 spec/navigation.md create mode 100644 spec/request.md create mode 100644 spec/response.md create mode 100644 spec/sessions.md create mode 100644 spec/transport.md create mode 100644 spec/versioning.md create mode 100644 test/apps.test.mjs create mode 100644 test/backends.test.mjs create mode 100644 test/docs.test.mjs create mode 100644 test/lib/traffic.mjs create mode 100644 test/portable.test.mjs create mode 100644 test/runner.test.mjs create mode 100644 test/schema.test.mjs create mode 100644 test/traffic.test.mjs create mode 100644 tools/portable-census/README.md create mode 100644 tools/portable-census/build.mjs create mode 100644 tools/portable-census/census.mjs create mode 100644 tools/portable-census/coverage.mjs create mode 100644 tools/portable-census/features.mjs create mode 100644 tools/portable-census/gen-coverage.mjs create mode 100644 tools/portable-census/gen-proposal.mjs create mode 100644 tools/portable-census/merge.mjs create mode 100644 tools/portable-census/proposal-head.md create mode 100644 tools/portable-census/proposal-tail.md create mode 100644 tools/portable-census/v1.json create mode 100644 tools/portable-census/v11.json create mode 100644 traffic/cap2ui5/agent-client.json create mode 100644 traffic/cap2ui5/suite.json create mode 100644 traffic/cap2ui5/ui5-frontend.json create mode 100644 traffic/node-runtime/agent-client.json create mode 100644 traffic/node-runtime/suite.json create mode 100644 traffic/node-runtime/ui5-frontend.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b6a1539 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +# The gate: npm test on Node 22 and 24 - schemas against the recorded +# traffic (shipped validator and ajv), docs links and anchors, the portable +# profile, the conformance apps' abapGit format, the CLI, and the backend +# suite against BOTH reference backends: +# node-runtime @abap2ui5/node-runtime + the ABAP apps, transpiled in the job +# (open-abap-core fetched with git, cached by its commit) +# cap2ui5 a CAP project + @cap2ui5/cds-plugin + the JS apps +# plus abaplint over the ABAP apps. Each backend run takes seconds; the +# install dominates. +name: ci + +on: + push: + branches: [ main ] + pull_request: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + node: [ '22', '24' ] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: ${{ matrix.node }} + cache: npm + cache-dependency-path: | + package-lock.json + conformance/hosts/cap2ui5/package-lock.json + - run: npm ci + - name: open-abap-core commit of the runtime + id: core-sha + run: echo "sha=$(node -p "require('@abap2ui5/node-runtime/package.json').abap2ui5.openAbapCore")" >> "$GITHUB_OUTPUT" + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .cache/open-abap-core + key: open-abap-core-${{ steps.core-sha.outputs.sha }} + - name: cap2UI5 host + run: npm ci --prefix conformance/hosts/cap2ui5 --no-audit --no-fund + - name: abaplint (ABAP conformance apps) + run: npm run lint:abap + - name: npm test (both backends) + run: npm test + - name: backend suite report - node-runtime + if: ${{ !cancelled() }} + run: node scripts/run-conformance.mjs node-runtime --json report-node-runtime.json + - name: backend suite report - cap2ui5 + if: ${{ !cancelled() }} + run: node scripts/run-conformance.mjs cap2ui5 --json report-cap2ui5.json + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: ${{ !cancelled() && matrix.node == '22' }} + with: + name: conformance-reports + path: report-*.json + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96eedb8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +.build/ +.cache/ +deps/ +conformance/apps/deps/ +conformance/hosts/cap2ui5/node_modules/ +conformance/hosts/cap2ui5/srv/apps/ +*.log diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7900664 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,74 @@ +# AGENTS.md — abap2UI5 protocol + +Guidance for agents and contributors. Read before making any change. + +## What this is + +The specification of the abap2UI5 roundtrip protocol (`spec/`), its view +profiles (`profiles/`), JSON Schemas (`schema/`) and conformance suites +(`conformance/`), plus recorded real traffic (`traffic/`). It is +**descriptive first**: the protocol was implemented before it was written +down, and the spec records what the implementations do. + +## Rules + +- **Every normative statement cites its source.** A MUST/SHOULD names the + file and method it comes from, with the abbreviations of + `spec/README.md#sources`. A statement you cannot trace to code is a + proposal - say so, or leave it out. +- **Accidental behaviour is an implementation note, not a rule.** When the + reference does something odd (draft ids that repeat after a leave, the URL + reflected into a 500 body), write it under *Implementation note* - another + implementation must not have to copy it. +- **A rule a backend can break gets a check.** New MUST/SHOULD for backends -> + a check in `conformance/backend/lib/checks/`, its id on the spec's + *Checked by* line, `node scripts/gen-check-list.mjs`. `npm test` fails when + a check cites a missing section or the check list is stale. +- **Behaviour the suite needs lives in a conformance app, twice.** A new app + or event goes into `conformance/apps/README.md`, the ABAP class + (`conformance/apps/abap`, abapGit format, abaplint-clean: + `npm run lint:abap`; also clean in the abap2UI5 linter) AND the cap2UI5 + twin (`conformance/apps/cap2ui5`). `test/traffic.test.mjs` requires both + backends to answer every check identically. +- **Traffic is recorded, never hand-edited.** `npm run record` rewrites + `traffic/`; record again after changing a check or an app. +- **Generated sections are generated.** `conformance/backend/README.md` + (check list), `profiles/portable.md` (between the `portable:*` markers, + from `profiles/portable-v1.json`) - run `npm run generate`. +- **`profiles/semantic.md` is the normative snapshot v1** (moved from + abap2UI5/mcp-server `docs/agent-snapshot.md`). Changes to the snapshot + shape are made here first; implementations follow. +- **The protocol number moves only with a breaking change** + (`spec/versioning.md`). Additive changes keep protocol 2. +- **Do not edit other repositories from here.** A difference found in a + backend is filed in `conformance/RESULTS.md` (and upstream), not patched. + +## Layout + +| Path | | +|---|---| +| `spec/` | the core protocol, one file per area | +| `profiles/` | UI5, portable (v1 + `portable-v1.json` + coverage) and semantic profiles | +| `schema/` | JSON Schemas 2020-12 | +| `conformance/backend/` | the backend suite - the published package's entry (`index.mjs`, `bin/`) | +| `conformance/frontend/` | reserved for the frontend suite | +| `conformance/apps/` | the conformance apps (ABAP + cap2UI5) and their abaplint config | +| `conformance/hosts/` | `node-runtime` (build + serve) and `cap2ui5` (a CAP project) reference hosts | +| `traffic/` | recorded traffic per backend: `suite.json`, `ui5-frontend.json`, `agent-client.json` | +| `scripts/` | recorder, runner, generators; `scripts/lib/ui5-frontend.mjs` runs the UI5 frontend's request code in Node | +| `tools/portable-census/` | the census scripts behind the portable profile, as run | +| `test/` | `node:test` - `npm test` | + +## Gates + +`npm test` is the gate: schemas (shipped validator vs ajv, strict mode), +recorded traffic, cross-backend equality, docs links and anchors, the +portable profile, the conformance apps' abapGit format, the CLI, and the +full suite against both reference backends (`PROTOCOL_SKIP_BACKENDS=1` +skips those; the cap2UI5 run skips itself when its host is not installed). +CI runs the same on Node 22 and 24. + +## Style + +ES modules, Node 22+, no runtime dependencies in `conformance/backend/`. +English, ASCII in source files. Markdown wrapped at ~78 columns. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7080602 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +## 0.1.0 - 2026-10-03 + +First version. + +- **Specification** of protocol 2 (revision 0.1), derived from abap2UI5 + 1.146.0: core (`spec/core.md`), transport, request, response, actions, + sessions, navigation, errors, versioning - every normative statement + traced to its source. +- **View profiles:** UI5 (`profiles/ui5.md`); portable v1 + (`profiles/portable.md`, `portable-v1.json`, `portable-coverage.md`) from a + census of 247 core apps and 642 samples-controls ports; semantic - agent + snapshot v1, moved here from abap2UI5/mcp-server as the normative version. +- **JSON Schemas** (2020-12) for the request, the response, the snapshot and + the portable profile. +- **Backend conformance suite** (`abap2ui5-conformance backend`, library + `runBackendSuite`): 75 checks, profiles `core` and `ui5`. +- **Conformance apps** `Z2UI5_CL_CONF_*` in ABAP (abapGit) and as cap2UI5 + apps; reference hosts for `@abap2ui5/node-runtime` and cap2UI5. +- **Recorded traffic** of both reference backends (suite, UI5 frontend code, + agent client). Both pass every MUST; cap2UI5 warns on `error.details`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fc563b2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,8 @@ +# CLAUDE.md + +All project guidance lives in **[AGENTS.md](AGENTS.md)** — the single source of +truth for this repository (what is normative and how it is derived, the +spec/schema/conformance layout, the generated sections, the reference +backends and the test gates). + +Read `AGENTS.md` before making any change. diff --git a/README.md b/README.md index e3b925c..1a71ce8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,101 @@ -# protocol -test +# abap2UI5 protocol + +**The written, versioned contract between abap2UI5 frontends and backends - +with JSON Schemas and conformance suites, so either side can be swapped.** + +abap2UI5 is server-driven UI ("UI5 over the wire"): the browser frontend +sends one JSON roundtrip per user event - the event, the model delta, the +draft id - and the backend runs the app's `main( )` and answers with views, +the model, messages and frontend actions. That protocol is spoken today by +three backends (abap2UI5 on an ABAP system, the same framework transpiled to +Node as `@abap2ui5/node-runtime`, cap2UI5 on CAP) and three frontends (the +UI5 SPA, the MCP server's agent client, the ABAP frontend simulator). This +repository writes it down. + +## The layers + +``` + profiles/ view profiles - what is inside a view string + ui5 full UI5 XML + the .eB/.eF wire forms + the boot page (the UI5 SPA) + portable a 61-control subset every renderer renders (Web Components, native, ...) + semantic agent snapshot v1 - the screen as data (agents, test drivers) + --------------------------------------------------------------------------------- + spec/ the core protocol - mandatory for every frontend and backend + transport one endpoint, GET page / POST roundtrip, headers, CSRF, HEAD, sap-contextid + request S_FRONT, events and their arguments, the model delta (__delta), CONFIG + response S_FRONT, T_SYSTEM / T_CUSTOM, the five view slots, MODEL, processing order + actions follow-up actions, messages + sessions drafts, draft ids as snapshots, expiry, failed roundtrips + navigation app start resolution, app stack, routes, app-state hash, ROUTER action + errors the 500 text body and what a frontend does with it + versioning PROTOCOL 2, compatible changes + schema/ JSON Schemas (2020-12): request, response, snapshot, portable profile + conformance/ + backend the backend suite - plays the frontend over HTTP (implemented) + frontend the frontend suite - plays the backend (reserved) + apps the conformance apps: ABAP classes + cap2UI5 twins + hosts the two reference backends, started with the apps deployed + traffic/ real traffic of both reference backends and three frontends +``` + +Start with [spec/core.md](spec/core.md). Every normative statement cites the +source file and method it was derived from ([spec/README.md](spec/README.md#sources)). + +## Status + +- **Protocol 2**, specification revision 0.1, derived from abap2UI5 1.146.0 + (commit `b812079`). +- **Backend suite: 75 checks** (61 MUST, 14 SHOULD; 68 core, 7 UI5 profile). + Green against both reference backends - `node-runtime` 75/75, + `cap2ui5` 74/75 with one SHOULD warning ([conformance/RESULTS.md](conformance/RESULTS.md)). +- **Schemas** validate all 334 recorded requests, 320 responses and 18 agent + snapshots, checked by the shipped validator and by ajv. +- **Portable profile v1** filled from a census of 247 core apps (73.7 % by + controls, 64.8 % run unchanged). +- Frontend suite: reserved ([conformance/frontend/README.md](conformance/frontend/README.md)). + Not yet run against an ABAP system. + +## Run the backend suite + +```bash +npm ci + +# against any backend that serves the conformance apps +npx abap2ui5-conformance backend --url http://localhost:3000/ --profile ui5 + +# against the reference backends +npm run conformance:node-runtime # transpiles the ABAP apps, serves, runs +npm ci --prefix conformance/hosts/cap2ui5 +npm run conformance:cap2ui5 +``` + +To test your backend: deploy the [conformance apps](conformance/apps/README.md) +(abapGit for ABAP; the JS twins for cap2UI5), point `--url` at the endpoint. +Options, the library API and the check list: +[conformance/backend/README.md](conformance/backend/README.md). + +## Develop + +```bash +npm test # schemas, traffic, docs, portable profile, CLI, and both backends + # (PROTOCOL_SKIP_BACKENDS=1 skips the two backend runs) +npm run record # re-record traffic/ from both backends +npm run lint:abap # abaplint over the ABAP conformance apps +npm run generate # regenerate the check list and the portable-profile sections +``` + +Node 22 or later. The package has no runtime dependencies; the dev +dependencies are the reference backend (`@abap2ui5/node-runtime`, the +transpiler, express), abaplint and ajv. + +## Related + +[abap2UI5](https://github.com/abap2UI5/abap2UI5) (backend + UI5 frontend) · +[cap2UI5](https://github.com/cap2UI5/cap2UI5) · +[mcp-server](https://github.com/abap2UI5/mcp-server) (agent client, snapshot implementation) · +[headless-frontend](https://github.com/abap2UI5/headless-frontend) · +[linter](https://github.com/abap2UI5/linter) + +## License + +MIT diff --git a/conformance/README.md b/conformance/README.md new file mode 100644 index 0000000..0abc85a --- /dev/null +++ b/conformance/README.md @@ -0,0 +1,21 @@ +# Conformance + +Conformance is tested in both directions, so backends and frontends can be +swapped independently: + +| Suite | Plays | Tests | Status | +|---|---|---|---| +| [backend/](backend/README.md) | the frontend | a backend, over HTTP, against the conformance apps | implemented - 75 checks, `abap2ui5-conformance backend` | +| [frontend/](frontend/README.md) | the backend | a frontend, by scripted responses | reserved | + +The **conformance apps** ([apps/](apps/README.md)) are what a backend serves +for the backend suite: small apps with exactly specified behaviour, shipped +as ABAP classes (for abap2UI5 and every backend that runs ABAP apps) and as +cap2UI5 JavaScript apps. + +The **reference hosts** ([hosts/](hosts/)) start the two reference backends +with the apps deployed: `hosts/node-runtime` (`@abap2ui5/node-runtime`, the +ABAP apps transpiled on the fly) and `hosts/cap2ui5` (a minimal CAP project +with `@cap2ui5/cds-plugin` and the JavaScript apps). + +Results per backend and the differences found: [RESULTS.md](RESULTS.md). diff --git a/conformance/RESULTS.md b/conformance/RESULTS.md new file mode 100644 index 0000000..abb9e39 --- /dev/null +++ b/conformance/RESULTS.md @@ -0,0 +1,86 @@ +# Results + +Backend suite, profile `ui5` (75 checks: 68 core, 7 UI5-profile; 61 MUST, +14 SHOULD), run 2026-10-03 with `npm run conformance:`. The traffic +of these runs is recorded in [`../traffic/`](../traffic/). + +| Backend | Version | Apps | Pass | Fail | Warn | Skip | Verdict | +|---|---|---|---:|---:|---:|---:|---| +| `node-runtime` | `@abap2ui5/node-runtime` 1.146.0 (abap2UI5 1.146.0, transpiled) | the ABAP apps, transpiled with `@abaplint/transpiler-cli` 2.13.93 | 75 | 0 | 0 | 0 | conformant, `core` and `ui5` | +| `cap2ui5` | `@cap2ui5/cds-plugin` 0.4.0 on `@abap2ui5/node-runtime` 1.146.0, `@sap/cds` 10 | the JavaScript apps | 74 | 0 | 1 | 0 | conformant, `core` and `ui5` | + +Per area (both backends identical except `errors`): + +| Area | Checks | node-runtime | cap2ui5 | +|---|---:|---|---| +| transport | 8 | 8 pass | 8 pass | +| response | 8 | 8 pass | 8 pass | +| event | 5 | 5 pass | 5 pass | +| model | 10 | 10 pass | 10 pass | +| message | 4 | 4 pass | 4 pass | +| slots | 7 | 7 pass | 7 pass | +| action | 3 | 3 pass | 3 pass | +| nav / route | 14 | 14 pass | 14 pass | +| session | 5 | 5 pass | 5 pass | +| error | 4 | 4 pass | 3 pass, 1 warn (`error.details`) | +| ui5 | 7 | 7 pass | 7 pass | + +The ABAP system itself (abap2UI5 on NetWeaver / ABAP Cloud) has not been run +yet - it needs a system with the apps pulled in by abapGit; the transpiled +runtime is the same framework source. + +## Differences between the two backends + +`test/traffic.test.mjs` compares the two recordings check by check - status, +media type and JSON body, draft ids normalized - and finds them +**identical** apart from the following. Both are the same framework +(cap2UI5 hosts `@abap2ui5/node-runtime` rather than porting it), so this +was the expected outcome; what differs is what the host adds around it. + +1. **Error body of a JavaScript app's exception** - *filed difference, + within the spec.* A `throw` in a cap2UI5 JavaScript app is no ABAP + exception: it passes the framework's `CATCH cx_root` and reaches the + plugin's own catch, which answers `500 text/plain` with + `roundtrip failed ()` and logs the cause server-side + (cap2UI5 `plugin/cds-plugin.js`, `roundtrip`). Status, media type and the + draft handling are the framework's (`error.app-exception`, + `session.failed-roundtrip-not-persisted` pass); only the body text names + nothing, so `error.details` (SHOULD) warns. + *Spec decision:* the content of an error body is not normative + ([../spec/errors.md](../spec/errors.md#the-error-response)); naming the + failure is a SHOULD with "MAY hide details on purpose", which is exactly + the reference's own `check_hide_error_details`. *Filed for cap2UI5:* the + masking is deliberate (no reconnaissance for clients), but it differs from + the framework's errors on the same route (an unknown draft shows the + framework's full body) - a project setting, or the user exit's + `check_hide_error_details`, could decide both the same way. +2. **`Content-Type` charset spelling of that body** - `text/plain; + charset=utf-8` (cap2UI5, express) vs `text/plain; charset=UTF-8` + (framework). Media types are case-insensitive in their parameters; not a + difference the spec cares about. + +## Spec decisions taken while running the suite + +Places where the implementations made a choice the specification now +records - as a rule, or as an implementation note: + +- **Only bound attributes are the model.** The called app's `MODEL` holds + `INPUT`/`OUTPUT`, not the unbound `HAS_PREV` - normative + ([../spec/response.md](../spec/response.md#model)). +- **An app that binds nothing sends no `MODEL`, even with a display** - + normative exception to "a display carries the model". +- **Draft ids repeat after `nav_app_leave`.** Leaving twice to the same + caller answered the same id twice - implementation note; a frontend must + not assume uniqueness ([../spec/sessions.md](../spec/sessions.md#draft-ids)). +- **An earlier draft id continues from its own snapshot** - normative + (retry safety, [../spec/sessions.md](../spec/sessions.md#drafts-are-snapshots)). +- **Unknown draft on an event = error; on a route = fresh start + toast** - + normative, both behaviours of the reference. +- **The request URL is reflected unescaped into the 500 body** - safe only + as `text/plain` + `nosniff` + a frontend that renders text; recorded as an + implementation note, the headers as SHOULD + ([../spec/errors.md](../spec/errors.md#the-error-response)). +- **`DEFAULT` routing mode on every hop to an app without a mode** - + normative ([../spec/navigation.md](../spec/navigation.md#the-router-action)). +- **Event argument conversion** (`true` -> `X`, `null`/`false` -> empty, + objects -> compact JSON text) - normative, the same on both backends. diff --git a/conformance/apps/README.md b/conformance/apps/README.md new file mode 100644 index 0000000..9fedba9 --- /dev/null +++ b/conformance/apps/README.md @@ -0,0 +1,131 @@ +# Conformance apps + +The backend suite drives a backend through these apps. A backend that wants +to be tested **serves all of them under exactly these class names** (the +CLI's `--app KEY=CLASS` maps a name when a backend cannot use it). Their +behaviour is specified here, observable on the wire only: the view XML each +app displays is the UI5 profile's (the suite reads it only in `ui5`-profile +checks), the **model** and the **actions** are what the core checks compare. + +Reference implementations - behaving identically, byte for byte on the wire +(`test/traffic.test.mjs` compares the recorded traffic): + +- [`abap/`](abap/) - ABAP classes in abapGit format (`.clas.abap` + + `.clas.xml`), built with `z2ui5_cl_ui5_view_builder`, abaplint-clean + against abap2UI5 with its shared app rule set + ([`abaplint.jsonc`](abaplint.jsonc), `npm run lint:abap`) and clean in the + abap2UI5 linter. Pull them into any abap2UI5 system with abapGit, or + transpile them for `@abap2ui5/node-runtime` + ([`../hosts/node-runtime/build.mjs`](../hosts/node-runtime/build.mjs)). +- [`cap2ui5/`](cap2ui5/) - the same apps as cap2UI5 JavaScript apps + (`defineApp`, [`../hosts/cap2ui5/`](../hosts/cap2ui5/package.json)). + +Conventions for every app: + +- The app displays its MAIN view (a `sap.m.Page`) on every roundtrip on + which `check_on_navigated( )` is true, and only then (and on `RERENDER`). +- Model names below are the JSON keys (upper case); in the app they are + lower-case attributes. +- "Pushes the model" means: the response carries `MODEL`, no display. + "No MODEL" means the key is absent. +- An event not listed is ignored (nothing changes, no MODEL, no action). + +## Z2UI5_CL_CONF_ECHO + +Event roundtrips and their arguments. + +| | | +|---|---| +| Model | `COUNT` (integer, 0), `LAST_EVENT` (string, ""), `LAST_ARGS` (string, "") | +| View wires | `ECHO` with the arguments `'alpha'`, `'beta'`; `NOOP`; `PUSH`; `RERENDER` | +| `ECHO` | `COUNT` + 1; `LAST_EVENT` = the event name; `LAST_ARGS` = the event's arguments as the app receives them, joined with `\|` | +| `PUSH` | `COUNT` + 100 - pushes the model | +| `RERENDER` | displays MAIN again (MAIN display + MODEL) | +| `NOOP` | nothing - no MODEL, no action | + +## Z2UI5_CL_CONF_BIND + +The model delta. `CHECK` writes what the backend holds into `SUMMARY`. + +| | | +|---|---| +| Model (initial) | `NAME` "start", `QTY` 1 (integer), `FLAG` false (boolean), `S_ADDR` { `CITY` "Berlin", `ZIP` "10115" }, `T_ITEMS` [{ `ID` 1, `TEXT` "one", `DONE` false }, { 2, "two", false }, { 3, "three", false }], `SUMMARY` "" | +| View | Input `{/NAME}`, StepInput `{/QTY}`, CheckBox `{/FLAG}`, Inputs `{/S_ADDR/CITY}`, `{/S_ADDR/ZIP}`, a Table over `{/T_ITEMS}` with an Input `{TEXT}` and a CheckBox `{DONE}` per row | +| `CHECK` | `SUMMARY` = `NAME;QTY;FLAG;CITY;ZIP;ROWS` with `FLAG` as `X` or empty and `ROWS` = `ID:TEXT:DONE` per row (`DONE` as `X` or empty) joined with `,` - initially `start;1;;Berlin;10115;1:one:,2:two:,3:three:` | +| `ADD_ROW` | appends { `ID` = rows + 1, `TEXT` "new", `DONE` false }, then as `CHECK` | + +## Z2UI5_CL_CONF_MSG + +Messages. + +| | | +|---|---| +| Model | `LAST_ACTION` (string, "") | +| `TOAST` | `["MESSAGE_TOAST", "show", "conformance toast"]` | +| `BOX` | `["MESSAGE_BOX", "error", "conformance box"]` | +| `BOX_CONFIRM` | `["MESSAGE_BOX", "confirm", "conformance confirm", { "actions": ["OK", "CANCEL"], "onClose": "BOX_CLOSED" }]` | +| `BOX_CLOSED` | `LAST_ACTION` = the first event argument (the pressed action) | + +## Z2UI5_CL_CONF_SLOTS + +The slots besides MAIN. + +| | | +|---|---| +| Model | `POPUP_TEXT` (string, "") - bound in the popup | +| View | Buttons `btn_popup`, `btn_popover` (ids), `Nest`, `Nest2`; VBoxes `nest_anchor`, `nest2_anchor` | +| `POPUP_OPEN` | POPUP display: a `Dialog` titled "conformance popup" with an Input `{/POPUP_TEXT}`, a button wired to `POPUP_CLOSE` and one with the frontend close (`cs_event-popup_close`) | +| `POPUP_REPLACE` | displays the popup twice in one roundtrip ("first", then "second") - one POPUP display, "second" | +| `POPUP_CLOSE` | `["VIEW_SLOTS", "destroy", "POPUP"]` | +| `MAIN_AND_POPUP` | calls `popup_display` ("with main") and then `view_display` - displays MAIN, then POPUP | +| `POPOVER_OPEN` | POPOVER display: a `Popover` titled "conformance popover", options `{ "openById": "btn_popover" }` | +| `POPOVER_CLOSE` | `["VIEW_SLOTS", "destroy", "POPOVER"]` | +| `NEST_OPEN` / `NEST2_OPEN` | NEST / NEST2 display: a view with a VBox, text "conformance NEST" / "conformance NEST2", options `{ "id": "nest_anchor" \| "nest2_anchor", "methodInsert": "addItem", "methodDestroy": "removeAllItems" }` | +| `NEST_CLOSE` / `NEST2_CLOSE` | destroy NEST / NEST2 | + +## Z2UI5_CL_CONF_NAV and Z2UI5_CL_CONF_NAV_TGT + +The app stack. + +| | | +|---|---| +| NAV model | `RESULT` (string, ""), `RETURNS` (integer, 0) | +| NAV `CALL` | `nav_app_call` of a new `Z2UI5_CL_CONF_NAV_TGT` with `INPUT` = "from caller" | +| NAV on a navigated roundtrip with the event `RETURNED` | `RESULT` = the target's `OUTPUT` (read from `get_app_prev( )`), `RETURNS` + 1, then display | +| NAV_TGT model | `INPUT` (string), `OUTPUT` (string, "") - `HAS_PREV` is an attribute it does not bind | +| NAV_TGT view | a Page with `showNavButton` = whether there is an app to go back to, `navButtonPress` = the leave wire (`_event_nav_app_leave( )`) | +| NAV_TGT `DONE` | `OUTPUT` = `INPUT` + "!", `nav_app_leave( event = 'RETURNED' )` | + +## Z2UI5_CL_CONF_ROUTE + +Hash routing. + +| | | +|---|---| +| Model | `COUNT` (integer, 0) | +| first roundtrip | `follow_up_action( cs_event-hash_routing )` with `KEEP` | +| `COUNT` | `COUNT` + 1 | +| `CALL` | `nav_app_call` of a new `Z2UI5_CL_CONF_NAV_TGT` with `INPUT` = "from route" | + +## Z2UI5_CL_CONF_ERROR + +The error response. + +| | | +|---|---| +| Model | `COUNT` (integer, 0) | +| `FAIL` | `COUNT` + 1, then an unhandled exception (ABAP: a failing conversion, `CX_SY_CONVERSION_NO_NUMBER`; JavaScript: `throw new Error("CONFORMANCE_FAILURE")`) | +| `COUNT` | `COUNT` + 1 | + +## Z2UI5_CL_CONF_ACTIONS + +Follow-up actions. + +| | | +|---|---| +| Model | `TICKS` (integer, 0), bound to an Input with id `inp` | +| View | also a button with the wired frontend action `SET_TITLE` "wired title" (`follow_up_action( )` as attribute) | +| `FOCUS` | `["SET_FOCUS", "inp"]` | +| `SEVERAL` | `["SET_TITLE", "conformance title"]`, `["MESSAGE_TOAST", "show", "between"]`, `["SET_FOCUS", "inp"]` - in this order | +| `TIMER` | `["START_TIMER", "TICK", "500"]` | +| `TICK` | `TICKS` + 1 | diff --git a/conformance/apps/abap/z2ui5_cl_conf_actions.clas.abap b/conformance/apps/abap/z2ui5_cl_conf_actions.clas.abap new file mode 100644 index 0000000..e4bacdd --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_actions.clas.abap @@ -0,0 +1,88 @@ +" Conformance app ACTIONS - follow-up actions: what an app queues for the +" frontend to do once the view is rendered (T_CUSTOM), in the order it +" queued them, and a frontend event wired into the view. Behaviour: +" conformance/apps/README.md, section ACTIONS. +CLASS z2ui5_cl_conf_actions DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA ticks TYPE i. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS on_event. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_conf_actions IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Page` + )->a( n = `title` v = `conformance - actions` + + )->tag( `Input` + )->a( n = `id` v = `inp` + )->a( n = `value` v = client->_bind( ticks ) + )->tag( `Button` + )->a( n = `text` v = `Focus` + )->a( n = `press` v = client->_event( `FOCUS` ) + )->tag( `Button` + )->a( n = `text` v = `Several` + )->a( n = `press` v = client->_event( `SEVERAL` ) + )->tag( `Button` + )->a( n = `text` v = `Timer` + )->a( n = `press` v = client->_event( `TIMER` ) + )->tag( `Button` + )->a( n = `text` v = `Title here` + )->a( n = `press` v = client->follow_up_action( val = z2ui5_if_client=>cs_event-set_title + t_arg = VALUE #( ( `wired title` ) ) ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + + METHOD on_event. + + CASE client->get_event( ). + WHEN `FOCUS`. + client->follow_up_action( val = z2ui5_if_client=>cs_event-set_focus + t_arg = VALUE #( ( `inp` ) ) ). + WHEN `SEVERAL`. + " three actions of two kinds - they must arrive in this order + client->follow_up_action( val = z2ui5_if_client=>cs_event-set_title + t_arg = VALUE #( ( `conformance title` ) ) ). + client->message_toast_display( `between` ). + client->follow_up_action( val = z2ui5_if_client=>cs_event-set_focus + t_arg = VALUE #( ( `inp` ) ) ). + WHEN `TIMER`. + client->follow_up_action( val = z2ui5_if_client=>cs_event-start_timer + t_arg = VALUE #( ( `TICK` ) ( `500` ) ) ). + WHEN `TICK`. + ticks = ticks + 1. + ENDCASE. + + ENDMETHOD. + +ENDCLASS. diff --git a/conformance/apps/abap/z2ui5_cl_conf_actions.clas.xml b/conformance/apps/abap/z2ui5_cl_conf_actions.clas.xml new file mode 100644 index 0000000..ccd3989 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_actions.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_CONF_ACTIONS + E + protocol conformance - follow-up actions + 1 + X + X + X + + + + diff --git a/conformance/apps/abap/z2ui5_cl_conf_bind.clas.abap b/conformance/apps/abap/z2ui5_cl_conf_bind.clas.abap new file mode 100644 index 0000000..1acf951 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_bind.clas.abap @@ -0,0 +1,159 @@ +" Conformance app BIND - the model delta: a scalar, a number, a boolean, a +" structure and a table edited by row (__delta). CHECK writes what the +" backend holds into SUMMARY, so a client can see which edits arrived. +" Behaviour: conformance/apps/README.md, section BIND. +CLASS z2ui5_cl_conf_bind DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_addr, + city TYPE string, + zip TYPE string, + END OF ty_s_addr. + TYPES: + BEGIN OF ty_s_item, + id TYPE i, + text TYPE string, + done TYPE abap_bool, + END OF ty_s_item. + TYPES ty_t_item TYPE STANDARD TABLE OF ty_s_item WITH EMPTY KEY. + + DATA name TYPE string. + DATA qty TYPE i. + DATA flag TYPE abap_bool. + DATA s_addr TYPE ty_s_addr. + DATA t_items TYPE ty_t_item. + DATA summary TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS on_event. + METHODS summary_build. + METHODS model_init. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_conf_bind IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + model_init( ). + view_display( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + + DATA(page) = view->ele( `Page` + )->a( n = `title` v = `conformance - bind` ). + + page->tag( `Input` + )->a( n = `id` v = `name` + )->a( n = `value` v = client->_bind( name ) + )->tag( `StepInput` + )->a( n = `id` v = `qty` + )->a( n = `value` v = client->_bind( qty ) + )->tag( `CheckBox` + )->a( n = `id` v = `flag` + )->a( n = `text` v = `Flag` + )->a( n = `selected` v = client->_bind( flag ) + )->tag( `Input` + )->a( n = `id` v = `city` + )->a( n = `value` v = client->_bind( s_addr-city ) + )->tag( `Input` + )->a( n = `id` v = `zip` + )->a( n = `value` v = client->_bind( s_addr-zip ) + )->tag( `Text` + )->a( n = `id` v = `summary` + )->a( n = `text` v = client->_bind( summary ) + )->tag( `Button` + )->a( n = `text` v = `Check` + )->a( n = `press` v = client->_event( `CHECK` ) + )->tag( `Button` + )->a( n = `text` v = `Add row` + )->a( n = `press` v = client->_event( `ADD_ROW` ) ). + + page->ele( `Table` + )->a( n = `id` v = `items` + )->a( n = `items` v = client->_bind( t_items ) + + )->ele( `columns` + + )->tag( `Column` + )->tag( `Column` + )->tag( `Column` + + )->end( + )->ele( `items` + )->ele( `ColumnListItem` + )->ele( `cells` + + )->tag( `Text` + )->a( n = `text` v = `{ID}` + )->tag( `Input` + )->a( n = `value` v = `{TEXT}` + )->tag( `CheckBox` + )->a( n = `selected` v = `{DONE}` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + + METHOD on_event. + + CASE client->get_event( ). + WHEN `CHECK`. + summary_build( ). + WHEN `ADD_ROW`. + INSERT VALUE #( id = lines( t_items ) + 1 + text = `new` ) INTO TABLE t_items. + summary_build( ). + ENDCASE. + + ENDMETHOD. + + METHOD summary_build. + + DATA lt_rows TYPE string_table. + + LOOP AT t_items INTO DATA(ls_item). + INSERT |{ ls_item-id }:{ ls_item-text }:{ ls_item-done }| INTO TABLE lt_rows. + ENDLOOP. + + summary = |{ name };{ qty };{ flag };{ s_addr-city };{ s_addr-zip };| && + concat_lines_of( table = lt_rows + sep = `,` ). + + ENDMETHOD. + + METHOD model_init. + + name = `start`. + qty = 1. + s_addr = VALUE #( city = `Berlin` + zip = `10115` ). + t_items = VALUE #( ( id = 1 text = `one` ) + ( id = 2 text = `two` ) + ( id = 3 text = `three` ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/conformance/apps/abap/z2ui5_cl_conf_bind.clas.xml b/conformance/apps/abap/z2ui5_cl_conf_bind.clas.xml new file mode 100644 index 0000000..1e3b35b --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_bind.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_CONF_BIND + E + protocol conformance - model delta + 1 + X + X + X + + + + diff --git a/conformance/apps/abap/z2ui5_cl_conf_echo.clas.abap b/conformance/apps/abap/z2ui5_cl_conf_echo.clas.abap new file mode 100644 index 0000000..b91091e --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_echo.clas.abap @@ -0,0 +1,92 @@ +" Conformance app ECHO - first start, the event roundtrip with arguments, +" a roundtrip that changes nothing, a model push without a view, and a +" re-render. Behaviour: conformance/apps/README.md, section ECHO. +CLASS z2ui5_cl_conf_echo DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA count TYPE i. + DATA last_event TYPE string. + DATA last_args TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS on_event. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_conf_echo IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Page` + )->a( n = `title` v = `conformance - echo` + + )->tag( `Text` + )->a( n = `id` v = `count` + )->a( n = `text` v = client->_bind( count ) + )->tag( `Text` + )->a( n = `id` v = `last_event` + )->a( n = `text` v = client->_bind( last_event ) + )->tag( `Text` + )->a( n = `id` v = `last_args` + )->a( n = `text` v = client->_bind( last_args ) + )->tag( `Button` + )->a( n = `text` v = `Echo` + )->a( n = `press` v = client->_event( val = `ECHO` + t_arg = VALUE #( ( `alpha` ) ( `beta` ) ) ) + )->tag( `Button` + )->a( n = `text` v = `Nothing` + )->a( n = `press` v = client->_event( `NOOP` ) + )->tag( `Button` + )->a( n = `text` v = `Push` + )->a( n = `press` v = client->_event( `PUSH` ) + )->tag( `Button` + )->a( n = `text` v = `Render` + )->a( n = `press` v = client->_event( `RERENDER` ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + + METHOD on_event. + + CASE client->get_event( ). + WHEN `ECHO`. + count = count + 1. + last_event = client->get_event( ). + last_args = concat_lines_of( table = client->get( )-t_event_arg + sep = `|` ). + WHEN `PUSH`. + count = count + 100. + WHEN `RERENDER`. + view_display( ). + WHEN OTHERS. + " NOOP and every unknown event: nothing bound changes, nothing is + " displayed - the response carries no MODEL and no action + ENDCASE. + + ENDMETHOD. + +ENDCLASS. diff --git a/conformance/apps/abap/z2ui5_cl_conf_echo.clas.xml b/conformance/apps/abap/z2ui5_cl_conf_echo.clas.xml new file mode 100644 index 0000000..a023d7a --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_echo.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_CONF_ECHO + E + protocol conformance - event roundtrip and echo + 1 + X + X + X + + + + diff --git a/conformance/apps/abap/z2ui5_cl_conf_error.clas.abap b/conformance/apps/abap/z2ui5_cl_conf_error.clas.abap new file mode 100644 index 0000000..3708cb5 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_error.clas.abap @@ -0,0 +1,68 @@ +" Conformance app ERROR - an unhandled exception in main( ). FAIL raises, +" NOOP answers normally, so a client can show that a failed roundtrip +" leaves the draft it started from usable. Behaviour: +" conformance/apps/README.md, section ERROR. +CLASS z2ui5_cl_conf_error DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA count TYPE i. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_conf_error IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + RETURN. + ENDIF. + + CASE client->get_event( ). + WHEN `FAIL`. + count = count + 1. + " an unhandled exception on purpose, raised by the language itself + " (CX_SY_CONVERSION_NO_NUMBER) rather than by a framework class an + " app must not name - the error body shows it with app and event + count = `CONFORMANCE_FAILURE`. + WHEN `COUNT`. + count = count + 1. + ENDCASE. + + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Page` + )->a( n = `title` v = `conformance - error` + + )->tag( `Text` + )->a( n = `id` v = `count` + )->a( n = `text` v = client->_bind( count ) + )->tag( `Button` + )->a( n = `text` v = `Fail` + )->a( n = `press` v = client->_event( `FAIL` ) + )->tag( `Button` + )->a( n = `text` v = `Count` + )->a( n = `press` v = client->_event( `COUNT` ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/conformance/apps/abap/z2ui5_cl_conf_error.clas.xml b/conformance/apps/abap/z2ui5_cl_conf_error.clas.xml new file mode 100644 index 0000000..10bad01 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_error.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_CONF_ERROR + E + protocol conformance - error response + 1 + X + X + X + + + + diff --git a/conformance/apps/abap/z2ui5_cl_conf_msg.clas.abap b/conformance/apps/abap/z2ui5_cl_conf_msg.clas.abap new file mode 100644 index 0000000..a6ae7eb --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_msg.clas.abap @@ -0,0 +1,80 @@ +" Conformance app MSG - messages: a toast, an error box, and a confirm box +" whose closing raises a backend event with the pressed action as its +" argument. Behaviour: conformance/apps/README.md, section MSG. +CLASS z2ui5_cl_conf_msg DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA last_action TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS on_event. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_conf_msg IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Page` + )->a( n = `title` v = `conformance - messages` + + )->tag( `Text` + )->a( n = `id` v = `last_action` + )->a( n = `text` v = client->_bind( last_action ) + )->tag( `Button` + )->a( n = `text` v = `Toast` + )->a( n = `press` v = client->_event( `TOAST` ) + )->tag( `Button` + )->a( n = `text` v = `Box` + )->a( n = `press` v = client->_event( `BOX` ) + )->tag( `Button` + )->a( n = `text` v = `Confirm` + )->a( n = `press` v = client->_event( `BOX_CONFIRM` ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + + METHOD on_event. + + CASE client->get_event( ). + WHEN `TOAST`. + client->message_toast_display( `conformance toast` ). + WHEN `BOX`. + client->message_box_display( text = `conformance box` + type = `error` ). + WHEN `BOX_CONFIRM`. + client->message_box_display( text = `conformance confirm` + type = `confirm` + onclose = `BOX_CLOSED` + actions = VALUE #( ( `OK` ) ( `CANCEL` ) ) ). + WHEN `BOX_CLOSED`. + last_action = client->get_event_arg( ). + ENDCASE. + + ENDMETHOD. + +ENDCLASS. diff --git a/conformance/apps/abap/z2ui5_cl_conf_msg.clas.xml b/conformance/apps/abap/z2ui5_cl_conf_msg.clas.xml new file mode 100644 index 0000000..ea1e610 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_msg.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_CONF_MSG + E + protocol conformance - messages + 1 + X + X + X + + + + diff --git a/conformance/apps/abap/z2ui5_cl_conf_nav.clas.abap b/conformance/apps/abap/z2ui5_cl_conf_nav.clas.abap new file mode 100644 index 0000000..2bc7b2e --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_nav.clas.abap @@ -0,0 +1,69 @@ +" Conformance app NAV - the app stack: CALL hands the screen to +" Z2UI5_CL_CONF_NAV_TGT, which hands it back with a result (event RETURNED) +" or without one (the reserved leave event). Behaviour: +" conformance/apps/README.md, section NAV. +CLASS z2ui5_cl_conf_nav DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA result TYPE string. + DATA returns TYPE i. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_conf_nav IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + + IF client->check_on_navigated( ). + " a return WITH a result arrives as the event RETURNED (named by the + " target's nav_app_leave) on a navigated roundtrip - read it, then render + IF client->get_event( ) = `RETURNED`. + DATA(lo_target) = CAST z2ui5_cl_conf_nav_tgt( client->get_app_prev( ) ). + result = lo_target->output. + returns = returns + 1. + ENDIF. + view_display( ). + ELSEIF client->check_on_event( `CALL` ). + DATA(lo_call) = NEW z2ui5_cl_conf_nav_tgt( ). + lo_call->input = `from caller`. + client->nav_app_call( lo_call ). + ENDIF. + + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Page` + )->a( n = `title` v = `conformance - nav caller` + + )->tag( `Text` + )->a( n = `id` v = `result` + )->a( n = `text` v = client->_bind( result ) + )->tag( `Text` + )->a( n = `id` v = `returns` + )->a( n = `text` v = client->_bind( returns ) + )->tag( `Button` + )->a( n = `text` v = `Call` + )->a( n = `press` v = client->_event( `CALL` ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/conformance/apps/abap/z2ui5_cl_conf_nav.clas.xml b/conformance/apps/abap/z2ui5_cl_conf_nav.clas.xml new file mode 100644 index 0000000..8bddbd4 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_nav.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_CONF_NAV + E + protocol conformance - navigation caller + 1 + X + X + X + + + + diff --git a/conformance/apps/abap/z2ui5_cl_conf_nav_tgt.clas.abap b/conformance/apps/abap/z2ui5_cl_conf_nav_tgt.clas.abap new file mode 100644 index 0000000..792ddcd --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_nav_tgt.clas.abap @@ -0,0 +1,66 @@ +" Conformance app NAV_TGT - the called app of NAV. DONE leaves with a +" result, the page's back button leaves through the reserved leave event. +" Behaviour: conformance/apps/README.md, section NAV. +CLASS z2ui5_cl_conf_nav_tgt DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA input TYPE string. + DATA output TYPE string. + DATA has_prev TYPE abap_bool. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_conf_nav_tgt IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + has_prev = client->check_app_prev_stack( ). + view_display( ). + RETURN. + ENDIF. + + IF client->check_on_event( `DONE` ). + output = |{ input }!|. + client->nav_app_leave( event = `RETURNED` ). + ENDIF. + + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Page` + )->a( n = `title` v = `conformance - nav target` + )->a( n = `showNavButton` b = has_prev + )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) + + )->tag( `Text` + )->a( n = `id` v = `input` + )->a( n = `text` v = client->_bind( input ) + )->tag( `Text` + )->a( n = `id` v = `output` + )->a( n = `text` v = client->_bind( output ) + )->tag( `Button` + )->a( n = `text` v = `Done` + )->a( n = `press` v = client->_event( `DONE` ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/conformance/apps/abap/z2ui5_cl_conf_nav_tgt.clas.xml b/conformance/apps/abap/z2ui5_cl_conf_nav_tgt.clas.xml new file mode 100644 index 0000000..00024b8 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_nav_tgt.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_CONF_NAV_TGT + E + protocol conformance - navigation target + 1 + X + X + X + + + + diff --git a/conformance/apps/abap/z2ui5_cl_conf_route.clas.abap b/conformance/apps/abap/z2ui5_cl_conf_route.clas.abap new file mode 100644 index 0000000..af060f6 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_route.clas.abap @@ -0,0 +1,70 @@ +" Conformance app ROUTE - hash routing: switches the KEEP mode on in its +" first roundtrip, so every response carries the ROUTER intent, and calls +" Z2UI5_CL_CONF_NAV_TGT to show the history push of a nav_app_call. +" Behaviour: conformance/apps/README.md, section ROUTE. +CLASS z2ui5_cl_conf_route DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA count TYPE i. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_conf_route IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + client->follow_up_action( val = z2ui5_if_client=>cs_event-hash_routing + t_arg = VALUE #( ( z2ui5_if_client=>cs_nav_mode-keep ) ) ). + ENDIF. + IF client->check_on_navigated( ). + view_display( ). + RETURN. + ENDIF. + + CASE client->get_event( ). + WHEN `COUNT`. + count = count + 1. + WHEN `CALL`. + DATA(lo_call) = NEW z2ui5_cl_conf_nav_tgt( ). + lo_call->input = `from route`. + client->nav_app_call( lo_call ). + ENDCASE. + + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `Page` + )->a( n = `title` v = `conformance - route` + + )->tag( `Text` + )->a( n = `id` v = `count` + )->a( n = `text` v = client->_bind( count ) + )->tag( `Button` + )->a( n = `text` v = `Count` + )->a( n = `press` v = client->_event( `COUNT` ) + )->tag( `Button` + )->a( n = `text` v = `Call` + )->a( n = `press` v = client->_event( `CALL` ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/conformance/apps/abap/z2ui5_cl_conf_route.clas.xml b/conformance/apps/abap/z2ui5_cl_conf_route.clas.xml new file mode 100644 index 0000000..8432b56 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_route.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_CONF_ROUTE + E + protocol conformance - hash routing + 1 + X + X + X + + + + diff --git a/conformance/apps/abap/z2ui5_cl_conf_slots.clas.abap b/conformance/apps/abap/z2ui5_cl_conf_slots.clas.abap new file mode 100644 index 0000000..15cc1fc --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_slots.clas.abap @@ -0,0 +1,181 @@ +" Conformance app SLOTS - the four view slots besides MAIN: a popup, a +" popover anchored to a button, and the two nested views, each opened and +" closed by a backend event. Behaviour: conformance/apps/README.md, +" section SLOTS. +CLASS z2ui5_cl_conf_slots DEFINITION PUBLIC FINAL CREATE PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA popup_text TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + METHODS on_event. + METHODS popup_display + IMPORTING + title TYPE string. + METHODS popover_display. + METHODS nest_display + IMPORTING + slot TYPE string. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_conf_slots IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + METHOD view_display. + + DATA(view) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ). + + DATA(page) = view->ele( `Page` + )->a( n = `title` v = `conformance - slots` ). + + page->tag( `Button` + )->a( n = `id` v = `btn_popup` + )->a( n = `text` v = `Popup` + )->a( n = `press` v = client->_event( `POPUP_OPEN` ) + )->tag( `Button` + )->a( n = `id` v = `btn_popover` + )->a( n = `text` v = `Popover` + )->a( n = `press` v = client->_event( `POPOVER_OPEN` ) + )->tag( `Button` + )->a( n = `text` v = `Nest` + )->a( n = `press` v = client->_event( `NEST_OPEN` ) + )->tag( `Button` + )->a( n = `text` v = `Nest2` + )->a( n = `press` v = client->_event( `NEST2_OPEN` ) + )->tag( `VBox` + )->a( n = `id` v = `nest_anchor` + )->tag( `VBox` + )->a( n = `id` v = `nest2_anchor` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + + METHOD on_event. + + CASE client->get_event( ). + WHEN `POPUP_OPEN`. + popup_display( `conformance popup` ). + WHEN `POPUP_REPLACE`. + " two displays of one slot in one roundtrip - the last one counts + popup_display( `first` ). + popup_display( `second` ). + WHEN `POPUP_CLOSE`. + client->popup_destroy( ). + WHEN `MAIN_AND_POPUP`. + " called in the reverse of the slot order on purpose + popup_display( `with main` ). + view_display( ). + WHEN `POPOVER_OPEN`. + popover_display( ). + WHEN `POPOVER_CLOSE`. + client->popover_destroy( ). + WHEN `NEST_OPEN`. + nest_display( z2ui5_if_client=>cs_view-nested ). + WHEN `NEST_CLOSE`. + client->nest_view_destroy( ). + WHEN `NEST2_OPEN`. + nest_display( z2ui5_if_client=>cs_view-nested2 ). + WHEN `NEST2_CLOSE`. + client->nest2_view_destroy( ). + ENDCASE. + + ENDMETHOD. + + METHOD popup_display. + + DATA(popup) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `FragmentDefinition` ns = `core` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:core` v = `sap.ui.core` ). + + popup->ele( `Dialog` + )->a( n = `title` v = title + + )->tag( `Input` + )->a( n = `id` v = `popup_text` + )->a( n = `value` v = client->_bind( popup_text ) + + )->ele( `buttons` + + )->tag( `Button` + )->a( n = `text` v = `Close` + )->a( n = `press` v = client->_event( `POPUP_CLOSE` ) + )->tag( `Button` + )->a( n = `text` v = `Close here` + )->a( n = `press` v = client->follow_up_action( z2ui5_if_client=>cs_event-popup_close ) ). + + client->popup_display( popup->stringify( ) ). + + ENDMETHOD. + + METHOD popover_display. + + DATA(popover) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `FragmentDefinition` ns = `core` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:core` v = `sap.ui.core` ). + + popover->ele( `Popover` + )->a( n = `title` v = `conformance popover` + + )->tag( `Button` + )->a( n = `text` v = `Close` + )->a( n = `press` v = client->_event( `POPOVER_CLOSE` ) ). + + client->popover_display( xml = popover->stringify( ) + by_id = `btn_popover` ). + + ENDMETHOD. + + METHOD nest_display. + + DATA(nest) = z2ui5_cl_ui5_view_builder=>factory( + )->ele( n = `View` ns = `mvc` + )->a( n = `xmlns` v = `sap.m` + )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` + + )->ele( `VBox` + + )->tag( `Text` + )->a( n = `text` t = |conformance { slot }| + )->tag( `Button` + )->a( n = `text` v = `Close` + )->a( n = `press` v = client->_event( |{ slot }_CLOSE| ) ). + + IF slot = z2ui5_if_client=>cs_view-nested. + client->nest_view_display( val = nest->stringify( ) + id = `nest_anchor` + method_insert = `addItem` + method_destroy = `removeAllItems` ). + ELSE. + client->nest2_view_display( val = nest->stringify( ) + id = `nest2_anchor` + method_insert = `addItem` + method_destroy = `removeAllItems` ). + ENDIF. + + ENDMETHOD. + +ENDCLASS. diff --git a/conformance/apps/abap/z2ui5_cl_conf_slots.clas.xml b/conformance/apps/abap/z2ui5_cl_conf_slots.clas.xml new file mode 100644 index 0000000..369d262 --- /dev/null +++ b/conformance/apps/abap/z2ui5_cl_conf_slots.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_CONF_SLOTS + E + protocol conformance - popup, popover, nested views + 1 + X + X + X + + + + diff --git a/conformance/apps/abaplint.jsonc b/conformance/apps/abaplint.jsonc new file mode 100644 index 0000000..9ee1355 --- /dev/null +++ b/conformance/apps/abaplint.jsonc @@ -0,0 +1,388 @@ +// abaplint configuration of the ABAP conformance apps (conformance/apps/abap). +// +// The rule block is abap2UI5's shared app rule set, +// abap2UI5/abap2UI5 .github/abaplint/app-rules.json (version 1), copied +// unchanged - except object_naming, which encodes this repository's prefix +// (Z2UI5_CL_CONF_). The framework dependency is the downported abap2UI5 of +// the @abap2ui5/node-runtime release the suite is run against (npm ci first), +// so the apps are checked against exactly the framework they are transpiled +// with. Run: npm run lint:abap +{ + "global": { + "files": "/abap/**/*.*" + }, + "dependencies": [ + { + "url": "https://github.com/abapedia/steampunk-2305-api-intersect-702", + "folder": "/deps/steampunk", + "files": "/src/**/*.*" + }, + { + "folder": "/../../node_modules/@abap2ui5/node-runtime/downport", + "files": "/**/*.*" + } + ], + "syntax": { + "version": "v750", + "errorNamespace": "." + }, + "rules": { + "7bit_ascii": { + "exclude": [ + "z2ui5_cl_smpc_app_(012|040|092|103|104|114|134|185|191|194|215|218|254|290|302|303|350|356|357|358|360|445|525|558|565|572|575|578|579|580|584|611)\\.clas\\.", + "z2ui5_cl_smpc_app_000\\.clas\\.", + "z2ui5_cl_smpc_demo_\\d+\\.clas\\." + ] + }, + "abapdoc": false, + "add_test_attributes": true, + "aff_and_xml": true, + "align_parameters": false, + "align_pseudo_comments": true, + "align_type_expressions": true, + "allowed_object_naming": true, + "allowed_object_types": { + "allowed": [ + "CLAS", + "DEVC", + "INTF", + "TABL", + "DTEL", + "DDLS", + "BDEF", + "SRVD", + "SRVB", + "SICF", + "SAPC", + "SAMC", + "SUSH", + "SMIM", + "G4BA" + ] + }, + "ambiguous_statement": true, + "avoid_use": { + "skipQuickFix": false, + "define": true, + "statics": true, + "defaultKey": true, + "break": true, + "testSeams": true, + "describeLines": true, + "exportToMemory": true, + "exportToDatabase": true + }, + "begin_end_names": true, + "begin_single_include": true, + "call_transaction_authority_check": true, + "catch_and_raise": true, + "cds_association_name": true, + "cds_comment_style": true, + "cds_field_order": true, + "cds_legacy_view": true, + "cds_naming": { + "basicInterfaceView": "Z2UI5_", + "compositeInterfaceView": "Z2UI5_", + "consumptionView": "Z2UI5_", + "basicRestrictedReuseView": "Z2UI5_", + "compositeRestrictedReuseView": "Z2UI5_", + "privateView": "Z2UI5_", + "remoteAPIView": "Z2UI5_", + "viewExtend": "Z2UI5_", + "extensionIncludeView": "Z2UI5_", + "derivationFunction": "Z2UI5_", + "abstractEntity": "Z2UI5_" + }, + "cds_parser_error": true, + "chain_mainly_declarations": true, + "change_if_to_case": true, + "check_abstract": { + "exclude": [ + "z2ui5_cl_smps_bp_[a-z]+\\.clas\\." + ] + }, + "check_comments": true, + "check_ddic": true, + "check_include": true, + "check_subrc": { + "openDataset": true, + "authorityCheck": true, + "selectSingle": false, + "selectTable": false, + "updateDatabase": true, + "insertDatabase": true, + "modifyDatabase": true, + "deleteDatabase": true, + "readTable": true, + "assign": true, + "find": true + }, + "check_syntax": { + "exclude": [ + "z2ui5_cl_smps_app_489(_ws)?\\.clas\\." + ] + }, + "check_text_elements": true, + "check_transformation_exists": true, + "class_attribute_names": false, + "classic_exceptions_overlap": true, + "clear_exporting_parameters": true, + "cloud_types": true, + "colon_missing_space": true, + "commented_code": true, + "constant_classes": true, + "constructor_visibility_public": true, + "contains_tab": true, + "cyclic_oo": true, + "cyclomatic_complexity": true, + "dangerous_statement": { + "execSQL": true, + "kernelCall": true, + "systemCall": true, + "insertReport": true, + "generateDynpro": true, + "generateReport": true, + "generateSubroutine": true, + "deleteReport": true, + "deleteTextpool": true, + "insertTextpool": true, + "deleteDynpro": true, + "exportDynpro": true, + "editorCallForReport": true, + "dynamicSQL": false, + "ignoreRAPQueryProvider": true + }, + "db_operation_in_loop": true, + "definitions_top": true, + "description_empty": true, + "double_space": { + "keywords": false, + "startParen": true, + "endParen": false, + "afterColon": true + }, + "downport": true, + "dynpro_checks": true, + "easy_to_find_messages": true, + "empty_event": true, + "empty_line_in_statement": false, + "empty_statement": true, + "empty_structure": { + "loop": true, + "loopAllowIfSubrc": true, + "if": true, + "while": true, + "case": true, + "select": true, + "do": true, + "at": true, + "try": true, + "when": false + }, + "exit_or_check": true, + "expand_macros": true, + "exporting": true, + "fm_global_parameters_obsolete": true, + "forbidden_identifier": true, + "forbidden_pseudo_and_pragma": true, + "forbidden_void_type": true, + "form_tables_obsolete": true, + "fully_type_constants": { + "checkData": true, + "exclude": [ + "z2ui5_cl_smps_app_(007|010)\\.clas\\." + ] + }, + "fully_type_itabs": true, + "function_module_recommendations": true, + "functional_writing": true, + "global_class": true, + "identical_conditions": true, + "identical_contents": true, + "identical_descriptions": true, + "identical_form_names": true, + "identical_move": true, + "if_in_if": true, + "implement_methods": true, + "implicit_start_of_selection": true, + "in_statement_indentation": false, + "indentation": false, + "index_completely_contained": true, + "inline_data_old_versions": true, + "intf_referencing_clas": true, + "invalid_table_index": true, + "keep_single_parameter_on_one_line": false, + "keyword_case": { + "style": "upper", + "ignoreExceptions": true, + "ignoreLowerClassImplmentationStatement": true, + "exclude": [ + "z2ui5_cl_smps_" + ], + "ignoreKeywords": [] + }, + "line_break_multiple_parameters": false, + "line_break_style": true, + "line_length": { + "length": 255 + }, + "line_only_punc": false, + "local_class_naming": { + "exclude": [ + "z2ui5_cl_smps_bp_[a-z]+\\.clas\\.locals_imp\\." + ], + "patternKind": "required", + "local": "^LCL_.+$", + "exception": "^LCX_.+$", + "test": "^LTCL_.+$" + }, + "local_testclass_consistency": true, + "local_variable_names": false, + "macro_naming": true, + "main_file_contents": true, + "many_parentheses": true, + "max_one_method_parameter_per_line": true, + "max_one_statement": { + "exclude": [ + "z2ui5_cl_smps_app_319\\.clas\\." + ] + }, + "message_exists": true, + "method_implemented_twice": true, + "method_length": { + "statements": 100, + "errorWhenEmpty": false, + "ignoreTestClasses": true, + "checkForms": true, + "checkFunctionModules": true, + "exclude": [ + "z2ui5_cl_smpc_app_000\\.clas\\." + ] + }, + "method_overwrites_builtin": true, + "method_parameter_names": false, + "mix_returning": true, + "modify_only_own_db_tables": true, + "msag_consistency": true, + "names_no_dash": true, + "nesting": true, + "newline_between_methods": true, + "no_aliases": true, + "no_chained_assignment": true, + "no_comments_between_methods": true, + "no_exclamation_escape": true, + "no_external_form_calls": true, + "no_inline_in_optional_branches": false, + "no_macros": true, + "no_mandt_in_database_operations": true, + "no_prefixes": false, + "no_public_attributes": false, + "no_yoda_conditions": { + "onlyConstants": true + }, + "nrob_consistency": true, + "obsolete_statement": true, + "omit_parameter_name": true, + "omit_preceding_zeros": true, + "omit_receiving": true, + "parser_702_chaining": true, + "parser_bad_exceptions": true, + "parser_error": true, + "parser_missing_space": true, + "pragma_style": true, + "prefer_corresponding": false, + "prefer_inline": false, + "prefer_is_not": true, + "prefer_pragmas": true, + "prefer_raise_exception_new": { + "exclude": [ + "z2ui5_cl_smps_app_485\\.clas\\." + ] + }, + "prefer_returning_to_exporting": true, + "prefer_xsdbool": true, + "preferred_compare_operator": true, + "prefix_is_current_class": true, + "reduce_procedural_code": true, + "reduce_string_templates": true, + "redundant_conversion": true, + "release_idoc": true, + "remove_descriptions": true, + "rfc_error_handling": true, + "select_add_order_by": true, + "select_performance": true, + "select_single_full_key": { + "allowPseudo": true, + "exclude": [ + "z2ui5_cl_smps_bp_[a-z]+\\.clas\\.locals_imp\\." + ] + }, + "selection_screen_naming": true, + "selection_screen_texts_missing": true, + "sequential_blank": { + "lines": 3 + }, + "short_case": true, + "sicf_consistency": true, + "slow_parameter_passing": true, + "smim_consistency": false, + "space_before_colon": true, + "space_before_dot": true, + "sql_escape_host_variables": true, + "sql_value_conversion": true, + "start_at_tab": true, + "static_call_via_instance": true, + "strict_sql": true, + "superclass_final": { + "exclude": [ + "z2ui5_cl_smps_app_489_ws\\.clas\\." + ] + }, + "superfluous_value": true, + "sy_modification": true, + "sy_read_restriction": true, + "tabl_enhancement_category": true, + "tables_declared_locally": true, + "try_without_catch": true, + "type_form_parameters": true, + "types_naming": { + "pattern": "^TY_" + }, + "uncaught_exception": true, + "unknown_types": true, + "unnecessary_chaining": true, + "unnecessary_pragma": true, + "unnecessary_return": true, + "unreachable_code": true, + "unsecure_fae": true, + "unused_ddic": { + "exclude": [ + "z2ui5_(t|d)_smps_" + ] + }, + "unused_macros": true, + "unused_methods": { + "exclude": [ + "z2ui5_cl_smps_bp_[a-z]+\\.clas\\.locals_imp\\." + ] + }, + "unused_types": true, + "unused_variables": { + "exclude": [ + "z2ui5_cl_smps_bp_[a-z]+\\.clas\\.locals_imp\\." + ] + }, + "use_bool_expression": true, + "use_class_based_exceptions": true, + "use_line_exists": true, + "use_new": true, + "when_others_last": true, + "whitespace_end": true, + "xml_consistency": true, + "object_naming": { + "patternKind": "required", + "severity": "Error", + "clas": "^(?=.{1,25}$)Z2UI5_CL_CONF_" + } + } +} diff --git a/conformance/apps/cap2ui5/actions.js b/conformance/apps/cap2ui5/actions.js new file mode 100644 index 0000000..2541e7c --- /dev/null +++ b/conformance/apps/cap2ui5/actions.js @@ -0,0 +1,41 @@ +// Conformance app ACTIONS - the cap2UI5 equivalent of +// conformance/apps/abap/z2ui5_cl_conf_actions.clas.abap. +// Behaviour: conformance/apps/README.md, section ACTIONS. +const { defineApp } = require("@cap2ui5/cds-plugin"); + +defineApp("Z2UI5_CL_CONF_ACTIONS", class { + ticks = 0; + + main(client) { + if (client.check_on_navigated()) { + client.view_display( + `` + + `` + + `` + + `