@omega.js/desktop 0.53.0 → 0.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -38
- package/dist/cli-run.js +4 -1
- package/dist/cli.js +2 -2
- package/dist/commands/cdp/client.js +1 -1
- package/dist/commands/cdp.js +1 -1
- package/dist/commands/clean.js +2 -3
- package/dist/commands/dev.js +25 -0
- package/dist/commands/lib/ensure-target.js +12 -17
- package/dist/commands/lib/migrate.js +17 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/release.js +1 -1
- package/dist/commands/test.js +4 -4
- package/dist/commands/update.js +5 -4
- package/dist/defaults/.github/workflows/build.yml +18 -18
- package/dist/defaults/_.gitignore +0 -2
- package/dist/defaults/_mas/README.md +3 -3
- package/dist/defaults/config/certs/README.md +1 -1
- package/dist/defaults/config/omega.json5 +36 -36
- package/dist/defaults/docs/README.md +3 -3
- package/dist/defaults/gulpfile.js +1 -1
- package/dist/defaults/hooks/build/post.js +1 -1
- package/dist/defaults/hooks/build/pre.js +1 -1
- package/dist/defaults/hooks/notarize/post.js +2 -2
- package/dist/defaults/hooks/release/post.js +1 -1
- package/dist/defaults/hooks/release/pre.js +1 -1
- package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
- package/dist/defaults/src/integrations/context-menu/index.js +11 -11
- package/dist/defaults/src/integrations/menu/index.js +5 -5
- package/dist/defaults/src/integrations/tray/index.js +9 -9
- package/dist/defaults/src/main.js +2 -2
- package/dist/defaults/src/preload.js +1 -1
- package/dist/defaults/test/README.md +3 -3
- package/dist/defaults/test/_init.js +1 -1
- package/dist/gulp/tasks/audit.js +5 -8
- package/dist/lib/restart-manager/index.js +1 -1
- package/dist/lib/restart-manager/install.js +1 -1
- package/dist/lib/restart-manager/protocol.js +1 -1
- package/dist/main.js +4 -3
- package/dist/preload.js +1 -1
- package/dist/test/suites/build/audit.test.js +20 -7
- package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
- package/dist/test/suites/build/cli.test.js +28 -0
- package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
- package/dist/test/suites/build/defaults-scaffold.test.js +19 -5
- package/dist/test/suites/build/deploy-direct.test.js +7 -5
- package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
- package/dist/test/suites/build/deploy-hook.test.js +4 -2
- package/dist/test/suites/build/dev-verb.test.js +67 -0
- package/dist/test/suites/build/ensure-target.test.js +11 -3
- package/dist/test/suites/build/merge-line-files.test.js +6 -6
- package/dist/test/suites/build/migrate.test.js +29 -0
- package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
- package/dist/test/suites/build/runner-env-write.test.js +73 -0
- package/dist/test/suites/build/runner.test.js +9 -8
- package/dist/test/suites/build/setup-scripts.test.js +27 -0
- package/dist/test/suites/build/validate-config.test.js +13 -2
- package/dist/test/suites/build/verb-logs.test.js +20 -0
- package/dist/test/suites/renderer/window-desktop-surface.test.js +1 -1
- package/dist/utils/build-pipeline.js +4 -4
- package/dist/utils/runner-env.js +13 -28
- package/dist/vendor/config/company.js +46 -14
- package/dist/vendor/config/defaults.js +30 -7
- package/dist/vendor/config/edit.js +25 -3
- package/dist/vendor/config/env-delivery.js +1 -1
- package/dist/vendor/config/env-schema.js +3 -6
- package/dist/vendor/config/env.js +34 -22
- package/dist/vendor/config/index.js +13 -17
- package/dist/vendor/config/load.js +15 -7
- package/dist/vendor/config/repo.js +10 -27
- package/dist/vendor/config/schema-client.js +64 -0
- package/dist/vendor/config/schema-cloud.js +38 -0
- package/dist/vendor/config/schema-manager.js +118 -0
- package/dist/vendor/config/schema-overrides.js +68 -0
- package/dist/vendor/config/schema.js +99 -152
- package/dist/vendor/config/validate.js +97 -77
- package/dist/vendor/devkit/agents-md.js +233 -0
- package/dist/vendor/devkit/attach-log-file.js +15 -1
- package/dist/vendor/devkit/ci-workflows.js +30 -30
- package/dist/vendor/devkit/cli-router.js +13 -7
- package/dist/vendor/devkit/defaults-engine.js +9 -43
- package/dist/vendor/devkit/deploy-snapshot.js +44 -9
- package/dist/vendor/devkit/env-lines.js +183 -0
- package/dist/vendor/devkit/local.js +62 -10
- package/dist/vendor/devkit/lockfile.js +32 -13
- package/dist/vendor/devkit/logger.js +7 -2
- package/dist/vendor/devkit/merge-line-files.js +219 -176
- package/dist/vendor/devkit/omega-bin.js +208 -111
- package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
- package/dist/vendor/devkit/preludes/index.js +1 -0
- package/dist/vendor/devkit/target-picker.js +45 -0
- package/dist/vendor/devkit/test/dashed-files.js +37 -0
- package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
- package/dist/vendor/devkit/update.js +15 -15
- package/dist/vendor/devkit/verb-scripts.js +40 -0
- package/dist/vendor/devkit/verbs.js +170 -0
- package/package.json +18 -24
- package/dist/commands/install.js +0 -37
- package/dist/defaults/AGENTS.md +0 -119
- package/dist/defaults/CLAUDE.md +0 -1
- package/dist/vendor/config/env-retired.js +0 -137
- package/dist/vendor/config/retired-keys.js +0 -635
- package/docs/analytics.md +0 -140
- package/docs/app-state.md +0 -92
- package/docs/audit.md +0 -69
- package/docs/auth.md +0 -284
- package/docs/auto-updater.md +0 -243
- package/docs/boot-sequence.md +0 -44
- package/docs/build-system.md +0 -169
- package/docs/cdp-debugging.md +0 -169
- package/docs/common-mistakes.md +0 -21
- package/docs/config-schema.md +0 -120
- package/docs/context-menu.md +0 -112
- package/docs/context.md +0 -81
- package/docs/css.md +0 -84
- package/docs/deep-link.md +0 -186
- package/docs/environment-detection.md +0 -112
- package/docs/fontawesome.md +0 -109
- package/docs/hooks.md +0 -89
- package/docs/icons.md +0 -79
- package/docs/index.md +0 -328
- package/docs/installer-options.md +0 -165
- package/docs/ipc.md +0 -61
- package/docs/lib-modules.md +0 -53
- package/docs/logging.md +0 -227
- package/docs/menu.md +0 -160
- package/docs/releasing.md +0 -239
- package/docs/remote-config.md +0 -118
- package/docs/remote-scripts.md +0 -144
- package/docs/restart-manager.md +0 -144
- package/docs/runner.md +0 -290
- package/docs/sentry.md +0 -97
- package/docs/shared/agent-docs.md +0 -89
- package/docs/shared/analytics.md +0 -612
- package/docs/shared/brands.md +0 -57
- package/docs/shared/breaking-changes.md +0 -917
- package/docs/shared/config.md +0 -1948
- package/docs/shared/deploys.md +0 -341
- package/docs/shared/icons.md +0 -219
- package/docs/shared/local-dev.md +0 -167
- package/docs/shared/logging.md +0 -205
- package/docs/shared/monitoring.md +0 -167
- package/docs/shared/publishing.md +0 -187
- package/docs/shared/rulings.md +0 -34
- package/docs/shared/testing.md +0 -147
- package/docs/shared/theming.md +0 -629
- package/docs/shared/translation.md +0 -342
- package/docs/shared/updates.md +0 -61
- package/docs/signing.md +0 -293
- package/docs/startup.md +0 -142
- package/docs/storage.md +0 -59
- package/docs/templating.md +0 -101
- package/docs/test-boot-layer.md +0 -157
- package/docs/test-framework.md +0 -362
- package/docs/themes.md +0 -149
- package/docs/tooltips.md +0 -99
- package/docs/tray.md +0 -164
- package/docs/usage.md +0 -58
- package/docs/verts.md +0 -62
- package/docs/windows.md +0 -149
package/docs/test-boot-layer.md
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# Test Framework — Boot Layer
|
|
2
|
-
|
|
3
|
-
The `boot` test layer runs against the consumer's **actual built `main.bundle.js`** — the real production main entry, loaded exactly as `electron .` loads one. Replaces shell-level `npm start && sleep 12 && kill` smoke tests with deterministic, signal-driven pass/fail.
|
|
4
|
-
|
|
5
|
-
The build and the boot happen in a **staged app root of their own**, `<project>/.omega/test-app/` (gitignored) — never the project's `dist/`, which belongs to the `npm start` watcher. See [Isolated build output](#isolated-build-output).
|
|
6
|
-
|
|
7
|
-
## When to use it
|
|
8
|
-
|
|
9
|
-
| Layer | What it tests | Speed |
|
|
10
|
-
|---|---|---|
|
|
11
|
-
| `build` | Plain Node — config parsing, util fns, schema validation. | Fast (ms) |
|
|
12
|
-
| `main` | @omega.js/desktop lib code in isolation (storage, ipc, tray, etc.) inside Electron. | Fast (~50ms each) |
|
|
13
|
-
| `renderer` | Inside a hidden BrowserWindow. | Fast |
|
|
14
|
-
| `boot` | The **whole boot integration**: consumer's main.js → omega.initialize → live state | ~1s startup, then fast |
|
|
15
|
-
|
|
16
|
-
Use `boot` for tests that need to verify **integration** rather than unit behavior:
|
|
17
|
-
- "Does the consumer's `src/main.js` actually wire up correctly?"
|
|
18
|
-
- "Did all 13 boot steps complete without throwing?"
|
|
19
|
-
- "Did config flow from JSON5 → omega.config → tray titles?"
|
|
20
|
-
- "Did `src/integrations/{tray,menu,context-menu}/index.js` load?"
|
|
21
|
-
- "Is the menu rendered with the expected default ids?"
|
|
22
|
-
|
|
23
|
-
## Test shape
|
|
24
|
-
|
|
25
|
-
```js
|
|
26
|
-
// test/boot.test.js (consumer-side)
|
|
27
|
-
module.exports = {
|
|
28
|
-
type: 'group',
|
|
29
|
-
layer: 'boot',
|
|
30
|
-
description: 'consumer boot smoke',
|
|
31
|
-
timeout: 20000,
|
|
32
|
-
tests: [
|
|
33
|
-
{
|
|
34
|
-
description: 'omega initialized end-to-end',
|
|
35
|
-
inspect: async ({ omega, expect, projectRoot }) => {
|
|
36
|
-
expect(omega._initialized).toBe(true);
|
|
37
|
-
expect(omega.config).toBeTruthy();
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
description: 'tray + menu rendered',
|
|
42
|
-
inspect: async ({ omega, expect }) => {
|
|
43
|
-
expect(omega.tray.has('open')).toBe(true);
|
|
44
|
-
expect(omega.menu.isRendered()).toBe(true);
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
};
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The `inspect` function receives:
|
|
52
|
-
| Arg | Description |
|
|
53
|
-
|---|---|
|
|
54
|
-
| `omega` | The fully-initialized live main-process instance, the same one your consumer code uses. |
|
|
55
|
-
| `expect` | @omega.js/desktop's [Jest-compatible assertion library](../src/test/assert.js). |
|
|
56
|
-
| `projectRoot` | Absolute path to the consumer project root (its `src/`, `config/` — and the `dist/` a boot run must never write). |
|
|
57
|
-
| `appRoot` | Absolute path to the staged app root Electron booted — `<projectRoot>/.omega/test-app`. Assert on built artifacts here (`<appRoot>/dist/main.bundle.js`), not under `projectRoot`. |
|
|
58
|
-
| `frameworkDistRoot` | Absolute path to `<@omega.js/desktop>/dist` — where framework test utilities live. |
|
|
59
|
-
| `distSnapshotBefore` | Fingerprint of `<projectRoot>/dist` taken before the test build, for the isolation assertion below. |
|
|
60
|
-
|
|
61
|
-
## How it works
|
|
62
|
-
|
|
63
|
-
1. Test runner discovers `test/**/*.js` files with `layer: 'boot'`.
|
|
64
|
-
2. Stages `<projectRoot>/.omega/test-app`, builds into its `dist/`, and aggregates each test's `inspect` source body into a JSON spec file.
|
|
65
|
-
3. Spawns a real Electron process: `electron <projectRoot>/.omega/test-app` — an app dir with a `package.json` whose `main` is the built bundle, same shape as `npm start`'s `electron .`.
|
|
66
|
-
4. Sets three env vars before spawn:
|
|
67
|
-
- `OMEGA_TEST_BOOT=1` — gate
|
|
68
|
-
- `OMEGA_TEST_BOOT_HARNESS=<absolute path to dist/test/harness/boot-entry.js>`
|
|
69
|
-
- `OMEGA_TEST_BOOT_SPEC=<temp file with test definitions>`
|
|
70
|
-
5. @omega.js/desktop's `main.js` boots normally; after `omega.initialize()` resolves, detects `OMEGA_TEST_BOOT=1`, reconstitutes each `inspect` from its serialized body string, runs them sequentially, and emits `__OMEGA_TEST__` JSON lines on stdout (the ONE prefix, `TEST_EVENT_PREFIX` in [src/utils/test-events.js](../src/utils/test-events.js)).
|
|
71
|
-
6. Test runner parses results, calls `app.exit()`. **No sleep, no kill.**
|
|
72
|
-
|
|
73
|
-
## View suites in this lane
|
|
74
|
-
|
|
75
|
-
The boot lane also carries the renderer suites that name a project view (`layer: 'renderer'` + `view: '<name>'`). They need exactly what this lane already produces: the staged, freshly built app. After the `inspect` tests finish, [harness/boot-entry.js](../src/test/harness/boot-entry.js) opens each such suite's view in a real window of the booted app (through the app's own window manager, hidden), evaluates `harness/renderer-entry.js` verbatim inside the page (one suite loop, one `expect`, no fork), runs the suite there, and destroys the window. So the page carries the project's real preload, the booted main process's real IPC handlers, and the real config. The knob and its rules are documented in [test-framework.md](test-framework.md#testing-your-own-views).
|
|
76
|
-
|
|
77
|
-
## Running
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
# All layers including boot
|
|
81
|
-
npx omega test
|
|
82
|
-
|
|
83
|
-
# Boot only
|
|
84
|
-
npx omega test --layer boot
|
|
85
|
-
|
|
86
|
-
# With debug output (shows electron's stderr + harness internals)
|
|
87
|
-
OMEGA_TEST_DEBUG=1 npx omega test --layer boot
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Isolated build output
|
|
91
|
-
|
|
92
|
-
`npm start`'s watcher owns `<project>/dist/`. When the boot runner built there too, a dev app and a boot-test run interleaved writes on one tree and either side could load a half-written bundle — a race that presents as a code bug. So the boot build gets its own output (#110).
|
|
93
|
-
|
|
94
|
-
**The seam** is [src/utils/dist-root.js](../src/utils/dist-root.js): every gulp task resolves its output through it instead of joining `dist/` by hand, and `OMEGA_BUILD_OUTPUT` (absolute, or relative to the project root) redirects the whole build. Nothing else in the build config is duplicated.
|
|
95
|
-
|
|
96
|
-
**The staged app root** is `<project>/.omega/test-app/`, built by `stageTestApp()` in [src/test/runners/boot.js](../src/test/runners/boot.js):
|
|
97
|
-
|
|
98
|
-
| Entry | What it is |
|
|
99
|
-
|---|---|
|
|
100
|
-
| `package.json` | The project's own, verbatim except `main`, pinned at the test build's bundle — so Electron derives the same app name, version and userData path a real boot does. |
|
|
101
|
-
| `dist/` | The isolated build output. `<appRoot>/dist/views/*`, `<appRoot>/dist/preload.bundle.js` and the tray icon lookup resolve against it with **no runtime change** — the app root moved, the layout under it did not. |
|
|
102
|
-
| `src`, `config` | Symlinks back to the project's. Runtime lookups against `app.getAppPath()` — `src/integrations/{tray,menu,context-menu}/index.js`, the unbundled config fallback — still find the consumer's real files. |
|
|
103
|
-
|
|
104
|
-
`package.json` and the symlinks are restaged on every run, and the runner clears the staged `dist/` before each build (kept only under `OMEGA_TEST_SKIP_BUILD`). `node_modules` resolution still walks up into the project's. The isolation promise is about `dist/`: a boot run still appends to the project's gitignored `logs/` (the gulp build log and the booted app's runtime log), same as any run.
|
|
105
|
-
|
|
106
|
-
The regression is covered end-to-end in the real run: the runner fingerprints `<project>/dist` before the build, and [src/test/suites/boot/build-isolation.test.js](../src/test/suites/boot/build-isolation.test.js) re-fingerprints it from inside the booted app and asserts nothing was touched.
|
|
107
|
-
|
|
108
|
-
## Prerequisites
|
|
109
|
-
|
|
110
|
-
**The runner always rebuilds the bundle first** (via the same gulp pipeline `npm run build` uses) so tests never see stale code. Adds ~10s to the boot-test run; correctness over speed.
|
|
111
|
-
|
|
112
|
-
Opt out for CI scenarios where build already ran in a separate step:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
OMEGA_TEST_SKIP_BUILD=1 npx omega test --layer boot
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Boot then runs against whatever is already in `<project>/.omega/test-app/dist/`. If there is no bundle there, the run **fails loudly** rather than skipping or falling back to the project's `dist/` — an absent test build means the build step that was promised never ran, and booting some other bundle would silently test the wrong code.
|
|
119
|
-
|
|
120
|
-
## Self-test from the framework repo (the bundled fixture)
|
|
121
|
-
|
|
122
|
-
Everything above describes a **consumer** running boot tests against their own built bundle. @omega.js/desktop also boot-tests *itself* — the same way BXM verifies "does the extension load?" and UJM verifies "does the site boot?".
|
|
123
|
-
|
|
124
|
-
When `npx omega test` runs from the @omega.js/desktop repo (the cwd's `package.json` name is `@omega.js/desktop`), two complementary mechanisms engage:
|
|
125
|
-
|
|
126
|
-
- **`isFrameworkSelfTest`** (in [src/test/runner.js](../src/test/runner.js)) — test discovery includes the framework's own `boot/**` suites. For a real consumer this flag is false and framework `boot/**` suites are **excluded** (they target @omega.js/desktop's fixture, not the consumer's app), so they never run in a consumer's `npx omega test`.
|
|
127
|
-
- **`OMEGA_TEST_BOOT_PROJECT`** — [src/commands/test.js](../src/commands/test.js) points the boot runner at the bundled fixture under `src/test/fixtures/consumer-app/` instead of the cwd.
|
|
128
|
-
|
|
129
|
-
The gate decides *whether* the framework boot suite runs; the env var decides *which project* gets booted. (@omega.js/backend's `OMEGA_TEST_BOOT_PROJECT`, BXM's `OMEGA_TEST_BOOT_PROJECT`, and UJM's `UJ_TEST_BOOT_PROJECT` are the exact analogs.)
|
|
130
|
-
|
|
131
|
-
### The bundled fixture
|
|
132
|
-
|
|
133
|
-
`src/test/fixtures/consumer-app/` — a minimal, committed @omega.js/desktop consumer (source only):
|
|
134
|
-
|
|
135
|
-
- `config/omega.json5` — fake brand (`desktop-fixture`), `releases.enabled: false` (no repo discovery during the build), empty `cloud.config` (no Firebase hang).
|
|
136
|
-
- `src/main.js` / `src/preload.js` — the one-line bootstraps a real consumer ships; `main.js` creates the `main` window (`show: false`).
|
|
137
|
-
- `src/views/main/index.html` + `src/assets/js/components/main/index.js` + `src/assets/scss/main.scss` — a real view/renderer/theme so the bundle + sass tasks run exactly as for a consumer.
|
|
138
|
-
|
|
139
|
-
**Runtime-only, gitignored** (never committed): before the boot build, the runner symlinks `@omega.js/desktop` (→ the @omega.js/desktop repo root) and `electron` (→ @omega.js/desktop's own copy) into the fixture's `node_modules` — the only two deps resolved by *explicit path* (the gulpfile location, the bundle's `require('@omega.js/desktop/main')`, and the runner's electron-binary lookup). Everything else (gulp, esbuild, …) resolves via the upward `node_modules` walk because the fixture lives inside the @omega.js/desktop repo. The links are **removed again when the run finishes** — the `@omega.js/desktop` link points back at the repo root, which *contains* the fixture, so a leftover link forms an infinite directory cycle inside `dist/` that crashes the next prepare-package tree walk (`npm run prepare` / `npm publish` → `ENAMETOOLONG`). The fixture `.gitignore` is belt-and-suspenders for crashed runs. See `ensureFixtureDeps()` / `removeFixtureDeps()` in [src/test/runners/boot.js](../src/test/runners/boot.js).
|
|
140
|
-
|
|
141
|
-
The fixture is then **built into a real `main.bundle.js`** (under its own `.omega/test-app/dist/`, like any other boot run) and booted — the same production path a consumer's boot test exercises (bundled, not the unbundled lib code the `main` layer covers). The boot smoke lives at [src/test/suites/boot/consumer-app-boots.test.js](../src/test/suites/boot/consumer-app-boots.test.js).
|
|
142
|
-
|
|
143
|
-
### `OMEGA_TEST_BOOT_PROJECT`
|
|
144
|
-
|
|
145
|
-
| Env | Purpose |
|
|
146
|
-
|---|---|
|
|
147
|
-
| `OMEGA_TEST_BOOT_PROJECT` | Root of a project to boot instead of the cwd. Auto-set to `src/test/fixtures/consumer-app` when @omega.js/desktop tests itself; set it explicitly to boot a **real consumer** (e.g. `deployment-playground-desktop`) without `cd`-ing into it. |
|
|
148
|
-
|
|
149
|
-
### Why this exists
|
|
150
|
-
|
|
151
|
-
The `build`/`main`/`renderer` layers cover @omega.js/desktop's lib code fast and in isolation. None of them prove the framework still assembles a consumer's `src/main.js` into a bundle that boots end-to-end. The fixture self-test fills that gap — @omega.js/desktop's analog of "does the extension load?" (BXM) / "does the site boot?" (UJM).
|
|
152
|
-
|
|
153
|
-
## Limitations
|
|
154
|
-
|
|
155
|
-
- Tests run sequentially in a single Electron process to amortize startup cost (~1s). State doesn't carry across tests: they all share one `omega` instance.
|
|
156
|
-
- `inspect` function bodies are serialized via `Function.prototype.toString` and reconstituted with `new Function(...)`. Closures over the test file's outer scope **don't survive** — only the `inspect` argument bag is available inside.
|
|
157
|
-
- We can't simulate user input (clicking the tray, right-clicking, typing). For that, you'd need `nut-js` or similar — out of scope.
|
package/docs/test-framework.md
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
# Test Framework
|
|
2
|
-
|
|
3
|
-
Built-in test framework for both @omega.js/desktop itself and consumer projects. Jest-like assertion syntax (`expect(actual).toBe(expected)`), layered runners, @omega.js/backend-style output.
|
|
4
|
-
|
|
5
|
-
## 🚫 NEVER mock — test against the real harness (HARD RULE)
|
|
6
|
-
|
|
7
|
-
**Do NOT hand-roll fake/stub/mock objects**: no mock `omega`, fake `ipc`/`storage`/`window`/`tray`, stubbed `app`/`BrowserWindow`, or fake IPC channels. Every test gets the **real** framework context:
|
|
8
|
-
|
|
9
|
-
- `build` runs real @omega.js/desktop helper code in plain Node.
|
|
10
|
-
- `main` / `renderer` / `boot` run inside a **real spawned Electron process**, where `ctx.omega` (and boot's `inspect({ omega })`) is the **real booted main-process instance**: real `omega.storage`, `omega.ipc`, `omega.tray`, `omega.windows`, etc. Use them; exercise the code the way production does.
|
|
11
|
-
|
|
12
|
-
**Pure functions are the ONLY exception.** A function with zero I/O (config-defaults merge, icon-path resolver, schema validator, CLI alias resolver, a string/number transform) can be `require()`d and called directly with plain inputs — that's not mocking, there's nothing to mock. The moment a function touches `app.*` / `BrowserWindow` / `ipcMain` / `Tray` / the real bundle / an external service, it MUST run against the real harness in the appropriate layer (`main` / `renderer` / `boot`), not a stub.
|
|
13
|
-
|
|
14
|
-
**Real external APIs are gated behind extended mode (`TEST_EXTENDED_MODE`), NOT mocked** (see [Extended vs normal mode](#extended-vs-normal-mode) below). Normal mode skips them *in the source*; extended mode runs them for real. The test never fakes them. **Anything an extended test creates in a real external system MUST be cleaned up** by the test (via the suite's `cleanup(ctx)` hook) — external systems are not reset between runs.
|
|
15
|
-
|
|
16
|
-
If you find yourself writing `const mockX = {...}` to satisfy code under test, STOP: pass the real `ctx.omega` (or its real sub-object), or, if the function is genuinely pure, call it directly with plain data.
|
|
17
|
-
|
|
18
|
-
### The ONLY two exceptions where a narrow stub is allowed
|
|
19
|
-
|
|
20
|
-
Mock **nothing** by default. There are exactly two cases where the real dependency genuinely cannot run in the test environment — and even then, stub the *smallest possible seam* (one method / one object), restore it immediately, and comment *why*:
|
|
21
|
-
|
|
22
|
-
1. **A side effect that would destroy the test run itself.** If the real call would kill or corrupt the harness — `app.quit()`, a process-exit, `autoUpdater.quitAndInstall()`, a destructive wipe — stub *that one call* to a no-op, assert the surrounding decision logic (e.g. that `_allowQuit` was set first), then restore. You are preventing the harness from terminating mid-assertion, not faking behavior. (Examples: `window-manager.test.js` stubs `app.quit`; `auto-updater.test.js` stubs `installNow`/`_promptToInstall`.)
|
|
23
|
-
2. **A real dependency the test environment can't provide.** When the real object only exists from infra you can't stand up in a unit test (e.g. a live `webContents` from a not-yet-created window, a second running app instance), a unit test may hand a minimal stub to verify a *narrow side effect* (e.g. that `attach()` registers the right listener). Prefer obtaining the real object from the harness if you can; only stub when you genuinely can't.
|
|
24
|
-
|
|
25
|
-
If you can run it for real, you must. These exceptions are not a license to unit-test in isolation when a real-harness layer (`main`/`renderer`/`boot`) would work.
|
|
26
|
-
|
|
27
|
-
## Test coverage — every surface gets a test (HARD RULE)
|
|
28
|
-
|
|
29
|
-
A feature is not done when it works — it's done when every surface it exposes is covered in the layer that owns that surface:
|
|
30
|
-
|
|
31
|
-
| Coverage | Layer | Proves |
|
|
32
|
-
|---|---|---|
|
|
33
|
-
| **Logic** | `build` / `main` | The feature's functions do the right thing when called directly (the real booted `omega`, real storage, real IPC) |
|
|
34
|
-
| **UI** | `renderer` | The feature's interface is WIRED — a real event on the real DOM triggers the behavior and the visible result appears |
|
|
35
|
-
| **End-to-end** | `boot` | The feature survives in the consumer's actual built bundle (extend the boot suite's `inspect` assertions) |
|
|
36
|
-
|
|
37
|
-
**Skipping a layer is the exception, not the default.** A layer may be skipped ONLY when the feature genuinely has no surface there — a pure build-time utility has no UI; a CSS-only tweak has no logic to call. Convenience is never a reason: "the logic test already covers it" does NOT excuse the UI test — logic tests prove the logic, UI tests prove the wiring (a button can come unhooked while every logic test stays green), boot tests prove the packaging. When in doubt, write the test.
|
|
38
|
-
|
|
39
|
-
## Running tests
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npx omega test # consumer: runs YOUR project suites (bare runs never include the framework corpus)
|
|
43
|
-
npx omega test --layer=main # only main-process suites (also: build, renderer, all)
|
|
44
|
-
npx omega test --filter="storage" # only suites/tests whose name contains "storage"
|
|
45
|
-
npx omega test --extended # opt into extended mode — real external APIs (also: TEST_EXTENDED_MODE=true)
|
|
46
|
-
npx omega test --reporter=json # pretty output + machine-readable {"event":"summary",...} line
|
|
47
|
-
OMEGA_TEST_DEBUG=1 npx omega test # see Electron stderr (otherwise drained silently)
|
|
48
|
-
OMEGA_TEST_BOOT_TIMEOUT_MS=120000 npx omega test # the boot lane's budget (default 60s)
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
In @omega.js/desktop itself, `npm test` runs the framework's own suite — the self-test context flips the no-target default.
|
|
52
|
-
|
|
53
|
-
### Filtering tests
|
|
54
|
-
|
|
55
|
-
Pass a path (relative to `test/`) as a positional **target** to select which test FILES run:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
# Run project test files under a path (a bare path binds to the PROJECT source)
|
|
59
|
-
npx omega test build/config
|
|
60
|
-
|
|
61
|
-
# Run BOTH sources — reaching the framework suite is always an explicit choice
|
|
62
|
-
npx omega test full:
|
|
63
|
-
npx omega test full:build/config
|
|
64
|
-
|
|
65
|
-
# Run ONLY consumer project tests (no framework suites at all)
|
|
66
|
-
npx omega test project:
|
|
67
|
-
|
|
68
|
-
# Run a single project test file
|
|
69
|
-
npx omega test project:main/tab-manager
|
|
70
|
-
|
|
71
|
-
# Run ONLY framework tests (universal cross-framework alias)
|
|
72
|
-
npx omega test mgr:
|
|
73
|
-
|
|
74
|
-
# Run ONLY @omega.js/desktop framework tests (desktop-specific aliases, equivalent to mgr:)
|
|
75
|
-
npx omega test desktop:
|
|
76
|
-
npx omega test framework:
|
|
77
|
-
|
|
78
|
-
# Run framework tests matching a path
|
|
79
|
-
npx omega test mgr:build/config
|
|
80
|
-
npx omega test desktop:build/config
|
|
81
|
-
|
|
82
|
-
# Combine with extended mode
|
|
83
|
-
TEST_EXTENDED_MODE=true npx omega test build/config
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
The target matches against the test file path. The source prefix scopes selection to framework-only or project-only tests — a prefixed target excludes the other source entirely:
|
|
87
|
-
|
|
88
|
-
- `mgr:`: the **universal cross-framework alias** for "the framework's own tests" (framework-only). Works identically in @omega.js/desktop, @omega.js/extension, @omega.js/web, and @omega.js/backend.
|
|
89
|
-
- `desktop:` / `framework:` — desktop-specific aliases for framework-only tests, equivalent to `mgr:`.
|
|
90
|
-
- `project:` — consumer project tests only.
|
|
91
|
-
|
|
92
|
-
A bare prefix (`mgr:` / `desktop:` / `project:` with no path) runs every test in that source. A bare path (no prefix) binds to the PROJECT source; `full:<path>` searches both sources by path.
|
|
93
|
-
|
|
94
|
-
A target that names a path and matches NO file is a hard error: the run prints `No test file matches "<target>"` and exits 1, so a typo'd path, or a suite renamed out from under a target, can never run silently green ([#814](https://github.com/Omega-JS-Stack/omega/issues/814)). A run that named no file (bare, or a bare source prefix) still exits 0 when there is nothing to run. Inside a brand-root fan-out the manager sets `OMEGA_TEST_FANOUT=1` on every forwarded run, and the same miss answers with exit 3 instead: a path another target carries is a no-op here, and the brand run fails only when EVERY target missed ([docs/shared/testing.md](../../../docs/shared/testing.md#brand-root-cp94b)).
|
|
95
|
-
|
|
96
|
-
> **Target vs `--filter`.** The positional target selects test FILES (by path + source). The `--filter=<substring>` flag is orthogonal: it matches test NAMES/descriptions within the selected files. Use them together, e.g. `npx omega test project: --filter="reorder"`.
|
|
97
|
-
|
|
98
|
-
### Layers
|
|
99
|
-
|
|
100
|
-
- **build** — runs in plain Node. Fast.
|
|
101
|
-
- **main** — spawns Electron and runs inside the main process. Required for anything touching `app`/`ipcMain`/`BrowserWindow`.
|
|
102
|
-
- **renderer** — runs inside a hidden `BrowserWindow` spawned by the main harness. Test functions are serialized + reconstructed via `new Function('ctx', body)`, so they only have access to `ctx` and the page's globals (`window`, `document`, `window.desktop.*`). No closures over module scope.
|
|
103
|
-
- **boot** — rebuilds the project and spawns Electron against the real bundle. The build goes to a staged app root of its own (`<project>/.omega/test-app/`, via `OMEGA_BUILD_OUTPUT`), never the project's `dist/` — so a boot-test run and a live `npm start` watcher never write the same tree. See [test-boot-layer.md](test-boot-layer.md).
|
|
104
|
-
- **all** (default) — build, then main, then renderer in a single Electron boot.
|
|
105
|
-
|
|
106
|
-
### Extended vs normal mode
|
|
107
|
-
|
|
108
|
-
Suites that hit a live backend (a real GitHub API call, a store publish, etc.) are gated behind **extended mode**: `npx omega test --extended` or `TEST_EXTENDED_MODE=true`. `TEST_EXTENDED_MODE` is the **shared, unprefixed env var across @omega.js/backend, @omega.js/extension, UJM, and @omega.js/desktop** (cross-framework parity): once set on `process.env` it propagates to every spawned test environment (the Electron main/renderer/boot children, the gulp boot build) automatically via `{ ...process.env }`. These external calls are **skipped in-source, NOT mocked**: the suite short-circuits / `ctx.skip()`s when `TEST_EXTENDED_MODE` is unset; with it set, it calls the real service. Default is to skip them so `npx omega test` is fast + green offline, and a warning prints when extended mode is on. The CI workflow runs normal mode by default; add a separate workflow that sets `TEST_EXTENDED_MODE: 'true'` for extended coverage.
|
|
109
|
-
|
|
110
|
-
Anything an extended suite creates externally must be torn down in its `cleanup(ctx)` — the harness only resets local Electron state between runs, never external systems.
|
|
111
|
-
|
|
112
|
-
### `OMEGA_ENVIRONMENT=testing`, the one input a test run names
|
|
113
|
-
|
|
114
|
-
Both @omega.js/desktop test runners (`runners/electron.js`, `runners/boot.js`) spawn their child with `OMEGA_ENVIRONMENT=testing`, the ONE environment input ([#817](https://github.com/Omega-JS-Stack/omega/issues/817)), and nothing writes over an explicit one: the word a build baked into the artifact is the fallback for a packaged app, never an override ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)). That powers `omega.isTesting()` (and the build module's `isTesting()`), the cross-context helper everything in @omega.js/desktop checks when it needs to behave differently in tests:
|
|
115
|
-
|
|
116
|
-
- `auto-updater` flips its idle threshold from 15min → 3s and its periodic tick from 60s → 500ms, AND short-circuits the native install-prompt dialog (so tests don't pop modal windows).
|
|
117
|
-
- **Every BrowserWindow surfaces stealth** — named windows via `window-manager._surface()` AND raw `new BrowserWindow()` ones (e.g. a consumer's automation popup) via a global `browser-window-created` hook registered in `main.js` step 1a-ii. The shared recipe lives in `src/utils/stealth-window.js`: shown INACTIVE (keyboard focus never leaves your editor), opacity 0, click-through (`setIgnoreMouseEvents`), and raw windows get `show()` rerouted to `showInactive()` + `focus()` no-op'd — a test run never interrupts you, and a stray real click physically can't land in the app, while synthetic test input (`executeJavaScript`, `sendInputEvent`, CDP) is unaffected. **`webContents.focus()` is suppressed separately** (a global `web-contents-created` hook in the same main.js block): it bypasses the window-level patches — it's a different object whose `focus()` reaches the native window directly and makes the invisible window KEY, grabbing the keyboard mid-typing even under the accessory policy (which only prevents *launch* activation, not key-window steals). Consumers call it legitimately (e.g. address-bar focus on tab select), so it's no-op'd per-contents under the same predicate. Set **`OMEGA_TEST_SHOW=1`** to surface windows normally and watch a run live (the predicate is evaluated per window, so flipping it mid-run works). Deliberately NOT `hide()`/`minimize()`: occluded windows get throttled by Chromium (`requestAnimationFrame` pauses, `document.visibilityState` flips to `hidden`) — tests would exercise a DIFFERENT runtime, whereas an opacity-0 shown-inactive window renders and behaves identically to a visible one. See [windows.md](windows.md).
|
|
118
|
-
- **App-level activation is suppressed too (macOS).** Launching a regular-policy app activates it — the menu bar and keyboard focus switch to the test process at launch even though every window surfaces inactive. Under the same stealth predicate (`src/utils/test-stealth.js` — Testing mode + `OMEGA_TEST_SHOW` unset), `main.js` (step 1a, before app ready) and the spawned test harness flip the app to the **accessory activation policy** (`app.dock.hide()` — the same switch `LSUIElement` bakes for packaged hidden-mode apps): the process never activates, never shows a dock icon, and never steals focus, while windows still render identically. `OMEGA_TEST_SHOW=1` restores normal activation along with visible windows. Verified by frontmost-app sampling across a full run: zero focus changes (previously four steals per run).
|
|
119
|
-
- `main.js#initialize` isolates userData per environment: testing runs get `<userData> (Testing)` — **wiped at boot**, so every test run starts from a clean slate (post-run state stays on disk for inspection until the next run; set `OMEGA_TEST_KEEP_USERDATA=1` to skip the wipe). Dev runs get ` (Development)`; production is untouched. See [boot-sequence.md](boot-sequence.md).
|
|
120
|
-
- **The main-layer harness exposes a CDP endpoint.** `test/harness/main-entry.js` appends `--remote-debugging-port=0` at require time (loopback-only, OS-assigned port) and publishes the resolved port as **`process.env.OMEGA_CDP_PORT`** before suites run (read from Chromium's `DevToolsActivePort` file; any value inherited from your shell is overwritten — that one points at your dev app, not the harness). Consumer suites can drive real browser automation against the harness Electron itself, e.g. `playwright-core`'s `connectOverCDP('http://127.0.0.1:' + process.env.OMEGA_CDP_PORT)`. Covered by `suites/main/harness-cdp.test.js`.
|
|
121
|
-
- Other lib code can branch on `omega.isTesting()` to suppress dock bounce, login-item changes, OS protocol-handler registration, etc.
|
|
122
|
-
|
|
123
|
-
Consumers writing their own tests name the same input in their own runner, for example in `package.json`:
|
|
124
|
-
```json
|
|
125
|
-
"test": "OMEGA_ENVIRONMENT=testing vitest"
|
|
126
|
-
```
|
|
127
|
-
Then in your code, gate test-only behavior on `omega.isTesting()` instead of inventing yet another env var.
|
|
128
|
-
|
|
129
|
-
## Test discovery
|
|
130
|
-
|
|
131
|
-
- **Framework defaults**: `<@omega.js/desktop>/dist/test/suites/**/*.js`
|
|
132
|
-
- **Consumer suites**: `<cwd>/test/**/*.js`
|
|
133
|
-
|
|
134
|
-
**The underscore convention** (`DISCOVERY_IGNORE` in `src/test/runner.js`): `_`-prefixed FILES (`test/_init.js`, `test/main/_helper.js`) and everything under a `_`-prefixed DIRECTORY at **any depth** (`test/_fixtures/**`, `test/boot/_private/**`) are excluded from suite discovery. Put shared helpers, fixture data, and non-test support files in `_`-prefixed paths — e.g. `test/_fixtures/`, `test/_helpers/`. The runner still specifically loads `test/_init.js` as the lifecycle hook. Matches the same convention in @omega.js/backend/BXM/UJM. Files load alphabetically (sorted globally per source).
|
|
135
|
-
|
|
136
|
-
**Framework boot suites are scoped to @omega.js/desktop self-test runs only.** When a consumer runs `npx omega test`, the framework's `dist/test/suites/boot/**` is excluded from discovery — those tests are meant to assert on @omega.js/desktop's own internal fixtures and would fail noisily against a real consumer app. Detection: the runner checks `cwd`'s `package.json#name === '@omega.js/desktop'`. Consumers write their own boot tests under `<cwd>/test/boot/`. Matches the same exclusion pattern in BXM and UJM. See [test-boot-layer.md](test-boot-layer.md).
|
|
137
|
-
|
|
138
|
-
## `test/_init.js` — pre-test lifecycle hook
|
|
139
|
-
|
|
140
|
-
The runner loads an optional `test/_init.js` from **both** test roots — the framework (`<@omega.js/desktop>/test/_init.js`) and the consumer project (`<cwd>/test/_init.js`) — and runs it **once, before any suite** (it is NOT itself run as a test; the `_`-prefix keeps it out of discovery). Mirrors the same hook in @omega.js/backend/UJM/BXM so all four frameworks share one shape.
|
|
141
|
-
|
|
142
|
-
The module **must export a function** — `module.exports = (ctx) => ({ ... })` — called with `{ projectRoot }` and returning the hook object. It may declare:
|
|
143
|
-
|
|
144
|
-
- `async setup({ projectRoot })` — runs once before the suites, e.g. to scaffold a fixture file the boot layer needs.
|
|
145
|
-
|
|
146
|
-
There is **no `cleanup` hook** and **no `accounts` field** (unlike @omega.js/backend — these frameworks have no auth/user system): tests clean up after themselves, so there is nothing project-level to tear down.
|
|
147
|
-
|
|
148
|
-
```javascript
|
|
149
|
-
// <cwd>/test/_init.js
|
|
150
|
-
const fs = require('fs');
|
|
151
|
-
const path = require('path');
|
|
152
|
-
|
|
153
|
-
module.exports = ({ projectRoot }) => ({
|
|
154
|
-
async setup() {
|
|
155
|
-
// Seed any fixture a suite needs before it runs.
|
|
156
|
-
fs.mkdirSync(path.join(projectRoot, '.temp'), { recursive: true });
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Test file shapes
|
|
162
|
-
|
|
163
|
-
Three forms — pick whichever fits.
|
|
164
|
-
|
|
165
|
-
### Suite (sequential, share state, stop on first failure)
|
|
166
|
-
|
|
167
|
-
```js
|
|
168
|
-
module.exports = {
|
|
169
|
-
type: 'suite',
|
|
170
|
-
layer: 'main', // 'build' | 'main' | 'renderer'
|
|
171
|
-
description: 'storage (main)',
|
|
172
|
-
cleanup: async (ctx) => { // runs after the last test
|
|
173
|
-
ctx.omega.storage.clear();
|
|
174
|
-
},
|
|
175
|
-
tests: [
|
|
176
|
-
{
|
|
177
|
-
name: 'set + get round-trip',
|
|
178
|
-
run: (ctx) => {
|
|
179
|
-
ctx.omega.storage.set('hello', 'world');
|
|
180
|
-
ctx.expect(ctx.omega.storage.get('hello')).toBe('world');
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
name: 'has reflects presence',
|
|
185
|
-
run: (ctx) => { /* ... */ },
|
|
186
|
-
cleanup: (ctx) => { /* ... */ }, // per-test cleanup
|
|
187
|
-
skip: 'reason', // skip this test
|
|
188
|
-
},
|
|
189
|
-
],
|
|
190
|
-
};
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Tests share `ctx.state` across the suite. If one fails, remaining tests are skipped (`stopOnFailure: false` to disable).
|
|
194
|
-
|
|
195
|
-
### Group (parallel-ish, share state, run all regardless of failures)
|
|
196
|
-
|
|
197
|
-
```js
|
|
198
|
-
module.exports = {
|
|
199
|
-
type: 'group',
|
|
200
|
-
layer: 'main',
|
|
201
|
-
description: 'boot sequence (main)',
|
|
202
|
-
tests: [ /* same shape as suite */ ],
|
|
203
|
-
};
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Same shape as suite, but all tests run even if some fail.
|
|
207
|
-
|
|
208
|
-
### Standalone (single test per file)
|
|
209
|
-
|
|
210
|
-
```js
|
|
211
|
-
module.exports = {
|
|
212
|
-
layer: 'build',
|
|
213
|
-
description: 'CLI alias resolves to a command file',
|
|
214
|
-
run: (ctx) => { /* ... */ },
|
|
215
|
-
cleanup: (ctx) => { /* ... */ },
|
|
216
|
-
timeout: 10000,
|
|
217
|
-
skip: false,
|
|
218
|
-
};
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Array shorthand (group of tests, no metadata)
|
|
222
|
-
|
|
223
|
-
```js
|
|
224
|
-
module.exports = [
|
|
225
|
-
{ name: 'A', run: (ctx) => { /* ... */ } },
|
|
226
|
-
{ name: 'B', run: (ctx) => { /* ... */ } },
|
|
227
|
-
];
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Layers
|
|
231
|
-
|
|
232
|
-
| Layer | Where it runs | Use for |
|
|
233
|
-
|---|---|---|
|
|
234
|
-
| `build` | Plain Node | CLI, package.json, config schema, gulp tasks |
|
|
235
|
-
| `main` | Spawned Electron main process | `omega.initialize()`, lib modules, IPC, windows |
|
|
236
|
-
| `renderer` | Hidden BrowserWindow: the framework's harness page by default, one of the project's own `src/views/` when the suite declares `view: '<name>'` | `window.desktop.*`, preload bridge, UI logic, your own views |
|
|
237
|
-
|
|
238
|
-
The runner partitions test files by layer at discovery time. The build layer runs inline; the main layer spawns Electron once with all main suites and parses JSON-line stdout.
|
|
239
|
-
|
|
240
|
-
## ctx (context object)
|
|
241
|
-
|
|
242
|
-
Every test fn receives a `ctx`:
|
|
243
|
-
|
|
244
|
-
```js
|
|
245
|
-
ctx.expect(actual) // Jest-compatible expect()
|
|
246
|
-
ctx.state // shared object across tests in a suite/group
|
|
247
|
-
ctx.layer // 'build' | 'main' | 'renderer'
|
|
248
|
-
ctx.skip(reason) // skip from inside the test
|
|
249
|
-
ctx.omega // (main layer only) the booted @omega.js/desktop main-process instance
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
## expect() matchers
|
|
253
|
-
|
|
254
|
-
Jest-compatible subset:
|
|
255
|
-
|
|
256
|
-
```js
|
|
257
|
-
.toBe(expected) // ===
|
|
258
|
-
.toEqual(expected) // deep equal
|
|
259
|
-
.toBeTruthy() / .toBeFalsy()
|
|
260
|
-
.toBeDefined() / .toBeUndefined() / .toBeNull()
|
|
261
|
-
.toContain(item) // array.includes / string.includes
|
|
262
|
-
.toHaveProperty(key)
|
|
263
|
-
.toMatch(regex)
|
|
264
|
-
.toBeInstanceOf(class)
|
|
265
|
-
.toBeGreaterThan(n) / .toBeLessThan(n)
|
|
266
|
-
.toThrow(regex|string) // also accepts async fns
|
|
267
|
-
|
|
268
|
-
.not.<anything> // negate any matcher
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
## Output
|
|
272
|
-
|
|
273
|
-
```
|
|
274
|
-
OMEGA Desktop Tests
|
|
275
|
-
|
|
276
|
-
Framework Tests
|
|
277
|
-
⤷ storage (main)
|
|
278
|
-
✓ set + get round-trip (7ms)
|
|
279
|
-
✓ has reflects presence (8ms)
|
|
280
|
-
...
|
|
281
|
-
|
|
282
|
-
Results
|
|
283
|
-
149 passing
|
|
284
|
-
|
|
285
|
-
Total: 149 tests in 1850ms
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
All test output is also teed (ANSI-stripped) to `<projectRoot>/logs/test.log`, truncated fresh on each run — same pattern as `dev.log` (and @omega.js/backend's `test.log`). Grep it after a run instead of scrolling terminal output.
|
|
289
|
-
|
|
290
|
-
## Test harness internals (main layer)
|
|
291
|
-
|
|
292
|
-
- `runners/electron.js` spawns Electron with `harness/main-entry.js` as the app.
|
|
293
|
-
- `harness/main-entry.js` boots `omega` with `skipWindowCreation: true`, runs the suites, emits results via `__OMEGA_TEST__{json}\n` lines on stdout (`TEST_EVENT_PREFIX`, [src/utils/test-events.js](../src/utils/test-events.js), the one prefix every harness writes and every runner reads).
|
|
294
|
-
- The runner parses those lines and renders @omega.js/backend-style output.
|
|
295
|
-
- stderr is always drained (otherwise the pipe fills and the harness blocks); printed only when `OMEGA_TEST_DEBUG=1`.
|
|
296
|
-
- `ELECTRON_RUN_AS_NODE` is stripped from the spawn env (would otherwise make Electron behave as Node and break the harness).
|
|
297
|
-
- **Consumer-scheme containment.** The consumer's `main.js` (where the real `protocol.handle('<brand.id>', …)` lives) never runs in this harness, so `brand://` URLs loaded by main-layer suites would be UNHANDLED. Chromium treats a load on an unhandled scheme as an *external protocol* and hands it to the OS: and if an installed copy of the app owns the scheme (e.g. `/Applications/Brand.app` on macOS, registered via its `Info.plist`), Launch Services launches the installed production app mid-test-run. The harness contains this two ways after `omega` boots: (1) it registers a stub handler for the consumer's brand scheme (read from `<cwd>/config/omega.json5`) on the default session, so `brand://` loads commit in-process as a blank page; (2) it denies the `openExternal` permission on the default session and every session created after it (partitions included), so no navigation can hand ANY scheme to the OS during a test run. Suites that need real page content for `brand://` URLs belong in the boot layer, where the real app (and its real protocol handler) runs. A main-layer suite that genuinely needs its OWN handler for the brand scheme must call `protocol.unhandle(brand.id)` first: the harness stub holds the scheme on the default session.
|
|
298
|
-
|
|
299
|
-
## A test run never touches the OS keychain, and a blocked boot reports
|
|
300
|
-
|
|
301
|
-
The harness resolves auth persistence to the **`none`** strategy on every layer, main and boot alike, before any config is read: a brand that declares `omega.authPersistence: 'safeStorage'` still runs its tests with Firebase auth in memory, because the lanes never sign a real user in. That is a hard rule, not a nicety. The boot lane spawns the raw, unsigned `Electron` binary, which carries no keychain ACL, so one `safeStorage` read parks the entire run behind a macOS SecurityAgent prompt ([#907](https://github.com/Omega-JS-Stack/omega/issues/907)). ONE signal answers "is this a test run" on both layers, `omega.isTesting()`: the boot lane boots the consumer's PRODUCTION artifact, and the word baked into it no longer writes over the `OMEGA_ENVIRONMENT=testing` the runner spawned it with ([#925](https://github.com/Omega-JS-Stack/omega/issues/925)), so the booted app reports the lane it is running in. And since a boot can block long before `main.js` requires the harness (where the per-test timeout lives), the boot runner carries its own budget, measured as SILENCE: every harness line rearms it, so a suite that legitimately runs longer keeps going, and a boot that produces no harness output for 60s (`OMEGA_TEST_BOOT_TIMEOUT_MS` overrides) counts its tests failed, prints `✗ boot: no harness output after 60s; last runtime.log line: "<line>"` from the booted app's `logs/runtime.log`, and ends the child instead of hanging `npx omega test`.
|
|
302
|
-
|
|
303
|
-
## Writing consumer tests
|
|
304
|
-
|
|
305
|
-
In a consumer project, drop files in `test/` (or `test/**`):
|
|
306
|
-
|
|
307
|
-
```js
|
|
308
|
-
// test/login-flow.test.js
|
|
309
|
-
module.exports = {
|
|
310
|
-
type: 'suite',
|
|
311
|
-
layer: 'main',
|
|
312
|
-
description: 'login flow',
|
|
313
|
-
tests: [
|
|
314
|
-
{
|
|
315
|
-
name: 'storage starts empty',
|
|
316
|
-
run: (ctx) => {
|
|
317
|
-
ctx.expect(ctx.omega.storage.get('user')).toBeUndefined();
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
],
|
|
321
|
-
};
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
`npx omega test` runs your project suites; scope `framework:` or `full:` to reach the framework's own.
|
|
325
|
-
|
|
326
|
-
### Testing your own views
|
|
327
|
-
|
|
328
|
-
A `renderer` suite that declares **`view: '<name>'`** runs against `src/views/<name>/` of YOUR app instead of the framework's harness page:
|
|
329
|
-
|
|
330
|
-
```js
|
|
331
|
-
// test/renderer/main-view.test.js
|
|
332
|
-
module.exports = {
|
|
333
|
-
type: 'group',
|
|
334
|
-
layer: 'renderer',
|
|
335
|
-
view: 'main', // <- src/views/main/, as built
|
|
336
|
-
description: 'the main view',
|
|
337
|
-
tests: [
|
|
338
|
-
{
|
|
339
|
-
name: 'the heading renders the product name',
|
|
340
|
-
run: (ctx) => {
|
|
341
|
-
ctx.expect(document.querySelector('h1').textContent.trim()).toBe('My App');
|
|
342
|
-
},
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
name: 'the preload reached the page',
|
|
346
|
-
run: (ctx) => {
|
|
347
|
-
ctx.expect(typeof window.desktop).toBe('object');
|
|
348
|
-
},
|
|
349
|
-
},
|
|
350
|
-
],
|
|
351
|
-
};
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
Such a suite rides the **boot lane**, because only that lane stages and builds the real app (`<project>/.omega/test-app/`) before running: the page gets the project's real `dist/preload.bundle.js`, the real IPC handlers of the booted main process, and the real config. The window is created through the app's own window manager (hidden, no bounds persistence) and destroyed when the suite ends. Cost is the boot lane's: one build (~10-30s) shared with your boot tests.
|
|
355
|
-
|
|
356
|
-
Consequences worth knowing:
|
|
357
|
-
|
|
358
|
-
- **`--layer=renderer` does NOT run a view suite** (it needs the boot). `--layer=boot` and the default `--layer=all` do.
|
|
359
|
-
- The test bodies still run as `new Function` inside the page: no closures over module scope, only `ctx` (`expect`, `state`, `layer`, `skip`) and the page globals (`window`, `document`, `window.desktop.*`).
|
|
360
|
-
- Without `view`, nothing changes: the suite runs on the harness page in the main test Electron, as it always has.
|
|
361
|
-
- A view that fails to load fails every test of the suite with `view "<name>" did not load`, rather than reporting an empty page.
|
|
362
|
-
- Each view suite has a hard 60 s ceiling for the whole suite (the same ceiling the harness page uses); a suite's own `timeout` applies per test underneath it.
|