@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.
Files changed (161) hide show
  1. package/README.md +38 -38
  2. package/dist/cli-run.js +4 -1
  3. package/dist/cli.js +2 -2
  4. package/dist/commands/cdp/client.js +1 -1
  5. package/dist/commands/cdp.js +1 -1
  6. package/dist/commands/clean.js +2 -3
  7. package/dist/commands/dev.js +25 -0
  8. package/dist/commands/lib/ensure-target.js +12 -17
  9. package/dist/commands/lib/migrate.js +17 -0
  10. package/dist/commands/logs.js +1 -1
  11. package/dist/commands/release.js +1 -1
  12. package/dist/commands/test.js +4 -4
  13. package/dist/commands/update.js +5 -4
  14. package/dist/defaults/.github/workflows/build.yml +18 -18
  15. package/dist/defaults/_.gitignore +0 -2
  16. package/dist/defaults/_mas/README.md +3 -3
  17. package/dist/defaults/config/certs/README.md +1 -1
  18. package/dist/defaults/config/omega.json5 +36 -36
  19. package/dist/defaults/docs/README.md +3 -3
  20. package/dist/defaults/gulpfile.js +1 -1
  21. package/dist/defaults/hooks/build/post.js +1 -1
  22. package/dist/defaults/hooks/build/pre.js +1 -1
  23. package/dist/defaults/hooks/notarize/post.js +2 -2
  24. package/dist/defaults/hooks/release/post.js +1 -1
  25. package/dist/defaults/hooks/release/pre.js +1 -1
  26. package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
  27. package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
  28. package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
  29. package/dist/defaults/src/integrations/context-menu/index.js +11 -11
  30. package/dist/defaults/src/integrations/menu/index.js +5 -5
  31. package/dist/defaults/src/integrations/tray/index.js +9 -9
  32. package/dist/defaults/src/main.js +2 -2
  33. package/dist/defaults/src/preload.js +1 -1
  34. package/dist/defaults/test/README.md +3 -3
  35. package/dist/defaults/test/_init.js +1 -1
  36. package/dist/gulp/tasks/audit.js +5 -8
  37. package/dist/lib/restart-manager/index.js +1 -1
  38. package/dist/lib/restart-manager/install.js +1 -1
  39. package/dist/lib/restart-manager/protocol.js +1 -1
  40. package/dist/main.js +4 -3
  41. package/dist/preload.js +1 -1
  42. package/dist/test/suites/build/audit.test.js +20 -7
  43. package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
  44. package/dist/test/suites/build/cli.test.js +28 -0
  45. package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
  46. package/dist/test/suites/build/defaults-scaffold.test.js +19 -5
  47. package/dist/test/suites/build/deploy-direct.test.js +7 -5
  48. package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
  49. package/dist/test/suites/build/deploy-hook.test.js +4 -2
  50. package/dist/test/suites/build/dev-verb.test.js +67 -0
  51. package/dist/test/suites/build/ensure-target.test.js +11 -3
  52. package/dist/test/suites/build/merge-line-files.test.js +6 -6
  53. package/dist/test/suites/build/migrate.test.js +29 -0
  54. package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
  55. package/dist/test/suites/build/runner-env-write.test.js +73 -0
  56. package/dist/test/suites/build/runner.test.js +9 -8
  57. package/dist/test/suites/build/setup-scripts.test.js +27 -0
  58. package/dist/test/suites/build/validate-config.test.js +13 -2
  59. package/dist/test/suites/build/verb-logs.test.js +20 -0
  60. package/dist/test/suites/renderer/window-desktop-surface.test.js +1 -1
  61. package/dist/utils/build-pipeline.js +4 -4
  62. package/dist/utils/runner-env.js +13 -28
  63. package/dist/vendor/config/company.js +46 -14
  64. package/dist/vendor/config/defaults.js +30 -7
  65. package/dist/vendor/config/edit.js +25 -3
  66. package/dist/vendor/config/env-delivery.js +1 -1
  67. package/dist/vendor/config/env-schema.js +3 -6
  68. package/dist/vendor/config/env.js +34 -22
  69. package/dist/vendor/config/index.js +13 -17
  70. package/dist/vendor/config/load.js +15 -7
  71. package/dist/vendor/config/repo.js +10 -27
  72. package/dist/vendor/config/schema-client.js +64 -0
  73. package/dist/vendor/config/schema-cloud.js +38 -0
  74. package/dist/vendor/config/schema-manager.js +118 -0
  75. package/dist/vendor/config/schema-overrides.js +68 -0
  76. package/dist/vendor/config/schema.js +99 -152
  77. package/dist/vendor/config/validate.js +97 -77
  78. package/dist/vendor/devkit/agents-md.js +233 -0
  79. package/dist/vendor/devkit/attach-log-file.js +15 -1
  80. package/dist/vendor/devkit/ci-workflows.js +30 -30
  81. package/dist/vendor/devkit/cli-router.js +13 -7
  82. package/dist/vendor/devkit/defaults-engine.js +9 -43
  83. package/dist/vendor/devkit/deploy-snapshot.js +44 -9
  84. package/dist/vendor/devkit/env-lines.js +183 -0
  85. package/dist/vendor/devkit/local.js +62 -10
  86. package/dist/vendor/devkit/lockfile.js +32 -13
  87. package/dist/vendor/devkit/logger.js +7 -2
  88. package/dist/vendor/devkit/merge-line-files.js +219 -176
  89. package/dist/vendor/devkit/omega-bin.js +208 -111
  90. package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
  91. package/dist/vendor/devkit/preludes/index.js +1 -0
  92. package/dist/vendor/devkit/target-picker.js +45 -0
  93. package/dist/vendor/devkit/test/dashed-files.js +37 -0
  94. package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
  95. package/dist/vendor/devkit/update.js +15 -15
  96. package/dist/vendor/devkit/verb-scripts.js +40 -0
  97. package/dist/vendor/devkit/verbs.js +170 -0
  98. package/package.json +18 -24
  99. package/dist/commands/install.js +0 -37
  100. package/dist/defaults/AGENTS.md +0 -119
  101. package/dist/defaults/CLAUDE.md +0 -1
  102. package/dist/vendor/config/env-retired.js +0 -137
  103. package/dist/vendor/config/retired-keys.js +0 -635
  104. package/docs/analytics.md +0 -140
  105. package/docs/app-state.md +0 -92
  106. package/docs/audit.md +0 -69
  107. package/docs/auth.md +0 -284
  108. package/docs/auto-updater.md +0 -243
  109. package/docs/boot-sequence.md +0 -44
  110. package/docs/build-system.md +0 -169
  111. package/docs/cdp-debugging.md +0 -169
  112. package/docs/common-mistakes.md +0 -21
  113. package/docs/config-schema.md +0 -120
  114. package/docs/context-menu.md +0 -112
  115. package/docs/context.md +0 -81
  116. package/docs/css.md +0 -84
  117. package/docs/deep-link.md +0 -186
  118. package/docs/environment-detection.md +0 -112
  119. package/docs/fontawesome.md +0 -109
  120. package/docs/hooks.md +0 -89
  121. package/docs/icons.md +0 -79
  122. package/docs/index.md +0 -328
  123. package/docs/installer-options.md +0 -165
  124. package/docs/ipc.md +0 -61
  125. package/docs/lib-modules.md +0 -53
  126. package/docs/logging.md +0 -227
  127. package/docs/menu.md +0 -160
  128. package/docs/releasing.md +0 -239
  129. package/docs/remote-config.md +0 -118
  130. package/docs/remote-scripts.md +0 -144
  131. package/docs/restart-manager.md +0 -144
  132. package/docs/runner.md +0 -290
  133. package/docs/sentry.md +0 -97
  134. package/docs/shared/agent-docs.md +0 -89
  135. package/docs/shared/analytics.md +0 -612
  136. package/docs/shared/brands.md +0 -57
  137. package/docs/shared/breaking-changes.md +0 -917
  138. package/docs/shared/config.md +0 -1948
  139. package/docs/shared/deploys.md +0 -341
  140. package/docs/shared/icons.md +0 -219
  141. package/docs/shared/local-dev.md +0 -167
  142. package/docs/shared/logging.md +0 -205
  143. package/docs/shared/monitoring.md +0 -167
  144. package/docs/shared/publishing.md +0 -187
  145. package/docs/shared/rulings.md +0 -34
  146. package/docs/shared/testing.md +0 -147
  147. package/docs/shared/theming.md +0 -629
  148. package/docs/shared/translation.md +0 -342
  149. package/docs/shared/updates.md +0 -61
  150. package/docs/signing.md +0 -293
  151. package/docs/startup.md +0 -142
  152. package/docs/storage.md +0 -59
  153. package/docs/templating.md +0 -101
  154. package/docs/test-boot-layer.md +0 -157
  155. package/docs/test-framework.md +0 -362
  156. package/docs/themes.md +0 -149
  157. package/docs/tooltips.md +0 -99
  158. package/docs/tray.md +0 -164
  159. package/docs/usage.md +0 -58
  160. package/docs/verts.md +0 -62
  161. package/docs/windows.md +0 -149
@@ -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.
@@ -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.