Skip to content

Commit c494c26

Browse files
committedApr 8, 2020
feat: 🎸 implement more user-friendly error handling in slides
When building a slide it will check all the semantics of slide declaration and all the references shapes <-> animations to prevent errors when showing off a presentation. This way, you will get all the errors of missed shapes or animations ahead-of-time.
1 parent fe5b05f commit c494c26

File tree

6 files changed

+112
-52
lines changed

6 files changed

+112
-52
lines changed
 

‎packages/kittik-slide/spec/AnimationBuilder.spec.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ describe('animation builder', () => {
4949
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
5050
// @ts-ignore
5151
AnimationBuilder.start('Nonsense').end();
52-
}).toThrow('Animation "Nonsense" you tried to build does not exist');
52+
}).toThrow(
53+
'You tried to build an animation with the type "Nonsense". ' +
54+
'But the animation of this type is not implemented or you made a typo.'
55+
);
5356
});
5457
});

‎packages/kittik-slide/spec/ShapeBuilder.spec.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ describe('shape builder', () => {
6161
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
6262
// @ts-ignore
6363
ShapeBuilder.start('Nonsense').end();
64-
}).toThrow('Shape "Nonsense" you tried to build does not exist');
64+
}).toThrow(
65+
'You tried to build a shape with the type "Nonsense". ' +
66+
'But the shape of this type is not implemented or you made a typo.'
67+
);
6568
});
6669
});

‎packages/kittik-slide/spec/Slide.spec.ts

+54-28
Original file line numberDiff line numberDiff line change
@@ -84,42 +84,54 @@ describe('slide', () => {
8484

8585
const cursor = new Canvas();
8686

87-
expect(
88-
() => new Slide(
89-
cursor, {
90-
name: 'Test',
91-
shapes: [{ name: 'Test', type: 'unknown' }],
92-
order: [{ shape: 'Test' }]
93-
}
94-
)
95-
).toThrow('Shape "Test" (unknown) is unknown for me, maybe you made a typo?');
87+
expect(() => new Slide(
88+
cursor,
89+
{
90+
name: 'Test',
91+
shapes: [{ name: 'Test', type: 'unknown' }],
92+
order: [{ shape: 'Test' }]
93+
}
94+
)).toThrow(
95+
'You have specified a shape with the name "Test" in slide "Test". ' +
96+
'This shape has an unknown type "unknown". ' +
97+
'Maybe you made a typo in "type" or tried to use a shape we do not have implemented.'
98+
);
9699
});
97100

98101
it('should properly throw an error if animation type is unknown', () => {
99102
expect.hasAssertions();
100103

101104
const cursor = new Canvas();
102-
expect(
103-
() => new Slide(
104-
cursor, {
105-
name: 'Test',
106-
shapes: [{ name: 'Test', type: 'Text' }],
107-
animations: [{ name: 'Test', type: 'unknown' }],
108-
order: [{ shape: 'Test' }]
109-
}
110-
)
111-
).toThrow('Animation "Test" (unknown) is unknown for me, maybe you made a typo?');
105+
106+
expect(() => new Slide(
107+
cursor,
108+
{
109+
name: 'Test',
110+
shapes: [{ name: 'Test', type: 'Text' }],
111+
animations: [{ name: 'Test', type: 'unknown' }],
112+
order: [{ shape: 'Test' }]
113+
}
114+
)).toThrow(
115+
'You have specified an animation with the name "Test" in slide "Test". ' +
116+
'This animation has an unknown type "unknown". ' +
117+
'Maybe you made a typo in "type" or tried to use an animation we do not have implemented.'
118+
);
112119
});
113120

