mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
362
blocksuite/framework/global/src/__tests__/di.unit.spec.ts
Normal file
362
blocksuite/framework/global/src/__tests__/di.unit.spec.ts
Normal file
@@ -0,0 +1,362 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import {
|
||||
CircularDependencyError,
|
||||
Container,
|
||||
createIdentifier,
|
||||
createScope,
|
||||
DuplicateServiceDefinitionError,
|
||||
MissingDependencyError,
|
||||
RecursionLimitError,
|
||||
ServiceNotFoundError,
|
||||
ServiceProvider,
|
||||
} from '../di/index.js';
|
||||
|
||||
describe('di', () => {
|
||||
test('basic', () => {
|
||||
const container = new Container();
|
||||
class TestService {
|
||||
a = 'b';
|
||||
}
|
||||
|
||||
container.add(TestService);
|
||||
|
||||
const provider = container.provider();
|
||||
expect(provider.get(TestService)).toEqual({ a: 'b' });
|
||||
});
|
||||
|
||||
test('size', () => {
|
||||
const container = new Container();
|
||||
class TestService {
|
||||
a = 'b';
|
||||
}
|
||||
|
||||
container.add(TestService);
|
||||
|
||||
expect(container.size).toEqual(1);
|
||||
});
|
||||
|
||||
test('dependency', () => {
|
||||
const container = new Container();
|
||||
|
||||
class A {
|
||||
value = 'hello world';
|
||||
}
|
||||
|
||||
class B {
|
||||
constructor(public a: A) {}
|
||||
}
|
||||
|
||||
class C {
|
||||
constructor(public b: B) {}
|
||||
}
|
||||
|
||||
container.add(A).add(B, [A]).add(C, [B]);
|
||||
|
||||
const provider = container.provider();
|
||||
|
||||
expect(provider.get(C).b.a.value).toEqual('hello world');
|
||||
});
|
||||
|
||||
test('identifier', () => {
|
||||
interface Animal {
|
||||
name: string;
|
||||
}
|
||||
const Animal = createIdentifier<Animal>('Animal');
|
||||
|
||||
class Cat {
|
||||
name = 'cat';
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
class Zoo {
|
||||
constructor(public animal: Animal) {}
|
||||
}
|
||||
|
||||
const container = new Container();
|
||||
container.addImpl(Animal, Cat).add(Zoo, [Animal]);
|
||||
|
||||
const provider = container.provider();
|
||||
expect(provider.get(Zoo).animal.name).toEqual('cat');
|
||||
});
|
||||
|
||||
test('variant', () => {
|
||||
const container = new Container();
|
||||
|
||||
interface USB {
|
||||
speed: number;
|
||||
}
|
||||
|
||||
const USB = createIdentifier<USB>('USB');
|
||||
|
||||
class TypeA implements USB {
|
||||
speed = 100;
|
||||
}
|
||||
class TypeC implements USB {
|
||||
speed = 300;
|
||||
}
|
||||
|
||||
class PC {
|
||||
constructor(
|
||||
public typeA: USB,
|
||||
public ports: USB[]
|
||||
) {}
|
||||
}
|
||||
|
||||
container
|
||||
.addImpl(USB('A'), TypeA)
|
||||
.addImpl(USB('C'), TypeC)
|
||||
.add(PC, [USB('A'), [USB]]);
|
||||
|
||||
const provider = container.provider();
|
||||
expect(provider.get(USB('A')).speed).toEqual(100);
|
||||
expect(provider.get(USB('C')).speed).toEqual(300);
|
||||
expect(provider.get(PC).typeA.speed).toEqual(100);
|
||||
expect(provider.get(PC).ports.length).toEqual(2);
|
||||
});
|
||||
|
||||
test('lazy initialization', () => {
|
||||
const container = new Container();
|
||||
interface Command {
|
||||
shortcut: string;
|
||||
callback: () => void;
|
||||
}
|
||||
const Command = createIdentifier<Command>('command');
|
||||
|
||||
let pageSystemInitialized = false;
|
||||
|
||||
class PageSystem {
|
||||
mode = 'page';
|
||||
|
||||
name = 'helloworld';
|
||||
|
||||
constructor() {
|
||||
pageSystemInitialized = true;
|
||||
}
|
||||
|
||||
rename() {
|
||||
this.name = 'foobar';
|
||||
}
|
||||
|
||||
switchToEdgeless() {
|
||||
this.mode = 'edgeless';
|
||||
}
|
||||
}
|
||||
|
||||
class CommandSystem {
|
||||
constructor(public commands: Command[]) {}
|
||||
|
||||
execute(shortcut: string) {
|
||||
const command = this.commands.find(c => c.shortcut === shortcut);
|
||||
if (command) {
|
||||
command.callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container.add(PageSystem);
|
||||
container.add(CommandSystem, [[Command]]);
|
||||
container.addImpl(Command('switch'), p => ({
|
||||
shortcut: 'option+s',
|
||||
callback: () => p.get(PageSystem).switchToEdgeless(),
|
||||
}));
|
||||
container.addImpl(Command('rename'), p => ({
|
||||
shortcut: 'f2',
|
||||
callback: () => p.get(PageSystem).rename(),
|
||||
}));
|
||||
|
||||
const provider = container.provider();
|
||||
const commandSystem = provider.get(CommandSystem);
|
||||
|
||||
expect(
|
||||
pageSystemInitialized,
|
||||
"PageSystem won't be initialized until command executed"
|
||||
).toEqual(false);
|
||||
|
||||
commandSystem.execute('option+s');
|
||||
expect(pageSystemInitialized).toEqual(true);
|
||||
expect(provider.get(PageSystem).mode).toEqual('edgeless');
|
||||
|
||||
expect(provider.get(PageSystem).name).toEqual('helloworld');
|
||||
expect(commandSystem.commands.length).toEqual(2);
|
||||
commandSystem.execute('f2');
|
||||
expect(provider.get(PageSystem).name).toEqual('foobar');
|
||||
});
|
||||
|
||||
test('duplicate, override', () => {
|
||||
const container = new Container();
|
||||
|
||||
const something = createIdentifier<any>('USB');
|
||||
|
||||
class A {
|
||||
a = 'i am A';
|
||||
}
|
||||
|
||||
class B {
|
||||
b = 'i am B';
|
||||
}
|
||||
|
||||
container.addImpl(something, A).override(something, B);
|
||||
|
||||
const provider = container.provider();
|
||||
expect(provider.get(something)).toEqual({ b: 'i am B' });
|
||||
});
|
||||
|
||||
test('scope', () => {
|
||||
const container = new Container();
|
||||
|
||||
const workspaceScope = createScope('workspace');
|
||||
const pageScope = createScope('page', workspaceScope);
|
||||
const editorScope = createScope('editor', pageScope);
|
||||
|
||||
class System {
|
||||
appName = 'affine';
|
||||
}
|
||||
|
||||
container.add(System);
|
||||
|
||||
class Workspace {
|
||||
name = 'workspace';
|
||||
|
||||
constructor(public system: System) {}
|
||||
}
|
||||
|
||||
container.scope(workspaceScope).add(Workspace, [System]);
|
||||
class Page {
|
||||
name = 'page';
|
||||
|
||||
constructor(
|
||||
public system: System,
|
||||
public workspace: Workspace
|
||||
) {}
|
||||
}
|
||||
|
||||
container.scope(pageScope).add(Page, [System, Workspace]);
|
||||
|
||||
class Editor {
|
||||
name = 'editor';
|
||||
|
||||
constructor(public page: Page) {}
|
||||
}
|
||||
|
||||
container.scope(editorScope).add(Editor, [Page]);
|
||||
|
||||
const root = container.provider();
|
||||
expect(root.get(System).appName).toEqual('affine');
|
||||
expect(() => root.get(Workspace)).toThrowError(ServiceNotFoundError);
|
||||
|
||||
const workspace = container.provider(workspaceScope, root);
|
||||
expect(workspace.get(Workspace).name).toEqual('workspace');
|
||||
expect(workspace.get(System).appName).toEqual('affine');
|
||||
expect(() => root.get(Page)).toThrowError(ServiceNotFoundError);
|
||||
|
||||
const page = container.provider(pageScope, workspace);
|
||||
expect(page.get(Page).name).toEqual('page');
|
||||
expect(page.get(Workspace).name).toEqual('workspace');
|
||||
expect(page.get(System).appName).toEqual('affine');
|
||||
|
||||
const editor = container.provider(editorScope, page);
|
||||
expect(editor.get(Editor).name).toEqual('editor');
|
||||
});
|
||||
|
||||
test('service not found', () => {
|
||||
const container = new Container();
|
||||
|
||||
const provider = container.provider();
|
||||
expect(() => provider.get(createIdentifier('SomeService'))).toThrowError(
|
||||
ServiceNotFoundError
|
||||
);
|
||||
});
|
||||
|
||||
test('missing dependency', () => {
|
||||
const container = new Container();
|
||||
|
||||
class A {
|
||||
value = 'hello world';
|
||||
}
|
||||
|
||||
class B {
|
||||
constructor(public a: A) {}
|
||||
}
|
||||
|
||||
container.add(B, [A]);
|
||||
|
||||
const provider = container.provider();
|
||||
expect(() => provider.get(B)).toThrowError(MissingDependencyError);
|
||||
});
|
||||
|
||||
test('circular dependency', () => {
|
||||
const container = new Container();
|
||||
|
||||
class A {
|
||||
constructor(public c: C) {}
|
||||
}
|
||||
|
||||
class B {
|
||||
constructor(public a: A) {}
|
||||
}
|
||||
|
||||
class C {
|
||||
constructor(public b: B) {}
|
||||
}
|
||||
|
||||
container.add(A, [C]).add(B, [A]).add(C, [B]);
|
||||
|
||||
const provider = container.provider();
|
||||
expect(() => provider.get(A)).toThrowError(CircularDependencyError);
|
||||
expect(() => provider.get(B)).toThrowError(CircularDependencyError);
|
||||
expect(() => provider.get(C)).toThrowError(CircularDependencyError);
|
||||
});
|
||||
|
||||
test('duplicate service definition', () => {
|
||||
const container = new Container();
|
||||
|
||||
class A {}
|
||||
|
||||
container.add(A);
|
||||
expect(() => container.add(A)).toThrowError(
|
||||
DuplicateServiceDefinitionError
|
||||
);
|
||||
|
||||
class B {}
|
||||
const Something = createIdentifier('something');
|
||||
container.addImpl(Something, A);
|
||||
expect(() => container.addImpl(Something, B)).toThrowError(
|
||||
DuplicateServiceDefinitionError
|
||||
);
|
||||
});
|
||||
|
||||
test('recursion limit', () => {
|
||||
// maxmium resolve depth is 100
|
||||
const container = new Container();
|
||||
const Something = createIdentifier('something');
|
||||
let i = 0;
|
||||
for (; i < 100; i++) {
|
||||
const next = i + 1;
|
||||
|
||||
class Test {
|
||||
constructor(_next: any) {}
|
||||
}
|
||||
|
||||
container.addImpl(Something(i.toString()), Test, [
|
||||
Something(next.toString()),
|
||||
]);
|
||||
}
|
||||
|
||||
class Final {
|
||||
a = 'b';
|
||||
}
|
||||
container.addImpl(Something(i.toString()), Final);
|
||||
const provider = container.provider();
|
||||
expect(() => provider.get(Something('0'))).toThrowError(
|
||||
RecursionLimitError
|
||||
);
|
||||
});
|
||||
|
||||
test('self resolve', () => {
|
||||
const container = new Container();
|
||||
const provider = container.provider();
|
||||
expect(provider.get(ServiceProvider)).toEqual(provider);
|
||||
});
|
||||
});
|
||||
58
blocksuite/framework/global/src/__tests__/slot.unit.spec.ts
Normal file
58
blocksuite/framework/global/src/__tests__/slot.unit.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { Slot } from '../utils.js';
|
||||
|
||||
describe('slot', () => {
|
||||
test('init', () => {
|
||||
const slot = new Slot();
|
||||
expect(slot).is.toBeDefined();
|
||||
});
|
||||
|
||||
test('emit', () => {
|
||||
const slot = new Slot<void>();
|
||||
const callback = vi.fn();
|
||||
slot.on(callback);
|
||||
slot.emit();
|
||||
expect(callback).toBeCalled();
|
||||
});
|
||||
|
||||
test('emit with value', () => {
|
||||
const slot = new Slot<number>();
|
||||
const callback = vi.fn(v => expect(v).toBe(5));
|
||||
slot.on(callback);
|
||||
slot.emit(5);
|
||||
expect(callback).toBeCalled();
|
||||
});
|
||||
|
||||
test('listen once', () => {
|
||||
const slot = new Slot<number>();
|
||||
const callback = vi.fn(v => expect(v).toBe(5));
|
||||
slot.once(callback);
|
||||
slot.emit(5);
|
||||
slot.emit(6);
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('listen once with dispose', () => {
|
||||
const slot = new Slot<void>();
|
||||
const callback = vi.fn(() => {
|
||||
throw new Error('');
|
||||
});
|
||||
const disposable = slot.once(callback);
|
||||
disposable.dispose();
|
||||
slot.emit();
|
||||
expect(callback).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test('subscribe', () => {
|
||||
type Data = {
|
||||
name: string;
|
||||
age: number;
|
||||
};
|
||||
const slot = new Slot<Data>();
|
||||
const callback = vi.fn(v => expect(v).toBe('田所'));
|
||||
slot.subscribe(v => v.name, callback);
|
||||
slot.emit({ name: '田所', age: 24 });
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
69
blocksuite/framework/global/src/__tests__/utils.unit.spec.ts
Normal file
69
blocksuite/framework/global/src/__tests__/utils.unit.spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { isEqual } from '../utils.js';
|
||||
|
||||
describe('isEqual', () => {
|
||||
test('number', () => {
|
||||
expect(isEqual(1, 1)).toBe(true);
|
||||
expect(isEqual(1, 114514)).toBe(false);
|
||||
expect(isEqual(NaN, NaN)).toBe(true);
|
||||
expect(isEqual(0, -0)).toBe(false);
|
||||
});
|
||||
|
||||
test('string', () => {
|
||||
expect(isEqual('', '')).toBe(true);
|
||||
expect(isEqual('', ' ')).toBe(false);
|
||||
});
|
||||
|
||||
test('array', () => {
|
||||
expect(isEqual([], [])).toBe(true);
|
||||
expect(isEqual([1, 1, 4, 5, 1, 4], [])).toBe(false);
|
||||
expect(isEqual([1, 1, 4, 5, 1, 4], [1, 1, 4, 5, 1, 4])).toBe(true);
|
||||
});
|
||||
|
||||
test('object', () => {
|
||||
expect(isEqual({}, {})).toBe(true);
|
||||
expect(
|
||||
isEqual(
|
||||
{
|
||||
f: 1,
|
||||
g: {
|
||||
o: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
f: 1,
|
||||
g: {
|
||||
o: '',
|
||||
},
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
expect(isEqual({}, { foo: 1 })).toBe(false);
|
||||
// @ts-expect-error FIXME: ts error
|
||||
expect(isEqual({ foo: 1 }, {})).toBe(false);
|
||||
});
|
||||
|
||||
test('nested', () => {
|
||||
const nested = {
|
||||
string: 'this is a string',
|
||||
integer: 42,
|
||||
array: [19, 19, 810, 'test', NaN],
|
||||
nestedArray: [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
],
|
||||
float: 114.514,
|
||||
undefined,
|
||||
object: {
|
||||
'first-child': true,
|
||||
'second-child': false,
|
||||
'last-child': null,
|
||||
},
|
||||
bigint: 110101195306153019n,
|
||||
};
|
||||
expect(isEqual(nested, nested)).toBe(true);
|
||||
// @ts-expect-error FIXME: ts error
|
||||
expect(isEqual({ foo: [] }, { foo: '' })).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user