From c428f3c4bb8f98aeb350ed3de4dfa5059e358a0e Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Thu, 27 Feb 2020 23:28:05 -0500 Subject: [PATCH] Add a test for Form8959. --- src/TaxReturn.ts | 2 +- src/fed2019/Form1040.test.ts | 26 +++++++++-- src/fed2019/Form1040.ts | 9 ++-- src/fed2019/Form8959.test.ts | 88 ++++++++++++++++++++++++++++++++++++ src/fed2019/Form8959.ts | 7 ++- 5 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 src/fed2019/Form8959.test.ts diff --git a/src/TaxReturn.ts b/src/TaxReturn.ts index b1d69ea..911da82 100644 --- a/src/TaxReturn.ts +++ b/src/TaxReturn.ts @@ -65,7 +65,7 @@ export default class TaxReturn { getForm>(cls: FormClass): T { const form = this.findForm(cls); if (!form) - throw new NotFoundError(`No form ${cls}`); + throw new NotFoundError(`No form ${cls.name}`); return form; } }; diff --git a/src/fed2019/Form1040.test.ts b/src/fed2019/Form1040.test.ts index d7cc234..6308113 100644 --- a/src/fed2019/Form1040.test.ts +++ b/src/fed2019/Form1040.test.ts @@ -1,5 +1,6 @@ import Person from '../Person'; import TaxReturn from '../TaxReturn'; +import { NotFoundError } from '../Errors'; import Form1040, { FilingStatus, Schedule2 } from './Form1040'; import Form1099DIV from './Form1099DIV'; @@ -17,7 +18,7 @@ test('w2 wages', () => { tr.addForm(new FormW2({ employer: 'AA', employee: pa, - wages: 1000000.00, + wages: 130000.00, fedIncomeTax: 0, medicareWages: 0, })); @@ -31,8 +32,7 @@ test('w2 wages', () => { const f1040 = new Form1040({ filingStatus: FilingStatus.MarriedFilingJoint }); tr.addForm(f1040); tr.addForm(new Schedule2); - tr.addForm(new Form8959); - expect(f1040.getValue(tr, '1')).toBe(1000036.32); + expect(f1040.getValue(tr, '1')).toBe(130036.32); f1040.getValue(tr, '23'); }); @@ -103,3 +103,23 @@ test('capital gain/loss', () => { tr.getForm(ScheduleD).getValue(tr, '21'); tr.getForm(Form1040).getValue(tr, '12a'); }); + +test('require Form8959', () => { + const p = Person.self('A'); + const tr = new TaxReturn(2019); + tr.addForm(new FormW2({ + employer: 'Company', + employee: p, + wages: 400000, + })); + const f1040 = new Form1040({ + filingStatus: FilingStatus.MarriedFilingSeparate, + }); + tr.addForm(f1040); + tr.addForm(new Schedule2); + + expect(() => f1040.getValue(tr, '15')).toThrow(NotFoundError); + expect(() => f1040.getValue(tr, '15')).toThrow('Form8959'); + expect(f1040.getValue(tr, '1')).toBe(400000); + expect(f1040.getValue(tr, '8b')).toBe(400000); +}); diff --git a/src/fed2019/Form1040.ts b/src/fed2019/Form1040.ts index ec36a01..3af3c05 100644 --- a/src/fed2019/Form1040.ts +++ b/src/fed2019/Form1040.ts @@ -10,9 +10,9 @@ import FormW2 from './FormW2'; import ScheduleD, { ScheduleDTaxWorksheet } from './ScheduleD'; export enum FilingStatus { - Single, - MarriedFilingSeparate, - MarriedFilingJoint, + Single = 'S', + MarriedFilingSeparate = 'MFS', + MarriedFilingJoint = 'MFJ', }; export interface Form1040Input { @@ -122,7 +122,7 @@ export default class Form1040 extends Form { return value < 0 ? 0 : value; }), - '15': new ReferenceLine(undefined /*'Schedule 2'*/, '10', undefined, 0), + '15': new ReferenceLine(Schedule2, '10', undefined, 0), '16': new ComputedLine((tr): number => { return this.getValue(tr, '14') + this.getValue(tr, '15'); @@ -203,7 +203,6 @@ export class Schedule2 extends Form { '8': new ComputedLine((tr): number => { const f1040 = tr.getForm(Form1040); const wages = f1040.getLine('1').value(tr); - const agi = f1040.getLine('8b').value(tr); let niit: boolean; const filingStatus = f1040.getInput('filingStatus'); diff --git a/src/fed2019/Form8959.test.ts b/src/fed2019/Form8959.test.ts new file mode 100644 index 0000000..474ca22 --- /dev/null +++ b/src/fed2019/Form8959.test.ts @@ -0,0 +1,88 @@ +import Person from '../Person'; +import TaxReturn from '../TaxReturn'; + +import FormW2 from './FormW2'; +import Form8959 from './Form8959'; +import Form1040, { Schedule2, FilingStatus } from './Form1040'; + +describe('additional medicare tax', () => { + const filingStatusToResults = { + [FilingStatus.Single]: { + '6': 100000, + }, + [FilingStatus.MarriedFilingJoint]: { + '6': 50000, + }, + [FilingStatus.MarriedFilingSeparate]: { + '6': 175000, + } + }; + + for (const filingStatus of Object.values(FilingStatus)) { + test(`filing status ${filingStatus}`, () => { + const p = Person.self('A'); + const tr = new TaxReturn(2019); + tr.addForm(new Form1040({ filingStatus })); + tr.addForm(new FormW2({ + employer: 'Acme', + employee: p, + wages: 300000, + fedIncomeTax: 0, + medicareWages: 300000, + medicareTax: 5000, + })); + + const form = new Form8959(); + tr.addForm(new Form8959()); + tr.addForm(new Schedule2()); + + expect(form.getValue(tr, '4')).toBe(300000); + expect(form.getValue(tr, '5')).toBe(Form8959.filingStatusLimit(filingStatus)); + expect(form.getValue(tr, '6')).toBe(filingStatusToResults[filingStatus]['6']); + expect(form.getValue(tr, '18')).toBeCloseTo(form.getValue(tr, '6') * 0.009); + + expect(form.getValue(tr, '19')).toBe(5000); + expect(form.getValue(tr, '20')).toBe(form.getValue(tr, '1')); + expect(form.getValue(tr, '21')).toBeCloseTo(300000 * 0.0145); + expect(form.getValue(tr, '22')).toBeCloseTo(650); + + expect(tr.getForm(Schedule2).getValue(tr, '8')).toBe(form.getValue(tr, '18')); + expect(tr.getForm(Form1040).getValue(tr, '17')).toBe(650); + }); + } +}); + +describe('no additional medicare tax', () => { + for (const filingStatus of Object.values(FilingStatus)) { + test(`filing status ${filingStatus}`, () => { + const p = Person.self('A'); + const tr = new TaxReturn(2019); + tr.addForm(new Form1040({ filingStatus })); + tr.addForm(new FormW2({ + employer: 'Acme', + employee: p, + wages: 110000, + fedIncomeTax: 0, + medicareWages: 110000, + medicareTax: 5000, + })); + + const form = new Form8959(); + tr.addForm(new Form8959()); + tr.addForm(new Schedule2()); + + expect(form.getValue(tr, '4')).toBe(110000); + expect(form.getValue(tr, '5')).toBe(Form8959.filingStatusLimit(filingStatus)); + expect(form.getValue(tr, '6')).toBe(0); + expect(form.getValue(tr, '18')).toBe(0); + + expect(form.getValue(tr, '19')).toBe(5000); + expect(form.getValue(tr, '20')).toBe(form.getValue(tr, '1')); + expect(form.getValue(tr, '21')).toBeCloseTo(110000 * 0.0145); + expect(form.getValue(tr, '22')).toBeCloseTo(3405); + + expect(tr.getForm(Schedule2).getValue(tr, '8')).toBe(form.getValue(tr, '18')); + expect(tr.getForm(Form1040).getValue(tr, '17')).toBe(3405); + }); + } +}); diff --git a/src/fed2019/Form8959.ts b/src/fed2019/Form8959.ts index 35041de..2c3e768 100644 --- a/src/fed2019/Form8959.ts +++ b/src/fed2019/Form8959.ts @@ -1,6 +1,7 @@ import Form from '../Form'; import TaxReturn from '../TaxReturn'; import { Line, AccumulatorLine, ComputedLine, ReferenceLine } from '../Line'; +import { clampToZero } from '../Math'; import Form1040, { FilingStatus } from './Form1040'; import FormW2 from './FormW2'; @@ -20,8 +21,7 @@ export default class Form8959 extends Form { return Form8959.filingStatusLimit(tr.getForm(Form1040).getInput('filingStatus')); }), '6': new ComputedLine((tr): number => { - const value = this.getValue(tr, '5') - this.getValue(tr, '4'); - return value < 0 ? 0 : value; + return clampToZero(this.getValue(tr, '4') - this.getValue(tr, '5')); }), '7': new ComputedLine((tr): number => { return this.getValue(tr, '6') * 0.009; @@ -40,8 +40,7 @@ export default class Form8959 extends Form { return this.getValue(tr, '20') * 0.0145; }, 'Regular Medicare withholding on Medicare wages'), '22': new ComputedLine((tr): number => { - const value = this.getValue(tr, '19') - this.getValue(tr, '21'); - return value < 0 ? 0 : value; + return clampToZero(this.getValue(tr, '19') - this.getValue(tr, '21')); }, 'Additional Medicare withholding on Medicare wages'), // 23 is not supported (Additional Medicare Tax withholding on railroad retirement (RRTA) compensation) '24': new ComputedLine((tr): number => { -- 2.22.5