Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,28 @@ The product is the generated class. It must:
- `defaultAgg` is UI5's default aggregation and nothing else, because
children of the default aggregation are written without a wrapper tag.
Where a drop should land somewhere else, use `dropAgg`.
- Generator changes (`src/abap.js`, `src/xml.js`) need all four checks:
`npm run verify` (unit, linter, runtime, browser).
- Generator changes (`src/abap.js`, `src/xml.js`, `src/clas.js`) need all
four checks: `npm run verify` (unit, linter, runtime, browser).
- An imported class (`src/clas.js`) is patched, not regenerated: only the
view chain is rewritten, every other line stays the author's. What the
editor cannot represent is kept as raw ABAP on its node or listed in the
import report - never dropped silently. Every starter's class must
round-trip byte-identically (`test/clas.test.mjs`).
- The class comes in two releases (`design.release`): 7.40 and later, and
7.02. A generator change keeps the 7.02 class free of what 7.02 lacks
(inline declarations, string templates, constructor and table
expressions); `npm test` runs abaplint at v702 on it (`abaplint-702.jsonc`,
the rules of abap2UI5's own 7.02 check), `npm run verify:abap` runs both.
- A starter (`src/starters.js`) uses only controls and properties of
`src/catalog.js`; every starter is a test design, rendered on 1.71 too.
- `test/builder.mjs` models `z2ui5_cl_ui5_view_builder`. When the builder's
semantics change upstream, change the model, and let `verify:abap` decide
who is right.
- A UI5 on another host runs with the editor's rights: `?ui5=` from a
link takes only this server, localhost and SAP's CDNs (`isLinkableUi5( )`
in `src/ui/store.js`, the same list in `preview.html`, which takes any
host only when the editor frames it). Any other host comes from the UI5
field, typed by the user. Never widen the list for a link.
- The preview module (`src/preview/init.js`) runs inside UI5 1.71: ES5
style, `sap.ui.define`, no API newer than 1.71.

Expand All @@ -56,6 +73,7 @@ The product is the generated class. It must:
| `npm run serve` | The designer on http://127.0.0.1:8080/ |
| `npm test` | Unit tests (catalogue vs. 1.71, class format, chain vs. builder model, import) |
| `npm run check:abap` | abap2UI5 linter over the generated classes |
| `npm run check:abap702` | abaplint at v702 over the generated 7.02 classes (also part of `npm test`) |
| `npm run verify:abap` | Transpile and run the generated classes on abap2UI5 |
| `npm run test:e2e` | The designer in Chromium, UI5 current and 1.71 |
| `npm run verify` | All of the above |
94 changes: 90 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,39 @@ From there it is your code:

Also in the editor:

