chore: merge blocksuite source code (#9213)

This commit is contained in:
Mirone
2024-12-20 15:38:06 +08:00
committed by GitHub
parent 2c9ef916f4
commit 30200ff86d
2031 changed files with 238888 additions and 229 deletions

View 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);
});
});

View 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);
});
});

View 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);
});
});