@ultimat3/testing 28.0.0 → 29.0.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/testing",
3
- "version": "28.0.0",
3
+ "version": "29.0.0",
4
4
  "description": "Test harness: cloned template DBs per worker, frozen clock, sealed network, 6 test types",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,27 +29,27 @@
29
29
  "LICENSE"
30
30
  ],
31
31
  "engines": {
32
- "bun": ">=1.4.3"
32
+ "bun": ">=1.4.4"
33
33
  },
34
34
  "scripts": {
35
35
  "typecheck": "tsc --noEmit -p tsconfig.json",
36
36
  "test": "bun test"
37
37
  },
38
38
  "dependencies": {
39
- "@ultimat3/action": "28.0.0",
40
- "@ultimat3/cache": "28.0.0",
41
- "@ultimat3/core": "28.0.0",
42
- "@ultimat3/db": "28.0.0",
43
- "@ultimat3/entity": "28.0.0",
44
- "@ultimat3/http": "28.0.0",
45
- "@ultimat3/i18n": "28.0.0",
46
- "@ultimat3/jobs": "28.0.0",
47
- "@ultimat3/mail": "28.0.0",
48
- "@ultimat3/policy": "28.0.0",
49
- "@ultimat3/pwa": "28.0.0",
50
- "@ultimat3/query": "28.0.0",
51
- "@ultimat3/realtime": "28.0.0",
52
- "@ultimat3/render": "28.0.0",
53
- "@ultimat3/time": "28.0.0"
39
+ "@ultimat3/action": "29.0.0",
40
+ "@ultimat3/cache": "29.0.0",
41
+ "@ultimat3/core": "29.0.0",
42
+ "@ultimat3/db": "29.0.0",
43
+ "@ultimat3/entity": "29.0.0",
44
+ "@ultimat3/http": "29.0.0",
45
+ "@ultimat3/i18n": "29.0.0",
46
+ "@ultimat3/jobs": "29.0.0",
47
+ "@ultimat3/mail": "29.0.0",
48
+ "@ultimat3/policy": "29.0.0",
49
+ "@ultimat3/pwa": "29.0.0",
50
+ "@ultimat3/query": "29.0.0",
51
+ "@ultimat3/realtime": "29.0.0",
52
+ "@ultimat3/render": "29.0.0",
53
+ "@ultimat3/time": "29.0.0"
54
54
  }
55
55
  }
package/src/preload.ts CHANGED
@@ -54,11 +54,18 @@ installPerTestReset();
54
54
  // actor's bucket left it spent for the next file on the worker, so a "the 31st call is a 429" test
55
55
  // was red whenever another file calling that action ran first. Each file starts with full buckets,
56
56
  // the store back to one process' memory, as a file run alone would have it.
57
+ //
58
+ // And the LIFECYCLE, which is one process' too: a drain is terminal, so a file that drained the
59
+ // process — or that emitted a real SIGINT at it, as a signal test does — left every later file on
60
+ // the worker answering 503 X_DRAINING for a reason nothing in that file explains. Each file starts
61
+ // from a live lifecycle, as a file run alone would have it.
57
62
  if (Bun.env[ISOLATED_ENV] !== '1') {
58
63
  const { forgetHandedOutActionPaths } = await import('@ultimat3/action');
59
64
  onFileBoundary(forgetHandedOutActionPaths);
60
65
  const { resetRateLimitStore } = await import('@ultimat3/http');
61
66
  onFileBoundary(resetRateLimitStore);
67
+ const { resetLifecycle } = await import('@ultimat3/core');
68
+ onFileBoundary(resetLifecycle);
62
69
  }
63
70
 
64
71
  // Isolated runs only (`x test` says so): Bun keeps every finished file alive while a plugin is
@@ -4,7 +4,7 @@
4
4
 
5
5
  import type { Actor } from '@ultimat3/core';
6
6
  import { assert, ctxOf, runWithContext } from '@ultimat3/core';
7
- import type { IslandDirective, RouteConfig, RouteParams } from '@ultimat3/render';
7
+ import type { AssetPath, IslandDirective, RouteConfig, RouteParams } from '@ultimat3/render';
8
8
 
9
9
  /** What a component rendered. */