- **Starters**: a blank page, a form, a table with search, and tabs.
- **Starters**: a blank page, a form, a table with search, tabs, and the
floorplans: a list report (filter fields and a search over a table), an
object page (header, a form section and an items section), a worklist (a
table with a status column and a row press) and master-detail (a list
next to the details of the selected entry, in two columns).
- **Any UI5 control** by its class name (`sap.m.Wizard`, a library of your
own): the field at the end of *Controls*, or Ctrl+K. The canvas asks its
UI5 for the control's properties, aggregations and events, and from then
on it works like one of the palette's. Nothing has checked it against
1.71, and *Checks* says so.
- **Open ABAP class** reads an existing abap2UI5 app class: paste it, open
a `.clas.abap` file, or link it with `?src=<raw URL>` (the playground's
parameter; raw.githubusercontent.com, a github.com file page, or a gist)
or `?class=<name>&repo=<owner/repo>[&ref=<branch>][&path=<folder>]`.
The `z2ui5_cl_ui5_view_builder` chain becomes the design, the public
attributes the data, `client->_event( )` the event names. Export writes
the chain back into that class and leaves every other line as it was;
new attributes go to the end of the public section, new events get a
`WHEN` branch in `on_event`. A view attribute the editor cannot edit
(`client->check_app_prev_stack( )`, a `COND #( )`, an `id`) is kept as
ABAP on its control and written back unchanged; the dialog after the
import lists it, and lists what could not be imported at all (a public
attribute of a type the *Data* tab has not, attributes of the View
element). A class that builds no view, or more than one, is refused
with the reason.
- **Import XML** brings in an existing UI5 XML view. Controls the palette
does not know are kept as they are.
- **Open in Playground** opens the class in the
[abap2UI5 playground](https://abap2ui5.github.io/playground/), in a share
link of the playground's own format; `?playground=<url>` points it at
another deployment. **Open in VS Code** hands the class to the abap2UI5
extension (`vscode://abap2ui5.abap2ui5/open`), which opens it as an
unsaved `.clas.abap` and offers to run it.
- **Save / Open** stores the design as JSON so you can continue later. The
design is also kept in the browser between visits; what New, Open or
Import replaced is under *Recent designs*. **Copy share link** puts the
Expand All @@ -110,21 +135,45 @@ Also in the editor:
- An abap2UI5 release that ships `z2ui5_cl_ui5_view_builder`. The class is
verified against **abap2UI5 1.147.0**.
- ABAP 7.40 or later, or ABAP Cloud. The class uses inline declarations and
`VALUE #( )`, like every abap2UI5 sample.
`VALUE #( )`, like every abap2UI5 sample. For an older system, choose
**ABAP 7.02** next to the class name: the class then declares its
variables at the top of a method, fills its sample rows with `APPEND`,
and writes a line break as `cl_abap_char_utilities=>newline` joined with
`&&` - no inline declarations, string templates or constructor
expressions. The choice is saved with the design.
- Every control and property the palette offers exists in **UI5 1.71**:
121 controls of sap.m, sap.ui.core, sap.ui.layout, sap.ui.unified,
sap.ui.table, sap.f and sap.uxap. A control inserted by class name is
whatever the canvas's UI5 says it is.

## Deploying to your system

The editor does not write to an SAP system, and neither does anything else
in the abap2UI5 tool set today: the MCP server's system mode
(`A2UI5_MCP_SYSTEM_URL`, see
[mcp-server](https://github.com/abap2UI5/mcp-server#system-mode-operating-apps-on-a-real-sap-system))
lists, starts and operates apps that are already on the system
(`system_status`, `app_list`, `app_start`, `app_describe`, `app_act`), and
its `deploy_app` writes into a local sandbox only. To get a class onto a
system:

1. *Export* the ZIP and pull it with abapGit (*New Offline* → *Import
ZIP*), or paste the `.clas.abap` into a new class in ADT or SE24 and
activate it.
2. Optionally, check it from an MCP client with the system mode switched
on: `system_status` first (does the logon work), then `app_start` with
the class name - the agent snapshot shows the view the class built.
`app_act` runs events for real, as the configured user.

## What is checked, and how

Each claim above is backed by a check that runs in CI:

| Check | What it proves |
|---|---|
| `npm test` | The palette against the **real metadata of UI5 1.71.80** (`test/ui5-1.71.json`): every property, enum value, aggregation, default aggregation and event exists. The class file format (no BOM, LF, max. 255 characters per line, no trailing blanks) and the `.clas.xml` format abapGit writes. The generated chain, replayed by a model of `z2ui5_cl_ui5_view_builder`, builds exactly the editor's view. An imported view comes back unchanged. |
| `npm test` | The palette against the **real metadata of UI5 1.71.80** (`test/ui5-1.71.json`): every property, enum value, aggregation, default aggregation and event exists. The class file format (no BOM, LF, max. 255 characters per line, no trailing blanks) and the `.clas.xml` format abapGit writes. The generated chain, replayed by a model of `z2ui5_cl_ui5_view_builder`, builds exactly the editor's view. An imported view comes back unchanged. The 7.02 classes contain none of the constructs 7.02 lacks, and pass **abaplint at syntax version 7.02** with the rules of abap2UI5's own 7.02 check (`abaplint-702.jsonc`, also `npm run check:abap702`). |
| `npm run check:abap` | The [abap2UI5 linter](https://abap2ui5.github.io/linter/) on the generated classes: controls, properties, enum values and icons against 1.71, plus the `chain-house-layout` rule. |
| `npm run verify:abap` | Every starter and a kitchen sink with every control of the palette is **transpiled against abap2UI5** (`@abap2ui5/node-runtime`, type-checked) and **run on it**. The view and the model of the first roundtrip have to equal the editor's, attribute for attribute and value for value. Every event has to reach `on_event`. |
| `npm run verify:abap` | Every starter and a kitchen sink with every control of the palette, each as a 7.40 and as a 7.02 class, is **transpiled against abap2UI5** (`@abap2ui5/node-runtime`, type-checked) and **run on it**. The view and the model of the first roundtrip have to equal the editor's, attribute for attribute and value for value. Every event has to reach `on_event`. |
| `npm run test:e2e` | The editor in Chromium: drag and drop, selection on canvas, outline and code, the control toolbar, the command palette, building blocks, properties, binding, events, undo, import, ZIP download. Every design renders on the current UI5 **and on 1.71**. |

## Development
Expand All @@ -150,6 +199,43 @@ npm run test:e2e:local # the browser tests on them
UI5_RESOURCES=.ui5/1.144.0 npm run serve # the editor with ?ui5=local
```

### A UI5 of your own

The canvas can load UI5 from any URL instead of the CDN: a UI5 on a web
server of your own, or a `resources/` tree on disk. Enter the URL in the UI5 field above the canvas, or open the editor
with `?ui5=<URL>`. The URL is the folder that holds `resources/`
(`https://host/ui5/` for `https://host/ui5/resources/sap-ui-core.js`); a
URL ending in `/resources` works too, and so does a path on the editor's
own server (`/…` or `./…`). Empty goes back to the CDN.

```
https://editor.example.com/index.html?ui5=http://127.0.0.1:8081/
```

A UI5 runs its scripts with the editor's rights, so a link may only
name a UI5 on the editor's own server, on `localhost` / `127.0.0.1`, or
on SAP's UI5 CDNs (`sdk.openui5.org`, `ui5.sap.com`). A link that names
any other host is not followed; the editor says so, and the URL works
once you type it into the UI5 field yourself.

A tree on disk needs a web server that sends
`Access-Control-Allow-Origin` (UI5 loads its modules and themes with
requests the browser checks), for example `npx http-server -p 8081 --cors`
in the folder above `resources/`, and `?ui5=http://127.0.0.1:8081/`. A
release before 1.102 has no Horizon theme; the canvas switches to Quartz
once UI5 has said which release it is. A built UI5 (as the CDN serves
it) brings its compiled themes; a tree from the npm packages
(`npm run ui5:trees`) has the theme sources only, and the canvas shows the
controls unthemed.

The browser tests take the same URLs, one for the current release and
one for 1.71 - and `E2E_PORT` when another checkout runs its tests on the
default port at the same time:

```bash
UI5_URL=http://127.0.0.1:8081/1.144.0/ UI5_URL_171=http://127.0.0.1:8081/1.71.80/ npm run test:e2e
```

`npm run ui5:metadata` regenerates `test/ui5-1.71.json` from a real UI5
runtime. Run it when the palette gains a control.

Expand Down
34 changes: 34 additions & 0 deletions abaplint-702.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
// abaplint on the classes the designer generates for ABAP 7.02
// (npm run check:abap702 writes them to .verify/abap first): the
// syntax version and the rules of abap2UI5's own 7.02 gate
// (.github/abaplint/abap_702.jsonc in abap2UI5/abap2UI5), against the
// downported framework that @abap2ui5/node-runtime ships.
"global": {
"files": "/.verify/abap/**/*_702.clas.abap"
},
"dependencies": [
{ "folder": "/node_modules/@abap2ui5/node-runtime/downport", "files": "/**/*.abap" },
{ "folder": "/test/abaplint-deps", "files": "/**/*.abap" }
],
"syntax": {
"version": "v702",
"errorNamespace": "."
},
"rules": {
"downport": true,
"begin_end_names": true,
"check_ddic": true,
"check_include": true,
"check_syntax": true,
"global_class": true,
"definitions_top": true,
"implement_methods": true,
"method_implemented_twice": true,
"parser_error": true,
"superclass_final": true,
"unknown_types": true,
"xml_bom": true,
"xml_consistency": true
}
}
11 changes: 9 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
<label for="className">Class</label>
<input id="className" spellcheck="false" autocomplete="off" maxlength="30">
<input id="description" placeholder="Description" maxlength="60" autocomplete="off" aria-label="Description">
<select id="abapRelease" aria-label="ABAP release" title="The ABAP release the class is written for">
<option value="">ABAP 7.40+</option>
<option value="702">ABAP 7.02</option>
</select>
</div>
<div class="bar-actions">
<button id="btnCmd" class="ghost cmd" title="Command palette (Ctrl+K)"><span>Search or insert…</span><kbd>Ctrl K</kbd></button>
Expand All @@ -27,6 +31,7 @@
<button id="btnNew" role="menuitem">New from template…</button>
<button id="btnOpen" role="menuitem">Open design (.json)…</button>
<button id="btnSave" role="menuitem">Save design (.json)</button>
<button id="btnOpenClass" role="menuitem" title="An abap2UI5 app class - its view, data and events become the design, the rest of the class is kept">Open ABAP class…</button>
<button id="btnImport" role="menuitem">Import UI5 XML view…</button>
<button id="btnRecent" role="menuitem">Recent designs…</button>
</div>
Expand All @@ -42,6 +47,8 @@
<button id="btnCopy" role="menuitem">Copy ABAP to clipboard</button>
<button id="btnCopyXml" role="menuitem">Copy XML view</button>
<button id="btnShare" role="menuitem" title="A link that carries the design">Copy share link</button>
<button id="btnPlayground" role="menuitem" title="Run the class in the browser playground - no system">Open in Playground</button>
<button id="btnVscode" role="menuitem" title="Open the class in VS Code with the abap2UI5 extension">Open in VS Code</button>
</div>
</div>
</div>
Expand Down Expand Up @@ -97,7 +104,7 @@
<option value="sap_fiori_3_dark">Quartz Dark</option>
<option value="sap_belize">Belize</option>
</select>
<input id="ui5version" placeholder="UI5 latest" title="UI5 version of the preview, e.g. 1.71.80 - empty: the latest" size="9" spellcheck="false">
<input id="ui5version" placeholder="UI5 latest" title="UI5 of the preview: a version, e.g. 1.71.80, or the URL of a UI5 of your own (the folder with resources/) - empty: the latest from the OpenUI5 CDN" size="9" spellcheck="false">
</div>
<div class="stage-body view-split" id="stageBody">
<div class="canvas-pane" id="canvasPane">
Expand Down Expand Up @@ -130,7 +137,7 @@
<div id="cmdkList" class="cmdk-list" role="listbox"></div>
</dialog>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<input id="fileInput" type="file" accept=".json,.xml,application/json,text/xml" hidden>
<input id="fileInput" type="file" accept=".json,.xml,.abap,application/json,text/xml,text/plain" hidden>

<script type="module" src="src/app.js"></script>
</body>
Expand Down
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"serve": "node tools/serve.mjs 8080",
"test": "node --test test/*.test.mjs",
"check:abap": "node tools/gen-classes.mjs && abap2ui5lint --config abap2ui5lint.jsonc --no-render",
"check:abap702": "node tools/gen-classes.mjs && abaplint abaplint-702.jsonc",
"verify:abap": "node tools/verify-abap.mjs",
"test:e2e": "playwright test",
"ui5:metadata": "node tools/ui5-metadata.mjs 1.71.80",
Expand All @@ -21,6 +22,7 @@
"devDependencies": {
"@abap2ui5/linter": "0.8.5",
"@abap2ui5/node-runtime": "1.147.0",
"@abaplint/cli": "2.120.80",
"@abaplint/transpiler-cli": "2.14.3",
"@playwright/test": "1.56.1",
"express": "5.3.0"
Expand Down
12 changes: 8 additions & 4 deletions playwright.config.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
// Browser tests of the designer (test/e2e). Two legs: the current UI5 and
// 1.71, the oldest release abap2UI5 supports - every design has to render
// in both. Both boot pinned releases from the OpenUI5 CDN; where the CDN
// is out of reach, local trees stand in (tools/serve.mjs):
// is out of reach, local trees stand in (tools/serve.mjs), or a UI5 of
// your own, by URL (the folder that holds resources/, as ?ui5=<URL>):
// UI5_RESOURCES=<1.144 tree> UI5_RESOURCES_171=<1.71 tree> npm run test:e2e
// UI5_URL=<current UI5> UI5_URL_171=<UI5 1.71> npm run test:e2e
import { defineConfig, devices } from '@playwright/test';

const PORT = 4173;
const current = process.env.UI5_RESOURCES ? 'local' : '1.144.0';
const oldest = process.env.UI5_RESOURCES_171 ? 'local:171' : '1.71.80';
const env = process.env;
// E2E_PORT: another port, for a second checkout testing at the same time
const PORT = Number(env.E2E_PORT) || 4173;
const current = env.UI5_URL || (env.UI5_RESOURCES ? 'local' : '1.144.0');
const oldest = env.UI5_URL_171 || (env.UI5_RESOURCES_171 ? 'local:171' : '1.71.80');

export default defineConfig({
testDir: 'test/e2e',
Expand Down
Loading
Loading