-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
615 additions
and
0 deletions.
There are no files selected for viewing
199 changes: 199 additions & 0 deletions
199
apps/server/src/modules/board/poc/board-node.do.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import { boardNodeFactory } from './board-node.factory'; | ||
import { INITIAL_PATH, joinPath } from './path-utils'; | ||
|
||
describe('BoardNode', () => { | ||
describe('a new instance', () => { | ||
const setup = () => { | ||
const boardNode = boardNodeFactory.build(); | ||
|
||
return { boardNode }; | ||
}; | ||
|
||
it('should have no ancestors', () => { | ||
const { boardNode } = setup(); | ||
expect(boardNode.ancestorIds).toHaveLength(0); | ||
}); | ||
|
||
it('should have no parent', () => { | ||
const { boardNode } = setup(); | ||
expect(boardNode.hasParent()).toBe(false); | ||
expect(boardNode.parentId).not.toBeDefined(); | ||
}); | ||
|
||
it('should have level = 0', () => { | ||
const { boardNode } = setup(); | ||
expect(boardNode.level).toBe(0); | ||
}); | ||
|
||
it('should have initial path', () => { | ||
const { boardNode } = setup(); | ||
expect(boardNode.path).toBe(INITIAL_PATH); | ||
}); | ||
}); | ||
|
||
describe('when adding to a parent', () => { | ||
const setup = () => { | ||
const parent = boardNodeFactory.build(); | ||
const child = boardNodeFactory.build(); | ||
|
||
return { parent, child }; | ||
}; | ||
|
||
it('should update the ancestor list', () => { | ||
const { parent, child } = setup(); | ||
|
||
child.addToParent(parent); | ||
|
||
expect(child.ancestorIds).toEqual([parent.id]); | ||
}); | ||
|
||
it('should update the children of the parent', () => { | ||
const { parent, child } = setup(); | ||
|
||
child.addToParent(parent); | ||
|
||
expect(parent.children).toEqual([child]); | ||
}); | ||
|
||
it('should update the level of the child', () => { | ||
const { parent, child } = setup(); | ||
|
||
child.addToParent(parent); | ||
|
||
expect(child.level).toEqual(1); | ||
}); | ||
|
||
it('should update the path', () => { | ||
const { parent, child } = setup(); | ||
|
||
child.addToParent(parent); | ||
|
||
expect(child.path).toEqual(joinPath(parent.path, parent.id)); | ||
}); | ||
}); | ||
|
||
describe('when adding a child', () => { | ||
const setup = () => { | ||
const parent = boardNodeFactory.build(); | ||
const child = boardNodeFactory.build(); | ||
|
||
return { parent, child }; | ||
}; | ||
|
||
it('should update the ancestor list', () => { | ||
const { parent, child } = setup(); | ||
|
||
parent.addChild(child); | ||
|
||
expect(child.ancestorIds).toEqual([parent.id]); | ||
}); | ||
|
||
it('should update the children of the parent', () => { | ||
const { parent, child } = setup(); | ||
|
||
parent.addChild(child); | ||
|
||
expect(parent.children).toEqual([child]); | ||
}); | ||
|
||
it('should update the level of the child', () => { | ||
const { parent, child } = setup(); | ||
|
||
parent.addChild(child); | ||
|
||
expect(child.level).toEqual(1); | ||
}); | ||
|
||
it('should update the path of the child', () => { | ||
const { parent, child } = setup(); | ||
|
||
parent.addChild(child); | ||
|
||
expect(child.path).toEqual(joinPath(parent.path, parent.id)); | ||
}); | ||
}); | ||
|
||
describe('when removing from a parent', () => { | ||
const setup = () => { | ||
const parent = boardNodeFactory.build(); | ||
const child = boardNodeFactory.build(); | ||
child.addToParent(parent); | ||
|
||
return { parent, child }; | ||
}; | ||
|
||
it('should update the ancestor list', () => { | ||
const { parent, child } = setup(); | ||
|
||
child.removeFromParent(parent); | ||
|
||
expect(child.ancestorIds).toEqual([]); | ||
}); | ||
|
||
it('should update the children of the parent', () => { | ||
const { parent, child } = setup(); | ||
|
||
child.removeFromParent(parent); | ||
|
||
expect(parent.children).toEqual([]); | ||
}); | ||
|
||
it('should update the level of the child', () => { | ||
const { parent, child } = setup(); | ||
|
||
child.removeFromParent(parent); | ||
|
||
expect(child.level).toEqual(0); | ||
}); | ||
|
||
it('should update the path', () => { | ||
const { parent, child } = setup(); | ||
|
||
child.removeFromParent(parent); | ||
|
||
expect(child.path).toEqual(INITIAL_PATH); | ||
}); | ||
}); | ||
|
||
describe('when removing a child', () => { | ||
const setup = () => { | ||
const parent = boardNodeFactory.build(); | ||
const child = boardNodeFactory.build(); | ||
parent.addChild(child); | ||
|
||
return { parent, child }; | ||
}; | ||
|
||
it('should update the ancestor list', () => { | ||
const { parent, child } = setup(); | ||
|
||
parent.removeChild(child); | ||
|
||
expect(child.ancestorIds).toEqual([]); | ||
}); | ||
|
||
it('should update the children of the parent', () => { | ||
const { parent, child } = setup(); | ||
|
||
parent.removeChild(child); | ||
|
||
expect(parent.children).toEqual([]); | ||
}); | ||
|
||
it('should update the level of the child', () => { | ||
const { parent, child } = setup(); | ||
|
||
parent.removeChild(child); | ||
|
||
expect(child.level).toEqual(0); | ||
}); | ||
|
||
it('should update the path of the child', () => { | ||
const { parent, child } = setup(); | ||
|
||
parent.removeChild(child); | ||
|
||
expect(child.path).toEqual(INITIAL_PATH); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { DomainObject } from '@shared/domain/domain-object'; | ||
import { EntityId } from '@shared/domain/types'; | ||
import { INITIAL_PATH, PATH_SEPARATOR, joinPath } from './path-utils'; | ||
|
||
export interface BoardNodeProps { | ||
id: EntityId; | ||
path: string; | ||
level: number; | ||
position: number; | ||
// type!: BoardNodeType; | ||
title?: string; | ||
children: BoardNode[]; | ||
createdAt: Date; | ||
updatedAt: Date; | ||
} | ||
|
||
export class BoardNode extends DomainObject<BoardNodeProps> { | ||
get level(): number { | ||
return this.ancestorIds.length; | ||
} | ||
|
||
get children(): readonly BoardNode[] { | ||
// return this.props.children; // should we clone the array? | ||
return [...this.props.children] as const; | ||
} | ||
|
||
get parentId(): EntityId | undefined { | ||
const parentId = this.hasParent() ? this.ancestorIds[this.ancestorIds.length - 1] : undefined; | ||
return parentId; | ||
} | ||
|
||
hasParent() { | ||
return this.ancestorIds.length > 0; | ||
} | ||
|
||
get ancestorIds(): EntityId[] { | ||
const parentIds = this.props.path.split(PATH_SEPARATOR).filter((id) => id !== ''); | ||
return parentIds; | ||
} | ||
|
||
get path(): string { | ||
return this.props.path; | ||
} | ||
|
||
addToParent(parent: BoardNode, position?: { index1: string; index2: string }): void { | ||
if (this.parentId !== parent.id) { | ||
this.props.path = joinPath(parent.path, parent.id); | ||
// TODO set index (position) | ||
this.props.level = parent.level + 1; | ||
} | ||
if (!parent.children.includes(this)) { | ||
parent.addChild(this, position); | ||
} | ||
} | ||
|
||
removeFromParent(parent: BoardNode): void { | ||
if (this.parentId === parent.id) { | ||
this.props.path = INITIAL_PATH; | ||
// TODO set index (position) | ||
this.props.level = 0; | ||
} | ||
if (parent.children.includes(this)) { | ||
parent.removeChild(this); | ||
} | ||
} | ||
|
||
addChild(child: BoardNode, position?: { index1: string; index2: string }): void { | ||
if (!this.children.includes(child)) { | ||
this.props.children.push(child); | ||
// TODO sort children | ||
} | ||
if (child.parentId !== this.id) { | ||
child.addToParent(this, position); | ||
} | ||
} | ||
|
||
removeChild(child: BoardNode): void { | ||
if (this.children.includes(child)) { | ||
const index = this.children.indexOf(child); | ||
this.props.children.splice(index, 1); | ||
} | ||
if (child.parentId === this.id) { | ||
child.removeFromParent(this); | ||
} | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
apps/server/src/modules/board/poc/board-node.entity.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { MikroORM } from '@mikro-orm/mongodb'; | ||
import { BaseEntityWithTimestamps } from '@shared/domain/entity'; | ||
import { BoardNodeEntity } from './board-node.entity'; | ||
|
||
describe('entity', () => { | ||
let orm: MikroORM; | ||
|
||
beforeAll(async () => { | ||
orm = await MikroORM.init({ | ||
entities: [BaseEntityWithTimestamps, BoardNodeEntity], | ||
clientUrl: 'mongodb://localhost:27017/boardtest', | ||
type: 'mongo', | ||
validate: true, | ||
allowGlobalContext: true, | ||
}); | ||
}); | ||
|
||
beforeEach(async () => { | ||
await orm.schema.clearDatabase(); | ||
}); | ||
|
||
afterAll(async () => { | ||
await orm.schema.dropSchema(); | ||
await orm.close(true); | ||
}); | ||
|
||
it('persists', async () => { | ||
const entity = new BoardNodeEntity(); | ||
|
||
await orm.em.persistAndFlush(entity); | ||
orm.em.clear(); | ||
|
||
expect(entity.id).toBeDefined(); | ||
|
||
const result = await orm.em.findOneOrFail(BoardNodeEntity, { id: entity.id }); | ||
|
||
expect(result).toBeDefined(); | ||
expect(result.id).toEqual(entity.id); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { BaseEntityWithTimestamps } from '@shared/domain/entity'; | ||
import { Entity, Index, Property } from '@mikro-orm/core'; | ||
import { BoardNode, BoardNodeProps } from './board-node.do'; | ||
|
||
@Entity({ tableName: 'boardnodes' }) | ||
export class BoardNodeEntity extends BaseEntityWithTimestamps implements BoardNodeProps { | ||
@Index() | ||
@Property({ nullable: false }) | ||
path = ','; // TODO find better way to provide defaults! | ||
|
||
@Property({ nullable: false }) | ||
level = 0; | ||
|
||
@Property({ nullable: false }) | ||
position = 0; | ||
|
||
// @Index() | ||
// @Enum(() => BoardNodeType) | ||
// type!: BoardNodeType; | ||
|
||
@Property({ nullable: true }) | ||
title?: string; | ||
|
||
@Property({ persist: false }) | ||
children: BoardNode[] = []; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* istanbul ignore file */ | ||
import { BaseFactory } from '@shared/testing'; | ||
import { ObjectId } from 'bson'; | ||
import { BoardNode, BoardNodeProps } from './board-node.do'; | ||
import { INITIAL_PATH } from './path-utils'; | ||
|
||
export const boardNodeFactory = BaseFactory.define<BoardNode, BoardNodeProps>(BoardNode, ({ sequence }) => { | ||
return { | ||
id: new ObjectId().toHexString(), | ||
path: INITIAL_PATH, | ||
level: 0, | ||
title: `column board #${sequence}`, | ||
position: 0, | ||
children: [], | ||
createdAt: new Date(), | ||
updatedAt: new Date(), | ||
}; | ||
}); |
Oops, something went wrong.