// src/plugin/__tests__/resolve-active-editor.test.ts // // Unit tests for `resolveActiveEditor` (#408 host contract). // // Asserts the single enforcement point used by `Workspace._syncActiveEditor`: // `workspace.activeEditor` is a `MarkdownFileInfo` only when the leaf // hosts a ready `MarkdownView`. Everything else collapses to `null` — that // is the invariant typed-file views and Patchwork integrations rely on. // // The tests drive `WorkspaceLeaf.openFile` against a real `Workspace` // wired up by `makeAppForTest`, so the resolution path exercised here is // the same one the production dispatch (`_openLeaf`) goes through. A // previous, weaker version of this file constructed `MarkdownView`-shaped // objects via `Object.create(MarkdownView.prototype)` — which pinned the // literal `instanceof` operator instead of the contract. Adversary review // S1. import { describe, it, expect } from 'vitest' import { resolveActiveEditor } from '../workspace' import { MarkdownView } from '../views/markdown-view' import { UnsupportedFileView } from '../views/unsupported-view' import { makeAppForTest, provisionBinaryFile, provisionMarkdownFile, tinyPng, TestView, } from './test-helpers' describe('resolveActiveEditor (#408 host contract)', () => { it('returns null for a null leaf', () => { expect(resolveActiveEditor(null)).toBeNull() }) it('returns null when the active leaf hosts a non-markdown ItemView (image)', async () => { const ctx = await makeAppForTest() const file = await provisionBinaryFile(ctx, 'a.png', 'image/png', tinyPng()) const leaf = ctx.workspace.getLeaf(true) await leaf.openFile(file) // Sanity: the dispatcher routed the PNG to the unsupported fallback // (no image-viewer plugin loaded in this test harness). Either way it // is NOT a MarkdownView — that's the contract under test. expect(leaf.view).not.toBeInstanceOf(MarkdownView) expect(resolveActiveEditor(leaf)).toBeNull() }) it('returns null when the active leaf hosts the unsupported fallback view', async () => { const ctx = await makeAppForTest() const file = await provisionBinaryFile(ctx, 'blob.xyz', 'application/octet-stream', new Uint8Array([0])) const leaf = ctx.workspace.getLeaf(true) await leaf.openFile(file) expect(leaf.view).toBeInstanceOf(UnsupportedFileView) expect(resolveActiveEditor(leaf)).toBeNull() }) it('returns the MarkdownView when the active leaf hosts a ready markdown view', async () => { const ctx = await makeAppForTest() const file = await provisionMarkdownFile(ctx, 'note.md', '# hi') const leaf = ctx.workspace.getLeaf(true) await leaf.openFile(file) expect(leaf.view).toBeInstanceOf(MarkdownView) expect(resolveActiveEditor(leaf)).toBe(leaf.view) }) it('returns null while a custom ItemView is active', async () => { // A future typed editor would extend ItemView. Confirm the contract // applies generically, not just to image-viewer / unsupported. const ctx = await makeAppForTest() ctx.workspace.registerViewType('custom-typed', (leaf) => new TestView(leaf, 'custom-typed')) ctx.workspace.registerViewTypeForContentType('public.data', 'custom-typed') const file = await provisionBinaryFile(ctx, 'blob2.xyz', 'application/octet-stream', new Uint8Array([0])) const leaf = ctx.workspace.getLeaf(true) await leaf.openFile(file) expect(leaf.view).not.toBeInstanceOf(MarkdownView) expect(resolveActiveEditor(leaf)).toBeNull() }) it('treats MarkdownView subclasses as markdown for contract purposes', async () => { // Subclassing MarkdownView is a documented extension point. Confirm // the contract walks the prototype chain rather than checking // constructor identity. const ctx = await makeAppForTest() class SubMarkdown extends MarkdownView {} ctx.workspace.registerViewType('sub-markdown', (leaf) => new SubMarkdown(leaf)) // Override the bound view for public.markdown so the dispatcher picks // up the subclass for this test only. ctx.workspace.registerViewTypeForContentType('public.markdown', 'sub-markdown') const file = await provisionMarkdownFile(ctx, 'sub.md', '# subclass') const leaf = ctx.workspace.getLeaf(true) await leaf.openFile(file) expect(leaf.view).toBeInstanceOf(SubMarkdown) expect(leaf.view).toBeInstanceOf(MarkdownView) expect(resolveActiveEditor(leaf)).toBe(leaf.view) }) it('returns null while the markdown view lifecycle is still pending', async () => { // The contract gates on `leaf.isViewReadyForActiveEditor()`. Drive a // real markdown leaf into an unready state and confirm resolution // returns null until it settles. const ctx = await makeAppForTest() const file = await provisionMarkdownFile(ctx, 'pending.md', '') const leaf = ctx.workspace.getLeaf(true) await leaf.openFile(file) expect(leaf.view).toBeInstanceOf(MarkdownView) expect(leaf.isViewReadyForActiveEditor()).toBe(true) expect(resolveActiveEditor(leaf)).toBe(leaf.view) // Simulate pending replacement: invalidating a pending host open // (the host's own teardown path) leaves the leaf in an unready state. // `resolveActiveEditor` must return null while that flag is false. type LeafInternals = { _viewReadyForActiveEditor: boolean } ;(leaf as unknown as LeafInternals)._viewReadyForActiveEditor = false expect(resolveActiveEditor(leaf)).toBeNull() }) })