114121
it('should properly throw an error if trying to use shape name in ordering that does not exist', async () => {
115122
expect.hasAssertions();
116123

117124
const cursor = new Canvas();
118-
const slide = new Slide(cursor, { name: 'Test', shapes: [], order: [{ shape: 'Not Exists' }] });
125+
const slide = new Slide(cursor, {
126+
name: 'Test',
127+
shapes: [],
128+
order: [{ shape: 'Not Exists' }]
129+
});
119130

120131
await expect(slide.render()).rejects.toThrow(
121132
'You specified shape "Not Exists" in slide "Test" as part of ordering, ' +
122-
'but it does not exist in shapes declaration.'
133+
'but it does not exist in shapes declaration for the slide. ' +
134+
'Maybe you forgot to create a shape you want to order or it is a typo in ordering itself.'
123135
);
124136
});
125137

@@ -214,7 +226,8 @@ describe('slide', () => {
214226
it('should properly instantiate an empty slide instance when nothing is passed but an empty arrays', () => {
215227
expect.hasAssertions();
216228

217-
const slide = new Slide(null, { name: 'Test', order: [], shapes: [] });
229+
const cursor = new Canvas();
230+
const slide = new Slide(cursor, { name: 'Test', order: [], shapes: [] });
218231
expect(slide.cursor).toBeInstanceOf(Canvas);
219232
expect(slide.shapes.size).toBe(0);
220233
expect(slide.animations.size).toBe(0);
@@ -224,29 +237,42 @@ describe('slide', () => {
224237
it('should properly throw an error when trying to add shape that is already added', () => {
225238
expect.hasAssertions();
226239

227-
const slide = new Slide(null, { name: 'Test', shapes: [{ name: 'Test', type: 'Text' }], order: [] });
228-
expect(() => slide.addShape('Test', Text.create())).toThrow('Shape "Test" already exists in slide');
240+
const canvas = new Canvas();
241+
const slide = new Slide(canvas, { name: 'Test', shapes: [{ name: 'Test', type: 'Text' }], order: [] });
242+
243+
expect(() => slide.addShape('Test', Text.create())).toThrow(
244+
'You are trying to add shape with the name "Test" into the slide "Test". ' +
245+
'But this shape already exists in slide "Test".'
246+
);
229247
});
230248

231249
it('should properly throw an error when trying to add animation that is already added', () => {
232250
expect.hasAssertions();
233251

234-
const slide = new Slide(null, {
252+
const canvas = new Canvas();
253+
const slide = new Slide(canvas, {
235254
name: 'Test',
236255
shapes: [],
237256
order: [],
238257
animations: [{ name: 'Test', type: 'Print' }]
239258
});
240259

241-
expect(() => slide.addAnimation('Test', Print.create())).toThrow('Animation "Test" already exists in slide');
260+
expect(() => slide.addAnimation('Test', Print.create())).toThrow(
261+
'You are trying to add animation with the name "Test" into the slide "Test". ' +
262+
'But this animation already exists in slide "Test".'
263+
);
242264
});
243265

244266
it('should properly throw an error when trying to add ordering for the shape that is already added', () => {
245267
expect.hasAssertions();
246268

247-
const slide = new Slide(null, { name: 'Test', shapes: [], order: [{ shape: 'Test' }] });
269+
const canvas = new Canvas();
270+
const slide = new Slide(canvas, { name: 'Test', shapes: [], order: [{ shape: 'Test' }] });
271+
248272
expect(() => slide.addOrder('Test')).toThrow(
249-
'You already have an ordering for shape "Test" in slide "Test"'
273+
'You already have specified an ordering for shape "Test" in slide "Test". ' +
274+
'Adding another one with the same name does not make any sense. ' +
275+
'Did you make a typo in shape name or forgot that you already added a shape to ordering?'
250276
);
251277
});
252278

‎packages/kittik-slide/src/animation/AnimationBuilder.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ export class AnimationBuilder implements AnimationObject {
4040
public end (): Animationable {
4141
const ctr = ANIMATIONS.get(this.type);
4242
if (typeof ctr === 'undefined') {
43-
throw new Error(`Animation "${this.type}" you tried to build does not exist`);
43+
throw new Error(
44+
`You tried to build an animation with the type "${this.type}". ` +
45+
'But the animation of this type is not implemented or you made a typo.'
46+
);
4447
}
4548

4649
return ctr.fromObject(this);

‎packages/kittik-slide/src/shape/ShapeBuilder.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ export class ShapeBuilder implements ShapeObject {
7070
public end (): ShapeRenderable {
7171
const ctr = SHAPES.get(this.type);
7272
if (typeof ctr === 'undefined') {
73-
throw new Error(`Shape "${this.type}" you tried to build does not exist`);
73+
throw new Error(
74+
`You tried to build a shape with the type "${this.type}". ` +
75+
'But the shape of this type is not implemented or you made a typo.'
76+
);
7477
}
7578

7679
return ctr.fromObject(this);

‎packages/kittik-slide/src/slide/Slide.ts

+42-20
Original file line numberDiff line numberDiff line change
@@ -47,59 +47,78 @@ export class Slide {
4747
}
4848
}
4949

50-
public static create (cursor: Canvas, declaration: SlideDeclaration): Slide {
50+
public static create (cursor?: Canvas, declaration?: SlideDeclaration): Slide {
5151
return new this(cursor, declaration);
5252
}
5353

54-
public static fromObject (obj: SlideDeclaration, cursor: Canvas): Slide {
54+
public static fromObject (obj: SlideDeclaration, cursor?: Canvas): Slide {
5555
return this.create(cursor, obj);
5656
}
5757

58-
public static fromJSON (json: string, cursor: Canvas): Slide {
58+
public static fromJSON (json: string, cursor?: Canvas): Slide {
5959
return this.fromObject(JSON.parse(json), cursor);
6060
}
6161

6262
public addShape (name: string, shape: ShapeRenderable, toOverride = false): void {
6363
if (this.shapes.has(name) && !toOverride) {
64-
throw new Error(`Shape "${name}" already exists in slide "${this.name}"`);
64+
throw new Error(
65+
`You are trying to add shape with the name "${name}" into the slide "${this.name}". ` +
66+
`But this shape already exists in slide "${this.name}".`
67+
);
6568
}
6669

6770
this.shapes.set(name, shape);
6871
}
6972

7073
public addAnimation (name: string, animation: Animationable, toOverride = false): void {
7174
if (this.animations.has(name) && !toOverride) {
72-
throw new Error(`Animation "${name}" already exists in slide "${this.name}"`);
75+
throw new Error(
76+
`You are trying to add animation with the name "${name}" into the slide "${this.name}". ` +
77+
`But this animation already exists in slide "${this.name}".`
78+
);
7379
}
7480

7581
this.animations.set(name, animation);
7682
}
7783

7884
public addOrder (shape: string, animations: string[] = []): void {
79-
if (this.order.some((order: OrderDeclaration) => order.shape === shape)) {
80-
throw new Error(`You already have an ordering for shape "${shape}" in slide "${this.name}"`);
85+
if (this.order.some((order) => order.shape === shape)) {
86+
throw new Error(
87+
`You already have specified an ordering for shape "${shape}" in slide "${this.name}". ` +
88+
'Adding another one with the same name does not make any sense. ' +
89+
'Did you make a typo in shape name or forgot that you already added a shape to ordering?'
90+
);
91+
}
92+
93+
const unknownAnimations = animations.filter((animation) => !this.animations.has(animation));
94+
if (unknownAnimations.length > 0) {
95+
throw new Error(
96+
`You have provided animations for the shape "${shape}" in slide "${this.name}". ` +
97+
`But, some of them could not be found in the slide "${this.name}". ` +
98+
`These animations are: [${unknownAnimations.join(', ')}]. ` +
99+
`Please, check if the animations from the list are declared in slide "${this.name}".`
100+
);
81101
}
82102

83103
this.order.push({ animations, shape });
84104
}
85105

86-
// eslint-disable-next-line max-statements
87106
public async render (): Promise<void> {
88-
const { shapes } = this;
89-
const { animations } = this;
107+
const { animations, shapes } = this;
90108
const shapesToRender: ShapeRenderable[] = [];
91109
const sequence: Array<() => void> = [];
110+
const onTick = (): void => this.renderShapes(shapesToRender);
92111

93112
// We need to re-render shapes each time when some of the animations made a tick
94113
// Hence, made an update in shape properties that we need to reflect on canvas
95-
animations.forEach((animation: Animationable) => animation.on('tick', () => this.renderShapes(shapesToRender)));
114+
animations.forEach((animation) => animation.on('tick', onTick));
96115

97116
for (const order of this.order) {
98117
const shapeToRender = shapes.get(order.shape);
99118
if (typeof shapeToRender === 'undefined') {
100119
throw new Error(
101120
`You specified shape "${order.shape}" in slide "${this.name}" ` +
102-
'as part of ordering, but it does not exist in shapes declaration.\n' +
121+
'as part of ordering, but it does not exist in shapes declaration for the slide. ' +
103122
'Maybe you forgot to create a shape you want to order or it is a typo in ordering itself.'
104123
);
105124
}
@@ -117,9 +136,9 @@ export class Slide {
117136
}
118137

119138
// When all of the rendering and animation is done - we can freely remove the listeners from animations
120-
sequence.push(() => animations.forEach((animation: Animationable) => animation.removeAllListeners()));
139+
sequence.push(() => animations.forEach((animation) => animation.removeListener('tick', onTick)));
121140

122-
// We can't allow Promise.all() here or anything that could render the shapes concurrently or parallel
141+
// We can't allow Promise.all() here or anything that could render the shapes concurrently
123142
// Hence, we need to reduce the sequence to the chain of promises, so we can get waterfall rendering
124143
return await sequence.reduce(async (promise, item) => await promise.then(item), Promise.resolve());
125144
}
@@ -141,12 +160,14 @@ export class Slide {
141160
}
142161

143162
private initShapes (declaration: ShapeDeclaration[]): void {
144-
declaration.forEach((shapeDeclaration: ShapeDeclaration) => {
163+
declaration.forEach((shapeDeclaration) => {
145164
const ctor = SHAPES.get(shapeDeclaration.type as ShapeType);
146165

147166
if (typeof ctor === 'undefined') {
148167
throw new Error(
149-
`Shape "${shapeDeclaration.name}" (${shapeDeclaration.type}) is unknown for me, maybe you made a typo?`
168+
`You have specified a shape with the name "${shapeDeclaration.name}" in slide "${this.name}". ` +
169+
`This shape has an unknown type "${shapeDeclaration.type}". ` +
170+
'Maybe you made a typo in "type" or tried to use a shape we do not have implemented.'
150171
);
151172
}
152173

@@ -155,13 +176,14 @@ export class Slide {
155176
}
156177

157178
private initAnimations (declaration: AnimationDeclaration[]): void {
158-
declaration.forEach((animationDeclaration: AnimationDeclaration) => {
179+
declaration.forEach((animationDeclaration) => {
159180
const ctor = ANIMATIONS.get(animationDeclaration.type as AnimationType);
160181

161182
if (typeof ctor === 'undefined') {
162183
throw new Error(
163-
`Animation "${animationDeclaration.name}" (${animationDeclaration.type}) is unknown for me, ` +
164-
'maybe you made a typo?'
184+
`You have specified an animation with the name "${animationDeclaration.name}" in slide "${this.name}". ` +
185+
`This animation has an unknown type "${animationDeclaration.type}". ` +
186+
'Maybe you made a typo in "type" or tried to use an animation we do not have implemented.'
165187
);
166188
}
167189

@@ -171,7 +193,7 @@ export class Slide {
171193

172194
private renderShapes (shapes: ShapeRenderable[]): void {
173195
this.cursor.eraseScreen();
174-
shapes.forEach((shape: ShapeRenderable) => shape.render(this.cursor));
196+
shapes.forEach((shape) => shape.render(this.cursor));
175197
this.cursor.flush();
176198
}
177199
}

0 commit comments

Comments
 (0)