Add monaco-prettier-formatter.service.spec.ts: lazy load, register idempotency, error channel #77

Closed
opened 2026-05-06 05:00:55 +00:00 by spikerj · 1 comment
Owner

Problem

MonacoPrettierFormatterService (libraries/tools/src/services/prettier/monaco-prettier-formatter.service.ts) is ~200 lines covering Prettier 3 module interop, lazy loading, the per-language idempotent register(), and the dual error-channel between provider hot path and the panel-safe path. There is no spec file for it.

Concrete fragility surfaces that would silently break on a Prettier upgrade with no test signal:

  1. Lazy-load interop (loadModules()): reads standaloneAny.format ?? standaloneAny.default?.format. One of those paths is always dead; if a Prettier 4 narrows the export shape, the dead path is the only surviving one and we get format === undefined at runtime.
  2. Plugins-array shape: passes the full module imports as plugins. Prettier 3 detects parser metadata on each item; Prettier 4 may narrow that to .default only.
  3. register() idempotency (just added in JRS-1): a registeredLanguages Set guard prevents double-registration when two shells share the root-provided service. No test asserts this.
  4. Error channel separation: computeFormattingEdits (provider hot path) collapses to [] on parse error; computeFormattingEditsSafe (panel path) returns { error }. Easy to accidentally invert.

Suggested test plan

A new monaco-prettier-formatter.service.spec.ts with at minimum:

  • lazy load smoke: after one formatModelInPlace call, the resolved Prettier format is callable and produces a string.
  • register idempotency: two register(monaco, ["javascript"]) calls register exactly one provider; first disposer’s dispose releases the slot so a subsequent register works again.
  • error channel: invalid JS source returns { ok: false, error: '...' } from the panel-safe path AND [] from the Monaco provider path (so Monaco doesn’t complain about an unexpected shape).
  • format-no-op short-circuit: when input already matches Prettier output, formatModelInPlace returns { ok: true } without calling pushEditOperations/executeEdits.
  • cursor preservation: with an editor arg, the post-format cursor stays within ±1 line of where it started (covers MPF-3 fix).

Owner pointers

  • libraries/tools/src/services/prettier/monaco-prettier-formatter.service.ts
  • Mock Monaco surface: monaco.languages.registerDocumentFormattingEditProvider, monaco.editor.createModel, editor.executeEdits. The existing monaco-editor.component.spec.ts (if any) can be a pattern reference for lazy-Monaco mocking.

Related

Found during code review of the Prettier integration + JS formatting options panel feature wave.

## Problem `MonacoPrettierFormatterService` (`libraries/tools/src/services/prettier/monaco-prettier-formatter.service.ts`) is ~200 lines covering Prettier 3 module interop, lazy loading, the per-language idempotent `register()`, and the dual error-channel between provider hot path and the panel-safe path. There is no spec file for it. Concrete fragility surfaces that would silently break on a Prettier upgrade with no test signal: 1. **Lazy-load interop** (`loadModules()`): reads `standaloneAny.format ?? standaloneAny.default?.format`. One of those paths is always dead; if a Prettier 4 narrows the export shape, the dead path is the only surviving one and we get `format === undefined` at runtime. 2. **Plugins-array shape**: passes the full module imports as plugins. Prettier 3 detects parser metadata on each item; Prettier 4 may narrow that to `.default` only. 3. **`register()` idempotency** (just added in JRS-1): a `registeredLanguages` Set guard prevents double-registration when two shells share the root-provided service. No test asserts this. 4. **Error channel separation**: `computeFormattingEdits` (provider hot path) collapses to `[]` on parse error; `computeFormattingEditsSafe` (panel path) returns `{ error }`. Easy to accidentally invert. ## Suggested test plan A new `monaco-prettier-formatter.service.spec.ts` with at minimum: - **lazy load smoke**: after one `formatModelInPlace` call, the resolved Prettier `format` is callable and produces a string. - **register idempotency**: two `register(monaco, ["javascript"])` calls register exactly one provider; first disposer’s `dispose` releases the slot so a subsequent register works again. - **error channel**: invalid JS source returns `{ ok: false, error: '...' }` from the panel-safe path AND `[]` from the Monaco provider path (so Monaco doesn’t complain about an unexpected shape). - **format-no-op short-circuit**: when input already matches Prettier output, `formatModelInPlace` returns `{ ok: true }` without calling `pushEditOperations`/`executeEdits`. - **cursor preservation**: with an `editor` arg, the post-format cursor stays within ±1 line of where it started (covers MPF-3 fix). ## Owner pointers - `libraries/tools/src/services/prettier/monaco-prettier-formatter.service.ts` - Mock Monaco surface: `monaco.languages.registerDocumentFormattingEditProvider`, `monaco.editor.createModel`, `editor.executeEdits`. The existing `monaco-editor.component.spec.ts` (if any) can be a pattern reference for lazy-Monaco mocking. ## Related Found during code review of the Prettier integration + JS formatting options panel feature wave.
Author
Owner

Added monaco-prettier-formatter.service.spec.ts in spikersoft-angular PR #57 — 11 tests covering all four seams from the suggested test plan: lazy-load interop (against REAL Prettier, to catch an export-shape change), register() per-language idempotency + slot release on dispose + throw-safe disposal, the dual error channel (panel-safe { error } vs provider-path []), and the no-op short-circuit + editor-aware/model-direct/no-editor edit application. Deterministic tests inject a fake modulesPromise; only the interop smoke test loads real Prettier. nx test feature-dev-tools-javascript-runner → 40 pass. Will close once PR #57 merges.

Added `monaco-prettier-formatter.service.spec.ts` in `spikersoft-angular` PR #57 — 11 tests covering all four seams from the suggested test plan: lazy-load interop (against REAL Prettier, to catch an export-shape change), `register()` per-language idempotency + slot release on dispose + throw-safe disposal, the dual error channel (panel-safe `{ error }` vs provider-path `[]`), and the no-op short-circuit + editor-aware/model-direct/no-editor edit application. Deterministic tests inject a fake `modulesPromise`; only the interop smoke test loads real Prettier. `nx test feature-dev-tools-javascript-runner` → 40 pass. Will close once PR #57 merges.
Sign in to join this conversation.