Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122// 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() })})