@fairfox/polly 0.38.2 → 0.47.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/dist/cli/polly.js.map +1 -1
- package/dist/src/background/index.js +1 -2
- package/dist/src/background/index.js.map +6 -6
- package/dist/src/background/message-router.js +1 -2
- package/dist/src/background/message-router.js.map +6 -6
- package/dist/src/client/index.js +46 -27
- package/dist/src/client/index.js.map +5 -5
- package/dist/src/client/wrapper.d.ts +8 -0
- package/dist/src/elysia/index.js +10 -31
- package/dist/src/elysia/index.js.map +5 -5
- package/dist/src/elysia/route-match.d.ts +9 -0
- package/dist/src/elysia/types.d.ts +4 -5
- package/dist/src/index.js +1 -2
- package/dist/src/index.js.map +7 -7
- package/dist/src/mesh-node.js.map +1 -1
- package/dist/src/mesh.js.map +7 -7
- package/dist/src/peer.js.map +3 -3
- package/dist/src/polly-ui/index.js.map +3 -3
- package/dist/src/polly-ui/markdown.js +583 -517
- package/dist/src/polly-ui/markdown.js.map +6 -6
- package/dist/src/polly-ui/registry.d.ts +16 -0
- package/dist/src/polly-ui/registry.generated.d.ts +20 -0
- package/dist/src/shared/adapters/index.js.map +3 -3
- package/dist/src/shared/lib/context-helpers.js +1 -2
- package/dist/src/shared/lib/context-helpers.js.map +6 -6
- package/dist/src/shared/lib/mesh-signaling-client.d.ts +1 -1
- package/dist/src/shared/lib/message-bus.js +1 -2
- package/dist/src/shared/lib/message-bus.js.map +6 -6
- package/dist/src/shared/lib/peer-relay-adapter.d.ts +3 -2
- package/dist/src/shared/lib/resource.js.map +3 -3
- package/dist/src/shared/lib/state.js.map +3 -3
- package/dist/src/shared/state/app-state.js.map +4 -4
- package/dist/src/shared/types/messages.d.ts +0 -9
- package/dist/src/shared/types/messages.js.map +1 -1
- package/dist/tools/quality/src/attest.d.ts +55 -0
- package/dist/tools/quality/src/cache.d.ts +34 -0
- package/dist/tools/quality/src/cli.js +1738 -2
- package/dist/tools/quality/src/cli.js.map +13 -4
- package/dist/tools/quality/src/config.d.ts +18 -0
- package/dist/tools/quality/src/host.d.ts +46 -0
- package/dist/tools/quality/src/index.d.ts +7 -0
- package/dist/tools/quality/src/index.js +1637 -1
- package/dist/tools/quality/src/index.js.map +13 -4
- package/dist/tools/quality/src/plugins/core-checks.d.ts +20 -0
- package/dist/tools/quality/src/plugins/core.d.ts +18 -0
- package/dist/tools/quality/src/plugins/extra-checks.d.ts +14 -0
- package/dist/tools/quality/src/plugins/import-checks.d.ts +16 -0
- package/dist/tools/quality/src/plugins/polly-ui.d.ts +31 -0
- package/dist/tools/quality/src/types.d.ts +104 -0
- package/dist/tools/test/src/adapters/index.js.map +1 -1
- package/dist/tools/test/src/index.js.map +1 -1
- package/dist/tools/test/src/visual/index.js +64 -38
- package/dist/tools/test/src/visual/index.js.map +4 -4
- package/dist/tools/verify/Dockerfile +1 -1
- package/dist/tools/verify/specs/Dockerfile +1 -1
- package/dist/tools/verify/src/cli.js +324 -226
- package/dist/tools/verify/src/cli.js.map +8 -7
- package/dist/tools/visualize/src/cli.js.map +3 -3
- package/package.json +40 -29
- package/dist/src/utils/function-serialization.d.ts +0 -14
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Additional checks bundled into `pollyCorePlugin`.
|
|
3
|
+
*
|
|
4
|
+
* Each check here is a port of an existing `scripts/check-*.ts` script
|
|
5
|
+
* from polly's repo into the plugin contract. The original scripts
|
|
6
|
+
* remain on disk for back-compat with the existing pre-commit
|
|
7
|
+
* orchestrator (`scripts/check.ts`); the plugin path is the new way
|
|
8
|
+
* for downstream consumers (lingua, fairfox, warehouse-experiments)
|
|
9
|
+
* who do not want to copy the script verbatim.
|
|
10
|
+
*
|
|
11
|
+
* Out of scope for this release:
|
|
12
|
+
* - `polly:boundaries` issue text describes a workspace-dependency
|
|
13
|
+
* model (each package may only import from packages it lists in
|
|
14
|
+
* its `dependencies`). Polly itself is a single-package repo and
|
|
15
|
+
* uses directional zone bans (`src/` cannot import `tools/`,
|
|
16
|
+
* etc.); that model is what ships here. The workspace-dep model
|
|
17
|
+
* can be layered on as an additional configuration mode later.
|
|
18
|
+
*/
|
|
19
|
+
import type { Check } from "../types";
|
|
20
|
+
export declare const additionalCoreChecks: Check<unknown>[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pollyCorePlugin` — the polly-provided core plugin.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the four checks polly already ships in `@fairfox/polly/quality`
|
|
5
|
+
* into the new `Check` contract. The wrapping is purely adaptive: each
|
|
6
|
+
* underlying function (`checkNoAsCasting`, `checkNoRequire`,
|
|
7
|
+
* `checkSecrets`, `checkGitignoreCoversAllowlist`) keeps its existing
|
|
8
|
+
* exports unchanged, so consumers wiring those up by hand continue to
|
|
9
|
+
* work. The plugin path is the new way; the function path stays the
|
|
10
|
+
* same.
|
|
11
|
+
*
|
|
12
|
+
* The CSS family and shared-components live in a separate `polly-ui`
|
|
13
|
+
* plugin (#92–#94) because they belong to the styled-component
|
|
14
|
+
* contract polly-ui owns. Issue #98's scope is the four core checks.
|
|
15
|
+
*/
|
|
16
|
+
import type { QualityPlugin } from "../types";
|
|
17
|
+
export declare const POLLY_CORE_VERSION = "0.45.0";
|
|
18
|
+
export declare const pollyCorePlugin: QualityPlugin;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three more checks bundled into `pollyCorePlugin`:
|
|
3
|
+
*
|
|
4
|
+
* - polly:forbidden-deps — import-graph ban list (#87)
|
|
5
|
+
* - polly:no-state-hooks — ban useState/useReducer/useSignal (#99)
|
|
6
|
+
* - polly:typographic-quotes — opt-in straight-vs-curly enforcement (#88)
|
|
7
|
+
*
|
|
8
|
+
* Each check is parameterised: defaults match polly's own pre-commit
|
|
9
|
+
* surface; consumers override via `polly.config.ts`. Test files are
|
|
10
|
+
* excluded by default for the import-walking checks since mocks and
|
|
11
|
+
* fixtures legitimately reference banned packages.
|
|
12
|
+
*/
|
|
13
|
+
import type { Check } from "../types";
|
|
14
|
+
export declare const extraCoreChecks: Check<unknown>[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import-graph checks for `pollyCorePlugin`:
|
|
3
|
+
*
|
|
4
|
+
* - polly:relative-imports — ban `../` imports beyond a depth threshold (#84)
|
|
5
|
+
* - polly:tsconfig-paths — ban `compilerOptions.paths` aliases (#84)
|
|
6
|
+
* - polly:no-raw-http — force HTTP through a canonical client (#86)
|
|
7
|
+
* - polly:types — multi-package tsc --noEmit orchestrator (#85)
|
|
8
|
+
*
|
|
9
|
+
* All four are parameterised. Defaults are silent or zero-impact unless a
|
|
10
|
+
* consumer opts in via `polly.config.ts`. Polly's own pre-commit pipeline
|
|
11
|
+
* does not currently run these — they exist primarily for downstream
|
|
12
|
+
* consumers (lingua, fairfox, warehouse-experiments) and ship under the
|
|
13
|
+
* core plugin namespace so adoption is one config-block change away.
|
|
14
|
+
*/
|
|
15
|
+
import type { Check } from "../types";
|
|
16
|
+
export declare const importCoreChecks: Check<unknown>[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pollyUiPlugin` — the polly-ui-provided plugin (#89, #90, #91–#94).
|
|
3
|
+
*
|
|
4
|
+
* Re-homes the CSS conformance family and shared-components ban under a
|
|
5
|
+
* dedicated `polly-ui` namespace, and adds a new `no-inline-handlers`
|
|
6
|
+
* check for JSX event handlers. Every wrap reuses the underlying
|
|
7
|
+
* function from `tools/quality/src/css/*` and
|
|
8
|
+
* `tools/quality/src/check-shared-components.ts` so behaviour is
|
|
9
|
+
* identical to the pre-host invocation; only the integration surface
|
|
10
|
+
* changes.
|
|
11
|
+
*
|
|
12
|
+
* Out of scope for this release:
|
|
13
|
+
* - The data-action dispatcher *runtime* described in #90. The check
|
|
14
|
+
* ships here so a project can ban inline handlers today; the
|
|
15
|
+
* dispatcher needs a real Preact implementation that mounts near
|
|
16
|
+
* <OverlayRoot> and registers a delegated event listener, and that
|
|
17
|
+
* work belongs in `src/polly-ui/actions.tsx` rather than under the
|
|
18
|
+
* quality plugin host. The check on its own is useful — it forces
|
|
19
|
+
* the consumer to pick an alternative — and the runtime can land
|
|
20
|
+
* in a follow-up release without changing the check's id.
|
|
21
|
+
* - Registry-driven CSS validation. The four CSS checks currently
|
|
22
|
+
* parse polly-ui's CSS at scan time. The new
|
|
23
|
+
* `@fairfox/polly/ui/registry` (introduced alongside this plugin)
|
|
24
|
+
* exposes the canonical token + component lists as data, and a
|
|
25
|
+
* follow-up can swap the parse-time discovery for a registry
|
|
26
|
+
* lookup. The shape of the wraps does not change when that
|
|
27
|
+
* happens; only the inner implementation does.
|
|
28
|
+
*/
|
|
29
|
+
import type { QualityPlugin } from "../types";
|
|
30
|
+
export declare const POLLY_UI_PLUGIN_VERSION = "0.46.0";
|
|
31
|
+
export declare const pollyUiPlugin: QualityPlugin;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-host contract for `@fairfox/polly/quality`.
|
|
3
|
+
*
|
|
4
|
+
* A `QualityPlugin` bundles a set of `Check`s under a namespace. The host
|
|
5
|
+
* loads one or more plugins from `polly.config.ts`, validates that each
|
|
6
|
+
* check's id is unique, and runs the requested set in parallel. A check
|
|
7
|
+
* declares the inputs it reads so the cache layer (see `cache.ts`) can
|
|
8
|
+
* compute a content-hash and skip re-execution when nothing has changed.
|
|
9
|
+
*
|
|
10
|
+
* Plugin and check ids are namespaced as `<plugin>:<name>`. Polly's own
|
|
11
|
+
* plugin uses the `polly` prefix; polly-ui will use `polly-ui`; consumer
|
|
12
|
+
* plugins use whatever prefix matches their package name.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Outcome of running a single check. The host aggregates these into a
|
|
16
|
+
* `RunReport` for the CLI. `cached` is true when the result came back from
|
|
17
|
+
* the on-disk cache without re-running the check body.
|
|
18
|
+
*/
|
|
19
|
+
export type CheckRunResult = {
|
|
20
|
+
id: string;
|
|
21
|
+
ok: boolean;
|
|
22
|
+
durationMs: number;
|
|
23
|
+
cached: boolean;
|
|
24
|
+
/** Human-readable summary line (one violation per element, or status). */
|
|
25
|
+
messages: string[];
|
|
26
|
+
/** Raw error if the check threw; surfaces in CLI output. */
|
|
27
|
+
error?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Context passed to a check at run time. The host fills in `rootDir`
|
|
31
|
+
* and `signal` (for cancellation) before invoking `run`.
|
|
32
|
+
*/
|
|
33
|
+
export type CheckContext<TConfig = unknown> = {
|
|
34
|
+
/** Repository root the consumer is checking. */
|
|
35
|
+
rootDir: string;
|
|
36
|
+
/** Resolved configuration for this check (already validated). */
|
|
37
|
+
config: TConfig;
|
|
38
|
+
/** Aborted when the run is cancelled (CLI Ctrl-C, watch reload, etc.). */
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* A single check. The host calls `validate(config)` once at load time
|
|
43
|
+
* and `run(ctx)` once per execution. `filesRead(config)` is consulted by
|
|
44
|
+
* the cache before `run` — return the absolute paths whose content must
|
|
45
|
+
* invalidate a cached result.
|
|
46
|
+
*/
|
|
47
|
+
export type Check<TConfig = unknown> = {
|
|
48
|
+
/** Namespaced id, e.g. `polly:no-as-casting`. */
|
|
49
|
+
id: string;
|
|
50
|
+
/** One-line description for `polly quality list`. */
|
|
51
|
+
description: string;
|
|
52
|
+
/**
|
|
53
|
+
* Validate user-supplied config. Return `null` for valid input or a
|
|
54
|
+
* non-empty array of error messages for invalid input. Errors are
|
|
55
|
+
* surfaced at load time, not at run time.
|
|
56
|
+
*/
|
|
57
|
+
validate?: (config: unknown) => string[] | null;
|
|
58
|
+
/**
|
|
59
|
+
* Files whose content affects the result of this check. Returned as
|
|
60
|
+
* absolute paths (or paths relative to `rootDir`; the cache normalises).
|
|
61
|
+
* The host hashes these and skips `run` on cache hit.
|
|
62
|
+
*/
|
|
63
|
+
filesRead?: (config: TConfig, rootDir: string) => Promise<string[]> | string[];
|
|
64
|
+
/**
|
|
65
|
+
* Environment variables and tool versions that contribute to the
|
|
66
|
+
* cache key alongside file content. Use this when the check's behaviour
|
|
67
|
+
* depends on something not captured by `filesRead` (e.g. a binary's
|
|
68
|
+
* version, an env var that flips a code path).
|
|
69
|
+
*/
|
|
70
|
+
cacheKeyExtras?: (config: TConfig) => Record<string, string>;
|
|
71
|
+
/** Run the check. Throws are caught by the host and reported as failures. */
|
|
72
|
+
run: (ctx: CheckContext<TConfig>) => Promise<CheckOutcome>;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* The body of a check returns either pass or fail with messages. The host
|
|
76
|
+
* adds id, duration, and cache status to produce a `CheckRunResult`.
|
|
77
|
+
*/
|
|
78
|
+
export type CheckOutcome = {
|
|
79
|
+
ok: boolean;
|
|
80
|
+
messages: string[];
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* A plugin contributes a namespaced bundle of checks.
|
|
84
|
+
*/
|
|
85
|
+
export type QualityPlugin = {
|
|
86
|
+
/** Namespace used as the prefix on every check id. */
|
|
87
|
+
name: string;
|
|
88
|
+
version: string;
|
|
89
|
+
checks: Check<unknown>[];
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Per-check configuration map keyed by check id. The host pulls the
|
|
93
|
+
* matching entry out of `qualityConfig` and passes it as `ctx.config`.
|
|
94
|
+
*/
|
|
95
|
+
export type QualityRunConfig = {
|
|
96
|
+
plugins: QualityPlugin[];
|
|
97
|
+
/** Per-check config keyed by check id (e.g. `"polly:no-as-casting"`). */
|
|
98
|
+
checks?: Record<string, unknown>;
|
|
99
|
+
};
|
|
100
|
+
export type RunReport = {
|
|
101
|
+
ok: boolean;
|
|
102
|
+
results: CheckRunResult[];
|
|
103
|
+
totalDurationMs: number;
|
|
104
|
+
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"// Mock logger adapter for testing\nimport type { LoggerAdapter } from \"@/shared/adapters/logger.adapter\";\nimport type { LogLevel } from \"@/shared/types/messages\";\n\nexport interface LogCall {\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n error?: Error;\n timestamp: number;\n}\n\nexport interface MockLogger extends LoggerAdapter {\n _calls: LogCall[];\n _clear(): void;\n}\n\nexport function createMockLogger(options?: { silent?: boolean }): MockLogger {\n const calls: LogCall[] = [];\n const silent = options?.silent ?? true;\n\n const logToConsole = (level: LogLevel, message: string, context?: Record<string, unknown>) => {\n if (!silent) {\n // biome-ignore lint/suspicious/noConsole: Mock logger intentionally uses console for testing\n const consoleMethod = level === \"debug\" ? console.log : console[level];\n consoleMethod(message, context);\n }\n };\n\n return {\n debug(message: string, context?: Record<string, unknown>): void {\n calls.push({\n level: \"debug\",\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"debug\", message, context);\n },\n\n info(message: string, context?: Record<string, unknown>): void {\n calls.push({\n level: \"info\",\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"info\", message, context);\n },\n\n warn(message: string, context?: Record<string, unknown>): void {\n calls.push({\n level: \"warn\",\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"warn\", message, context);\n },\n\n error(message: string, error?: Error, context?: Record<string, unknown>): void {\n calls.push({\n level: \"error\",\n message,\n ...(error && { error }),\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"error\", message, { ...context, error });\n },\n\n log(level: LogLevel, message: string, context?: Record<string, unknown>): void {\n calls.push({\n level,\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(level, message, context);\n },\n\n // Test-only internals\n _calls: calls,\n _clear() {\n calls.length = 0;\n },\n };\n}\n",
|
|
8
8
|
"import type {\n CreateOffscreenDocumentParameters,\n OffscreenAdapter,\n} from \"@/shared/adapters/offscreen.adapter\";\n\nexport interface MockOffscreen extends OffscreenAdapter {\n _hasDocument: boolean;\n}\n\nexport function createMockOffscreen(): MockOffscreen {\n let hasDocument = false;\n\n return {\n createDocument: async (_parameters: CreateOffscreenDocumentParameters): Promise<void> => {\n hasDocument = true;\n },\n closeDocument: async (): Promise<void> => {\n hasDocument = false;\n },\n hasDocument: async (): Promise<boolean> => {\n return hasDocument;\n },\n _hasDocument: hasDocument,\n };\n}\n",
|
|
9
9
|
"import type { MessageSender, PortAdapter, RuntimeAdapter } from \"@/shared/adapters/runtime.adapter\";\n\nexport interface MockPort extends PortAdapter {\n _listeners: Set<(message: unknown) => void>;\n _disconnectListeners: Set<() => void>;\n}\n\nexport function createMockPort(name: string): MockPort {\n const listeners = new Set<(message: unknown) => void>();\n const disconnectListeners = new Set<() => void>();\n\n return {\n name,\n onMessage: (callback) => listeners.add(callback),\n onDisconnect: (callback) => disconnectListeners.add(callback),\n postMessage: (message) => {\n for (const listener of listeners) {\n listener(message);\n }\n },\n disconnect: () => {\n for (const listener of disconnectListeners) {\n listener();\n }\n },\n _listeners: listeners,\n _disconnectListeners: disconnectListeners,\n };\n}\n\nexport interface MockRuntime extends RuntimeAdapter {\n id: string;\n _messageListeners: Set<\n (message: unknown, sender: MessageSender, sendResponse: (response: unknown) => void) => void\n >;\n _connectListeners: Set<(port: PortAdapter) => void>;\n}\n\nexport function createMockRuntime(id = \"test-extension-id\"): MockRuntime {\n const messageListeners = new Set<\n (message: unknown, sender: MessageSender, sendResponse: (response: unknown) => void) => void\n >();\n const connectListeners = new Set<(port: PortAdapter) => void>();\n\n return {\n id,\n sendMessage: async <T>(message: T): Promise<unknown> => {\n // Check if this is a response message\n if (typeof message === \"object\" && message !== null && \"success\" in message) {\n // This is a response, route it back to all listeners\n for (const listener of messageListeners) {\n listener(message, { url: \"\" }, () => {\n // Empty response handler for mock\n });\n }\n return undefined;\n }\n\n // This is a request, call ALL listeners (Chrome calls all, but only first response is used)\n if (messageListeners.size === 0) {\n return undefined;\n }\n\n return new Promise((resolve) => {\n let resolved = false;\n const sharedSendResponse = (res: unknown) => {\n if (!resolved) {\n resolved = true;\n resolve(res);\n }\n };\n\n // Call all listeners (Chrome behavior)\n for (const listener of messageListeners) {\n const result = listener(message, { url: \"\" }, sharedSendResponse);\n // If listener returns true, it will send response asynchronously\n // If it returns false/undefined/void and we haven't resolved yet, continue to next listener\n if (typeof result === \"boolean\" && result === true) {\n // Listener will send response asynchronously, wait for it\n }\n }\n\n // If no listener handled it, resolve with undefined\n if (!resolved) {\n resolve(undefined);\n }\n });\n },\n onMessage: (\n callback: (\n message: unknown,\n sender: MessageSender,\n sendResponse: (response: unknown) => void\n ) => undefined | boolean\n ) => {\n messageListeners.add(callback);\n },\n removeMessageListener: (\n callback: (\n message: unknown,\n sender: MessageSender,\n sendResponse: (response: unknown) => void\n ) => undefined | boolean\n ) => {\n messageListeners.delete(callback);\n },\n connect: (name: string): PortAdapter => {\n const port = createMockPort(name);\n for (const listener of connectListeners) {\n listener(port);\n }\n return port;\n },\n onConnect: (callback: (port: PortAdapter) => void) => {\n connectListeners.add(callback);\n },\n getURL: (path: string): string => {\n return `chrome-extension://${id}/${path}`;\n },\n getId: (): string => {\n return id;\n },\n openOptionsPage: (): void => {\n // Mock implementation - no-op for tests\n },\n _messageListeners: messageListeners,\n _connectListeners: connectListeners,\n };\n}\n",
|
|
10
|
-
"import type { StorageAdapter, StorageChanges } from \"@/shared/adapters/storage.adapter\";\n\nexport interface MockStorageArea extends StorageAdapter {\n _data: Map<string, unknown>;\n}\n\nexport function createMockStorageArea(): MockStorageArea {\n const data = new Map<string, unknown>();\n\n return {\n get: async <T = Record<string, unknown>>(\n keys?: string | string[] | Record<string, unknown> | null\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Mock storage needs to handle multiple key types\n ): Promise<T> => {\n if (!keys) {\n return Object.fromEntries(data) as T;\n }\n if (typeof keys === \"string\") {\n return (data.has(keys) ? { [keys]: data.get(keys) } : {}) as T;\n }\n if (Array.isArray(keys)) {\n const result: Record<string, unknown> = {};\n for (const key of keys) {\n if (data.has(key)) {\n result[key] = data.get(key);\n }\n }\n return result as unknown as T;\n }\n // Object with defaults\n const result: Record<string, unknown> = {};\n for (const [key, defaultValue] of Object.entries(keys)) {\n result[key] = data.has(key) ? data.get(key) : defaultValue;\n }\n return result as unknown as T;\n },\n set: async (items) => {\n for (const [key, value] of Object.entries(items)) {\n data.set(key, value);\n }\n },\n remove: async (keys) => {\n const keyArray = Array.isArray(keys) ? keys : [keys];\n for (const key of keyArray) {\n data.delete(key);\n }\n },\n clear: async () => {\n data.clear();\n },\n onChanged: (_callback: (changes: StorageChanges, areaName: string) => void) => {\n // Mock implementation - not needed for current tests\n },\n _data: data,\n };\n}\n",
|
|
10
|
+
"import type { StorageAdapter, StorageChanges } from \"@/shared/adapters/storage.adapter\";\n\nexport interface MockStorageArea extends StorageAdapter {\n _data: Map<string, unknown>;\n}\n\nexport function createMockStorageArea(): MockStorageArea {\n const data = new Map<string, unknown>();\n\n return {\n get: async <T = Record<string, unknown>>(\n keys?: string | string[] | Record<string, unknown> | null\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Mock storage needs to handle multiple key types\n ): Promise<T> => {\n if (!keys) {\n return Object.fromEntries(data) as unknown as T;\n }\n if (typeof keys === \"string\") {\n return (data.has(keys) ? { [keys]: data.get(keys) } : {}) as unknown as T;\n }\n if (Array.isArray(keys)) {\n const result: Record<string, unknown> = {};\n for (const key of keys) {\n if (data.has(key)) {\n result[key] = data.get(key);\n }\n }\n return result as unknown as T;\n }\n // Object with defaults\n const result: Record<string, unknown> = {};\n for (const [key, defaultValue] of Object.entries(keys)) {\n result[key] = data.has(key) ? data.get(key) : defaultValue;\n }\n return result as unknown as T;\n },\n set: async (items) => {\n for (const [key, value] of Object.entries(items)) {\n data.set(key, value);\n }\n },\n remove: async (keys) => {\n const keyArray = Array.isArray(keys) ? keys : [keys];\n for (const key of keyArray) {\n data.delete(key);\n }\n },\n clear: async () => {\n data.clear();\n },\n onChanged: (_callback: (changes: StorageChanges, areaName: string) => void) => {\n // Mock implementation - not needed for current tests\n },\n _data: data,\n };\n}\n",
|
|
11
11
|
"import type { TabsAdapter } from \"@/shared/adapters/tabs.adapter\";\n\nexport interface MockTabs extends TabsAdapter {\n _tabs: Map<number, chrome.tabs.Tab>;\n}\n\nexport function createMockTabs(): MockTabs {\n const tabs = new Map<number, chrome.tabs.Tab>();\n\n return {\n query: async (queryInfo: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab[]> => {\n const results: chrome.tabs.Tab[] = [];\n for (const tab of tabs.values()) {\n let matches = true;\n if (queryInfo.active !== undefined && tab.active !== queryInfo.active) {\n matches = false;\n }\n if (queryInfo.currentWindow !== undefined) {\n matches = false;\n }\n if (matches) {\n results.push(tab);\n }\n }\n return results;\n },\n get: async (tabId: number): Promise<chrome.tabs.Tab> => {\n const tab = tabs.get(tabId);\n if (!tab) {\n throw new Error(`Tab ${tabId} not found`);\n }\n return tab;\n },\n sendMessage: async (_tabId: number, _message: unknown): Promise<unknown> => {\n return Promise.resolve({ success: true });\n },\n reload: async (\n _tabId: number,\n _reloadProperties?: { bypassCache?: boolean }\n ): Promise<void> => {\n // Mock implementation\n },\n onRemoved: (\n _callback: (tabId: number, removeInfo: chrome.tabs.OnRemovedInfo) => void\n ): void => {\n // Mock implementation - register listener\n },\n onUpdated: (\n _callback: (\n tabId: number,\n changeInfo: chrome.tabs.OnUpdatedInfo,\n tab: chrome.tabs.Tab\n ) => void\n ): void => {\n // Mock implementation - register listener\n },\n onActivated: (_callback: (activeInfo: { tabId: number; windowId: number }) => void): void => {\n // Mock implementation - register listener\n },\n create: async (createProperties: chrome.tabs.CreateProperties): Promise<chrome.tabs.Tab> => {\n const newTab: chrome.tabs.Tab = {\n id: Math.floor(Math.random() * 10000),\n index: tabs.size,\n pinned: false,\n highlighted: false,\n windowId: 1,\n active: true,\n incognito: false,\n selected: true,\n discarded: false,\n autoDiscardable: true,\n groupId: -1,\n url: createProperties.url || \"about:blank\",\n title: createProperties.url || \"New Tab\",\n frozen: false,\n };\n if (newTab.id !== undefined) {\n tabs.set(newTab.id, newTab);\n }\n return newTab;\n },\n _tabs: tabs,\n };\n}\n",
|
|
12
12
|
"import type { WindowAdapter } from \"@/shared/adapters/window.adapter\";\n\nexport interface MockWindow extends WindowAdapter {\n _messageListeners: Set<(event: MessageEvent) => void>;\n}\n\nexport function createMockWindow(): MockWindow {\n const messageListeners = new Set<(event: MessageEvent) => void>();\n\n return {\n postMessage: (message: unknown, targetOrigin: string) => {\n const event = new MessageEvent(\"message\", {\n data: message,\n origin: targetOrigin,\n source: null,\n });\n for (const listener of messageListeners) {\n listener(event);\n }\n },\n addEventListener: (type: string, listener: (event: MessageEvent) => void) => {\n if (type === \"message\") {\n messageListeners.add(listener);\n }\n },\n removeEventListener: (type: string, listener: (event: MessageEvent) => void) => {\n if (type === \"message\") {\n messageListeners.delete(listener);\n }\n },\n _messageListeners: messageListeners,\n };\n}\n",
|
|
13
13
|
"import { createMockContextMenus, type MockContextMenus } from \"./context-menus.mock\";\nimport { createMockFetch, type MockFetch } from \"./fetch.mock\";\nimport { createMockLogger, type MockLogger } from \"./logger.mock\";\nimport { createMockOffscreen, type MockOffscreen } from \"./offscreen.mock\";\nimport { createMockPort, createMockRuntime, type MockPort, type MockRuntime } from \"./runtime.mock\";\nimport { createMockStorageArea, type MockStorageArea } from \"./storage.mock\";\nimport { createMockTabs, type MockTabs } from \"./tabs.mock\";\nimport { createMockWindow, type MockWindow } from \"./window.mock\";\n\n/**\n * Mock adapters with full type information including mock-specific properties\n */\nexport interface MockExtensionAdapters {\n runtime: MockRuntime;\n storage: MockStorageArea;\n tabs: MockTabs;\n window: MockWindow;\n offscreen: MockOffscreen;\n contextMenus: MockContextMenus;\n fetch: MockFetch;\n logger: MockLogger;\n}\n\n/**\n * Convenience interface grouping Chrome-like mock APIs\n * Useful when tests need direct access to internal mock state\n */\nexport interface MockChrome {\n runtime: MockRuntime;\n storage: {\n local: MockStorageArea;\n };\n tabs: MockTabs;\n}\n\n/**\n * Create a mock Chrome object with grouped APIs\n * Use this when you need access to internal mock state (e.g., mockChrome.tabs._tabs)\n */\nexport function createMockChrome(): MockChrome {\n return {\n runtime: createMockRuntime(),\n storage: {\n local: createMockStorageArea(),\n },\n tabs: createMockTabs(),\n };\n}\n\n/**\n * Create a complete set of mock adapters for testing\n * Returns mock adapters with full type information\n */\nexport function createMockAdapters(): MockExtensionAdapters {\n return {\n runtime: createMockRuntime(),\n storage: createMockStorageArea(),\n tabs: createMockTabs(),\n window: createMockWindow(),\n offscreen: createMockOffscreen(),\n contextMenus: createMockContextMenus(),\n fetch: createMockFetch(),\n logger: createMockLogger({ silent: true }),\n };\n}\n\nexport type {\n MockContextMenus,\n MockFetch,\n MockLogger,\n MockOffscreen,\n MockPort,\n MockRuntime,\n MockStorageArea,\n MockTabs,\n MockWindow,\n};\n// Re-export individual mock factories and types for convenience\nexport {\n createMockContextMenus,\n createMockFetch,\n createMockLogger,\n createMockOffscreen,\n createMockPort,\n createMockRuntime,\n createMockStorageArea,\n createMockTabs,\n createMockWindow,\n};\n"
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"// Mock logger adapter for testing\nimport type { LoggerAdapter } from \"@/shared/adapters/logger.adapter\";\nimport type { LogLevel } from \"@/shared/types/messages\";\n\nexport interface LogCall {\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n error?: Error;\n timestamp: number;\n}\n\nexport interface MockLogger extends LoggerAdapter {\n _calls: LogCall[];\n _clear(): void;\n}\n\nexport function createMockLogger(options?: { silent?: boolean }): MockLogger {\n const calls: LogCall[] = [];\n const silent = options?.silent ?? true;\n\n const logToConsole = (level: LogLevel, message: string, context?: Record<string, unknown>) => {\n if (!silent) {\n // biome-ignore lint/suspicious/noConsole: Mock logger intentionally uses console for testing\n const consoleMethod = level === \"debug\" ? console.log : console[level];\n consoleMethod(message, context);\n }\n };\n\n return {\n debug(message: string, context?: Record<string, unknown>): void {\n calls.push({\n level: \"debug\",\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"debug\", message, context);\n },\n\n info(message: string, context?: Record<string, unknown>): void {\n calls.push({\n level: \"info\",\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"info\", message, context);\n },\n\n warn(message: string, context?: Record<string, unknown>): void {\n calls.push({\n level: \"warn\",\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"warn\", message, context);\n },\n\n error(message: string, error?: Error, context?: Record<string, unknown>): void {\n calls.push({\n level: \"error\",\n message,\n ...(error && { error }),\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(\"error\", message, { ...context, error });\n },\n\n log(level: LogLevel, message: string, context?: Record<string, unknown>): void {\n calls.push({\n level,\n message,\n ...(context && { context }),\n timestamp: Date.now(),\n });\n logToConsole(level, message, context);\n },\n\n // Test-only internals\n _calls: calls,\n _clear() {\n calls.length = 0;\n },\n };\n}\n",
|
|
8
8
|
"import type {\n CreateOffscreenDocumentParameters,\n OffscreenAdapter,\n} from \"@/shared/adapters/offscreen.adapter\";\n\nexport interface MockOffscreen extends OffscreenAdapter {\n _hasDocument: boolean;\n}\n\nexport function createMockOffscreen(): MockOffscreen {\n let hasDocument = false;\n\n return {\n createDocument: async (_parameters: CreateOffscreenDocumentParameters): Promise<void> => {\n hasDocument = true;\n },\n closeDocument: async (): Promise<void> => {\n hasDocument = false;\n },\n hasDocument: async (): Promise<boolean> => {\n return hasDocument;\n },\n _hasDocument: hasDocument,\n };\n}\n",
|
|
9
9
|
"import type { MessageSender, PortAdapter, RuntimeAdapter } from \"@/shared/adapters/runtime.adapter\";\n\nexport interface MockPort extends PortAdapter {\n _listeners: Set<(message: unknown) => void>;\n _disconnectListeners: Set<() => void>;\n}\n\nexport function createMockPort(name: string): MockPort {\n const listeners = new Set<(message: unknown) => void>();\n const disconnectListeners = new Set<() => void>();\n\n return {\n name,\n onMessage: (callback) => listeners.add(callback),\n onDisconnect: (callback) => disconnectListeners.add(callback),\n postMessage: (message) => {\n for (const listener of listeners) {\n listener(message);\n }\n },\n disconnect: () => {\n for (const listener of disconnectListeners) {\n listener();\n }\n },\n _listeners: listeners,\n _disconnectListeners: disconnectListeners,\n };\n}\n\nexport interface MockRuntime extends RuntimeAdapter {\n id: string;\n _messageListeners: Set<\n (message: unknown, sender: MessageSender, sendResponse: (response: unknown) => void) => void\n >;\n _connectListeners: Set<(port: PortAdapter) => void>;\n}\n\nexport function createMockRuntime(id = \"test-extension-id\"): MockRuntime {\n const messageListeners = new Set<\n (message: unknown, sender: MessageSender, sendResponse: (response: unknown) => void) => void\n >();\n const connectListeners = new Set<(port: PortAdapter) => void>();\n\n return {\n id,\n sendMessage: async <T>(message: T): Promise<unknown> => {\n // Check if this is a response message\n if (typeof message === \"object\" && message !== null && \"success\" in message) {\n // This is a response, route it back to all listeners\n for (const listener of messageListeners) {\n listener(message, { url: \"\" }, () => {\n // Empty response handler for mock\n });\n }\n return undefined;\n }\n\n // This is a request, call ALL listeners (Chrome calls all, but only first response is used)\n if (messageListeners.size === 0) {\n return undefined;\n }\n\n return new Promise((resolve) => {\n let resolved = false;\n const sharedSendResponse = (res: unknown) => {\n if (!resolved) {\n resolved = true;\n resolve(res);\n }\n };\n\n // Call all listeners (Chrome behavior)\n for (const listener of messageListeners) {\n const result = listener(message, { url: \"\" }, sharedSendResponse);\n // If listener returns true, it will send response asynchronously\n // If it returns false/undefined/void and we haven't resolved yet, continue to next listener\n if (typeof result === \"boolean\" && result === true) {\n // Listener will send response asynchronously, wait for it\n }\n }\n\n // If no listener handled it, resolve with undefined\n if (!resolved) {\n resolve(undefined);\n }\n });\n },\n onMessage: (\n callback: (\n message: unknown,\n sender: MessageSender,\n sendResponse: (response: unknown) => void\n ) => undefined | boolean\n ) => {\n messageListeners.add(callback);\n },\n removeMessageListener: (\n callback: (\n message: unknown,\n sender: MessageSender,\n sendResponse: (response: unknown) => void\n ) => undefined | boolean\n ) => {\n messageListeners.delete(callback);\n },\n connect: (name: string): PortAdapter => {\n const port = createMockPort(name);\n for (const listener of connectListeners) {\n listener(port);\n }\n return port;\n },\n onConnect: (callback: (port: PortAdapter) => void) => {\n connectListeners.add(callback);\n },\n getURL: (path: string): string => {\n return `chrome-extension://${id}/${path}`;\n },\n getId: (): string => {\n return id;\n },\n openOptionsPage: (): void => {\n // Mock implementation - no-op for tests\n },\n _messageListeners: messageListeners,\n _connectListeners: connectListeners,\n };\n}\n",
|
|
10
|
-
"import type { StorageAdapter, StorageChanges } from \"@/shared/adapters/storage.adapter\";\n\nexport interface MockStorageArea extends StorageAdapter {\n _data: Map<string, unknown>;\n}\n\nexport function createMockStorageArea(): MockStorageArea {\n const data = new Map<string, unknown>();\n\n return {\n get: async <T = Record<string, unknown>>(\n keys?: string | string[] | Record<string, unknown> | null\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Mock storage needs to handle multiple key types\n ): Promise<T> => {\n if (!keys) {\n return Object.fromEntries(data) as T;\n }\n if (typeof keys === \"string\") {\n return (data.has(keys) ? { [keys]: data.get(keys) } : {}) as T;\n }\n if (Array.isArray(keys)) {\n const result: Record<string, unknown> = {};\n for (const key of keys) {\n if (data.has(key)) {\n result[key] = data.get(key);\n }\n }\n return result as unknown as T;\n }\n // Object with defaults\n const result: Record<string, unknown> = {};\n for (const [key, defaultValue] of Object.entries(keys)) {\n result[key] = data.has(key) ? data.get(key) : defaultValue;\n }\n return result as unknown as T;\n },\n set: async (items) => {\n for (const [key, value] of Object.entries(items)) {\n data.set(key, value);\n }\n },\n remove: async (keys) => {\n const keyArray = Array.isArray(keys) ? keys : [keys];\n for (const key of keyArray) {\n data.delete(key);\n }\n },\n clear: async () => {\n data.clear();\n },\n onChanged: (_callback: (changes: StorageChanges, areaName: string) => void) => {\n // Mock implementation - not needed for current tests\n },\n _data: data,\n };\n}\n",
|
|
10
|
+
"import type { StorageAdapter, StorageChanges } from \"@/shared/adapters/storage.adapter\";\n\nexport interface MockStorageArea extends StorageAdapter {\n _data: Map<string, unknown>;\n}\n\nexport function createMockStorageArea(): MockStorageArea {\n const data = new Map<string, unknown>();\n\n return {\n get: async <T = Record<string, unknown>>(\n keys?: string | string[] | Record<string, unknown> | null\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Mock storage needs to handle multiple key types\n ): Promise<T> => {\n if (!keys) {\n return Object.fromEntries(data) as unknown as T;\n }\n if (typeof keys === \"string\") {\n return (data.has(keys) ? { [keys]: data.get(keys) } : {}) as unknown as T;\n }\n if (Array.isArray(keys)) {\n const result: Record<string, unknown> = {};\n for (const key of keys) {\n if (data.has(key)) {\n result[key] = data.get(key);\n }\n }\n return result as unknown as T;\n }\n // Object with defaults\n const result: Record<string, unknown> = {};\n for (const [key, defaultValue] of Object.entries(keys)) {\n result[key] = data.has(key) ? data.get(key) : defaultValue;\n }\n return result as unknown as T;\n },\n set: async (items) => {\n for (const [key, value] of Object.entries(items)) {\n data.set(key, value);\n }\n },\n remove: async (keys) => {\n const keyArray = Array.isArray(keys) ? keys : [keys];\n for (const key of keyArray) {\n data.delete(key);\n }\n },\n clear: async () => {\n data.clear();\n },\n onChanged: (_callback: (changes: StorageChanges, areaName: string) => void) => {\n // Mock implementation - not needed for current tests\n },\n _data: data,\n };\n}\n",
|
|
11
11
|
"import type { TabsAdapter } from \"@/shared/adapters/tabs.adapter\";\n\nexport interface MockTabs extends TabsAdapter {\n _tabs: Map<number, chrome.tabs.Tab>;\n}\n\nexport function createMockTabs(): MockTabs {\n const tabs = new Map<number, chrome.tabs.Tab>();\n\n return {\n query: async (queryInfo: chrome.tabs.QueryInfo): Promise<chrome.tabs.Tab[]> => {\n const results: chrome.tabs.Tab[] = [];\n for (const tab of tabs.values()) {\n let matches = true;\n if (queryInfo.active !== undefined && tab.active !== queryInfo.active) {\n matches = false;\n }\n if (queryInfo.currentWindow !== undefined) {\n matches = false;\n }\n if (matches) {\n results.push(tab);\n }\n }\n return results;\n },\n get: async (tabId: number): Promise<chrome.tabs.Tab> => {\n const tab = tabs.get(tabId);\n if (!tab) {\n throw new Error(`Tab ${tabId} not found`);\n }\n return tab;\n },\n sendMessage: async (_tabId: number, _message: unknown): Promise<unknown> => {\n return Promise.resolve({ success: true });\n },\n reload: async (\n _tabId: number,\n _reloadProperties?: { bypassCache?: boolean }\n ): Promise<void> => {\n // Mock implementation\n },\n onRemoved: (\n _callback: (tabId: number, removeInfo: chrome.tabs.OnRemovedInfo) => void\n ): void => {\n // Mock implementation - register listener\n },\n onUpdated: (\n _callback: (\n tabId: number,\n changeInfo: chrome.tabs.OnUpdatedInfo,\n tab: chrome.tabs.Tab\n ) => void\n ): void => {\n // Mock implementation - register listener\n },\n onActivated: (_callback: (activeInfo: { tabId: number; windowId: number }) => void): void => {\n // Mock implementation - register listener\n },\n create: async (createProperties: chrome.tabs.CreateProperties): Promise<chrome.tabs.Tab> => {\n const newTab: chrome.tabs.Tab = {\n id: Math.floor(Math.random() * 10000),\n index: tabs.size,\n pinned: false,\n highlighted: false,\n windowId: 1,\n active: true,\n incognito: false,\n selected: true,\n discarded: false,\n autoDiscardable: true,\n groupId: -1,\n url: createProperties.url || \"about:blank\",\n title: createProperties.url || \"New Tab\",\n frozen: false,\n };\n if (newTab.id !== undefined) {\n tabs.set(newTab.id, newTab);\n }\n return newTab;\n },\n _tabs: tabs,\n };\n}\n",
|
|
12
12
|
"import type { WindowAdapter } from \"@/shared/adapters/window.adapter\";\n\nexport interface MockWindow extends WindowAdapter {\n _messageListeners: Set<(event: MessageEvent) => void>;\n}\n\nexport function createMockWindow(): MockWindow {\n const messageListeners = new Set<(event: MessageEvent) => void>();\n\n return {\n postMessage: (message: unknown, targetOrigin: string) => {\n const event = new MessageEvent(\"message\", {\n data: message,\n origin: targetOrigin,\n source: null,\n });\n for (const listener of messageListeners) {\n listener(event);\n }\n },\n addEventListener: (type: string, listener: (event: MessageEvent) => void) => {\n if (type === \"message\") {\n messageListeners.add(listener);\n }\n },\n removeEventListener: (type: string, listener: (event: MessageEvent) => void) => {\n if (type === \"message\") {\n messageListeners.delete(listener);\n }\n },\n _messageListeners: messageListeners,\n };\n}\n",
|
|
13
13
|
"import { createMockContextMenus, type MockContextMenus } from \"./context-menus.mock\";\nimport { createMockFetch, type MockFetch } from \"./fetch.mock\";\nimport { createMockLogger, type MockLogger } from \"./logger.mock\";\nimport { createMockOffscreen, type MockOffscreen } from \"./offscreen.mock\";\nimport { createMockPort, createMockRuntime, type MockPort, type MockRuntime } from \"./runtime.mock\";\nimport { createMockStorageArea, type MockStorageArea } from \"./storage.mock\";\nimport { createMockTabs, type MockTabs } from \"./tabs.mock\";\nimport { createMockWindow, type MockWindow } from \"./window.mock\";\n\n/**\n * Mock adapters with full type information including mock-specific properties\n */\nexport interface MockExtensionAdapters {\n runtime: MockRuntime;\n storage: MockStorageArea;\n tabs: MockTabs;\n window: MockWindow;\n offscreen: MockOffscreen;\n contextMenus: MockContextMenus;\n fetch: MockFetch;\n logger: MockLogger;\n}\n\n/**\n * Convenience interface grouping Chrome-like mock APIs\n * Useful when tests need direct access to internal mock state\n */\nexport interface MockChrome {\n runtime: MockRuntime;\n storage: {\n local: MockStorageArea;\n };\n tabs: MockTabs;\n}\n\n/**\n * Create a mock Chrome object with grouped APIs\n * Use this when you need access to internal mock state (e.g., mockChrome.tabs._tabs)\n */\nexport function createMockChrome(): MockChrome {\n return {\n runtime: createMockRuntime(),\n storage: {\n local: createMockStorageArea(),\n },\n tabs: createMockTabs(),\n };\n}\n\n/**\n * Create a complete set of mock adapters for testing\n * Returns mock adapters with full type information\n */\nexport function createMockAdapters(): MockExtensionAdapters {\n return {\n runtime: createMockRuntime(),\n storage: createMockStorageArea(),\n tabs: createMockTabs(),\n window: createMockWindow(),\n offscreen: createMockOffscreen(),\n contextMenus: createMockContextMenus(),\n fetch: createMockFetch(),\n logger: createMockLogger({ silent: true }),\n };\n}\n\nexport type {\n MockContextMenus,\n MockFetch,\n MockLogger,\n MockOffscreen,\n MockPort,\n MockRuntime,\n MockStorageArea,\n MockTabs,\n MockWindow,\n};\n// Re-export individual mock factories and types for convenience\nexport {\n createMockContextMenus,\n createMockFetch,\n createMockLogger,\n createMockOffscreen,\n createMockPort,\n createMockRuntime,\n createMockStorageArea,\n createMockTabs,\n createMockWindow,\n};\n",
|
|
@@ -13351,13 +13351,14 @@ var require_png = __commonJS((exports) => {
|
|
|
13351
13351
|
// tools/test/src/visual/compare.ts
|
|
13352
13352
|
var {readFileSync, writeFileSync} = (() => ({}));
|
|
13353
13353
|
|
|
13354
|
-
// node_modules/.bun/pixelmatch@7.
|
|
13354
|
+
// node_modules/.bun/pixelmatch@7.2.0/node_modules/pixelmatch/index.js
|
|
13355
13355
|
function pixelmatch(img1, img2, output, width, height, options = {}) {
|
|
13356
13356
|
const {
|
|
13357
13357
|
threshold = 0.1,
|
|
13358
13358
|
alpha = 0.1,
|
|
13359
13359
|
aaColor = [255, 255, 0],
|
|
13360
13360
|
diffColor = [255, 0, 0],
|
|
13361
|
+
checkerboard = true,
|
|
13361
13362
|
includeAA,
|
|
13362
13363
|
diffColorAlt,
|
|
13363
13364
|
diffMask
|
|
@@ -13365,9 +13366,9 @@ function pixelmatch(img1, img2, output, width, height, options = {}) {
|
|
|
13365
13366
|
if (!isPixelData(img1) || !isPixelData(img2) || output && !isPixelData(output))
|
|
13366
13367
|
throw new Error("Image data: Uint8Array, Uint8ClampedArray or Buffer expected.");
|
|
13367
13368
|
if (img1.length !== img2.length || output && output.length !== img1.length)
|
|
13368
|
-
throw new Error(
|
|
13369
|
+
throw new Error(`Image sizes do not match. Image 1 size: ${img1.length}, image 2 size: ${img2.length}`);
|
|
13369
13370
|
if (img1.length !== width * height * 4)
|
|
13370
|
-
throw new Error(
|
|
13371
|
+
throw new Error(`Image data size does not match width/height. Expecting ${width * height * 4}. Got ${img1.length}`);
|
|
13371
13372
|
const len = width * height;
|
|
13372
13373
|
const a32 = new Uint32Array(img1.buffer, img1.byteOffset, len);
|
|
13373
13374
|
const b32 = new Uint32Array(img2.buffer, img2.byteOffset, len);
|
|
@@ -13380,8 +13381,8 @@ function pixelmatch(img1, img2, output, width, height, options = {}) {
|
|
|
13380
13381
|
}
|
|
13381
13382
|
if (identical) {
|
|
13382
13383
|
if (output && !diffMask) {
|
|
13383
|
-
for (let i = 0;i < len; i
|
|
13384
|
-
drawGrayPixel(img1,
|
|
13384
|
+
for (let i = 0, pos = 0;i < len; i++, pos += 4)
|
|
13385
|
+
drawGrayPixel(img1, pos, alpha, output);
|
|
13385
13386
|
}
|
|
13386
13387
|
return 0;
|
|
13387
13388
|
}
|
|
@@ -13390,29 +13391,27 @@ function pixelmatch(img1, img2, output, width, height, options = {}) {
|
|
|
13390
13391
|
const [diffR, diffG, diffB] = diffColor;
|
|
13391
13392
|
const [altR, altG, altB] = diffColorAlt || diffColor;
|
|
13392
13393
|
let diff = 0;
|
|
13393
|
-
for (let
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
const
|
|
13397
|
-
const
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
if (
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
if (
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
drawPixel(output, pos, diffR, diffG, diffB);
|
|
13409
|
-
}
|
|
13394
|
+
for (let i = 0, pos = 0;i < len; i++, pos += 4) {
|
|
13395
|
+
const delta = a32[i] === b32[i] ? 0 : colorDelta(img1, img2, pos, pos, checkerboard);
|
|
13396
|
+
if (Math.abs(delta) > maxDelta) {
|
|
13397
|
+
const x = i % width;
|
|
13398
|
+
const y = i / width | 0;
|
|
13399
|
+
const isExcludedAA = !includeAA && (antialiased(img1, x, y, width, height, a32, b32, checkerboard) || antialiased(img2, x, y, width, height, b32, a32, checkerboard));
|
|
13400
|
+
if (isExcludedAA) {
|
|
13401
|
+
if (output && !diffMask)
|
|
13402
|
+
drawPixel(output, pos, aaR, aaG, aaB);
|
|
13403
|
+
} else {
|
|
13404
|
+
if (output) {
|
|
13405
|
+
if (delta < 0) {
|
|
13406
|
+
drawPixel(output, pos, altR, altG, altB);
|
|
13407
|
+
} else {
|
|
13408
|
+
drawPixel(output, pos, diffR, diffG, diffB);
|
|
13410
13409
|
}
|
|
13411
|
-
diff++;
|
|
13412
13410
|
}
|
|
13413
|
-
|
|
13414
|
-
drawGrayPixel(img1, pos, alpha, output);
|
|
13411
|
+
diff++;
|
|
13415
13412
|
}
|
|
13413
|
+
} else if (output && !diffMask) {
|
|
13414
|
+
drawGrayPixel(img1, pos, alpha, output);
|
|
13416
13415
|
}
|
|
13417
13416
|
}
|
|
13418
13417
|
return diff;
|
|
@@ -13420,12 +13419,16 @@ function pixelmatch(img1, img2, output, width, height, options = {}) {
|
|
|
13420
13419
|
function isPixelData(arr) {
|
|
13421
13420
|
return ArrayBuffer.isView(arr) && arr.BYTES_PER_ELEMENT === 1;
|
|
13422
13421
|
}
|
|
13423
|
-
function antialiased(img, x1, y1, width, height, a32, b32) {
|
|
13422
|
+
function antialiased(img, x1, y1, width, height, a32, b32, checkerboard) {
|
|
13424
13423
|
const x0 = Math.max(x1 - 1, 0);
|
|
13425
13424
|
const y0 = Math.max(y1 - 1, 0);
|
|
13426
13425
|
const x2 = Math.min(x1 + 1, width - 1);
|
|
13427
13426
|
const y2 = Math.min(y1 + 1, height - 1);
|
|
13428
|
-
const
|
|
13427
|
+
const pos4 = (y1 * width + x1) * 4;
|
|
13428
|
+
const cr = img[pos4];
|
|
13429
|
+
const cg = img[pos4 + 1];
|
|
13430
|
+
const cb = img[pos4 + 2];
|
|
13431
|
+
const ca = img[pos4 + 3];
|
|
13429
13432
|
let zeroes = x1 === x0 || x1 === x2 || y1 === y0 || y1 === y2 ? 1 : 0;
|
|
13430
13433
|
let min = 0;
|
|
13431
13434
|
let max = 0;
|
|
@@ -13437,7 +13440,7 @@ function antialiased(img, x1, y1, width, height, a32, b32) {
|
|
|
13437
13440
|
for (let y = y0;y <= y2; y++) {
|
|
13438
13441
|
if (x === x1 && y === y1)
|
|
13439
13442
|
continue;
|
|
13440
|
-
const delta =
|
|
13443
|
+
const delta = brightnessDelta(img, pos4, (y * width + x) * 4, cr, cg, cb, ca, checkerboard);
|
|
13441
13444
|
if (delta === 0) {
|
|
13442
13445
|
zeroes++;
|
|
13443
13446
|
if (zeroes > 2)
|
|
@@ -13475,7 +13478,7 @@ function hasManySiblings(img, x1, y1, width, height) {
|
|
|
13475
13478
|
}
|
|
13476
13479
|
return false;
|
|
13477
13480
|
}
|
|
13478
|
-
function colorDelta(img1, img2, k, m,
|
|
13481
|
+
function colorDelta(img1, img2, k, m, checkerboard) {
|
|
13479
13482
|
const r1 = img1[k];
|
|
13480
13483
|
const g1 = img1[k + 1];
|
|
13481
13484
|
const b1 = img1[k + 2];
|
|
@@ -13488,26 +13491,49 @@ function colorDelta(img1, img2, k, m, yOnly) {
|
|
|
13488
13491
|
let dg = g1 - g2;
|
|
13489
13492
|
let db = b1 - b2;
|
|
13490
13493
|
const da = a1 - a2;
|
|
13491
|
-
if (!dr && !dg && !db && !da)
|
|
13492
|
-
return 0;
|
|
13493
13494
|
if (a1 < 255 || a2 < 255) {
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13495
|
+
let rb = 255, gb = 255, bb = 255;
|
|
13496
|
+
if (checkerboard) {
|
|
13497
|
+
rb = 48 + 159 * (k % 2);
|
|
13498
|
+
gb = 48 + 159 * ((k / 1.618033988749895 | 0) % 2);
|
|
13499
|
+
bb = 48 + 159 * ((k / 2.618033988749895 | 0) % 2);
|
|
13500
|
+
}
|
|
13497
13501
|
dr = (r1 * a1 - r2 * a2 - rb * da) / 255;
|
|
13498
13502
|
dg = (g1 * a1 - g2 * a2 - gb * da) / 255;
|
|
13499
13503
|
db = (b1 * a1 - b2 * a2 - bb * da) / 255;
|
|
13500
13504
|
}
|
|
13501
13505
|
const y = dr * 0.29889531 + dg * 0.58662247 + db * 0.11448223;
|
|
13502
|
-
if (yOnly)
|
|
13503
|
-
return y;
|
|
13504
13506
|
const i = dr * 0.59597799 - dg * 0.2741761 - db * 0.32180189;
|
|
13505
13507
|
const q = dr * 0.21147017 - dg * 0.52261711 + db * 0.31114694;
|
|
13506
13508
|
const delta = 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q;
|
|
13507
13509
|
return y > 0 ? -delta : delta;
|
|
13508
13510
|
}
|
|
13511
|
+
function brightnessDelta(img, k, m, r1, g1, b1, a1, checkerboard) {
|
|
13512
|
+
const r2 = img[m];
|
|
13513
|
+
const g2 = img[m + 1];
|
|
13514
|
+
const b2 = img[m + 2];
|
|
13515
|
+
const a2 = img[m + 3];
|
|
13516
|
+
let dr = r1 - r2;
|
|
13517
|
+
let dg = g1 - g2;
|
|
13518
|
+
let db = b1 - b2;
|
|
13519
|
+
const da = a1 - a2;
|
|
13520
|
+
if (!dr && !dg && !db && !da)
|
|
13521
|
+
return 0;
|
|
13522
|
+
if (a1 < 255 || a2 < 255) {
|
|
13523
|
+
let rb = 255, gb = 255, bb = 255;
|
|
13524
|
+
if (checkerboard) {
|
|
13525
|
+
rb = 48 + 159 * (k % 2);
|
|
13526
|
+
gb = 48 + 159 * ((k / 1.618033988749895 | 0) % 2);
|
|
13527
|
+
bb = 48 + 159 * ((k / 2.618033988749895 | 0) % 2);
|
|
13528
|
+
}
|
|
13529
|
+
dr = (r1 * a1 - r2 * a2 - rb * da) / 255;
|
|
13530
|
+
dg = (g1 * a1 - g2 * a2 - gb * da) / 255;
|
|
13531
|
+
db = (b1 * a1 - b2 * a2 - bb * da) / 255;
|
|
13532
|
+
}
|
|
13533
|
+
return dr * 0.29889531 + dg * 0.58662247 + db * 0.11448223;
|
|
13534
|
+
}
|
|
13509
13535
|
function drawPixel(output, pos, r, g, b) {
|
|
13510
|
-
output[pos
|
|
13536
|
+
output[pos] = r;
|
|
13511
13537
|
output[pos + 1] = g;
|
|
13512
13538
|
output[pos + 2] = b;
|
|
13513
13539
|
output[pos + 3] = 255;
|
|
@@ -13965,4 +13991,4 @@ export {
|
|
|
13965
13991
|
assertSafeUpdateMode
|
|
13966
13992
|
};
|
|
13967
13993
|
|
|
13968
|
-
//# debugId=
|
|
13994
|
+
//# debugId=A8F0CA1BEDC12C8F64756E2164756E21
|