Files
AFFiNE-Mirror/blocksuite/affine/ext-loader/src/__tests__/ext-loader.unit.spec.ts
T
Saul-Mirone 41d404f7f8 refactor(editor): improve implementation of lit adapter (#12101)
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Improved mobile experience by disabling certain toolbars and slash menu features on mobile devices.
  - Introduced new modular extension classes for editor and view customization, enabling more flexible configuration of themes, AI features, and editor enhancements.
  - Added clipboard adapter configurations for a wide range of data types, improving clipboard compatibility.
  - Added a new theme extension specifically for preview scenarios.
  - Provided new hooks for block scope management in document modules.

- **Refactor**
  - Streamlined editor extension setup, consolidating options and reducing complexity for better maintainability.
  - Reorganized mobile-specific extension exports for clearer usage.
  - Refined React-to-Lit rendering API by introducing a typed alias and updating related function signatures.
  - Simplified extension registration by splitting monolithic view extension into separate common and editor view extensions.

- **Bug Fixes**
  - Corrected naming inconsistencies in internal effect tracking.

- **Chores**
  - Updated type exports and documentation comments for improved clarity and consistency.
  - Removed unused or redundant exports and functions to clean up the codebase.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-01 14:29:11 +00:00

230 lines
5.8 KiB
TypeScript

import { Container } from '@blocksuite/global/di';
import { type ExtensionType } from '@blocksuite/store';
import { describe, expect, it, vi } from 'vitest';
import { z } from 'zod';
import { ExtensionManager } from '../manager';
import {
StoreExtensionManager,
StoreExtensionManagerIdentifier,
} from '../store-manager';
import {
type StoreExtensionContext,
StoreExtensionProvider,
} from '../store-provider';
import { ViewExtensionManager } from '../view-manager';
import {
type ViewExtensionContext,
ViewExtensionProvider,
} from '../view-provider';
export const Ext1: ExtensionType = {
setup: () => {},
};
export const Ext2: ExtensionType = {
setup: () => {},
};
export const Ext3: ExtensionType = {
setup: () => {},
};
export const Ext4: ExtensionType = {
setup: () => {},
};
export const Ext5: ExtensionType = {
setup: () => {},
};
it('should be able to load extensions', () => {
class StoreExt1 extends StoreExtensionProvider {
override name = 'StoreExt1';
override setup(context: StoreExtensionContext) {
super.setup(context);
context.register(Ext1);
}
}
const manager = new ExtensionManager([StoreExt1]);
const storeExtensions = manager.get('store');
expect(storeExtensions).toEqual([Ext1]);
});
describe('multiple scopes', () => {
const setup1 = vi.fn();
const setup2 = vi.fn();
class ViewExt1 extends ViewExtensionProvider {
override name = 'ViewExt1';
constructor() {
super();
setup1();
}
override setup(context: ViewExtensionContext, option?: { foo: number }) {
super.setup(context, option);
if (context.scope === 'page') {
context.register(Ext2);
}
if (context.scope === 'edgeless') {
context.register(Ext3);
}
}
}
class ViewExt2 extends ViewExtensionProvider {
override name = 'ViewExt2';
constructor() {
super();
setup2();
}
override setup(context: ViewExtensionContext) {
super.setup(context);
if (context.scope === 'page') {
context.register(Ext4);
}
if (context.scope === 'edgeless') {
context.register(Ext5);
}
}
}
const manager = new ExtensionManager([ViewExt1, ViewExt2]);
const pageExtensions = manager.get('page');
const edgelessExtensions = manager.get('edgeless');
it('should be able to load extensions from different scopes', () => {
expect(pageExtensions).toEqual([Ext2, Ext4]);
expect(edgelessExtensions).toEqual([Ext3, Ext5]);
});
it('should cache provider instances', () => {
manager.get('page');
manager.get('edgeless');
expect(setup1).toHaveBeenCalledTimes(1);
expect(setup2).toHaveBeenCalledTimes(1);
manager.get('page');
manager.get('edgeless');
expect(setup1).toHaveBeenCalledTimes(1);
expect(setup2).toHaveBeenCalledTimes(1);
});
});
it('should be able to validate schema', () => {
type Option = { foo: number; bar: string };
const setupOption = vi.fn();
class ViewExt1 extends ViewExtensionProvider<Option> {
override name = 'ViewExt1';
override schema = z.object({
foo: z.number(),
bar: z.string(),
});
override setup(context: ViewExtensionContext, option?: Option) {
super.setup(context, option);
if (context.scope === 'page') {
setupOption(option);
context.register(Ext1);
}
if (context.scope === 'edgeless') {
setupOption(option);
context.register(Ext2);
}
}
}
const manager = new ExtensionManager([ViewExt1]);
manager.configure(ViewExt1, { foo: 1, bar: '2' });
manager.configure(ViewExt1, prev => {
if (!prev) {
return prev;
}
return {
...prev,
foo: prev.foo + 1,
};
});
let viewExtensions = manager.get('page');
expect(viewExtensions).toEqual([Ext1]);
expect(setupOption).toHaveBeenCalledWith({ foo: 2, bar: '2' });
setupOption.mockClear();
manager.configure(ViewExt1, undefined);
viewExtensions = manager.get('edgeless');
expect(viewExtensions).toEqual([Ext2]);
expect(setupOption).toHaveBeenCalledWith(undefined);
viewExtensions = manager.get('page');
expect(setupOption).toHaveBeenCalledWith(undefined);
});
it('should extension manager be able to be injected', () => {
class StoreExt1 extends StoreExtensionProvider {
override name = 'StoreExt1';
override setup(context: StoreExtensionContext) {
super.setup(context);
context.register(Ext1);
}
}
const manager = new StoreExtensionManager([StoreExt1]);
const extensions = manager.get('store');
const container = new Container();
extensions.forEach(ext => {
ext.setup(container);
});
const provider = container.provider();
expect(provider.get(StoreExtensionManagerIdentifier)).toBe(manager);
});
it('should effect only run once', () => {
const effect1 = vi.fn();
const effect2 = vi.fn();
class ViewExt1 extends ViewExtensionProvider {
override name = 'ViewExt1';
override effect() {
super.effect();
effect1();
}
override setup(context: ViewExtensionContext) {
super.setup(context);
context.register(Ext1);
}
}
class ViewExt2 extends ViewExtensionProvider {
override name = 'ViewExt2';
override effect() {
super.effect();
effect2();
}
override setup(context: ViewExtensionContext) {
super.setup(context);
context.register(Ext2);
}
}
const manager = new ViewExtensionManager([ViewExt1]);
expect(ViewExt1.effectRan).toBe(false);
expect(ViewExt2.effectRan).toBe(false);
manager.get('page');
expect(ViewExt1.effectRan).toBe(true);
expect(ViewExt2.effectRan).toBe(false);
expect(effect1).toHaveBeenCalledTimes(1);
expect(effect2).toHaveBeenCalledTimes(0);
manager.get('edgeless');
expect(ViewExt1.effectRan).toBe(true);
expect(ViewExt2.effectRan).toBe(false);
expect(effect1).toHaveBeenCalledTimes(1);
expect(effect2).toHaveBeenCalledTimes(0);
});