10
10
  export interface RenderedView {
@@ -24,6 +24,16 @@ export interface RenderedRoute<TData> extends RenderedView {
24
24
  readonly meta: Awaited<ReturnType<RouteConfig<TData>['meta']>>;
25
25
  /** The islands this render emitted, in order. Empty on a page that ships no JavaScript. */
26
26
  readonly islands: readonly IslandDirective[];
27
+ /**
28
+ * The app-wide favicon's URL, when one was declared and the app's site asset table answered:
29
+ * the content-hashed URL `/assets/*` serves — the same one the document's
30
+ * `<link rel="icon" type="image/svg+xml">` carries.
31
+ *
32
+ * `undefined` when the app declares no favicon, and `undefined` when the URL cannot be
33
+ * resolved: a unit test that never loaded the app has no site asset table installed, and a
34
+ * route still renders.
35
+ */
36
+ readonly favicon?: string | undefined;
27
37
  }
28
38
 
29
39
  /**
@@ -42,6 +52,16 @@ export interface RouteRequest {
42
52
  readonly params?: RouteParams;
43
53
  /** Who is asking. `load` and the page run inside a request context holding this actor. */
44
54
  readonly actor?: Actor;
55
+ /**
56
+ * The app's favicon — the app-wide declaration in `app.config.ts` (`site.favicon`), the one
57
+ * SVG every document of the app links. It is the APP's fact, not the route's: the route
58
+ * repeats nothing, and the render resolves it through the app's own site asset table so a
59
+ * test can see the URL `/assets/*` will serve.
60
+ *
61
+ * Optional: an app that declares no favicon, and a test that never loaded the app, both ask
62
+ * for a route with no favicon to resolve.
63
+ */
64
+ readonly favicon?: AssetPath | undefined;
45
65
  }
46
66
 
47
67
  const DEFAULT_URL = 'http://localhost/';
@@ -64,6 +84,29 @@ const visibleText = (html: string): string =>
64
84
 
65
85
  const viewOf = (html: string): RenderedView => ({ html, text: visibleText(html) });
66
86
 
87
+ /**
88
+ * The favicon's URL through the app's own site asset table — `asset()`'s answer, the same URL
89
+ * the document's `<link rel="icon">` carries and `/assets/*` serves.
90
+ *
91
+ * `undefined` when the app declares no favicon, and `undefined` when the URL cannot be
92
+ * resolved: a unit test that never loaded the app has no site asset table installed, and
93
+ * `asset()` refuses by name there. A route render is not a document, so a favicon it cannot
94
+ * resolve is no reason to fail the render — the app's BOOT is where a declared favicon that is
95
+ * not on disk is refused (`X_ASSET_MISSING`), and a test asks about a route, not about the
96
+ * app's icon.
97
+ */
98
+ const faviconUrl = async (
99
+ render: typeof import('@ultimat3/render'),
100
+ favicon: AssetPath | undefined,
101
+ ): Promise<string | undefined> => {
102
+ if (favicon === undefined) return undefined;
103
+ try {
104
+ return render.asset(favicon);
105
+ } catch {
106
+ return undefined;
107
+ }
108
+ };
109
+
67
110
  /**
68
111
  * Render one component with the props a page would hand it. The props are the component's own
69
112
  * type, so a renamed prop is a compile error in the test rather than an `undefined` in the markup.
@@ -102,6 +145,9 @@ export async function renderRoute<TData>(
102
145
  const url = request.url ?? DEFAULT_URL;
103
146
  const ctx = { params: request.params ?? {}, url };
104
147
  const islands = render.islandCollector({ file: url, hydrate: config.hydrate });
148
+ // The app's favicon, resolved through the app's own asset table — and skipped when there is
149
+ // no table to ask, so a route renders with no favicon setup at all.
150
+ const favicon = await faviconUrl(render, request.favicon);
105
151
 
106
152
  const run = async (): Promise<RenderedRoute<TData>> => {
107
153
  const data = await render.routeDataFor(config, ctx);
@@ -110,7 +156,7 @@ export async function renderRoute<TData>(
110
156
  const html = await renderToHtml(component({ data, params: ctx.params, url, query }), {
111
157
  islands,
112
158
  });
113
- return { ...viewOf(html), data, meta, islands: islands.directives };
159
+ return { ...viewOf(html), data, meta, islands: islands.directives, favicon };
114
160
  };
115
161
 
116
162
  if (request.actor === undefined) return run();