-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 🎸 add Builder Pattern to Shape/Animation/Slide
With this commit I'm introducing new features in the kittik-slide package that allows you to build animations via AnimationBuilder, shapes via ShapeBuilder and slides via SlideBuilder. These builders provide you typed API that checks everything you passed as their arguments, so there is a lesser room for some errors.
- Loading branch information
Showing
8 changed files
with
444 additions
and
23 deletions.
There are no files selected for viewing
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,48 @@ | ||
import { AnimationBuilder } from '../src/animation/AnimationBuilder'; | ||
|
||
describe('AnimationBuilder', () => { | ||
it('Should properly create animation from the builder', () => { | ||
const animation = AnimationBuilder | ||
.start('Focus') | ||
.withType('Focus') | ||
.withDuration(2000) | ||
.withEasing('inBack') | ||
.end(); | ||
|
||
expect(animation.toObject()).toEqual({ | ||
type: 'Focus', | ||
options: { | ||
direction: 'shakeX', | ||
duration: 2000, | ||
easing: 'inBack', | ||
offset: 5, | ||
repeat: 1 | ||
} | ||
}); | ||
}); | ||
|
||
it('Should properly build animation using withOptions()', () => { | ||
const animation = AnimationBuilder | ||
.start('Print') | ||
.withOptions({ duration: 5000 }) | ||
.end(); | ||
|
||
expect(animation.toObject()).toEqual({ | ||
type: 'Print', | ||
options: { | ||
duration: 5000, | ||
easing: 'outQuad' | ||
} | ||
}); | ||
}); | ||
|
||
it('Should properly throw an error if animation is absent', () => { | ||
expect(() => { | ||
// This is a specific case where I check if someone tries to build not existing animation | ||
// Though, this case was covered by types, so I need to disable it to write the test | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
AnimationBuilder.start('Nonsense').end(); | ||
}).toThrowError('Animation "Nonsense" you tried to build does not exist'); | ||
}); | ||
}); |
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,62 @@ | ||
import { ShapeBuilder } from '../src/shape/ShapeBuilder'; | ||
import { Canvas } from 'terminal-canvas'; | ||
|
||
describe('ShapeBuilder', () => { | ||
it('Should properly build the shape via builder', () => { | ||
const shape = ShapeBuilder | ||
.start('Rectangle') | ||
.withCursor(Canvas.create()) | ||
.withType('Rectangle') | ||
.withText('Hello, World') | ||
.withX('10%') | ||
.withY('10%') | ||
.withWidth('60%') | ||
.withHeight('40%') | ||
.withBackground('white') | ||
.withForeground('black') | ||
.end(); | ||
|
||
expect(shape.toObject()).toEqual({ | ||
type: 'Rectangle', | ||
options: { | ||
background: 'white', | ||
foreground: 'black', | ||
height: '40%', | ||
text: 'Hello, World', | ||
width: '60%', | ||
x: '10%', | ||
y: '10%' | ||
} | ||
}); | ||
}); | ||
|
||
it('Should properly build the shape via withOptions()', () => { | ||
const shape = ShapeBuilder | ||
.start('Rectangle') | ||
.withOptions({ text: 'Hello, World' }) | ||
.end(); | ||
|
||
expect(shape.toObject()).toEqual({ | ||
type: 'Rectangle', | ||
options: { | ||
background: 'none', | ||
foreground: 'none', | ||
height: '25%', | ||
text: 'Hello, World', | ||
width: '50%', | ||
x: 'left', | ||
y: 'top' | ||
} | ||
}); | ||
}); | ||
|
||
it('Should properly throw an error if shape is absent', () => { | ||
expect(() => { | ||
// This is a corner case where I check if builder throws an error when providing wrong type | ||
// Since this case has been covered by type system of TypeScript, I need to disable it | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore | ||
// @ts-ignore | ||
ShapeBuilder.start('Nonsense').end(); | ||
}).toThrowError('Shape "Nonsense" you tried to build does not exist'); | ||
}); | ||
}); |
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
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,62 @@ | ||
import { SlideBuilder } from '../src/slide/SlideBuilder'; | ||
import { ShapeBuilder, AnimationBuilder } from '../src/slide/Slide'; | ||
|
||
describe('SlideBuilder', () => { | ||
it('Should properly instantiate slide via builder', () => { | ||
const slide = SlideBuilder | ||
.start() | ||
.withShape( | ||
'Hello, World', | ||
ShapeBuilder | ||
.start('Text') | ||
.withText('Hello, World') | ||
.withBackground('white') | ||
.withForeground('black') | ||
.end() | ||
) | ||
.withAnimation( | ||
'Print', | ||
AnimationBuilder | ||
.start('Print') | ||
.withDuration(2000) | ||
.end() | ||
) | ||
.withOrder('Hello, World', ['Print']) | ||
.end(); | ||
|
||
expect(slide.shapes.size).toBe(1); | ||
expect(slide.shapes.get('Hello, World')?.toObject()).toEqual({ | ||
type: 'Text', | ||
options: { | ||
align: 'center', | ||
background: 'white', | ||
blink: false, | ||
bold: false, | ||
dim: false, | ||
foreground: 'black', | ||
height: '25%', | ||
hidden: false, | ||
reverse: false, | ||
text: 'Hello, World', | ||
underlined: false, | ||
width: '50%', | ||
x: 'left', | ||
y: 'top' | ||
} | ||
}); | ||
|
||
expect(slide.animations.size).toBe(1); | ||
expect(slide.animations.get('Print')?.toObject()).toEqual({ | ||
type: 'Print', | ||
options: { | ||
duration: 2000, | ||
easing: 'outQuad' | ||
} | ||
}); | ||
|
||
expect(slide.order).toEqual([{ | ||
shape: 'Hello, World', | ||
animations: ['Print'] | ||
}]); | ||
}); | ||
}); |
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,48 @@ | ||
import { AnimationObject, AnimationOptions, Animationable, Easing } from 'kittik-animation-basic'; | ||
import { AnimationType, ANIMATIONS } from './Animations'; | ||
|
||
export class AnimationBuilder implements AnimationObject { | ||
type: AnimationType; | ||
options?: Partial<AnimationOptions>; | ||
|
||
constructor (type: AnimationType) { | ||
this.type = type; | ||
} | ||
|
||
withType (type: AnimationType): this { | ||
this.type = type; | ||
|
||
return this; | ||
} | ||
|
||
withOptions (options: Partial<AnimationOptions>): this { | ||
this.options = { ...this.options, ...options }; | ||
|
||
return this; | ||
} | ||
|
||
withDuration (duration: number): this { | ||
this.options = { ...this.options, duration }; | ||
|
||
return this; | ||
} | ||
|
||
withEasing (easing: Easing): this { | ||
this.options = { ...this.options, easing }; | ||
|
||
return this; | ||
} | ||
|
||
end (): Animationable { | ||
const ctr = ANIMATIONS.get(this.type); | ||
if (ctr === undefined) { | ||
throw new Error(`Animation "${this.type}" you tried to build does not exist`); | ||
} | ||
|
||
return ctr.fromObject(this); | ||
} | ||
|
||
static start (type: AnimationType): AnimationBuilder { | ||
return new this(type); | ||
} | ||
} |
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 { ShapeObject, ShapeOptions, ShapeRenderable } from 'kittik-shape-basic'; | ||
import { ShapeType, SHAPES } from './Shapes'; | ||
import { Canvas } from 'terminal-canvas'; | ||
|
||
export class ShapeBuilder implements ShapeObject { | ||
cursor: Canvas = Canvas.create(); | ||
type: ShapeType; | ||
options?: Partial<ShapeOptions>; | ||
|
||
constructor (type: ShapeType) { | ||
this.type = type; | ||
} | ||
|
||
withCursor (cursor: Canvas): this { | ||
this.cursor = cursor; | ||
|
||
return this; | ||
} | ||
|
||
withType (type: ShapeType): this { | ||
this.type = type; | ||
|
||
return this; | ||
} | ||
|
||
withOptions (options: Partial<ShapeOptions>): this { | ||
this.options = { ...this.options, ...options }; | ||
|
||
return this; | ||
} | ||
|
||
withText (text: string): this { | ||
this.options = { ...this.options, text }; | ||
|
||
return this; | ||
} | ||
|
||
withX (x: string): this { | ||
this.options = { ...this.options, x }; | ||
|
||
return this; | ||
} | ||
|
||
withY (y: string): this { | ||
this.options = { ...this.options, y }; | ||
|
||
return this; | ||
} | ||
|
||
withWidth (width: string): this { | ||
this.options = { ...this.options, width }; | ||
|
||
return this; | ||
} | ||
|
||
withHeight (height: string): this { | ||
this.options = { ...this.options, height }; | ||
|
||
return this; | ||
} | ||
|
||
withBackground (background: string): this { | ||
this.options = { ...this.options, background }; | ||
|
||
return this; | ||
} | ||
|
||
withForeground (foreground: string): this { | ||
this.options = { ...this.options, foreground }; | ||
|
||
return this; | ||
} | ||
|
||
end (): ShapeRenderable { | ||
const ctr = SHAPES.get(this.type); | ||
if (ctr === undefined) { | ||
throw new Error(`Shape "${this.type}" you tried to build does not exist`); | ||
} | ||
|
||
return ctr.fromObject(this, this.cursor); | ||
} | ||
|
||
static start (type: ShapeType): ShapeBuilder { | ||
return new this(type); | ||
} | ||
} |
Oops, something went wrong.