From e80c981924ae663a6b64909780d20d4ba19a9603 Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 17 Feb 2020 22:57:49 -0500 Subject: [PATCH] Start modeling Line, Form and their relation to TaxReturn. --- src/Errors.ts | 20 ++++++++++++++++ src/Form.test.ts | 53 +++++++++++++++++++++++++++++++++++++++++++ src/Form.ts | 36 ++++++++++++++++++++++++++++- src/Line.ts | 51 +++++++++++++++++++++++++++++++++++++++++ src/TaxReturn.test.ts | 47 ++++++++++++++++++++++++++++++++++++++ src/TaxReturn.ts | 25 ++++++++++++++++---- 6 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 src/Errors.ts create mode 100644 src/Form.test.ts create mode 100644 src/Line.ts diff --git a/src/Errors.ts b/src/Errors.ts new file mode 100644 index 0000000..1c1ea80 --- /dev/null +++ b/src/Errors.ts @@ -0,0 +1,20 @@ +export class InconsistencyError extends Error { + constructor(m: string) { + super(m); + Object.setPrototypeOf(this, InconsistencyError.prototype); + } +}; + +export class UnsupportedFeatureError extends Error { + constructor(m: string) { + super(m); + Object.setPrototypeOf(this, UnsupportedFeatureError.prototype); + } +}; + +export class NotFoundError extends Error { + constructor(m: string) { + super(m); + Object.setPrototypeOf(this, NotFoundError.prototype); + } +}; diff --git a/src/Form.test.ts b/src/Form.test.ts new file mode 100644 index 0000000..24a93e6 --- /dev/null +++ b/src/Form.test.ts @@ -0,0 +1,53 @@ +import { ComputedLine, Line } from './Line'; +import TaxReturn from './TaxReturn'; +import Form from './Form'; +import { InconsistencyError, NotFoundError } from './Errors'; + +test('add and get line', () => { + const l = new ComputedLine('1', () => 42); + + class TestForm extends Form { + get name(): string { + return 'Test Form'; + } + + protected addLines() { + this.addLine(l); + } + }; + + const f = new TestForm(); + expect(f.getLine('1')).toBe(l); +}); + +test('get non-existent line', () => { + class TestForm extends Form { + get name(): string { + return 'Test Form'; + } + + protected addLines() { + } + }; + + const f = new TestForm(); + expect(() => f.getLine('1')).toThrow(NotFoundError); +}); + +test('add duplicate line', () => { + const l1 = new ComputedLine('1', () => 42); + const l2 = new ComputedLine('1', () => 36); + + class TestForm extends Form { + get name(): string { + return 'Test Form'; + } + + protected addLines() { + this.addLine(l1); + this.addLine(l2); + } + }; + + expect(() => new TestForm()).toThrow(InconsistencyError); +}); diff --git a/src/Form.ts b/src/Form.ts index 1e2cb9a..fe1493d 100644 --- a/src/Form.ts +++ b/src/Form.ts @@ -1,2 +1,36 @@ -export default class Form { +import { Line } from './Line'; +import { InconsistencyError, NotFoundError } from './Errors'; + +export default abstract class Form { + private _lines: Line[] = []; + + abstract get name(): string; + + constructor() { + this.addLines(); + } + + protected abstract addLines(): void; + + get allowMultipleCopies(): boolean { + return false; + } + + protected addLine(line: Line) { + try { + this.getLine(line.id); + } catch { + this._lines.push(line); + return; + } + throw new InconsistencyError('Cannot add a line with a duplicate identifier'); + } + + getLine(id: string): Line { + const lines = this._lines.filter(l => l.id === id); + if (lines.length == 0) { + throw new NotFoundError(id); + } + return lines[0]; + } }; diff --git a/src/Line.ts b/src/Line.ts new file mode 100644 index 0000000..e395d0b --- /dev/null +++ b/src/Line.ts @@ -0,0 +1,51 @@ +import TaxReturn from './TaxReturn'; + +export abstract class Line { + private _id: string; + private _description?: string; + + constructor(id: string, description?: string) { + this._id = id; + this._description = description; + } + + get id(): string { + return this._id; + } + + get description(): string { + return this._description; + } + + abstract value(tr: TaxReturn): T; +}; + +type ComputeFunc = (tr: TaxReturn, l: ComputedLine) => T; + +export class ComputedLine extends Line { + private _compute: ComputeFunc; + + constructor(id: string, compute: ComputeFunc, description?: string) { + super(id, description); + this._compute = compute; + } + + value(tr: TaxReturn): T { + return this._compute(tr, this); + } +}; + +export class ReferenceLine extends Line { + private _form: string; + private _line: string; + + constructor(id: string, form: string, line: string) { + super(id, `Reference F${form}.L${line}`); + this._form = form; + this._line = line; + } + + value(tr: TaxReturn): T { + return tr.getForm(this._form).getLine(this._line).value(tr); + } +}; diff --git a/src/TaxReturn.test.ts b/src/TaxReturn.test.ts index 0c0aab6..e6a6165 100644 --- a/src/TaxReturn.test.ts +++ b/src/TaxReturn.test.ts @@ -1,5 +1,7 @@ import TaxReturn from './TaxReturn'; import { Person } from './Person'; +import Form from './Form'; +import { NotFoundError, InconsistencyError } from './Errors'; test('constructor', () => { const tr = new TaxReturn(2019); @@ -49,3 +51,48 @@ test('get non-existent person', () => { expect(tr.getPerson('Billy Bob')).toBe(self); expect(() => tr.getPerson('Jilly')).toThrow('not found'); }); + +test('single-copy forms', () => { + class TestForm extends Form { + get name(): string { return 'Test Form'; } + + protected addLines() {} + }; + + const tr = new TaxReturn(2019); + const f = new TestForm(); + tr.addForm(f); + expect(() => tr.addForm(new TestForm)).toThrow(InconsistencyError); + expect(tr.getForm(f.name)).toBe(f); +}); + +test('multiple-copy forms', () => { + class TestForm extends Form { + get name(): string { return 'Test Form'; } + + get allowMultipleCopies(): boolean { return true; } + + protected addLines() {} + }; + + const tr = new TaxReturn(2019); + const f1 = new TestForm(); + const f2 = new TestForm(); + const f3 = new TestForm(); + tr.addForm(f1); + tr.addForm(f2); + + expect(() => tr.getForm(f1.name)).toThrow(InconsistencyError); + + const forms = tr.getForms(f1.name); + expect(forms.length).toBe(2); + expect(forms).toContain(f1); + expect(forms).toContain(f2); + expect(forms).not.toContain(f3); +}); + +test('get non-existent form', () => { + const tr = new TaxReturn(2019); + expect(() => tr.getForm('form')).toThrow(NotFoundError); + expect(tr.getForms('form')).toEqual([]); +}); diff --git a/src/TaxReturn.ts b/src/TaxReturn.ts index 192a41e..1614a1c 100644 --- a/src/TaxReturn.ts +++ b/src/TaxReturn.ts @@ -1,5 +1,6 @@ import Form from './Form'; import { Person, Relation } from './Person'; +import { NotFoundError, InconsistencyError, UnsupportedFeatureError } from './Errors'; export default class TaxReturn { private _year: number; @@ -36,11 +37,27 @@ export default class TaxReturn { } addForm(form: Form) { + if (!form.allowMultipleCopies) { + const other = this.getForms(form.name); + if (other.length > 0) { + throw new InconsistencyError(`Cannot have more than one type of form ${form.name}`); + } + } + this._forms.push(form); } -}; -export class InconsistencyError extends Error { -}; + getForm(name: string): Form { + const forms = this.getForms(name); + if (forms.length == 0) { + throw new NotFoundError(`No form named ${name}`); + } + if (forms.length > 1) { + throw new InconsistencyError(`More than 1 form named ${name}`); + } + return forms[0]; + } -export class UnsupportedFeatureError extends Error { + getForms(name: string): Form[] { + return this._forms.filter(f => f.name == name); + } }; -- 2.22.5