--- /dev/null
+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);
+ }
+};
--- /dev/null
+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<number>('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<number>('1', () => 42);
+ const l2 = new ComputedLine<number>('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);
+});
-export default class Form {
+import { Line } from './Line';
+import { InconsistencyError, NotFoundError } from './Errors';
+
+export default abstract class Form {
+ private _lines: Line<any>[] = [];
+
+ abstract get name(): string;
+
+ constructor() {
+ this.addLines();
+ }
+
+ protected abstract addLines(): void;
+
+ get allowMultipleCopies(): boolean {
+ return false;
+ }
+
+ protected addLine(line: Line<any>) {
+ 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<any> {
+ const lines = this._lines.filter(l => l.id === id);
+ if (lines.length == 0) {
+ throw new NotFoundError(id);
+ }
+ return lines[0];
+ }
};
--- /dev/null
+import TaxReturn from './TaxReturn';
+
+export abstract class Line<T> {
+ 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<T> = (tr: TaxReturn, l: ComputedLine<T>) => T;
+
+export class ComputedLine<T> extends Line<T> {
+ private _compute: ComputeFunc<T>;
+
+ constructor(id: string, compute: ComputeFunc<T>, description?: string) {
+ super(id, description);
+ this._compute = compute;
+ }
+
+ value(tr: TaxReturn): T {
+ return this._compute(tr, this);
+ }
+};
+
+export class ReferenceLine<T> extends Line<T> {
+ 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);
+ }
+};
import TaxReturn from './TaxReturn';
import { Person } from './Person';
+import Form from './Form';
+import { NotFoundError, InconsistencyError } from './Errors';
test('constructor', () => {
const tr = new TaxReturn(2019);
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([]);
+});
import Form from './Form';
import { Person, Relation } from './Person';
+import { NotFoundError, InconsistencyError, UnsupportedFeatureError } from './Errors';
export default class TaxReturn {
private _year: number;
}
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);
+ }
};