From 2e27a538087036319ae0c93b38c9bdbf6d0f03bc Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Mon, 14 Oct 2024 14:40:02 +0100 Subject: [PATCH 01/11] Fix initial syntax errors --- src/models/Chemistry.ts | 6 ++--- test/models/Chemistry.test.ts | 45 ++++++++++++++++++++++------------- test/models/Nuclear.test.ts | 21 ++++++++-------- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/models/Chemistry.ts b/src/models/Chemistry.ts index 6a4aa60..4f7f558 100644 --- a/src/models/Chemistry.ts +++ b/src/models/Chemistry.ts @@ -109,9 +109,9 @@ export interface ChemAST { result: Result; } -const STARTING_COEFFICIENT: Fraction = { numerator: 0, denominator: 1 }; -const ERROR_COEFFICIENT: Fraction = { numerator: -1, denominator: -1 }; -const EQUAL_COEFFICIENT: Fraction = { numerator: 1, denominator: 1 }; +export const STARTING_COEFFICIENT: Fraction = { numerator: 0, denominator: 1 }; +export const ERROR_COEFFICIENT: Fraction = { numerator: -1, denominator: -1 }; +export const EQUAL_COEFFICIENT: Fraction = { numerator: 1, denominator: 1 }; function augmentNode(node: T): T { // The if statements signal to the type checker what we already know diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 73d82e3..891eb75 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -1,5 +1,5 @@ -import { Bracket, Compound, Element, exportedForTesting, Ion, Term, Expression, Statement, ParseError, check, ChemAST, augment } from "../../src/models/Chemistry"; -import { ChemicalSymbol, CheckerResponse, listComparison } from "../../src/models/common"; +import { Bracket, Compound, Element, exportedForTesting, Ion, Term, Expression, Statement, ParseError, check, ChemAST, augment, STARTING_COEFFICIENT } from "../../src/models/Chemistry"; +import { CheckerResponse, listComparison, ChemistryOptions } from "../../src/models/common"; const { checkNodesEqual } = exportedForTesting; import { parseChemistryExpression } from "inequality-grammar"; @@ -14,39 +14,49 @@ afterEach(() => { }) // TODO: Add augmenting tests +const options: ChemistryOptions = { + allowPermutations: false, + allowScalingCoefficients: false +} // Generic response object const response: CheckerResponse = { containsError: false, error: { message: "" }, expectedType: "statement", + receivedType: "statement", typeMismatch: false, sameState: true, sameCoefficient: true, + sameArrow: true, + sameBrackets: true, + sameElements: true, isBalanced: true, isEqual: true, isNuclear: false, - atomCount: {} as Record, chargeCount: 0, - receivedType: "statement", - allowPermutations: false -}; + options: options, + coefficientScalingValue: STARTING_COEFFICIENT, +} // Alternative response object const newResponse: CheckerResponse = { containsError: false, error: { message: "" }, expectedType: "statement", + receivedType: "statement", typeMismatch: false, - sameState: false, - sameCoefficient: false, + sameState: true, + sameCoefficient: true, + sameArrow: true, + sameBrackets: true, + sameElements: true, isBalanced: true, isEqual: true, isNuclear: false, - atomCount: {} as Record, chargeCount: 0, - receivedType: "statement", - allowPermutations: false -}; + options: options, + coefficientScalingValue: STARTING_COEFFICIENT, +} const trueResponse: CheckerResponse = structuredClone(newResponse); const falseResponse: CheckerResponse = structuredClone(newResponse); @@ -62,7 +72,8 @@ const minimalCompound: Compound = { const bracket: Bracket = { type: "bracket", compound: structuredClone(minimalCompound), - coeff: 1 + coeff: 1, + bracket: "round" }; const compound: Compound = { type: "compound", @@ -641,7 +652,7 @@ describe("Check", () => { result: error } - const response: CheckerResponse = check(errorAST, ast); + const response: CheckerResponse = check(errorAST, ast, options); // Assert expect(response.error).toBeDefined(); expect(response.error?.message).toBe("Sphinx of black quartz, judge my vow"); @@ -655,7 +666,7 @@ describe("Check", () => { result: structuredClone(expression) } - const response: CheckerResponse = check(ast, expressionAST); + const response: CheckerResponse = check(ast, expressionAST, options); // Assert expect(response.typeMismatch).toBeTruthy(); expect(response.expectedType).toBe("expr"); @@ -667,8 +678,8 @@ describe("Check", () => { const unchangedAST: ChemAST = augment(parseChemistryExpression("C10H22")[0]); const permutedAST: ChemAST = augment(parseChemistryExpression("CH3(CH2)8CH3")[0]); - const permutedResponse: CheckerResponse = check(unchangedAST, permutedAST, true); - const unpermutedResponse: CheckerResponse = check(unchangedAST, permutedAST, false); + const permutedResponse: CheckerResponse = check(unchangedAST, permutedAST, { ...options, allowPermutations: true }); + const unpermutedResponse: CheckerResponse = check(unchangedAST, permutedAST, options); // Assert expect(permutedResponse.isEqual).toBeTruthy(); expect(unpermutedResponse.isEqual).toBeFalsy(); diff --git a/test/models/Nuclear.test.ts b/test/models/Nuclear.test.ts index 16544fb..6061b6a 100644 --- a/test/models/Nuclear.test.ts +++ b/test/models/Nuclear.test.ts @@ -1,6 +1,5 @@ -import { iteratee } from "lodash"; import { Particle, Isotope, Term, Expression, Statement, ParseError, check, NuclearAST, exportedForTesting } from "../../src/models/Nuclear"; -import { ChemicalSymbol, CheckerResponse, listComparison } from "../../src/models/common"; +import { CheckerResponse } from "../../src/models/common"; const { checkNodesEqual } = exportedForTesting; const original = console.error; @@ -20,29 +19,29 @@ const response: CheckerResponse = { containsError: false, error: { message: "" }, expectedType: "statement", + receivedType: "statement", typeMismatch: false, sameState: true, sameCoefficient: true, + sameElements: true, isBalanced: true, isEqual: true, isNuclear: true, - receivedType: "statement", - allowPermutations: false -}; +} // Alternative response object const newResponse: CheckerResponse = { containsError: false, error: { message: "" }, - expectedType: "unknown", + expectedType: "statement", + receivedType: "statement", typeMismatch: false, - sameState: false, - sameCoefficient: false, + sameState: true, + sameCoefficient: true, + sameElements: true, isBalanced: true, isEqual: true, isNuclear: true, - receivedType: "unknown", - allowPermutations: false -}; +} const trueResponse: CheckerResponse = structuredClone(response); trueResponse.balancedAtom = true; From a575383e2c9687907964f7935240dbfd7229e396 Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:57:37 +0100 Subject: [PATCH 02/11] Make all pre-existing tests pass --- src/models/Chemistry.ts | 7 +- src/models/common.ts | 4 +- test/models/Chemistry.test.ts | 162 ++++++++++++++++++---------------- test/models/Nuclear.test.ts | 2 +- 4 files changed, 92 insertions(+), 83 deletions(-) diff --git a/src/models/Chemistry.ts b/src/models/Chemistry.ts index 4f7f558..af1d0dd 100644 --- a/src/models/Chemistry.ts +++ b/src/models/Chemistry.ts @@ -377,8 +377,9 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon } else if (isIon(test) && isIon(target)) { if (test.molecules && target.molecules) { - if (test.molecules.length !== target.molecules.length) { + if (!response.options?.allowPermutations && test.molecules.length !== target.molecules.length) { // fail early if molecule lengths not the same + response.sameElements = false; response.isEqual = false; return response; } @@ -531,10 +532,10 @@ export function check(test: ChemAST, target: ChemAST, options: ChemistryOptions) const newResponse = checkNodesEqual(test.result, target.result, response); - delete newResponse.chargeCount; + /* delete newResponse.chargeCount; delete newResponse.termAtomCount; delete newResponse.bracketAtomCount; - delete newResponse.atomCount; + delete newResponse.atomCount; */ return newResponse; } diff --git a/src/models/common.ts b/src/models/common.ts index e9d0215..404d258 100644 --- a/src/models/common.ts +++ b/src/models/common.ts @@ -93,7 +93,9 @@ export function listComparison( returnResponse.bracketAtomCount = aggregatesResponse.bracketAtomCount; returnResponse.termAtomCount = aggregatesResponse.termAtomCount; returnResponse.atomCount = aggregatesResponse.atomCount; - returnResponse.nucleonCount = aggregatesResponse.nucleonCount; + if (aggregatesResponse.nucleonCount) { + returnResponse.nucleonCount = aggregatesResponse.nucleonCount; + } return returnResponse; } diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 891eb75..245bace 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -1,6 +1,6 @@ import { Bracket, Compound, Element, exportedForTesting, Ion, Term, Expression, Statement, ParseError, check, ChemAST, augment, STARTING_COEFFICIENT } from "../../src/models/Chemistry"; -import { CheckerResponse, listComparison, ChemistryOptions } from "../../src/models/common"; -const { checkNodesEqual } = exportedForTesting; +import { CheckerResponse, listComparison, ChemistryOptions, Fraction, ChemicalSymbol } from "../../src/models/common"; +const { augmentNode, checkNodesEqual } = exportedForTesting; import { parseChemistryExpression } from "inequality-grammar"; const original = console.error; @@ -37,6 +37,9 @@ const response: CheckerResponse = { chargeCount: 0, options: options, coefficientScalingValue: STARTING_COEFFICIENT, + atomCount: undefined, + bracketAtomCount: undefined, + termAtomCount: undefined, } // Alternative response object const newResponse: CheckerResponse = { @@ -56,6 +59,9 @@ const newResponse: CheckerResponse = { chargeCount: 0, options: options, coefficientScalingValue: STARTING_COEFFICIENT, + atomCount: undefined, + bracketAtomCount: undefined, + termAtomCount: undefined, } const trueResponse: CheckerResponse = structuredClone(newResponse); @@ -78,20 +84,15 @@ const bracket: Bracket = { const compound: Compound = { type: "compound", head: structuredClone(element), - elements: [ - structuredClone(element), - structuredClone(bracket) - ] + tail: structuredClone(bracket) }; const ion: Ion = { type: "ion", molecule: structuredClone(compound), charge: -1, - molecules: [ - [structuredClone(compound), -1], - [{ type: "element", value: "Na", coeff: 1 }, 1] - ] + chain: { type: "ion", molecule: {type: "element", value: "Na", coeff: 1 }, charge: 1 } }; + const term: Term = { type: "term", value: structuredClone(minimalCompound), @@ -112,7 +113,7 @@ const hydrate: Term = { isHydrate: true }; const hydrate2: Term = structuredClone(hydrate); -hydrate2.coeff = { numerator: 2, denominator: 1}; +hydrate2.coeff = { numerator: 6, denominator: 4 }; hydrate2.state = "(l)" hydrate2.hydrate = 3; @@ -200,7 +201,7 @@ describe("checkNodesEqual Elements", () => { // Assert expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.sameCoefficient).toBeTruthy(); - expect(testResponse.atomCount?.C).toBe(1); + expect(testResponse.termAtomCount?.C).toBe(1); } ); it("Returns falsy CheckerResponse when elements don't match", @@ -217,11 +218,11 @@ describe("checkNodesEqual Elements", () => { // Assert expect(elementIncorrect.isEqual).toBeFalsy(); expect(elementIncorrect.sameCoefficient).toBeTruthy(); - expect(elementIncorrect.atomCount?.O).toBe(1); + expect(elementIncorrect.termAtomCount?.O).toBe(1); expect(coeffIncorrect.isEqual).toBeFalsy(); expect(coeffIncorrect.sameCoefficient).toBeFalsy(); - expect(coeffIncorrect.atomCount?.C).toBe(2); + expect(coeffIncorrect.termAtomCount?.C).toBe(2); } ); }); @@ -232,12 +233,12 @@ describe("checkNodesEqual Brackets", () => { // Act const bracketCopy: Bracket = structuredClone(bracket); - const testResponse = checkNodesEqual(bracketCopy, bracket, structuredClone(response)); + const testResponse = checkNodesEqual(augmentNode(bracketCopy), augmentNode(bracket), structuredClone(response)); // Assert expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.sameCoefficient).toBeTruthy(); - expect(testResponse.atomCount?.O).toBe(1); + expect(testResponse.termAtomCount?.O).toBe(1); } ); it("Returns falsy CheckerResponse when brackets don't match", @@ -246,12 +247,12 @@ describe("checkNodesEqual Brackets", () => { const coefficientMismatch: Bracket = structuredClone(bracket); coefficientMismatch.coeff = 2; - const coeffIncorrect = checkNodesEqual(coefficientMismatch, bracket, structuredClone(response)); + const coeffIncorrect = checkNodesEqual(augmentNode(coefficientMismatch), augmentNode(bracket), structuredClone(response)); // Assert expect(coeffIncorrect.isEqual).toBeFalsy(); expect(coeffIncorrect.sameCoefficient).toBeFalsy(); - expect(coeffIncorrect.atomCount?.O).toBe(2); + expect(coeffIncorrect.termAtomCount?.O).toBe(2); } ); }); @@ -260,27 +261,26 @@ describe("checkNodesEqual Compounds", () => { it("Returns truthy CheckerResponse when compounds match", () => { // Act - const permutedCompound: Compound = structuredClone(compound); - permutedCompound.elements?.reverse; + const compoundCopy: Compound = structuredClone(compound); - const testResponse = checkNodesEqual(permutedCompound, compound, structuredClone(response)); + const testResponse = checkNodesEqual(augmentNode(compoundCopy), augmentNode(structuredClone(compound)), structuredClone(response)); // Assert expect(testResponse.isEqual).toBeTruthy(); - expect(testResponse.atomCount?.C).toBe(1) - expect(testResponse.atomCount?.O).toBe(1) + expect(testResponse.termAtomCount?.C).toBe(1) + expect(testResponse.termAtomCount?.O).toBe(1) } ); it("Returns falsy CheckerResponse when compounds don't match", () => { // Act - const typeMismatch: Compound = structuredClone(compound); + const typeMismatch: Compound = augmentNode(structuredClone(compound)); typeMismatch.elements = [structuredClone(element), structuredClone(element)] - const lengthMismatch: Compound = structuredClone(compound); + const lengthMismatch: Compound = augmentNode(structuredClone(compound)); lengthMismatch.elements?.push(structuredClone(element)); - const typesIncorrect = checkNodesEqual(typeMismatch, compound, structuredClone(response)); - const lengthIncorrect = checkNodesEqual(lengthMismatch, compound, structuredClone(response)); + const typesIncorrect = checkNodesEqual(typeMismatch, augmentNode(structuredClone(compound)), structuredClone(response)); + const lengthIncorrect = checkNodesEqual(lengthMismatch, augmentNode(structuredClone(compound)), structuredClone(response)); // Assert expect(typesIncorrect.isEqual).toBeFalsy(); @@ -292,25 +292,25 @@ describe("checkNodesEqual Compounds", () => { // Arrange // If the lengths or types are different the wrong thing will fail // So create a single bracket compound to test bracket mismatches - const minimalBracketCompound: Compound = structuredClone(minimalCompound) - minimalBracketCompound.elements = [structuredClone(bracket)] + const minimalBracketCompound: Compound = augmentNode(structuredClone(minimalCompound)) + minimalBracketCompound.elements = [augmentNode(structuredClone(bracket))] - const bracketCoeffMismatch: Bracket = structuredClone(bracket); + const bracketCoeffMismatch: Bracket = augmentNode(structuredClone(bracket)); bracketCoeffMismatch.coeff = 2; - const elementCoeffMismatch: Element = structuredClone(element); + const elementCoeffMismatch: Element = augmentNode(structuredClone(element)); elementCoeffMismatch.coeff = 2; - const bracketMismatch: Compound = structuredClone(compound); + const bracketMismatch: Compound = augmentNode(structuredClone(compound)); bracketMismatch.elements = [bracketCoeffMismatch]; - const elementMismatch: Compound = structuredClone(compound); + const elementMismatch: Compound = augmentNode(structuredClone(compound)); elementMismatch.elements = [elementCoeffMismatch]; - const bracketResponse: CheckerResponse = checkNodesEqual(bracketCoeffMismatch, bracket, structuredClone(response)); - const elementResponse: CheckerResponse = checkNodesEqual(elementCoeffMismatch, element, structuredClone(response)); + const bracketResponse: CheckerResponse = checkNodesEqual(bracketCoeffMismatch, augmentNode(structuredClone(bracket)), structuredClone(response)); + const elementResponse: CheckerResponse = checkNodesEqual(elementCoeffMismatch, augmentNode(structuredClone(element)), structuredClone(response)); // Assert - expect(checkNodesEqual(bracketMismatch, minimalBracketCompound, structuredClone(response))).toEqual(bracketResponse); - expect(checkNodesEqual(elementMismatch, minimalCompound, structuredClone(response))).toEqual(elementResponse); + expect(checkNodesEqual(bracketMismatch, minimalBracketCompound, structuredClone(response))).toEqual({...bracketResponse, sameElements: true}); + expect(checkNodesEqual(elementMismatch, minimalCompound, structuredClone(response))).toEqual({...elementResponse, sameElements: false}); } ); it("Returns an error if the AST is not augmented", @@ -338,36 +338,35 @@ describe("CheckNodesEqual Ions", () => { it("Returns truthy CheckerResponse when ions match", () => { // Act - const permutedIon: Ion = structuredClone(ion); - permutedIon.molecules?.reverse; + const ionClone: Ion = structuredClone(ion); - const testResponse = checkNodesEqual(permutedIon, ion, structuredClone(response)); + const testResponse = checkNodesEqual(augmentNode(structuredClone(ion)), augmentNode(ionClone), structuredClone(response)); // Assert expect(testResponse.isEqual).toBeTruthy(); - expect(testResponse.atomCount?.O).toBe(1); - expect(testResponse.atomCount?.Na).toBe(1); + expect(testResponse.termAtomCount?.O).toBe(1); + expect(testResponse.termAtomCount?.Na).toBe(1); expect(testResponse.chargeCount).toBe(0); } ); it("Returns falsy CheckerResponse when ions do not match", () => { - const moleculeMismatch: Ion = structuredClone(ion); + const moleculeMismatch: Ion = augmentNode(structuredClone(ion)); if (moleculeMismatch.molecules) { moleculeMismatch.molecules[0] = [{ type: "element", value: "Cl", coeff: 1 }, -1]; } - const chargeMismatch: Ion = structuredClone(ion); + const chargeMismatch: Ion = augmentNode(structuredClone(ion)); if (chargeMismatch.molecules) { - chargeMismatch.molecules[1] = [{ type: "element", value: "Na", coeff: 1 }, -1]; + chargeMismatch.molecules[0] = [{ type: "element", value: "Na", coeff: 1 }, -1] } - const lengthMismatch: Ion = structuredClone(ion); + const lengthMismatch: Ion = augmentNode(structuredClone(ion)); lengthMismatch.molecules?.push([structuredClone(element), 1]); - const moleculeIncorrect = checkNodesEqual(moleculeMismatch, ion, structuredClone(response)); - const chargeIncorrect = checkNodesEqual(chargeMismatch, ion, structuredClone(response)); - const lengthIncorrect = checkNodesEqual(lengthMismatch, ion, structuredClone(response)); + const moleculeIncorrect = checkNodesEqual(moleculeMismatch, augmentNode(structuredClone(ion)), structuredClone(response)); + const chargeIncorrect = checkNodesEqual(chargeMismatch, augmentNode(structuredClone(ion)), structuredClone(response)); + const lengthIncorrect = checkNodesEqual(lengthMismatch, augmentNode(structuredClone(ion)), structuredClone(response)); // Assert expect(moleculeIncorrect.isEqual).toBeFalsy(); @@ -407,26 +406,28 @@ describe("CheckNodesEqual Term", () => { // TODO: Separate into different tests it("Returns truthy CheckerResponse when terms match", () => { + const scaledResponse = { ...response, options: { ...options, allowScalingCoefficients: true } }; + // Scalar multiple coefficient - let perturbedTerm: Term = structuredClone(term); + let perturbedTerm: Term = augmentNode(structuredClone(term)); perturbedTerm.coeff = { numerator: 6, denominator: 4 }; - let testResponse = checkNodesEqual(perturbedTerm, term, structuredClone(response)) + let testResponse = checkNodesEqual(perturbedTerm, augmentNode(structuredClone(term)), scaledResponse); expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.sameState).toBeTruthy(); - expect(testResponse.atomCount?.O).toBe(1); + expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 2}); // Term with a state - perturbedTerm = structuredClone(term); + perturbedTerm = augmentNode(structuredClone(term)); perturbedTerm.state = "(aq)"; - let termCopy: Term = structuredClone(perturbedTerm); + let termCopy: Term = augmentNode(structuredClone(perturbedTerm)); testResponse = checkNodesEqual(perturbedTerm, termCopy, structuredClone(response)) expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.sameState).toBeTruthy(); // Hydrate term - perturbedTerm = structuredClone(term); + perturbedTerm = augmentNode(structuredClone(term)); termCopy.isHydrate = true; termCopy.hydrate = 7; termCopy = structuredClone(perturbedTerm); @@ -434,7 +435,7 @@ describe("CheckNodesEqual Term", () => { expect(checkNodesEqual(termCopy, perturbedTerm, structuredClone(response)).isEqual).toBeTruthy(); // Electron - perturbedTerm = structuredClone(term); + perturbedTerm = augmentNode(structuredClone(term)); perturbedTerm.isElectron = true perturbedTerm.value = { type: "electron" } termCopy = structuredClone(perturbedTerm); @@ -444,23 +445,23 @@ describe("CheckNodesEqual Term", () => { it("Returns falsy CheckerResponse when terms don't match", () => { // Mismatched coefficients - let mismatchTerm: Term = structuredClone(term); + let mismatchTerm: Term = augmentNode(structuredClone(term)); mismatchTerm.coeff = { numerator: 1, denominator: 5 }; - expect(checkNodesEqual(mismatchTerm, term, structuredClone(response)).isEqual).toBeFalsy(); - expect(checkNodesEqual(mismatchTerm, term, structuredClone(response)).sameCoefficient).toBeFalsy(); + expect(checkNodesEqual(mismatchTerm, augmentNode(structuredClone(term)), structuredClone(response)).isEqual).toBeFalsy(); + expect(checkNodesEqual(mismatchTerm, augmentNode(structuredClone(term)), structuredClone(response)).sameCoefficient).toBeFalsy(); // Mismatched state - let perturbedTerm: Term = structuredClone(term); - mismatchTerm = structuredClone(term) + let perturbedTerm: Term = augmentNode(structuredClone(term)); + mismatchTerm = augmentNode(structuredClone(term)); perturbedTerm.state = "(aq)"; mismatchTerm.state = "(g)"; expect(checkNodesEqual(perturbedTerm, mismatchTerm, structuredClone(response)).isEqual).toBeFalsy(); expect(checkNodesEqual(perturbedTerm, mismatchTerm, structuredClone(response)).sameState).toBeFalsy(); // Mismatched hydrate - perturbedTerm = structuredClone(term); - mismatchTerm = structuredClone(term); + perturbedTerm = augmentNode(structuredClone(term)); + mismatchTerm = augmentNode(structuredClone(term)); perturbedTerm.isHydrate = true; perturbedTerm.hydrate = 7; mismatchTerm.isHydrate = true; @@ -468,11 +469,11 @@ describe("CheckNodesEqual Term", () => { expect(checkNodesEqual(perturbedTerm, mismatchTerm, structuredClone(response)).isEqual).toBeFalsy(); // Mismatched type - mismatchTerm = structuredClone(term); - mismatchTerm.isElectron = true - mismatchTerm.value = { type: "electron" } - expect(checkNodesEqual(mismatchTerm, term, structuredClone(response)).isEqual).toBeFalsy(); - expect(checkNodesEqual(mismatchTerm, term, structuredClone(response)).typeMismatch).toBeFalsy(); + mismatchTerm = augmentNode(structuredClone(term)); + mismatchTerm.isElectron = true; + mismatchTerm.value = { type: "electron" }; + expect(checkNodesEqual(mismatchTerm, augmentNode(structuredClone(term)), structuredClone(response)).isEqual).toBeFalsy(); + expect(checkNodesEqual(mismatchTerm, augmentNode(structuredClone(term)), structuredClone(response)).typeMismatch).toBeFalsy(); } ); it("Retains CheckerResponse properties", @@ -481,12 +482,16 @@ describe("CheckNodesEqual Term", () => { const complexTerm: Term = structuredClone(term); complexTerm.value = structuredClone(compound); - const compoundResponse = checkNodesEqual(compound, minimalCompound, structuredClone(response)); - const testResponse = checkNodesEqual(complexTerm, term, structuredClone(response)); + const testResponse = checkNodesEqual(augmentNode(complexTerm), augmentNode(structuredClone(term)), structuredClone(response)); + const compoundResponse = checkNodesEqual(augmentNode(structuredClone(compound)), augmentNode(minimalCompound), structuredClone(response)); + + const expectedResponse = structuredClone(compoundResponse); + + expectedResponse.atomCount = {"C": {"numerator": 3, "denominator": 2}, "O": {"numerator": 3, "denominator": 2}} as Record; + expectedResponse.termAtomCount = {} as Record; // Assert - expect(testResponse.atomCount).toEqual(compoundResponse.atomCount); - expect(testResponse.chargeCount).toEqual(compoundResponse.chargeCount); + expect(testResponse).toEqual(expectedResponse); } ); }); @@ -501,7 +506,7 @@ describe("CheckNodesEqual Expression", () => { const testResponse: CheckerResponse = checkNodesEqual(permutedExpression, expression, structuredClone(response)); // Assert expect(testResponse.isEqual).toBeTruthy(); - expect(testResponse.atomCount?.O).toBe(2); + expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 1}); } ); it("Returns falsy CheckerResponse when expressions do not match", @@ -522,16 +527,17 @@ describe("CheckNodesEqual Expression", () => { () => { // Act const adjustedExpression: Expression = structuredClone(expression); + adjustedExpression.term = structuredClone(hydrate); adjustedExpression.terms = [structuredClone(hydrate)]; + const mismatchedHydrate: Expression = structuredClone(expression); + mismatchedHydrate.term = structuredClone(hydrate2); mismatchedHydrate.terms = [structuredClone(hydrate2)]; - const hydrateResponse: CheckerResponse = checkNodesEqual(hydrate, hydrate2, structuredClone(response)); + const hydrateResponse: CheckerResponse = checkNodesEqual(augmentNode(hydrate), augmentNode(hydrate2), structuredClone(response)); // Assert - expect( - checkNodesEqual(mismatchedHydrate, adjustedExpression, structuredClone(response)) - ).toEqual(hydrateResponse); + expect(checkNodesEqual(augmentNode(mismatchedHydrate), augmentNode(adjustedExpression), structuredClone(response))).toEqual(hydrateResponse); } ); it("Returns an error if the AST is not augmented", @@ -603,7 +609,7 @@ describe("CheckNodesEqual Statement", () => { unbalancedStatement.right = structuredClone(expression); // Assert - expect((checkNodesEqual(statement, statement, structuredClone(response))).isBalanced).toBeTruthy(); + expect((checkNodesEqual(augmentNode(statement), augmentNode(statement), structuredClone(response))).isBalanced).toBeTruthy(); expect((checkNodesEqual(unbalancedStatement, statement, structuredClone(response))).isBalanced).toBeFalsy(); } ); diff --git a/test/models/Nuclear.test.ts b/test/models/Nuclear.test.ts index 6061b6a..7f1b126 100644 --- a/test/models/Nuclear.test.ts +++ b/test/models/Nuclear.test.ts @@ -255,7 +255,7 @@ describe("CheckNodesEqual Expression", () => { // Assert expect(checkNodesEqual(unaugmentedExpression, expression, structuredClone(response)).containsError).toBeTruthy(); expect(checkNodesEqual(unaugmentedExpression, expression, structuredClone(response)).error).toEqual( - { message: "Received unaugmenttened AST during checking process." } + { message: "Received unaugmented AST during checking process." } ); expect(console.error).toHaveBeenCalled(); From e5ff8a5677ab8eb7e760ef04582b94eceec3d825 Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:18:33 +0000 Subject: [PATCH 03/11] Add allowPermutations tests --- test/models/Chemistry.test.ts | 91 ++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 245bace..16eacea 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -271,6 +271,20 @@ describe("checkNodesEqual Compounds", () => { expect(testResponse.termAtomCount?.O).toBe(1) } ); + it("Returns truthy CheckerResponse when a permutation of compounds match with allowPermutations", + () => { + // Act + const permutedCompound: Compound = structuredClone(compound); + permutedCompound.elements?.reverse; + + const permutationsResponse = { ...response, options: {...options, allowPermutations: true} }; + + const testResponse = checkNodesEqual(augmentNode(permutedCompound), augmentNode(structuredClone(compound)), permutationsResponse); + + // Assert + expect(testResponse.isEqual).toBeTruthy(); + } + ); it("Returns falsy CheckerResponse when compounds don't match", () => { // Act @@ -349,6 +363,21 @@ describe("CheckNodesEqual Ions", () => { expect(testResponse.chargeCount).toBe(0); } ); + it("Returns truthy CheckerResponse when a permutation of ions match with allowPermutations", + () => { + // Act + const permutedIon: Ion = augmentNode(structuredClone(ion)); + permutedIon.molecules?.reverse; + + const permutationsResponse = { ...response, options: {...options, allowPermutations: true} }; + + const testResponse = checkNodesEqual(permutedIon, augmentNode(structuredClone(ion)), permutationsResponse); + + // Assert + expect(testResponse.isEqual).toBeTruthy(); + expect(testResponse.chargeCount).toBe(0); + } + ); it("Returns falsy CheckerResponse when ions do not match", () => { const moleculeMismatch: Ion = augmentNode(structuredClone(ion)); @@ -404,42 +433,58 @@ describe("CheckNodesEqual Ions", () => { describe("CheckNodesEqual Term", () => { // TODO: Separate into different tests - it("Returns truthy CheckerResponse when terms match", + it("Returns truthy CheckerResponse when terms match with allowScalingCoefficients", () => { - const scaledResponse = { ...response, options: { ...options, allowScalingCoefficients: true } }; + // Act + const scaledResponse = { ...response, options: { ...options, allowScalingCoefficients: true } }; - // Scalar multiple coefficient - let perturbedTerm: Term = augmentNode(structuredClone(term)); + const perturbedTerm: Term = augmentNode(structuredClone(term)); perturbedTerm.coeff = { numerator: 6, denominator: 4 }; - let testResponse = checkNodesEqual(perturbedTerm, augmentNode(structuredClone(term)), scaledResponse); + const testResponse = checkNodesEqual(perturbedTerm, augmentNode(structuredClone(term)), scaledResponse); + + // Assert expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.sameState).toBeTruthy(); expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 2}); + } + ); + it("Returns truthy CheckerResponse when terms have equal states", + () => { + // Act + const stateTerm = augmentNode(structuredClone(term)); + stateTerm.state = "(aq)"; + let stateTermCopy: Term = augmentNode(structuredClone(stateTerm)); - // Term with a state - perturbedTerm = augmentNode(structuredClone(term)); - perturbedTerm.state = "(aq)"; - let termCopy: Term = augmentNode(structuredClone(perturbedTerm)); + const testResponse = checkNodesEqual(stateTerm, stateTermCopy, structuredClone(response)) - testResponse = checkNodesEqual(perturbedTerm, termCopy, structuredClone(response)) + // Assert expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.sameState).toBeTruthy(); + } + ); + it("Returns truthy CheckerResponse when terms has equal hydrates", + () => { + // Act + const hydratedTerm = augmentNode(structuredClone(term)); + hydratedTerm.isHydrate = true; + hydratedTerm.hydrate = 7; + const hydratedTermCopy = structuredClone(hydratedTerm); - // Hydrate term - perturbedTerm = augmentNode(structuredClone(term)); - termCopy.isHydrate = true; - termCopy.hydrate = 7; - termCopy = structuredClone(perturbedTerm); - - expect(checkNodesEqual(termCopy, perturbedTerm, structuredClone(response)).isEqual).toBeTruthy(); + // Assert + expect(checkNodesEqual(hydratedTermCopy, hydratedTerm, structuredClone(response)).isEqual).toBeTruthy(); + } + ); + it("Returns truthy CheckerResponse when electron terms match", + () => { + // Act + const electronTerm = augmentNode(structuredClone(term)); + electronTerm.isElectron = true + electronTerm.value = { type: "electron" } + const electronTermCopy = structuredClone(electronTerm); - // Electron - perturbedTerm = augmentNode(structuredClone(term)); - perturbedTerm.isElectron = true - perturbedTerm.value = { type: "electron" } - termCopy = structuredClone(perturbedTerm); - expect(checkNodesEqual(termCopy, perturbedTerm, structuredClone(response)).isEqual).toBeTruthy(); + // Assert + expect(checkNodesEqual(electronTermCopy, electronTerm, structuredClone(response)).isEqual).toBeTruthy(); } ); it("Returns falsy CheckerResponse when terms don't match", From c04b02be2d35137e4beff540417d32bf211b2264 Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:11:12 +0000 Subject: [PATCH 04/11] Restructure and fix newly broken Nuclear tests --- src/models/Chemistry.ts | 12 +- src/models/Nuclear.ts | 34 ++++-- src/models/common.ts | 10 +- test/models/Chemistry.test.ts | 199 ++++++++++++++++------------------ test/models/Nuclear.test.ts | 80 +++++++------- 5 files changed, 173 insertions(+), 162 deletions(-) diff --git a/src/models/Chemistry.ts b/src/models/Chemistry.ts index 6998f76..85499a9 100644 --- a/src/models/Chemistry.ts +++ b/src/models/Chemistry.ts @@ -7,7 +7,7 @@ export type Arrow = 'SArr'|'DArr'; export type Molecule = Element | Compound; export type Result = Statement | Expression | Term | ParseError; -interface ASTNode { +export interface ASTNode { type: Type; } @@ -111,7 +111,7 @@ export interface ChemAST { result: Result; } -const STARTING_COEFFICIENT: Fraction = { numerator: 0, denominator: 1 }; +export const STARTING_COEFFICIENT: Fraction = { numerator: 0, denominator: 1 }; const EQUAL_COEFFICIENT: Fraction = { numerator: 1, denominator: 1 }; const STARTING_RESPONSE: (options?: ChemistryOptions, coefficientScalingValue?: Fraction) => CheckerResponse = (options, coefficientScalingValue) => { return { @@ -579,6 +579,10 @@ export function check(test: ChemAST, target: ChemAST, options: ChemistryOptions) response.expectedType = target.result.type; response.receivedType = test.result.type; + if (isEqual(test.result, target.result)) { + return response; + } + // Return shortcut response if (test.result.type === "error") { const message = @@ -607,7 +611,9 @@ export function check(test: ChemAST, target: ChemAST, options: ChemistryOptions) // We set flags for these properties in checkNodesEqual, but we only apply the isEqual check here due to listComparison newResponse.isEqual = newResponse.isEqual && newResponse.sameCoefficient && (newResponse.sameState == true) && (newResponse.sameBrackets == true); - newResponse = removeAggregates(newResponse); + if (!newResponse.options?.keepAggregates) { + newResponse = removeAggregates(newResponse); + } return newResponse; } diff --git a/src/models/Nuclear.ts b/src/models/Nuclear.ts index 34a1b17..33aaa59 100644 --- a/src/models/Nuclear.ts +++ b/src/models/Nuclear.ts @@ -1,10 +1,11 @@ +import { isEqual } from 'lodash'; import { CheckerResponse, ChemicalSymbol, chemicalSymbol, ChemistryOptions, listComparison, mergeResponses, removeAggregates } from './common' export type ParticleString = 'alphaparticle'|'betaparticle'|'gammaray'|'neutrino'|'antineutrino'|'electron'|'positron'|'neutron'|'proton'; export type Type = 'error'|'particle'|'isotope'|'term'|'expr'|'statement'; export type Result = Statement | Expression | Term | ParseError; -interface ASTNode { +export interface ASTNode { type: Type; } @@ -71,7 +72,7 @@ export interface NuclearAST { result: Result; } -function augmentNode(node: T): T { +export function augmentNode(node: T): T { // The if statements signal to the type checker what we already know switch (node.type) { case "expr": { @@ -177,6 +178,8 @@ const STARTING_RESPONSE: (options?: ChemistryOptions) => CheckerResponse = (opti isNuclear: true, containsError: false, isEqual: true, + balancedMass: true, + balancedAtom: true, isBalanced: true, typeMismatch: false, sameCoefficient: true, @@ -195,7 +198,7 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon return response; } - response.validAtomicNumber = (response.validAtomicNumber ?? true) && isValidAtomicNumber(test); + response.validAtomicNumber = (response.validAtomicNumber === true) && isValidAtomicNumber(test); response.sameElements = response.sameElements && checkParticlesEqual(test, target); response.isEqual = response.isEqual && response.sameElements && response.validAtomicNumber; @@ -219,8 +222,8 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon return response; } - response.validAtomicNumber = (response.validAtomicNumber ?? true) && isValidAtomicNumber(test) && test.mass === target.mass && test.atomic === target.atomic; response.sameElements = response.sameElements && test.element === target.element; + response.validAtomicNumber = (response.validAtomicNumber ?? true) && isValidAtomicNumber(test) && (response.sameElements ? test.mass === target.mass && test.atomic === target.atomic : true); response.isEqual = response.isEqual && response.sameElements && response.validAtomicNumber; // Add the term's nucleon counts to the term's nucleon count @@ -246,12 +249,14 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon newResponse.sameCoefficient = test.coeff === target.coeff; // Add the term's nucleon counts to the overall expression nucleon count - if (newResponse.nucleonCount) { - newResponse.nucleonCount = [ - newResponse.nucleonCount[0] + (newResponse.termNucleonCount ?? [0,0])[0] * test.coeff, - newResponse.nucleonCount[1] + (newResponse.termNucleonCount ?? [0,0])[1] * test.coeff, - ] + if (!newResponse.nucleonCount) { + newResponse.nucleonCount = [0,0]; } + newResponse.nucleonCount = [ + newResponse.nucleonCount[0] + (newResponse.termNucleonCount ?? [0,0])[0] * test.coeff, + newResponse.nucleonCount[1] + (newResponse.termNucleonCount ?? [0,0])[1] * test.coeff, + ] + newResponse.termNucleonCount = [0,0] return newResponse; } else if (isExpression(test) && isExpression(target)) { @@ -273,11 +278,12 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon // Determine responses for both the left and right side of the statement const leftResponse = checkNodesEqual(test.left, target.left, response); let rightResponse = STARTING_RESPONSE(leftResponse.options); - rightResponse = checkNodesEqual(test.right, target.right, leftResponse); + rightResponse = checkNodesEqual(test.right, target.right, rightResponse); // Merge the responses so that the final response contains all the information const finalResponse = mergeResponses(leftResponse, rightResponse); + // Nuclear question balance is determined by atom/mass count equality finalResponse.balancedAtom = leftResponse.nucleonCount && rightResponse.nucleonCount ? leftResponse.nucleonCount[0] === rightResponse.nucleonCount[0] : false; @@ -308,6 +314,10 @@ export function check(test: NuclearAST, target: NuclearAST): CheckerResponse { response.expectedType = target.result.type; response.receivedType = test.result.type; + if (isEqual(test.result, target.result)) { + return response; + } + // Return shortcut response if (test.result.type === "error") { const message = @@ -335,7 +345,9 @@ export function check(test: NuclearAST, target: NuclearAST): CheckerResponse { let newResponse = checkNodesEqual(test.result, target.result, response); // We set flags for this properties in checkNodesEqual, but we only apply the isEqual check here due to listComparison newResponse.isEqual = newResponse.isEqual && newResponse.sameCoefficient; - newResponse = removeAggregates(newResponse); + if (!newResponse.options?.keepAggregates) { + newResponse = removeAggregates(newResponse); + } return newResponse; } diff --git a/src/models/common.ts b/src/models/common.ts index 9ca6e35..fe5286d 100644 --- a/src/models/common.ts +++ b/src/models/common.ts @@ -11,6 +11,7 @@ export interface Fraction { export interface ChemistryOptions { allowPermutations?: boolean; allowScalingCoefficients?: boolean; + keepAggregates?: boolean; } export interface CheckerResponse { @@ -57,10 +58,13 @@ export function mergeResponses(response1: CheckerResponse, response2: CheckerRes newResponse.isEqual = response1.isEqual && response2.isEqual; newResponse.typeMismatch = response1.typeMismatch || response2.typeMismatch; newResponse.sameCoefficient = response1.sameCoefficient && response2.sameCoefficient; - newResponse.sameState = response1.sameState && response2.sameState; newResponse.sameElements = response1.sameElements && response2.sameElements; - newResponse.sameBrackets = response1.sameBrackets && response2.sameBrackets; - newResponse.validAtomicNumber = response1.validAtomicNumber && response2.validAtomicNumber; + if (!response1.isNuclear) { + newResponse.sameState = response1.sameState && response2.sameState; + newResponse.sameBrackets = response1.sameBrackets && response2.sameBrackets; + } else { + newResponse.validAtomicNumber = response1.validAtomicNumber && response2.validAtomicNumber; + } return newResponse; } diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 16eacea..9cf8597 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -1,6 +1,6 @@ -import { Bracket, Compound, Element, exportedForTesting, Ion, Term, Expression, Statement, ParseError, check, ChemAST, augment, STARTING_COEFFICIENT } from "../../src/models/Chemistry"; +import { Bracket, Compound, Element, exportedForTesting, Ion, Term, Expression, Statement, ParseError, check, ChemAST, augment, STARTING_COEFFICIENT, Result, ASTNode } from "../../src/models/Chemistry"; import { CheckerResponse, listComparison, ChemistryOptions, Fraction, ChemicalSymbol } from "../../src/models/common"; -const { augmentNode, checkNodesEqual } = exportedForTesting; +const { augmentNode } = exportedForTesting; import { parseChemistryExpression } from "inequality-grammar"; const original = console.error; @@ -22,7 +22,6 @@ const options: ChemistryOptions = { // Generic response object const response: CheckerResponse = { containsError: false, - error: { message: "" }, expectedType: "statement", receivedType: "statement", typeMismatch: false, @@ -34,17 +33,13 @@ const response: CheckerResponse = { isBalanced: true, isEqual: true, isNuclear: false, - chargeCount: 0, options: options, coefficientScalingValue: STARTING_COEFFICIENT, - atomCount: undefined, - bracketAtomCount: undefined, - termAtomCount: undefined, } // Alternative response object const newResponse: CheckerResponse = { containsError: false, - error: { message: "" }, + error: "", expectedType: "statement", receivedType: "statement", typeMismatch: false, @@ -56,12 +51,8 @@ const newResponse: CheckerResponse = { isBalanced: true, isEqual: true, isNuclear: false, - chargeCount: 0, options: options, coefficientScalingValue: STARTING_COEFFICIENT, - atomCount: undefined, - bracketAtomCount: undefined, - termAtomCount: undefined, } const trueResponse: CheckerResponse = structuredClone(newResponse); @@ -132,6 +123,14 @@ const ast: ChemAST = { result: structuredClone(statement) } +function testCheck(target: T, test: T, options?: ChemistryOptions): CheckerResponse { + return check({result: augmentNode(target) as unknown as Result}, {result: augmentNode(test) as unknown as Result}, options ?? {}); +} + +function unaugmentedTestCheck(target: T, test: T, options?: ChemistryOptions): CheckerResponse { + return check({result: target as unknown as Result}, {result: test as unknown as Result}, options ?? {}); +} + describe("listComparison", () => { function comparatorNumberMock( test: number, target: number, response: CheckerResponse @@ -190,13 +189,13 @@ describe("listComparison", () => { ); }); -describe("checkNodesEqual Elements", () => { +describe("testCheck Elements", () => { it("Returns truthy CheckerResponse when elements match", () => { // Act const elementCopy: Element = structuredClone(element) - const testResponse = checkNodesEqual(elementCopy, element, structuredClone(response)); + const testResponse = testCheck(elementCopy, element); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -212,8 +211,8 @@ describe("checkNodesEqual Elements", () => { const coefficientMismatch: Element = structuredClone(element); coefficientMismatch.coeff = 2; - const elementIncorrect = checkNodesEqual(valueMismatch, element, structuredClone(response)); - const coeffIncorrect = checkNodesEqual(coefficientMismatch, element, structuredClone(response)); + const elementIncorrect = testCheck(valueMismatch, element); + const coeffIncorrect = testCheck(coefficientMismatch, element); // Assert expect(elementIncorrect.isEqual).toBeFalsy(); @@ -227,13 +226,13 @@ describe("checkNodesEqual Elements", () => { ); }); -describe("checkNodesEqual Brackets", () => { +describe("testCheck Brackets", () => { it("Returns truthy CheckerResponse when brackets match", () => { // Act const bracketCopy: Bracket = structuredClone(bracket); - const testResponse = checkNodesEqual(augmentNode(bracketCopy), augmentNode(bracket), structuredClone(response)); + const testResponse = testCheck(bracketCopy, bracket); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -247,7 +246,7 @@ describe("checkNodesEqual Brackets", () => { const coefficientMismatch: Bracket = structuredClone(bracket); coefficientMismatch.coeff = 2; - const coeffIncorrect = checkNodesEqual(augmentNode(coefficientMismatch), augmentNode(bracket), structuredClone(response)); + const coeffIncorrect = testCheck(coefficientMismatch, bracket); // Assert expect(coeffIncorrect.isEqual).toBeFalsy(); @@ -257,13 +256,13 @@ describe("checkNodesEqual Brackets", () => { ); }); -describe("checkNodesEqual Compounds", () => { +describe("testCheck Compounds", () => { it("Returns truthy CheckerResponse when compounds match", () => { // Act const compoundCopy: Compound = structuredClone(compound); - const testResponse = checkNodesEqual(augmentNode(compoundCopy), augmentNode(structuredClone(compound)), structuredClone(response)); + const testResponse = testCheck(compoundCopy, structuredClone(compound)); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -277,9 +276,9 @@ describe("checkNodesEqual Compounds", () => { const permutedCompound: Compound = structuredClone(compound); permutedCompound.elements?.reverse; - const permutationsResponse = { ...response, options: {...options, allowPermutations: true} }; + const permutationsOptions = { allowPermutations: true }; - const testResponse = checkNodesEqual(augmentNode(permutedCompound), augmentNode(structuredClone(compound)), permutationsResponse); + const testResponse = testCheck(permutedCompound, structuredClone(compound), permutationsOptions); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -288,13 +287,13 @@ describe("checkNodesEqual Compounds", () => { it("Returns falsy CheckerResponse when compounds don't match", () => { // Act - const typeMismatch: Compound = augmentNode(structuredClone(compound)); + const typeMismatch: Compound = structuredClone(compound); typeMismatch.elements = [structuredClone(element), structuredClone(element)] - const lengthMismatch: Compound = augmentNode(structuredClone(compound)); + const lengthMismatch: Compound = structuredClone(compound); lengthMismatch.elements?.push(structuredClone(element)); - const typesIncorrect = checkNodesEqual(typeMismatch, augmentNode(structuredClone(compound)), structuredClone(response)); - const lengthIncorrect = checkNodesEqual(lengthMismatch, augmentNode(structuredClone(compound)), structuredClone(response)); + const typesIncorrect = testCheck(typeMismatch, structuredClone(compound)); + const lengthIncorrect = testCheck(lengthMismatch, structuredClone(compound)); // Assert expect(typesIncorrect.isEqual).toBeFalsy(); @@ -306,25 +305,25 @@ describe("checkNodesEqual Compounds", () => { // Arrange // If the lengths or types are different the wrong thing will fail // So create a single bracket compound to test bracket mismatches - const minimalBracketCompound: Compound = augmentNode(structuredClone(minimalCompound)) - minimalBracketCompound.elements = [augmentNode(structuredClone(bracket))] + const minimalBracketCompound: Compound = structuredClone(minimalCompound); + minimalBracketCompound.elements = [structuredClone(bracket)]; - const bracketCoeffMismatch: Bracket = augmentNode(structuredClone(bracket)); + const bracketCoeffMismatch: Bracket = structuredClone(bracket); bracketCoeffMismatch.coeff = 2; - const elementCoeffMismatch: Element = augmentNode(structuredClone(element)); + const elementCoeffMismatch: Element = structuredClone(element); elementCoeffMismatch.coeff = 2; - const bracketMismatch: Compound = augmentNode(structuredClone(compound)); + const bracketMismatch: Compound = structuredClone(compound); bracketMismatch.elements = [bracketCoeffMismatch]; - const elementMismatch: Compound = augmentNode(structuredClone(compound)); + const elementMismatch: Compound = structuredClone(compound); elementMismatch.elements = [elementCoeffMismatch]; - const bracketResponse: CheckerResponse = checkNodesEqual(bracketCoeffMismatch, augmentNode(structuredClone(bracket)), structuredClone(response)); - const elementResponse: CheckerResponse = checkNodesEqual(elementCoeffMismatch, augmentNode(structuredClone(element)), structuredClone(response)); + const bracketResponse: CheckerResponse = testCheck(bracketCoeffMismatch, structuredClone(bracket)); + const elementResponse: CheckerResponse = testCheck(elementCoeffMismatch, structuredClone(element)); // Assert - expect(checkNodesEqual(bracketMismatch, minimalBracketCompound, structuredClone(response))).toEqual({...bracketResponse, sameElements: true}); - expect(checkNodesEqual(elementMismatch, minimalCompound, structuredClone(response))).toEqual({...elementResponse, sameElements: false}); + expect(testCheck(bracketMismatch, minimalBracketCompound)).toEqual({...bracketResponse, sameElements: true}); + expect(testCheck(elementMismatch, minimalCompound)).toEqual({...elementResponse, sameElements: false}); } ); it("Returns an error if the AST is not augmented", @@ -338,23 +337,21 @@ describe("checkNodesEqual Compounds", () => { }; // Assert - expect(checkNodesEqual(unaugmentedCompound, compound, structuredClone(response)).containsError).toBeTruthy(); - expect(checkNodesEqual(unaugmentedCompound, compound, structuredClone(response)).error).toEqual( - { message: "Received unaugmented AST during checking process." } - ); + expect(testCheck(unaugmentedCompound, compound).containsError).toBeTruthy(); + expect(testCheck(unaugmentedCompound, compound).error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } ); }); -describe("CheckNodesEqual Ions", () => { +describe("testCheck Ions", () => { it("Returns truthy CheckerResponse when ions match", () => { // Act const ionClone: Ion = structuredClone(ion); - const testResponse = checkNodesEqual(augmentNode(structuredClone(ion)), augmentNode(ionClone), structuredClone(response)); + const testResponse = testCheck(structuredClone(ion), ionClone); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -366,12 +363,12 @@ describe("CheckNodesEqual Ions", () => { it("Returns truthy CheckerResponse when a permutation of ions match with allowPermutations", () => { // Act - const permutedIon: Ion = augmentNode(structuredClone(ion)); + const permutedIon: Ion = structuredClone(ion); permutedIon.molecules?.reverse; - const permutationsResponse = { ...response, options: {...options, allowPermutations: true} }; + const permutationsOptions = { allowPermutations: true, keepAggregates: true }; - const testResponse = checkNodesEqual(permutedIon, augmentNode(structuredClone(ion)), permutationsResponse); + const testResponse = testCheck(permutedIon, structuredClone(ion), permutationsOptions); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -393,9 +390,9 @@ describe("CheckNodesEqual Ions", () => { const lengthMismatch: Ion = augmentNode(structuredClone(ion)); lengthMismatch.molecules?.push([structuredClone(element), 1]); - const moleculeIncorrect = checkNodesEqual(moleculeMismatch, augmentNode(structuredClone(ion)), structuredClone(response)); - const chargeIncorrect = checkNodesEqual(chargeMismatch, augmentNode(structuredClone(ion)), structuredClone(response)); - const lengthIncorrect = checkNodesEqual(lengthMismatch, augmentNode(structuredClone(ion)), structuredClone(response)); + const moleculeIncorrect = unaugmentedTestCheck(moleculeMismatch, augmentNode(structuredClone(ion))); + const chargeIncorrect = unaugmentedTestCheck(chargeMismatch, augmentNode(structuredClone(ion))); + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentNode(structuredClone(ion))); // Assert expect(moleculeIncorrect.isEqual).toBeFalsy(); @@ -421,27 +418,25 @@ describe("CheckNodesEqual Ions", () => { }; // Assert - expect(checkNodesEqual(unaugmentedIon, ion, structuredClone(response)).containsError).toBeTruthy(); - expect(checkNodesEqual(unaugmentedIon, ion, structuredClone(response)).error).toEqual( - { message: "Received unaugmented AST during checking process." } - ); + expect(testCheck(unaugmentedIon, ion).containsError).toBeTruthy(); + expect(testCheck(unaugmentedIon, ion).error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } ); }); -describe("CheckNodesEqual Term", () => { +describe("testCheck Term", () => { // TODO: Separate into different tests it("Returns truthy CheckerResponse when terms match with allowScalingCoefficients", () => { - // Act - const scaledResponse = { ...response, options: { ...options, allowScalingCoefficients: true } }; + // Act + const scaledOptions = { allowScalingCoefficients: true }; - const perturbedTerm: Term = augmentNode(structuredClone(term)); + const perturbedTerm: Term = structuredClone(term); perturbedTerm.coeff = { numerator: 6, denominator: 4 }; - const testResponse = checkNodesEqual(perturbedTerm, augmentNode(structuredClone(term)), scaledResponse); + const testResponse = testCheck(perturbedTerm, structuredClone(term), scaledOptions); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -452,11 +447,11 @@ describe("CheckNodesEqual Term", () => { it("Returns truthy CheckerResponse when terms have equal states", () => { // Act - const stateTerm = augmentNode(structuredClone(term)); + const stateTerm = structuredClone(term); stateTerm.state = "(aq)"; - let stateTermCopy: Term = augmentNode(structuredClone(stateTerm)); + let stateTermCopy: Term = structuredClone(stateTerm); - const testResponse = checkNodesEqual(stateTerm, stateTermCopy, structuredClone(response)) + const testResponse = testCheck(stateTerm, stateTermCopy) // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -466,59 +461,59 @@ describe("CheckNodesEqual Term", () => { it("Returns truthy CheckerResponse when terms has equal hydrates", () => { // Act - const hydratedTerm = augmentNode(structuredClone(term)); + const hydratedTerm = structuredClone(term); hydratedTerm.isHydrate = true; hydratedTerm.hydrate = 7; const hydratedTermCopy = structuredClone(hydratedTerm); // Assert - expect(checkNodesEqual(hydratedTermCopy, hydratedTerm, structuredClone(response)).isEqual).toBeTruthy(); + expect(testCheck(hydratedTermCopy, hydratedTerm).isEqual).toBeTruthy(); } ); it("Returns truthy CheckerResponse when electron terms match", () => { // Act - const electronTerm = augmentNode(structuredClone(term)); + const electronTerm = structuredClone(term); electronTerm.isElectron = true electronTerm.value = { type: "electron" } const electronTermCopy = structuredClone(electronTerm); // Assert - expect(checkNodesEqual(electronTermCopy, electronTerm, structuredClone(response)).isEqual).toBeTruthy(); + expect(testCheck(electronTermCopy, electronTerm).isEqual).toBeTruthy(); } ); it("Returns falsy CheckerResponse when terms don't match", () => { // Mismatched coefficients - let mismatchTerm: Term = augmentNode(structuredClone(term)); + let mismatchTerm: Term = structuredClone(term); mismatchTerm.coeff = { numerator: 1, denominator: 5 }; - expect(checkNodesEqual(mismatchTerm, augmentNode(structuredClone(term)), structuredClone(response)).isEqual).toBeFalsy(); - expect(checkNodesEqual(mismatchTerm, augmentNode(structuredClone(term)), structuredClone(response)).sameCoefficient).toBeFalsy(); + expect(testCheck(mismatchTerm, structuredClone(term)).isEqual).toBeFalsy(); + expect(testCheck(mismatchTerm, structuredClone(term)).sameCoefficient).toBeFalsy(); // Mismatched state - let perturbedTerm: Term = augmentNode(structuredClone(term)); - mismatchTerm = augmentNode(structuredClone(term)); + let perturbedTerm: Term = structuredClone(term); + mismatchTerm = structuredClone(term); perturbedTerm.state = "(aq)"; mismatchTerm.state = "(g)"; - expect(checkNodesEqual(perturbedTerm, mismatchTerm, structuredClone(response)).isEqual).toBeFalsy(); - expect(checkNodesEqual(perturbedTerm, mismatchTerm, structuredClone(response)).sameState).toBeFalsy(); + expect(testCheck(perturbedTerm, mismatchTerm).isEqual).toBeFalsy(); + expect(testCheck(perturbedTerm, mismatchTerm).sameState).toBeFalsy(); // Mismatched hydrate - perturbedTerm = augmentNode(structuredClone(term)); - mismatchTerm = augmentNode(structuredClone(term)); + perturbedTerm = structuredClone(term); + mismatchTerm = structuredClone(term); perturbedTerm.isHydrate = true; perturbedTerm.hydrate = 7; mismatchTerm.isHydrate = true; mismatchTerm.hydrate = 1; - expect(checkNodesEqual(perturbedTerm, mismatchTerm, structuredClone(response)).isEqual).toBeFalsy(); + expect(testCheck(perturbedTerm, mismatchTerm).isEqual).toBeFalsy(); // Mismatched type - mismatchTerm = augmentNode(structuredClone(term)); + mismatchTerm = structuredClone(term); mismatchTerm.isElectron = true; mismatchTerm.value = { type: "electron" }; - expect(checkNodesEqual(mismatchTerm, augmentNode(structuredClone(term)), structuredClone(response)).isEqual).toBeFalsy(); - expect(checkNodesEqual(mismatchTerm, augmentNode(structuredClone(term)), structuredClone(response)).typeMismatch).toBeFalsy(); + expect(testCheck(mismatchTerm, structuredClone(term)).isEqual).toBeFalsy(); + expect(testCheck(mismatchTerm, structuredClone(term)).typeMismatch).toBeFalsy(); } ); it("Retains CheckerResponse properties", @@ -527,8 +522,8 @@ describe("CheckNodesEqual Term", () => { const complexTerm: Term = structuredClone(term); complexTerm.value = structuredClone(compound); - const testResponse = checkNodesEqual(augmentNode(complexTerm), augmentNode(structuredClone(term)), structuredClone(response)); - const compoundResponse = checkNodesEqual(augmentNode(structuredClone(compound)), augmentNode(minimalCompound), structuredClone(response)); + const testResponse = testCheck(complexTerm, structuredClone(term)); + const compoundResponse = testCheck(structuredClone(compound), minimalCompound); const expectedResponse = structuredClone(compoundResponse); @@ -541,14 +536,14 @@ describe("CheckNodesEqual Term", () => { ); }); -describe("CheckNodesEqual Expression", () => { +describe("testCheck Expression", () => { it("Returns truthy CheckerResponse when expressions match", () => { // Act const permutedExpression: Expression = structuredClone(expression); permutedExpression.terms?.reverse; - const testResponse: CheckerResponse = checkNodesEqual(permutedExpression, expression, structuredClone(response)); + const testResponse: CheckerResponse = testCheck(permutedExpression, expression); // Assert expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 1}); @@ -564,8 +559,8 @@ describe("CheckNodesEqual Expression", () => { if (termMismatch.terms) termMismatch.terms[1] = structuredClone(hydrate); // Assert - expect(checkNodesEqual(lengthMismatch, expression, structuredClone(response)).isEqual).toBeFalsy(); - expect(checkNodesEqual(termMismatch, expression, structuredClone(response)).isEqual).toBeFalsy(); + expect(testCheck(lengthMismatch, expression).isEqual).toBeFalsy(); + expect(testCheck(termMismatch, expression).isEqual).toBeFalsy(); } ); it("Retains CheckerResponse properties", @@ -579,10 +574,10 @@ describe("CheckNodesEqual Expression", () => { mismatchedHydrate.term = structuredClone(hydrate2); mismatchedHydrate.terms = [structuredClone(hydrate2)]; - const hydrateResponse: CheckerResponse = checkNodesEqual(augmentNode(hydrate), augmentNode(hydrate2), structuredClone(response)); + const hydrateResponse: CheckerResponse = testCheck(hydrate, hydrate2); // Assert - expect(checkNodesEqual(augmentNode(mismatchedHydrate), augmentNode(adjustedExpression), structuredClone(response))).toEqual(hydrateResponse); + expect(testCheck(mismatchedHydrate, adjustedExpression)).toEqual(hydrateResponse); } ); it("Returns an error if the AST is not augmented", @@ -596,27 +591,25 @@ describe("CheckNodesEqual Expression", () => { } // Assert - expect(checkNodesEqual(unaugmentedExpression, expression, structuredClone(response)).containsError).toBeTruthy(); - expect(checkNodesEqual(unaugmentedExpression, expression, structuredClone(response)).error).toEqual( - { message: "Received unaugmented AST during checking process." } - ); + expect(unaugmentedTestCheck(unaugmentedExpression, expression).containsError).toBeTruthy(); + expect(unaugmentedTestCheck(unaugmentedExpression, expression).error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } ); }); -describe("CheckNodesEqual Statement", () => { +describe("testCheck Statement", () => { it("Returns truthy CheckerResponse when expressions match", () => { // Act const copy: Statement = structuredClone(statement); - const copyResult: CheckerResponse = checkNodesEqual(copy, statement, structuredClone(response)); + const copyResult: CheckerResponse = testCheck(copy, statement); copy.arrow = "DArr"; const doubleArrowCopy: Statement = structuredClone(copy); - const arrowResult = checkNodesEqual(copy, doubleArrowCopy, structuredClone(response)); + const arrowResult = testCheck(copy, doubleArrowCopy); // Assert expect(copyResult.isEqual).toBeTruthy(); expect(copyResult.sameArrow).toBeTruthy(); @@ -636,8 +629,8 @@ describe("CheckNodesEqual Statement", () => { const doubleArrow: Statement = structuredClone(statement); doubleArrow.arrow = "DArr"; - const swapResult = checkNodesEqual(swappedExpressions, statement, structuredClone(response)); - const arrowResult = checkNodesEqual(doubleArrow, statement, structuredClone(response)); + const swapResult = testCheck(swappedExpressions, statement); + const arrowResult = testCheck(doubleArrow, statement); // Assert expect(swapResult.isEqual).toBeFalsy(); @@ -654,8 +647,8 @@ describe("CheckNodesEqual Statement", () => { unbalancedStatement.right = structuredClone(expression); // Assert - expect((checkNodesEqual(augmentNode(statement), augmentNode(statement), structuredClone(response))).isBalanced).toBeTruthy(); - expect((checkNodesEqual(unbalancedStatement, statement, structuredClone(response))).isBalanced).toBeFalsy(); + expect((testCheck(statement, statement)).isBalanced).toBeTruthy(); + expect((testCheck(unbalancedStatement, statement)).isBalanced).toBeFalsy(); } ); it("Correctly checks whether charges are balanced", @@ -679,12 +672,8 @@ describe("CheckNodesEqual Statement", () => { unbalancedCharges.left = structuredClone(chargedExpr); // Assert - expect( - checkNodesEqual(balancedCharges, balancedCharges, structuredClone(response)).balancedCharge - ).toBeTruthy(); - expect( - checkNodesEqual(unbalancedCharges, unbalancedCharges, structuredClone(response)).balancedCharge - ).toBeFalsy(); + expect(testCheck(balancedCharges, balancedCharges).isChargeBalanced).toBeTruthy(); + expect(testCheck(unbalancedCharges, unbalancedCharges).isChargeBalanced).toBeFalsy(); } ); }); @@ -705,8 +694,8 @@ describe("Check", () => { const response: CheckerResponse = check(errorAST, ast, options); // Assert - expect(response.error).toBeDefined(); - expect(response.error?.message).toBe("Sphinx of black quartz, judge my vow"); + expect(response.containsError).toBeTruthy(); + expect(response.error).toBe("Sphinx of black quartz, judge my vow"); expect(response.expectedType).toBe("statement"); } ); diff --git a/test/models/Nuclear.test.ts b/test/models/Nuclear.test.ts index 7f1b126..1c35c45 100644 --- a/test/models/Nuclear.test.ts +++ b/test/models/Nuclear.test.ts @@ -1,6 +1,5 @@ -import { Particle, Isotope, Term, Expression, Statement, ParseError, check, NuclearAST, exportedForTesting } from "../../src/models/Nuclear"; +import { Particle, Isotope, Term, Expression, Statement, ParseError, check, NuclearAST, exportedForTesting, Result, ASTNode, augmentNode } from "../../src/models/Nuclear"; import { CheckerResponse } from "../../src/models/common"; -const { checkNodesEqual } = exportedForTesting; const original = console.error; @@ -17,30 +16,28 @@ afterEach(() => { // Generic response object const response: CheckerResponse = { containsError: false, - error: { message: "" }, expectedType: "statement", receivedType: "statement", typeMismatch: false, - sameState: true, sameCoefficient: true, sameElements: true, isBalanced: true, isEqual: true, isNuclear: true, + options: {} } // Alternative response object const newResponse: CheckerResponse = { containsError: false, - error: { message: "" }, expectedType: "statement", receivedType: "statement", typeMismatch: false, - sameState: true, sameCoefficient: true, sameElements: true, isBalanced: true, isEqual: true, isNuclear: true, + options: {} } const trueResponse: CheckerResponse = structuredClone(response); @@ -81,13 +78,20 @@ const statement: Statement = { right: structuredClone(particleTerm), } -describe("checkNodesEqual Particle", () => { +function testCheck(target: T, test: T): CheckerResponse { + return check({result: augmentNode(target) as unknown as Result}, {result: augmentNode(test) as unknown as Result}); +} +function unaugmentedTestCheck(target: T, test: T): CheckerResponse { + return check({result: target as unknown as Result}, {result: test as unknown as Result}); +} + +describe("testCheck Particle", () => { it("Returns truthy CheckerResponse when particles match", () => { // Act const particleCopy: Particle = structuredClone(particle) - const testResponse = checkNodesEqual(particleCopy, particle, structuredClone(response)); + const testResponse = testCheck(particleCopy, particle); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -102,7 +106,7 @@ describe("checkNodesEqual Particle", () => { valueMismatch.mass = 0; valueMismatch.atomic = -1; - const elementIncorrect = checkNodesEqual(valueMismatch, particle, structuredClone(response)); + const elementIncorrect = testCheck(valueMismatch, particle); // Assert expect(elementIncorrect.isEqual).toBeFalsy(); @@ -115,7 +119,7 @@ describe("checkNodesEqual Particle", () => { const nucleonMismatch: Particle = structuredClone(particle); nucleonMismatch.particle = "betaparticle"; - const nucleonIncorrect = checkNodesEqual(nucleonMismatch, particle, structuredClone(response)); + const nucleonIncorrect = testCheck(nucleonMismatch, particle); // Assert expect(nucleonIncorrect.isEqual).toBeFalsy(); @@ -124,13 +128,13 @@ describe("checkNodesEqual Particle", () => { ); }); -describe("CheckNodesEqual Isotope", () => { +describe("testCheck Isotope", () => { it("Returns truthy CheckerResponse when isotope match", () => { // Act const isotopeCopy: Isotope = structuredClone(isotope); - const testResponse = checkNodesEqual(isotopeCopy, isotope, structuredClone(response)); + const testResponse = testCheck(isotopeCopy, isotope); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -144,7 +148,7 @@ describe("CheckNodesEqual Isotope", () => { elementMismatch.mass = 35; elementMismatch.atomic = 17; - const elementIncorrect = checkNodesEqual(elementMismatch, isotope, structuredClone(response)); + const elementIncorrect = testCheck(elementMismatch, isotope); // Assert expect(elementIncorrect.isEqual).toBeFalsy(); @@ -156,7 +160,7 @@ describe("CheckNodesEqual Isotope", () => { const nucleonMismatch: Isotope = structuredClone(isotope); nucleonMismatch.element = "Cl"; - const nucleonIncorrect = checkNodesEqual(nucleonMismatch, isotope, structuredClone(response)); + const nucleonIncorrect = testCheck(nucleonMismatch, isotope); // Assert expect(nucleonIncorrect.isEqual).toBeFalsy(); @@ -165,12 +169,12 @@ describe("CheckNodesEqual Isotope", () => { ); }); -describe("CheckNodesEqual Term", () => { +describe("testCheck Term", () => { it("Returns truthy CheckerResponse when terms match", () => { let termCopy: Term = structuredClone(term); - let testResponse = checkNodesEqual(termCopy, term, structuredClone(response)) + let testResponse = testCheck(termCopy, term) expect(testResponse.isEqual).toBeTruthy(); } ); @@ -180,11 +184,11 @@ describe("CheckNodesEqual Term", () => { let mismatchTerm: Term = structuredClone(term); mismatchTerm.coeff = 3; - expect(checkNodesEqual(mismatchTerm, term, structuredClone(response)).isEqual).toBeFalsy(); - expect(checkNodesEqual(mismatchTerm, term, structuredClone(response)).sameCoefficient).toBeFalsy(); + expect(testCheck(mismatchTerm, term).isEqual).toBeFalsy(); + expect(testCheck(mismatchTerm, term).sameCoefficient).toBeFalsy(); // Mismatched type - expect(checkNodesEqual(particleTerm, term, structuredClone(response)).isEqual).toBeFalsy(); + expect(testCheck(particleTerm, term).isEqual).toBeFalsy(); } ); it("Retains CheckerResponse properties", @@ -201,13 +205,11 @@ describe("CheckNodesEqual Term", () => { const particleCopyTerm: Term = structuredClone(particleTerm); particleCopyTerm.value = particleCopy; - const isotopeResponse = checkNodesEqual(isotopeError, isotope, structuredClone(response)); - const particleResponse = checkNodesEqual(particleCopy, particle, structuredClone(response)); + const isotopeResponse = testCheck(isotopeError, isotope); + const particleResponse = testCheck(particleCopy, particle); - const isotopeTermResponse = - checkNodesEqual(isotopeErrorTerm, term, structuredClone(response)) - const particleTermResponse = - checkNodesEqual(particleCopyTerm, particleTerm, structuredClone(response)) + const isotopeTermResponse = testCheck(isotopeErrorTerm, term) + const particleTermResponse = testCheck(particleCopyTerm, particleTerm) // Assert expect(isotopeTermResponse.validAtomicNumber).toBe(isotopeResponse.validAtomicNumber); @@ -216,14 +218,14 @@ describe("CheckNodesEqual Term", () => { ); }); -describe("CheckNodesEqual Expression", () => { +describe("testCheck Expression", () => { it("Returns truthy CheckerResponse when expressions match", () => { // Act const permutedExpression: Expression = structuredClone(expression); permutedExpression.terms?.reverse; - const testResponse: CheckerResponse = checkNodesEqual(permutedExpression, expression, structuredClone(response)); + const testResponse: CheckerResponse = testCheck(permutedExpression, expression); // Assert expect(testResponse.isEqual).toBeTruthy(); } @@ -238,8 +240,8 @@ describe("CheckNodesEqual Expression", () => { if (termMismatch.terms) termMismatch.terms[1] = structuredClone(term); // Assert - expect(checkNodesEqual(lengthMismatch, expression, structuredClone(response)).isEqual).toBeFalsy(); - expect(checkNodesEqual(termMismatch, expression, structuredClone(response)).isEqual).toBeFalsy(); + expect(testCheck(lengthMismatch, expression).isEqual).toBeFalsy(); + expect(testCheck(termMismatch, expression).isEqual).toBeFalsy(); } ); it("Returns an error if the AST is not augmented", @@ -253,23 +255,21 @@ describe("CheckNodesEqual Expression", () => { } // Assert - expect(checkNodesEqual(unaugmentedExpression, expression, structuredClone(response)).containsError).toBeTruthy(); - expect(checkNodesEqual(unaugmentedExpression, expression, structuredClone(response)).error).toEqual( - { message: "Received unaugmented AST during checking process." } - ); + expect(unaugmentedTestCheck(unaugmentedExpression, expression).containsError).toBeTruthy(); + expect(unaugmentedTestCheck(unaugmentedExpression, expression).error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } ); }); -describe("CheckNodesEqual Statement", () => { +describe("testCheck Statement", () => { it("Returns truthy CheckerResponse when expressions match", () => { // Act const copy: Statement = structuredClone(statement); - const copyResult: CheckerResponse = checkNodesEqual(copy, statement, structuredClone(response)); + const copyResult: CheckerResponse = testCheck(copy, statement); // Assert expect(copyResult.isEqual).toBeTruthy(); @@ -283,7 +283,7 @@ describe("CheckNodesEqual Statement", () => { swappedExpressions.left = swappedExpressions.right; swappedExpressions.right = tempExpression; - const swapResult = checkNodesEqual(swappedExpressions, statement, structuredClone(response)); + const swapResult = testCheck(swappedExpressions, statement); // Assert expect(swapResult.isEqual).toBeFalsy(); @@ -296,8 +296,8 @@ describe("CheckNodesEqual Statement", () => { balancedStatement.right = structuredClone(term); // Act - const balancedResponse = checkNodesEqual(balancedStatement, balancedStatement, structuredClone(response)); - const unbalancedResponse = checkNodesEqual(statement, statement, structuredClone(response)); + const balancedResponse = testCheck(balancedStatement, balancedStatement); + const unbalancedResponse = testCheck(statement, balancedStatement); // Assert expect(balancedResponse.isBalanced).toBeTruthy(); @@ -336,8 +336,8 @@ describe("Check", () => { const response: CheckerResponse = check(errorAST, ast); // Assert - expect(response.error).toBeDefined(); - expect(response.error?.message).toBe("Sphinx of black quartz, judge my vow"); + expect(response.containsError).toBeTruthy(); + expect(response.error).toBe("Sphinx of black quartz, judge my vow"); expect(response.expectedType).toBe("statement"); } ); From d7491be169632d8f63bae90448b479d1516a0fab Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:29:04 +0000 Subject: [PATCH 05/11] Restructure and fix newly broken Chemistry tests --- src/models/Chemistry.ts | 7 ++- src/models/common.ts | 1 + test/models/Chemistry.test.ts | 114 ++++++++++++++++------------------ 3 files changed, 59 insertions(+), 63 deletions(-) diff --git a/src/models/Chemistry.ts b/src/models/Chemistry.ts index 85499a9..148fad6 100644 --- a/src/models/Chemistry.ts +++ b/src/models/Chemistry.ts @@ -123,6 +123,7 @@ const STARTING_RESPONSE: (options?: ChemistryOptions, coefficientScalingValue?: sameCoefficient: true, sameElements: true, sameState: true, + sameHydrate: true, sameCharge: true, sameArrow: true, sameBrackets: true, @@ -493,7 +494,7 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon } newResponse.sameState = newResponse.sameState && test.state === target.state; - // TODO: add a new property stating the hydrate was wrong? + newResponse.sameHydrate = newResponse.sameHydrate && test.isHydrate === target.isHydrate && test.hydrate === target.hydrate; // Add the term's atomCount (* coefficient) to the overall expression atomCount if (newResponse.termAtomCount) { @@ -579,7 +580,7 @@ export function check(test: ChemAST, target: ChemAST, options: ChemistryOptions) response.expectedType = target.result.type; response.receivedType = test.result.type; - if (isEqual(test.result, target.result)) { + if (isEqual(test.result, target.result) && !options.keepAggregates) { return response; } @@ -609,7 +610,7 @@ export function check(test: ChemAST, target: ChemAST, options: ChemistryOptions) let newResponse = checkNodesEqual(test.result, target.result, response); // We set flags for these properties in checkNodesEqual, but we only apply the isEqual check here due to listComparison - newResponse.isEqual = newResponse.isEqual && newResponse.sameCoefficient && (newResponse.sameState == true) && (newResponse.sameBrackets == true); + newResponse.isEqual = newResponse.isEqual && newResponse.sameCoefficient && (newResponse.sameState === true) && (newResponse.sameBrackets === true) && (newResponse.sameHydrate === true); if (!newResponse.options?.keepAggregates) { newResponse = removeAggregates(newResponse); diff --git a/src/models/common.ts b/src/models/common.ts index fe5286d..d0ec383 100644 --- a/src/models/common.ts +++ b/src/models/common.ts @@ -24,6 +24,7 @@ export interface CheckerResponse { sameElements: boolean; // properties dependent on type sameState?: boolean; + sameHydrate?: boolean; sameCharge?: boolean; sameArrow?: boolean; sameBrackets?: boolean; diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 9cf8597..88c8605 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -83,6 +83,7 @@ const ion: Ion = { charge: -1, chain: { type: "ion", molecule: {type: "element", value: "Na", coeff: 1 }, charge: 1 } }; +const augmentedIon = augmentNode(structuredClone(ion)); const term: Term = { type: "term", @@ -111,8 +112,9 @@ hydrate2.hydrate = 3; const expression: Expression = { type: "expr", term: structuredClone(hydrate), - terms: [structuredClone(hydrate), structuredClone(hydrate2)] + rest: structuredClone(hydrate2) } +const augmentedExpression: Expression = augmentNode(structuredClone(expression)); const statement: Statement = { type: "statement", left: structuredClone(hydrate), @@ -124,7 +126,7 @@ const ast: ChemAST = { } function testCheck(target: T, test: T, options?: ChemistryOptions): CheckerResponse { - return check({result: augmentNode(target) as unknown as Result}, {result: augmentNode(test) as unknown as Result}, options ?? {}); + return check({result: augmentNode(structuredClone(target)) as unknown as Result}, {result: augmentNode(structuredClone(test)) as unknown as Result}, options ?? {}); } function unaugmentedTestCheck(target: T, test: T, options?: ChemistryOptions): CheckerResponse { @@ -195,7 +197,7 @@ describe("testCheck Elements", () => { // Act const elementCopy: Element = structuredClone(element) - const testResponse = testCheck(elementCopy, element); + const testResponse = testCheck(elementCopy, element, { keepAggregates: true }); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -211,16 +213,14 @@ describe("testCheck Elements", () => { const coefficientMismatch: Element = structuredClone(element); coefficientMismatch.coeff = 2; - const elementIncorrect = testCheck(valueMismatch, element); - const coeffIncorrect = testCheck(coefficientMismatch, element); + const elementIncorrect = testCheck(valueMismatch, element, { keepAggregates: true }); + const coeffIncorrect = testCheck(coefficientMismatch, element, { keepAggregates: true }); // Assert expect(elementIncorrect.isEqual).toBeFalsy(); - expect(elementIncorrect.sameCoefficient).toBeTruthy(); expect(elementIncorrect.termAtomCount?.O).toBe(1); expect(coeffIncorrect.isEqual).toBeFalsy(); - expect(coeffIncorrect.sameCoefficient).toBeFalsy(); expect(coeffIncorrect.termAtomCount?.C).toBe(2); } ); @@ -232,11 +232,10 @@ describe("testCheck Brackets", () => { // Act const bracketCopy: Bracket = structuredClone(bracket); - const testResponse = testCheck(bracketCopy, bracket); + const testResponse = testCheck(bracketCopy, bracket, { keepAggregates: true }); // Assert expect(testResponse.isEqual).toBeTruthy(); - expect(testResponse.sameCoefficient).toBeTruthy(); expect(testResponse.termAtomCount?.O).toBe(1); } ); @@ -246,11 +245,10 @@ describe("testCheck Brackets", () => { const coefficientMismatch: Bracket = structuredClone(bracket); coefficientMismatch.coeff = 2; - const coeffIncorrect = testCheck(coefficientMismatch, bracket); + const coeffIncorrect = testCheck(coefficientMismatch, bracket, { keepAggregates: true }); // Assert expect(coeffIncorrect.isEqual).toBeFalsy(); - expect(coeffIncorrect.sameCoefficient).toBeFalsy(); expect(coeffIncorrect.termAtomCount?.O).toBe(2); } ); @@ -262,7 +260,7 @@ describe("testCheck Compounds", () => { // Act const compoundCopy: Compound = structuredClone(compound); - const testResponse = testCheck(compoundCopy, structuredClone(compound)); + const testResponse = testCheck(compoundCopy, structuredClone(compound), { keepAggregates: true}); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -288,12 +286,12 @@ describe("testCheck Compounds", () => { () => { // Act const typeMismatch: Compound = structuredClone(compound); - typeMismatch.elements = [structuredClone(element), structuredClone(element)] - const lengthMismatch: Compound = structuredClone(compound); + typeMismatch.elements = [structuredClone(element),structuredClone(element)]; + const lengthMismatch: Compound = augmentNode(structuredClone(compound)); lengthMismatch.elements?.push(structuredClone(element)); - const typesIncorrect = testCheck(typeMismatch, structuredClone(compound)); - const lengthIncorrect = testCheck(lengthMismatch, structuredClone(compound)); + const typesIncorrect = unaugmentedTestCheck(typeMismatch, augmentNode(structuredClone(compound))); + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentNode(structuredClone(compound))); // Assert expect(typesIncorrect.isEqual).toBeFalsy(); @@ -322,8 +320,8 @@ describe("testCheck Compounds", () => { const elementResponse: CheckerResponse = testCheck(elementCoeffMismatch, structuredClone(element)); // Assert - expect(testCheck(bracketMismatch, minimalBracketCompound)).toEqual({...bracketResponse, sameElements: true}); - expect(testCheck(elementMismatch, minimalCompound)).toEqual({...elementResponse, sameElements: false}); + expect(testCheck(bracketMismatch, minimalBracketCompound)).toEqual({...bracketResponse, expectedType: "compound", receivedType: "compound"}); + expect(testCheck(elementMismatch, minimalCompound)).toEqual({...elementResponse, expectedType: "compound", receivedType: "compound"}); } ); it("Returns an error if the AST is not augmented", @@ -337,8 +335,8 @@ describe("testCheck Compounds", () => { }; // Assert - expect(testCheck(unaugmentedCompound, compound).containsError).toBeTruthy(); - expect(testCheck(unaugmentedCompound, compound).error).toEqual("Received unaugmented AST during checking process."); + expect(unaugmentedTestCheck(unaugmentedCompound, compound, { keepAggregates: true }).containsError).toBeTruthy(); + expect(unaugmentedTestCheck(unaugmentedCompound, compound, { keepAggregates: true }).error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } @@ -351,13 +349,14 @@ describe("testCheck Ions", () => { // Act const ionClone: Ion = structuredClone(ion); - const testResponse = testCheck(structuredClone(ion), ionClone); + const testResponse = testCheck(structuredClone(ion), ionClone, { keepAggregates: true }); // Assert expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.termAtomCount?.O).toBe(1); expect(testResponse.termAtomCount?.Na).toBe(1); - expect(testResponse.chargeCount).toBe(0); + expect(testResponse.termAtomCount?.C).toBe(1); + expect(testResponse.termChargeCount).toBe(0); } ); it("Returns truthy CheckerResponse when a permutation of ions match with allowPermutations", @@ -372,54 +371,54 @@ describe("testCheck Ions", () => { // Assert expect(testResponse.isEqual).toBeTruthy(); - expect(testResponse.chargeCount).toBe(0); + expect(testResponse.termChargeCount).toBe(0); } ); it("Returns falsy CheckerResponse when ions do not match", () => { - const moleculeMismatch: Ion = augmentNode(structuredClone(ion)); + const moleculeMismatch: Ion = structuredClone(augmentedIon); if (moleculeMismatch.molecules) { moleculeMismatch.molecules[0] = [{ type: "element", value: "Cl", coeff: 1 }, -1]; } - const chargeMismatch: Ion = augmentNode(structuredClone(ion)); + const chargeMismatch: Ion = structuredClone(augmentedIon); if (chargeMismatch.molecules) { chargeMismatch.molecules[0] = [{ type: "element", value: "Na", coeff: 1 }, -1] } - const lengthMismatch: Ion = augmentNode(structuredClone(ion)); + const lengthMismatch: Ion = structuredClone(augmentedIon); lengthMismatch.molecules?.push([structuredClone(element), 1]); - const moleculeIncorrect = unaugmentedTestCheck(moleculeMismatch, augmentNode(structuredClone(ion))); - const chargeIncorrect = unaugmentedTestCheck(chargeMismatch, augmentNode(structuredClone(ion))); - const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentNode(structuredClone(ion))); + const moleculeIncorrect = unaugmentedTestCheck(moleculeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); + const chargeIncorrect = unaugmentedTestCheck(chargeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, structuredClone(augmentedIon)); // Assert expect(moleculeIncorrect.isEqual).toBeFalsy(); expect(moleculeIncorrect.typeMismatch).toBeFalsy(); + expect(moleculeIncorrect.termAtomCount?.Cl).toBe(1); expect(chargeIncorrect.isEqual).toBeFalsy(); - expect(chargeIncorrect.chargeCount).toBe(-2); + expect(chargeIncorrect.termChargeCount).toBe(-1); expect(lengthIncorrect.isEqual).toBeFalsy(); } ); it("Returns an error if the AST is not augmented", () => { // Act - // This is the same as ion just not augmented const unaugmentedIon: Ion = { type: "ion", molecule: structuredClone(compound), charge: -1, chain: { type: "ion", - molecule: { type: "element", value: "Na", coeff: 1 }, - charge: 1 + molecule: { type: "element", value: "Cl", coeff: 1 }, + charge: -1 } }; // Assert - expect(testCheck(unaugmentedIon, ion).containsError).toBeTruthy(); - expect(testCheck(unaugmentedIon, ion).error).toEqual("Received unaugmented AST during checking process."); + expect(unaugmentedTestCheck(unaugmentedIon, ion).containsError).toBeTruthy(); + expect(unaugmentedTestCheck(unaugmentedIon, ion).error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } @@ -428,10 +427,10 @@ describe("testCheck Ions", () => { describe("testCheck Term", () => { // TODO: Separate into different tests - it("Returns truthy CheckerResponse when terms match with allowScalingCoefficients", + it("Returns truthy CheckerResponse when terms match with allowScalingCoefficients", () => { // Act - const scaledOptions = { allowScalingCoefficients: true }; + const scaledOptions = { allowScalingCoefficients: true, keepAggregates: true }; const perturbedTerm: Term = structuredClone(term); perturbedTerm.coeff = { numerator: 6, denominator: 4 }; @@ -525,13 +524,8 @@ describe("testCheck Term", () => { const testResponse = testCheck(complexTerm, structuredClone(term)); const compoundResponse = testCheck(structuredClone(compound), minimalCompound); - const expectedResponse = structuredClone(compoundResponse); - - expectedResponse.atomCount = {"C": {"numerator": 3, "denominator": 2}, "O": {"numerator": 3, "denominator": 2}} as Record; - expectedResponse.termAtomCount = {} as Record; - // Assert - expect(testResponse).toEqual(expectedResponse); + expect(testResponse).toEqual({...compoundResponse, expectedType: "term", receivedType: "term"}); } ); }); @@ -540,10 +534,11 @@ describe("testCheck Expression", () => { it("Returns truthy CheckerResponse when expressions match", () => { // Act - const permutedExpression: Expression = structuredClone(expression); + const permutedExpression: Expression = structuredClone(augmentedExpression); permutedExpression.terms?.reverse; + permutedExpression.term = permutedExpression.terms ? permutedExpression.terms[0] : permutedExpression.term; - const testResponse: CheckerResponse = testCheck(permutedExpression, expression); + const testResponse: CheckerResponse = unaugmentedTestCheck(permutedExpression, augmentedExpression, { keepAggregates: true }); // Assert expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 1}); @@ -552,15 +547,15 @@ describe("testCheck Expression", () => { it("Returns falsy CheckerResponse when expressions do not match", () => { // Act - const lengthMismatch: Expression = structuredClone(expression); + const lengthMismatch: Expression = structuredClone(augmentedExpression); lengthMismatch.terms?.push(structuredClone(hydrate)); - const termMismatch: Expression = structuredClone(expression); + const termMismatch: Expression = structuredClone(augmentedExpression); if (termMismatch.terms) termMismatch.terms[1] = structuredClone(hydrate); // Assert - expect(testCheck(lengthMismatch, expression).isEqual).toBeFalsy(); - expect(testCheck(termMismatch, expression).isEqual).toBeFalsy(); + expect(unaugmentedTestCheck(lengthMismatch, augmentedExpression).isEqual).toBeFalsy(); + expect(unaugmentedTestCheck(termMismatch, augmentedExpression).isEqual).toBeFalsy(); } ); it("Retains CheckerResponse properties", @@ -577,17 +572,16 @@ describe("testCheck Expression", () => { const hydrateResponse: CheckerResponse = testCheck(hydrate, hydrate2); // Assert - expect(testCheck(mismatchedHydrate, adjustedExpression)).toEqual(hydrateResponse); + expect(testCheck(mismatchedHydrate, adjustedExpression)).toEqual({...hydrateResponse, expectedType: "expr", receivedType: "expr"}); } ); it("Returns an error if the AST is not augmented", () => { // Act - // This is the same as expression just unaugmented const unaugmentedExpression: Expression = { type: "expr", term: structuredClone(hydrate), - rest: structuredClone(hydrate2) + rest: structuredClone(hydrate) } // Assert @@ -647,33 +641,33 @@ describe("testCheck Statement", () => { unbalancedStatement.right = structuredClone(expression); // Assert - expect((testCheck(statement, statement)).isBalanced).toBeTruthy(); + expect((testCheck(statement, unbalancedStatement)).isBalanced).toBeTruthy(); expect((testCheck(unbalancedStatement, statement)).isBalanced).toBeFalsy(); } ); it("Correctly checks whether charges are balanced", () => { // Arrange - const chargedIon: Ion = structuredClone(ion); + const chargedIon: Ion = augmentedIon; chargedIon.molecules = [[structuredClone(element), 1]]; - const chargedTerm: Term = structuredClone(term); + const chargedTerm: Term = augmentNode(structuredClone(term)); chargedTerm.value = chargedIon; // expression is otherwise neutral - const chargedExpr: Expression = structuredClone(expression); + const chargedExpr: Expression = augmentedExpression; chargedExpr.terms?.push(chargedTerm); - const balancedCharges: Statement = structuredClone(statement); + const balancedCharges: Statement = augmentNode(structuredClone(statement)); balancedCharges.left = structuredClone(chargedExpr); balancedCharges.right = structuredClone(chargedExpr); - const unbalancedCharges: Statement = structuredClone(statement); + const unbalancedCharges: Statement = augmentNode(structuredClone(statement)); unbalancedCharges.left = structuredClone(chargedExpr); // Assert - expect(testCheck(balancedCharges, balancedCharges).isChargeBalanced).toBeTruthy(); - expect(testCheck(unbalancedCharges, unbalancedCharges).isChargeBalanced).toBeFalsy(); + expect(unaugmentedTestCheck(balancedCharges, unbalancedCharges).isChargeBalanced).toBeTruthy(); + expect(unaugmentedTestCheck(unbalancedCharges, balancedCharges).isChargeBalanced).toBeFalsy(); } ); }); From 68a038f8d6e72e8683193b5fa89ea6e41d57b250 Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Tue, 26 Nov 2024 11:54:38 +0000 Subject: [PATCH 06/11] Remove additional sameHydrate from merge --- src/models/Chemistry.ts | 1 - test/models/Chemistry.test.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/Chemistry.ts b/src/models/Chemistry.ts index 2dc8ba6..80e3cda 100644 --- a/src/models/Chemistry.ts +++ b/src/models/Chemistry.ts @@ -124,7 +124,6 @@ const STARTING_RESPONSE: (options?: ChemistryOptions, coefficientScalingValue?: sameHydrate: true, sameElements: true, sameState: true, - sameHydrate: true, sameCharge: true, sameArrow: true, sameBrackets: true, diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 88c8605..18bebc6 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -398,7 +398,7 @@ describe("testCheck Ions", () => { expect(moleculeIncorrect.typeMismatch).toBeFalsy(); expect(moleculeIncorrect.termAtomCount?.Cl).toBe(1); expect(chargeIncorrect.isEqual).toBeFalsy(); - expect(chargeIncorrect.termChargeCount).toBe(-1); + expect(chargeIncorrect.termChargeCount).toBe(-2); expect(lengthIncorrect.isEqual).toBeFalsy(); } ); From 42fa53af3b3987203f5a473fcf55b89488b0a0c5 Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:13:39 +0000 Subject: [PATCH 07/11] Add scaling coefficient tests --- test/models/Chemistry.test.ts | 153 ++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 73 deletions(-) diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 18bebc6..8ddba35 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -1,5 +1,5 @@ -import { Bracket, Compound, Element, exportedForTesting, Ion, Term, Expression, Statement, ParseError, check, ChemAST, augment, STARTING_COEFFICIENT, Result, ASTNode } from "../../src/models/Chemistry"; -import { CheckerResponse, listComparison, ChemistryOptions, Fraction, ChemicalSymbol } from "../../src/models/common"; +import { Bracket, Compound, Element, exportedForTesting, Ion, Term, Expression, Statement, ParseError, check, ChemAST, augment, STARTING_COEFFICIENT, Result, ASTNode, isExpression } from "../../src/models/Chemistry"; +import { CheckerResponse, listComparison, ChemistryOptions } from "../../src/models/common"; const { augmentNode } = exportedForTesting; import { parseChemistryExpression } from "inequality-grammar"; @@ -13,7 +13,7 @@ afterEach(() => { console.error = original; }) -// TODO: Add augmenting tests + const options: ChemistryOptions = { allowPermutations: false, allowScalingCoefficients: false @@ -77,6 +77,8 @@ const compound: Compound = { head: structuredClone(element), tail: structuredClone(bracket) }; +const augmentedCompound: Compound = augmentNode(structuredClone(compound)); + const ion: Ion = { type: "ion", molecule: structuredClone(compound), @@ -115,12 +117,15 @@ const expression: Expression = { rest: structuredClone(hydrate2) } const augmentedExpression: Expression = augmentNode(structuredClone(expression)); + const statement: Statement = { type: "statement", - left: structuredClone(hydrate), - right: structuredClone(hydrate2), + left: structuredClone(expression), + right: structuredClone(expression), arrow: "SArr" } +const augmentedStatement: Statement = augmentNode(structuredClone(statement)); + const ast: ChemAST = { result: structuredClone(statement) } @@ -260,7 +265,7 @@ describe("testCheck Compounds", () => { // Act const compoundCopy: Compound = structuredClone(compound); - const testResponse = testCheck(compoundCopy, structuredClone(compound), { keepAggregates: true}); + const testResponse = testCheck(compoundCopy, structuredClone(compound), { keepAggregates: true }); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -273,20 +278,25 @@ describe("testCheck Compounds", () => { // Act const permutedCompound: Compound = structuredClone(compound); permutedCompound.elements?.reverse; + + const hydrocarbonCompound: ASTNode = structuredClone(parseChemistryExpression("C10H22")[0].result); + const permutedHydrocarbonCompound: ASTNode = structuredClone(parseChemistryExpression("CH3(CH2)8CH3")[0].result); const permutationsOptions = { allowPermutations: true }; const testResponse = testCheck(permutedCompound, structuredClone(compound), permutationsOptions); + const hydrocarbonResponse = testCheck(hydrocarbonCompound, permutedHydrocarbonCompound, permutationsOptions); // Assert expect(testResponse.isEqual).toBeTruthy(); + expect(hydrocarbonResponse.isEqual).toBeTruthy(); } ); it("Returns falsy CheckerResponse when compounds don't match", () => { // Act const typeMismatch: Compound = structuredClone(compound); - typeMismatch.elements = [structuredClone(element),structuredClone(element)]; + typeMismatch.elements = [structuredClone(element), structuredClone(element)]; const lengthMismatch: Compound = augmentNode(structuredClone(compound)); lengthMismatch.elements?.push(structuredClone(element)); @@ -326,17 +336,9 @@ describe("testCheck Compounds", () => { ); it("Returns an error if the AST is not augmented", () => { - // Act - // This is the same as compound just not augmented - const unaugmentedCompound: Compound = { - type: "compound", - head: { type: "element", value: "O", coeff: 2 }, - tail: structuredClone(bracket) - }; - // Assert - expect(unaugmentedTestCheck(unaugmentedCompound, compound, { keepAggregates: true }).containsError).toBeTruthy(); - expect(unaugmentedTestCheck(unaugmentedCompound, compound, { keepAggregates: true }).error).toEqual("Received unaugmented AST during checking process."); + expect(unaugmentedTestCheck(structuredClone(compound), augmentedCompound, { keepAggregates: true }).containsError).toBeTruthy(); + expect(unaugmentedTestCheck(structuredClone(compound), augmentedCompound, { keepAggregates: true }).error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } @@ -374,7 +376,7 @@ describe("testCheck Ions", () => { expect(testResponse.termChargeCount).toBe(0); } ); - it("Returns falsy CheckerResponse when ions do not match", + it("Returns falsy CheckerResponse when ions don't match", () => { const moleculeMismatch: Ion = structuredClone(augmentedIon); if (moleculeMismatch.molecules) { @@ -391,7 +393,7 @@ describe("testCheck Ions", () => { const moleculeIncorrect = unaugmentedTestCheck(moleculeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); const chargeIncorrect = unaugmentedTestCheck(chargeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); - const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, structuredClone(augmentedIon)); + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, structuredClone(augmentedIon), { keepAggregates: true}); // Assert expect(moleculeIncorrect.isEqual).toBeFalsy(); @@ -404,21 +406,9 @@ describe("testCheck Ions", () => { ); it("Returns an error if the AST is not augmented", () => { - // Act - const unaugmentedIon: Ion = { - type: "ion", - molecule: structuredClone(compound), - charge: -1, - chain: { - type: "ion", - molecule: { type: "element", value: "Cl", coeff: 1 }, - charge: -1 - } - }; - // Assert - expect(unaugmentedTestCheck(unaugmentedIon, ion).containsError).toBeTruthy(); - expect(unaugmentedTestCheck(unaugmentedIon, ion).error).toEqual("Received unaugmented AST during checking process."); + expect(unaugmentedTestCheck(structuredClone(ion), augmentedIon).containsError).toBeTruthy(); + expect(unaugmentedTestCheck(structuredClone(ion), augmentedIon).error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } @@ -426,23 +416,6 @@ describe("testCheck Ions", () => { }); describe("testCheck Term", () => { - // TODO: Separate into different tests - it("Returns truthy CheckerResponse when terms match with allowScalingCoefficients", - () => { - // Act - const scaledOptions = { allowScalingCoefficients: true, keepAggregates: true }; - - const perturbedTerm: Term = structuredClone(term); - perturbedTerm.coeff = { numerator: 6, denominator: 4 }; - - const testResponse = testCheck(perturbedTerm, structuredClone(term), scaledOptions); - - // Assert - expect(testResponse.isEqual).toBeTruthy(); - expect(testResponse.sameState).toBeTruthy(); - expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 2}); - } - ); it("Returns truthy CheckerResponse when terms have equal states", () => { // Act @@ -481,6 +454,21 @@ describe("testCheck Term", () => { expect(testCheck(electronTermCopy, electronTerm).isEqual).toBeTruthy(); } ); + it("Returns truthy CheckerResponse when terms match with allowScalingCoefficients", + () => { + // Act + const scaledOptions = { allowScalingCoefficients: true, keepAggregates: true }; + + const perturbedTerm: Term = structuredClone(term); + perturbedTerm.coeff = { numerator: 6, denominator: 4 }; + + const testResponse = testCheck(perturbedTerm, structuredClone(term), scaledOptions); + + // Assert + expect(testResponse.isEqual).toBeTruthy(); + expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 2}); + } + ); it("Returns falsy CheckerResponse when terms don't match", () => { // Mismatched coefficients @@ -544,7 +532,25 @@ describe("testCheck Expression", () => { expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 1}); } ); - it("Returns falsy CheckerResponse when expressions do not match", + it("Returns truthy CheckerResponse when expressions match with allowScalingCoefficients", + () => { + // Act + const scaledOptions = { allowScalingCoefficients: true, keepAggregates: true }; + + const perturbedExpression: Expression = structuredClone(augmentedExpression); + if (perturbedExpression.terms) { + perturbedExpression.terms = [{...perturbedExpression.terms[0], coeff: { numerator: 10, denominator: 5} }, + {...perturbedExpression.terms[1], coeff: { numerator: 16, denominator: 8} }]; + } + + const testResponse = unaugmentedTestCheck(perturbedExpression, structuredClone(augmentedExpression), scaledOptions); + + // Assert + expect(testResponse.isEqual).toBeTruthy(); + expect(testResponse.atomCount?.O).toEqual({"numerator": 4, "denominator": 1}); + } + ); + it("Returns falsy CheckerResponse when expressions don't match", () => { // Act const lengthMismatch: Expression = structuredClone(augmentedExpression); @@ -577,16 +583,9 @@ describe("testCheck Expression", () => { ); it("Returns an error if the AST is not augmented", () => { - // Act - const unaugmentedExpression: Expression = { - type: "expr", - term: structuredClone(hydrate), - rest: structuredClone(hydrate) - } - // Assert - expect(unaugmentedTestCheck(unaugmentedExpression, expression).containsError).toBeTruthy(); - expect(unaugmentedTestCheck(unaugmentedExpression, expression).error).toEqual("Received unaugmented AST during checking process."); + expect(unaugmentedTestCheck(structuredClone(expression), augmentedExpression).containsError).toBeTruthy(); + expect(unaugmentedTestCheck(structuredClone(expression), augmentedExpression).error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } @@ -612,7 +611,27 @@ describe("testCheck Statement", () => { expect(arrowResult.sameArrow).toBeTruthy(); } ); - it("Returns falsy CheckerResponse when expressions do not match", + it("Returns truthy CheckerResponse when statements match with allowScalingCoefficients", + () => { + // Act + const scaledOptions = { allowScalingCoefficients: true, keepAggregates: true }; + + const perturbedStatement: Statement = structuredClone(augmentedStatement); + if (isExpression(perturbedStatement.left) && perturbedStatement.left.terms && isExpression(perturbedStatement.right) && perturbedStatement.right.terms) { + perturbedStatement.left.terms = [{...perturbedStatement.left.terms[0], coeff: { numerator: 1, denominator: 3} }, + {...perturbedStatement.left.terms[1], coeff: { numerator: 2, denominator: 6} }]; + perturbedStatement.right.terms = [{...perturbedStatement.right.terms[0], coeff: { numerator: 3, denominator: 9} }, + {...perturbedStatement.right.terms[1], coeff: { numerator: 1, denominator: 3} }]; + } + + const testResponse = unaugmentedTestCheck(perturbedStatement, structuredClone(augmentedStatement), scaledOptions); + + // Assert + expect(testResponse.isEqual).toBeTruthy(); + expect(testResponse.atomCount?.O).toEqual({"numerator": 2, "denominator": 3}); + } + ); + it("Returns falsy CheckerResponse when expressions don't match", () => { // Act const swappedExpressions: Statement = structuredClone(statement); @@ -706,16 +725,4 @@ describe("Check", () => { expect(response.expectedType).toBe("expr"); } ); - - it("Allows molecule permutations when allowPermutations set", () => { - // Act - const unchangedAST: ChemAST = augment(parseChemistryExpression("C10H22")[0]); - const permutedAST: ChemAST = augment(parseChemistryExpression("CH3(CH2)8CH3")[0]); - - const permutedResponse: CheckerResponse = check(unchangedAST, permutedAST, { ...options, allowPermutations: true }); - const unpermutedResponse: CheckerResponse = check(unchangedAST, permutedAST, options); - // Assert - expect(permutedResponse.isEqual).toBeTruthy(); - expect(unpermutedResponse.isEqual).toBeFalsy(); - }); }); From 50ff875bc2c7a4e249bacfdd6237355e70b9ee60 Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:15:22 +0000 Subject: [PATCH 08/11] Convert Chemistry to Arrange/Act/Assert structure --- test/models/Chemistry.test.ts | 302 +++++++++++++++++++++++++--------- 1 file changed, 221 insertions(+), 81 deletions(-) diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 8ddba35..5ed525e 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -1,3 +1,4 @@ +import exp from "constants"; import { Bracket, Compound, Element, exportedForTesting, Ion, Term, Expression, Statement, ParseError, check, ChemAST, augment, STARTING_COEFFICIENT, Result, ASTNode, isExpression } from "../../src/models/Chemistry"; import { CheckerResponse, listComparison, ChemistryOptions } from "../../src/models/common"; const { augmentNode } = exportedForTesting; @@ -124,6 +125,9 @@ const statement: Statement = { right: structuredClone(expression), arrow: "SArr" } +if ("rest" in statement.right && statement.right.rest && "hydrate" in statement.right.rest) { + statement.right.rest.hydrate = 1; +} const augmentedStatement: Statement = augmentNode(structuredClone(statement)); const ast: ChemAST = { @@ -156,36 +160,39 @@ describe("listComparison", () => { it("Returns truthy CheckerResponse when lists match", () => { - // Act + // Arrange const l1: number[] = [1, 2, 3, 4]; const l2: number[] = [2, 4, 1, 3]; + // Act + const testComparison = listComparison(l1, l2, structuredClone(response), comparatorNumberMock) + // Assert - expect( - listComparison(l1, l2, structuredClone(response), comparatorNumberMock).isEqual - ).toBeTruthy(); + expect(testComparison.isEqual).toBeTruthy(); } ); it("Returns falsy CheckerResponse when lists don't match", () => { - // Act + // Arrange const l1: number[] = [1, 2, 3, 4]; const l2: number[] = [2, 5, 1, 3]; + // Act + const testComparison = listComparison(l1, l2, structuredClone(response), comparatorNumberMock) + // Assert - expect( - listComparison(l1, l2, structuredClone(response), comparatorNumberMock).isEqual - ).toBeFalsy(); + expect(testComparison.isEqual).toBeFalsy(); } ); it("Returns updated CheckerResponse if it changes", () => { - // Act + // Arrange const match1: number[] = [1, 2, 3, 4]; const match2: number[] = [2, 4, 1, 3]; const mismatch1: number[] = [1, 2, 3, 4]; const mismatch2: number[] = [2, 5, 1, 3]; + // Act const matchResponse = listComparison(match1, match2, structuredClone(response), comparatorResponseChangeMock); const mismatchResponse = listComparison(mismatch1, mismatch2, structuredClone(response), comparatorResponseChangeMock); @@ -199,9 +206,10 @@ describe("listComparison", () => { describe("testCheck Elements", () => { it("Returns truthy CheckerResponse when elements match", () => { - // Act + // Arrange const elementCopy: Element = structuredClone(element) + // Act const testResponse = testCheck(elementCopy, element, { keepAggregates: true }); // Assert @@ -212,12 +220,13 @@ describe("testCheck Elements", () => { ); it("Returns falsy CheckerResponse when elements don't match", () => { - // Act + // Arrange const valueMismatch: Element = structuredClone(element); valueMismatch.value = "O"; const coefficientMismatch: Element = structuredClone(element); coefficientMismatch.coeff = 2; + // Act const elementIncorrect = testCheck(valueMismatch, element, { keepAggregates: true }); const coeffIncorrect = testCheck(coefficientMismatch, element, { keepAggregates: true }); @@ -234,9 +243,10 @@ describe("testCheck Elements", () => { describe("testCheck Brackets", () => { it("Returns truthy CheckerResponse when brackets match", () => { - // Act + // Arrange const bracketCopy: Bracket = structuredClone(bracket); + // Act const testResponse = testCheck(bracketCopy, bracket, { keepAggregates: true }); // Assert @@ -246,10 +256,11 @@ describe("testCheck Brackets", () => { ); it("Returns falsy CheckerResponse when brackets don't match", () => { - // Act + // Arrange const coefficientMismatch: Bracket = structuredClone(bracket); coefficientMismatch.coeff = 2; + // Act const coeffIncorrect = testCheck(coefficientMismatch, bracket, { keepAggregates: true }); // Assert @@ -262,9 +273,10 @@ describe("testCheck Brackets", () => { describe("testCheck Compounds", () => { it("Returns truthy CheckerResponse when compounds match", () => { - // Act + // Arrange const compoundCopy: Compound = structuredClone(compound); + // Act const testResponse = testCheck(compoundCopy, structuredClone(compound), { keepAggregates: true }); // Assert @@ -275,7 +287,7 @@ describe("testCheck Compounds", () => { ); it("Returns truthy CheckerResponse when a permutation of compounds match with allowPermutations", () => { - // Act + // Arrange const permutedCompound: Compound = structuredClone(compound); permutedCompound.elements?.reverse; @@ -284,6 +296,7 @@ describe("testCheck Compounds", () => { const permutationsOptions = { allowPermutations: true }; + // Act const testResponse = testCheck(permutedCompound, structuredClone(compound), permutationsOptions); const hydrocarbonResponse = testCheck(hydrocarbonCompound, permutedHydrocarbonCompound, permutationsOptions); @@ -294,12 +307,13 @@ describe("testCheck Compounds", () => { ); it("Returns falsy CheckerResponse when compounds don't match", () => { - // Act + // Arrange const typeMismatch: Compound = structuredClone(compound); typeMismatch.elements = [structuredClone(element), structuredClone(element)]; const lengthMismatch: Compound = augmentNode(structuredClone(compound)); lengthMismatch.elements?.push(structuredClone(element)); + // Act const typesIncorrect = unaugmentedTestCheck(typeMismatch, augmentNode(structuredClone(compound))); const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentNode(structuredClone(compound))); @@ -326,6 +340,7 @@ describe("testCheck Compounds", () => { const elementMismatch: Compound = structuredClone(compound); elementMismatch.elements = [elementCoeffMismatch]; + // Act const bracketResponse: CheckerResponse = testCheck(bracketCoeffMismatch, structuredClone(bracket)); const elementResponse: CheckerResponse = testCheck(elementCoeffMismatch, structuredClone(element)); @@ -336,9 +351,12 @@ describe("testCheck Compounds", () => { ); it("Returns an error if the AST is not augmented", () => { + // Act + const testResponse = unaugmentedTestCheck(structuredClone(compound), augmentedCompound, { keepAggregates: true }); + // Assert - expect(unaugmentedTestCheck(structuredClone(compound), augmentedCompound, { keepAggregates: true }).containsError).toBeTruthy(); - expect(unaugmentedTestCheck(structuredClone(compound), augmentedCompound, { keepAggregates: true }).error).toEqual("Received unaugmented AST during checking process."); + expect(testResponse.containsError).toBeTruthy(); + expect(testResponse.error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } @@ -348,9 +366,10 @@ describe("testCheck Compounds", () => { describe("testCheck Ions", () => { it("Returns truthy CheckerResponse when ions match", () => { - // Act + // Arrange const ionClone: Ion = structuredClone(ion); + // Act const testResponse = testCheck(structuredClone(ion), ionClone, { keepAggregates: true }); // Assert @@ -363,12 +382,13 @@ describe("testCheck Ions", () => { ); it("Returns truthy CheckerResponse when a permutation of ions match with allowPermutations", () => { - // Act + // Arrange const permutedIon: Ion = structuredClone(ion); permutedIon.molecules?.reverse; const permutationsOptions = { allowPermutations: true, keepAggregates: true }; + // Act const testResponse = testCheck(permutedIon, structuredClone(ion), permutationsOptions); // Assert @@ -376,39 +396,60 @@ describe("testCheck Ions", () => { expect(testResponse.termChargeCount).toBe(0); } ); - it("Returns falsy CheckerResponse when ions don't match", + it("Returns falsy CheckerResponse when ions have mismatched molecules", () => { + // Arrange const moleculeMismatch: Ion = structuredClone(augmentedIon); if (moleculeMismatch.molecules) { moleculeMismatch.molecules[0] = [{ type: "element", value: "Cl", coeff: 1 }, -1]; } + // Act + const moleculeIncorrect = unaugmentedTestCheck(moleculeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); + + // Assert + expect(moleculeIncorrect.isEqual).toBeFalsy(); + expect(moleculeIncorrect.typeMismatch).toBeFalsy(); + expect(moleculeIncorrect.termAtomCount?.Cl).toBe(1); + } + ); + it("Returns falsy CheckerResponse when ions have mismatched charges", + () => { + // Arrange const chargeMismatch: Ion = structuredClone(augmentedIon); if (chargeMismatch.molecules) { chargeMismatch.molecules[0] = [{ type: "element", value: "Na", coeff: 1 }, -1] } + // Act + const chargeIncorrect = unaugmentedTestCheck(chargeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); + + // Assert + expect(chargeIncorrect.isEqual).toBeFalsy(); + expect(chargeIncorrect.termChargeCount).toBe(-2); + } + ); + it("Returns falsy CheckerResponse when ions have mismatched length", + () => { + // Arrange const lengthMismatch: Ion = structuredClone(augmentedIon); lengthMismatch.molecules?.push([structuredClone(element), 1]); - const moleculeIncorrect = unaugmentedTestCheck(moleculeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); - const chargeIncorrect = unaugmentedTestCheck(chargeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); + // Act const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, structuredClone(augmentedIon), { keepAggregates: true}); // Assert - expect(moleculeIncorrect.isEqual).toBeFalsy(); - expect(moleculeIncorrect.typeMismatch).toBeFalsy(); - expect(moleculeIncorrect.termAtomCount?.Cl).toBe(1); - expect(chargeIncorrect.isEqual).toBeFalsy(); - expect(chargeIncorrect.termChargeCount).toBe(-2); expect(lengthIncorrect.isEqual).toBeFalsy(); } ); it("Returns an error if the AST is not augmented", () => { + // Act + const testResponse = unaugmentedTestCheck(structuredClone(ion), augmentedIon); + // Assert - expect(unaugmentedTestCheck(structuredClone(ion), augmentedIon).containsError).toBeTruthy(); - expect(unaugmentedTestCheck(structuredClone(ion), augmentedIon).error).toEqual("Received unaugmented AST during checking process."); + expect(testResponse.containsError).toBeTruthy(); + expect(testResponse.error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } @@ -418,11 +459,12 @@ describe("testCheck Ions", () => { describe("testCheck Term", () => { it("Returns truthy CheckerResponse when terms have equal states", () => { - // Act + // Arrange const stateTerm = structuredClone(term); stateTerm.state = "(aq)"; let stateTermCopy: Term = structuredClone(stateTerm); + // Act const testResponse = testCheck(stateTerm, stateTermCopy) // Assert @@ -432,36 +474,43 @@ describe("testCheck Term", () => { ); it("Returns truthy CheckerResponse when terms has equal hydrates", () => { - // Act + // Arrange const hydratedTerm = structuredClone(term); hydratedTerm.isHydrate = true; hydratedTerm.hydrate = 7; const hydratedTermCopy = structuredClone(hydratedTerm); + // Act + const testResponse = testCheck(hydratedTermCopy, hydratedTerm); + // Assert - expect(testCheck(hydratedTermCopy, hydratedTerm).isEqual).toBeTruthy(); + expect(testResponse.isEqual).toBeTruthy(); } ); it("Returns truthy CheckerResponse when electron terms match", () => { - // Act + // Arrange const electronTerm = structuredClone(term); electronTerm.isElectron = true electronTerm.value = { type: "electron" } const electronTermCopy = structuredClone(electronTerm); + // Act + const testResponse = testCheck(electronTermCopy, electronTerm); + // Assert - expect(testCheck(electronTermCopy, electronTerm).isEqual).toBeTruthy(); + expect(testResponse.isEqual).toBeTruthy(); } ); it("Returns truthy CheckerResponse when terms match with allowScalingCoefficients", () => { - // Act + // Arrange const scaledOptions = { allowScalingCoefficients: true, keepAggregates: true }; const perturbedTerm: Term = structuredClone(term); perturbedTerm.coeff = { numerator: 6, denominator: 4 }; + // Act const testResponse = testCheck(perturbedTerm, structuredClone(term), scaledOptions); // Assert @@ -469,46 +518,78 @@ describe("testCheck Term", () => { expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 2}); } ); - it("Returns falsy CheckerResponse when terms don't match", + it("Returns falsy CheckerResponse when terms have mismatched coefficients", // to seperate (working on it) () => { - // Mismatched coefficients - let mismatchTerm: Term = structuredClone(term); + // Arrange + const mismatchTerm: Term = structuredClone(term); mismatchTerm.coeff = { numerator: 1, denominator: 5 }; - expect(testCheck(mismatchTerm, structuredClone(term)).isEqual).toBeFalsy(); - expect(testCheck(mismatchTerm, structuredClone(term)).sameCoefficient).toBeFalsy(); + // Act + const testResponse = testCheck(mismatchTerm, structuredClone(term)); + + // Assert + expect(testResponse.isEqual).toBeFalsy(); + expect(testResponse.sameCoefficient).toBeFalsy(); + } + ) + it("Returns falsy CheckerResponse when terms have mismatched states", + () => { + // Arrange + const perturbedTerm: Term = structuredClone(term); + const mismatchTerm = structuredClone(term); - // Mismatched state - let perturbedTerm: Term = structuredClone(term); - mismatchTerm = structuredClone(term); perturbedTerm.state = "(aq)"; mismatchTerm.state = "(g)"; - expect(testCheck(perturbedTerm, mismatchTerm).isEqual).toBeFalsy(); - expect(testCheck(perturbedTerm, mismatchTerm).sameState).toBeFalsy(); - // Mismatched hydrate - perturbedTerm = structuredClone(term); - mismatchTerm = structuredClone(term); + // Act + const testResponse = testCheck(perturbedTerm, mismatchTerm); + + // Assert + expect(testResponse.isEqual).toBeFalsy(); + expect(testResponse.sameState).toBeFalsy(); + } + ) + it("Returns falsy CheckerResponse when terms have mismatched hydrates", + () => { + // Arrange + const perturbedTerm = structuredClone(term); + const mismatchTerm = structuredClone(term); + perturbedTerm.isHydrate = true; perturbedTerm.hydrate = 7; + mismatchTerm.isHydrate = true; mismatchTerm.hydrate = 1; - expect(testCheck(perturbedTerm, mismatchTerm).isEqual).toBeFalsy(); - // Mismatched type - mismatchTerm = structuredClone(term); + // Act + const testResponse = testCheck(perturbedTerm, mismatchTerm); + + // Assert + expect(testResponse.isEqual).toBeFalsy(); + } + ) + it("Returns falsy CheckerResponse when terms have mismatched types", + () => { + // Arrange + const mismatchTerm = structuredClone(term); mismatchTerm.isElectron = true; mismatchTerm.value = { type: "electron" }; - expect(testCheck(mismatchTerm, structuredClone(term)).isEqual).toBeFalsy(); - expect(testCheck(mismatchTerm, structuredClone(term)).typeMismatch).toBeFalsy(); + + // Act + const testResponse = testCheck(mismatchTerm, structuredClone(term)); + + // Assert + expect(testResponse.isEqual).toBeFalsy(); + expect(testResponse.typeMismatch).toBeFalsy(); } - ); + ) it("Retains CheckerResponse properties", () => { - // Act + // Arrange const complexTerm: Term = structuredClone(term); complexTerm.value = structuredClone(compound); + // Act const testResponse = testCheck(complexTerm, structuredClone(term)); const compoundResponse = testCheck(structuredClone(compound), minimalCompound); @@ -521,12 +602,14 @@ describe("testCheck Term", () => { describe("testCheck Expression", () => { it("Returns truthy CheckerResponse when expressions match", () => { - // Act + // Arrange const permutedExpression: Expression = structuredClone(augmentedExpression); permutedExpression.terms?.reverse; permutedExpression.term = permutedExpression.terms ? permutedExpression.terms[0] : permutedExpression.term; + // Act const testResponse: CheckerResponse = unaugmentedTestCheck(permutedExpression, augmentedExpression, { keepAggregates: true }); + // Assert expect(testResponse.isEqual).toBeTruthy(); expect(testResponse.atomCount?.O).toEqual({"numerator": 3, "denominator": 1}); @@ -534,7 +617,7 @@ describe("testCheck Expression", () => { ); it("Returns truthy CheckerResponse when expressions match with allowScalingCoefficients", () => { - // Act + // Arrange const scaledOptions = { allowScalingCoefficients: true, keepAggregates: true }; const perturbedExpression: Expression = structuredClone(augmentedExpression); @@ -543,6 +626,7 @@ describe("testCheck Expression", () => { {...perturbedExpression.terms[1], coeff: { numerator: 16, denominator: 8} }]; } + // Act const testResponse = unaugmentedTestCheck(perturbedExpression, structuredClone(augmentedExpression), scaledOptions); // Assert @@ -552,21 +636,25 @@ describe("testCheck Expression", () => { ); it("Returns falsy CheckerResponse when expressions don't match", () => { - // Act + // Arrange const lengthMismatch: Expression = structuredClone(augmentedExpression); lengthMismatch.terms?.push(structuredClone(hydrate)); const termMismatch: Expression = structuredClone(augmentedExpression); if (termMismatch.terms) termMismatch.terms[1] = structuredClone(hydrate); + // Act + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentedExpression); + const termIncorrect = unaugmentedTestCheck(termMismatch, augmentedExpression); + // Assert - expect(unaugmentedTestCheck(lengthMismatch, augmentedExpression).isEqual).toBeFalsy(); - expect(unaugmentedTestCheck(termMismatch, augmentedExpression).isEqual).toBeFalsy(); + expect(lengthIncorrect.isEqual).toBeFalsy(); + expect(termIncorrect.isEqual).toBeFalsy(); } ); it("Retains CheckerResponse properties", () => { - // Act + // Arrange const adjustedExpression: Expression = structuredClone(expression); adjustedExpression.term = structuredClone(hydrate); adjustedExpression.terms = [structuredClone(hydrate)]; @@ -575,6 +663,7 @@ describe("testCheck Expression", () => { mismatchedHydrate.term = structuredClone(hydrate2); mismatchedHydrate.terms = [structuredClone(hydrate2)]; + // Act const hydrateResponse: CheckerResponse = testCheck(hydrate, hydrate2); // Assert @@ -583,9 +672,12 @@ describe("testCheck Expression", () => { ); it("Returns an error if the AST is not augmented", () => { + // Act + const testResponse = unaugmentedTestCheck(structuredClone(expression), augmentedExpression); + // Assert - expect(unaugmentedTestCheck(structuredClone(expression), augmentedExpression).containsError).toBeTruthy(); - expect(unaugmentedTestCheck(structuredClone(expression), augmentedExpression).error).toEqual("Received unaugmented AST during checking process."); + expect(testResponse.containsError).toBeTruthy(); + expect(testResponse.error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } @@ -595,14 +687,17 @@ describe("testCheck Expression", () => { describe("testCheck Statement", () => { it("Returns truthy CheckerResponse when expressions match", () => { - // Act + // Arrange const copy: Statement = structuredClone(statement); const copyResult: CheckerResponse = testCheck(copy, statement); copy.arrow = "DArr"; const doubleArrowCopy: Statement = structuredClone(copy); + + // Act const arrowResult = testCheck(copy, doubleArrowCopy); + // Assert expect(copyResult.isEqual).toBeTruthy(); expect(copyResult.sameArrow).toBeTruthy(); @@ -613,7 +708,7 @@ describe("testCheck Statement", () => { ); it("Returns truthy CheckerResponse when statements match with allowScalingCoefficients", () => { - // Act + // Arrange const scaledOptions = { allowScalingCoefficients: true, keepAggregates: true }; const perturbedStatement: Statement = structuredClone(augmentedStatement); @@ -624,6 +719,7 @@ describe("testCheck Statement", () => { {...perturbedStatement.right.terms[1], coeff: { numerator: 1, denominator: 3} }]; } + // Act const testResponse = unaugmentedTestCheck(perturbedStatement, structuredClone(augmentedStatement), scaledOptions); // Assert @@ -633,7 +729,7 @@ describe("testCheck Statement", () => { ); it("Returns falsy CheckerResponse when expressions don't match", () => { - // Act + // Arrange const swappedExpressions: Statement = structuredClone(statement); const tempExpression = swappedExpressions.left; swappedExpressions.left = swappedExpressions.right; @@ -642,6 +738,7 @@ describe("testCheck Statement", () => { const doubleArrow: Statement = structuredClone(statement); doubleArrow.arrow = "DArr"; + // Act const swapResult = testCheck(swappedExpressions, statement); const arrowResult = testCheck(doubleArrow, statement); @@ -652,41 +749,80 @@ describe("testCheck Statement", () => { expect(arrowResult.sameArrow).toBeFalsy(); } ); - it("Correctly checks whether statements are balanced", + it("Returns truthy CheckerResponse when statements are balanced", + () => { + // Act + const balancedCheck = testCheck(statement, statement, { keepAggregates: true}); + + // Asssert + expect(balancedCheck.isEqual).toBeTruthy(); + expect(balancedCheck.isBalanced).toBeTruthy(); + expect(balancedCheck.atomCount?.O).toEqual({"numerator": 3, "denominator": 1}); + } + ); + it("Returns falsy CheckerResponse when statements are unbalanced", () => { // Arrange const unbalancedStatement: Statement = structuredClone(statement); // expression has two atoms unlike statements single right expression - unbalancedStatement.right = structuredClone(expression); + unbalancedStatement.right = structuredClone(hydrate); - // Assert - expect((testCheck(statement, unbalancedStatement)).isBalanced).toBeTruthy(); - expect((testCheck(unbalancedStatement, statement)).isBalanced).toBeFalsy(); + // Act + const unbalancedCheck = testCheck(unbalancedStatement, statement, { keepAggregates: true}); + + // Asssert + expect(unbalancedCheck.isEqual).toBeFalsy(); + expect(unbalancedCheck.isBalanced).toBeFalsy(); + expect(unbalancedCheck.atomCount?.O).toEqual({"numerator": 3, "denominator": 1}); } ); - it("Correctly checks whether charges are balanced", + it("Returns truthy CheckerResponse when statements charges are balanced", () => { // Arrange - const chargedIon: Ion = augmentedIon; + const chargedIon: Ion = structuredClone(augmentedIon); chargedIon.molecules = [[structuredClone(element), 1]]; const chargedTerm: Term = augmentNode(structuredClone(term)); chargedTerm.value = chargedIon; - // expression is otherwise neutral - const chargedExpr: Expression = augmentedExpression; + const chargedExpr: Expression = structuredClone(augmentedExpression); chargedExpr.terms?.push(chargedTerm); const balancedCharges: Statement = augmentNode(structuredClone(statement)); balancedCharges.left = structuredClone(chargedExpr); balancedCharges.right = structuredClone(chargedExpr); + // Act + const balancedCheck = unaugmentedTestCheck(balancedCharges, balancedCharges, { keepAggregates: true }); + + // Assert + expect(balancedCheck.isEqual).toBeTruthy(); + expect(balancedCheck.isChargeBalanced).toBeTruthy(); + expect(balancedCheck.chargeCount).toEqual({"numerator": 3, "denominator": 2}); + } + ); + it("Returns falsy CheckerResponse when statements charges are unbalanced", + () => { + // Arrange + const chargedIon: Ion = structuredClone(augmentedIon); + chargedIon.molecules = [[structuredClone(element), 1]]; + + const chargedTerm: Term = augmentNode(structuredClone(term)); + chargedTerm.value = chargedIon; + + const chargedExpr: Expression = structuredClone(augmentedExpression); + chargedExpr.terms?.push(chargedTerm); + const unbalancedCharges: Statement = augmentNode(structuredClone(statement)); unbalancedCharges.left = structuredClone(chargedExpr); + // Act + const unbalancedCheck = unaugmentedTestCheck(unbalancedCharges, unbalancedCharges, { keepAggregates: true}); + // Assert - expect(unaugmentedTestCheck(balancedCharges, unbalancedCharges).isChargeBalanced).toBeTruthy(); - expect(unaugmentedTestCheck(unbalancedCharges, balancedCharges).isChargeBalanced).toBeFalsy(); + expect(unbalancedCheck.isEqual).toBeFalsy(); + expect(unbalancedCheck.isChargeBalanced).toBeFalsy(); + expect(unbalancedCheck.chargeCount).toEqual({"numerator": 3, "denominator": 2}); } ); }); @@ -694,7 +830,7 @@ describe("testCheck Statement", () => { describe("Check", () => { it("Returns error message when given one", () => { - // Act + // Arrange const error: ParseError = { type: "error", value: "Sphinx of black quartz, judge my vow", @@ -705,21 +841,25 @@ describe("Check", () => { result: error } + // Act const response: CheckerResponse = check(errorAST, ast, options); + // Assert expect(response.containsError).toBeTruthy(); expect(response.error).toBe("Sphinx of black quartz, judge my vow"); expect(response.expectedType).toBe("statement"); } ); - it("Returns type mismatch when appropriate", + it("Returns type mismatch when types are different", () => { - // Act + // Arrange const expressionAST: ChemAST = { result: structuredClone(expression) } + // Act const response: CheckerResponse = check(ast, expressionAST, options); + // Assert expect(response.typeMismatch).toBeTruthy(); expect(response.expectedType).toBe("expr"); From bc54b94af5627c492436ad1aac93564d40ac50ca Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:29:59 +0000 Subject: [PATCH 09/11] Convert Nuclear to Arrange/Act/Assert structure --- test/models/Chemistry.test.ts | 28 ++++--------- test/models/Nuclear.test.ts | 77 +++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 5ed525e..d028193 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -462,7 +462,7 @@ describe("testCheck Term", () => { // Arrange const stateTerm = structuredClone(term); stateTerm.state = "(aq)"; - let stateTermCopy: Term = structuredClone(stateTerm); + const stateTermCopy: Term = structuredClone(stateTerm); // Act const testResponse = testCheck(stateTerm, stateTermCopy) @@ -685,6 +685,14 @@ describe("testCheck Expression", () => { }); describe("testCheck Statement", () => { + const chargedIon: Ion = structuredClone(augmentedIon); + chargedIon.molecules = [[structuredClone(element), 1]]; + + const chargedTerm: Term = augmentNode(structuredClone(term)); + chargedTerm.value = chargedIon; + + const chargedExpr: Expression = structuredClone(augmentedExpression); + chargedExpr.terms?.push(chargedTerm); it("Returns truthy CheckerResponse when expressions match", () => { // Arrange @@ -779,15 +787,6 @@ describe("testCheck Statement", () => { it("Returns truthy CheckerResponse when statements charges are balanced", () => { // Arrange - const chargedIon: Ion = structuredClone(augmentedIon); - chargedIon.molecules = [[structuredClone(element), 1]]; - - const chargedTerm: Term = augmentNode(structuredClone(term)); - chargedTerm.value = chargedIon; - - const chargedExpr: Expression = structuredClone(augmentedExpression); - chargedExpr.terms?.push(chargedTerm); - const balancedCharges: Statement = augmentNode(structuredClone(statement)); balancedCharges.left = structuredClone(chargedExpr); balancedCharges.right = structuredClone(chargedExpr); @@ -804,15 +803,6 @@ describe("testCheck Statement", () => { it("Returns falsy CheckerResponse when statements charges are unbalanced", () => { // Arrange - const chargedIon: Ion = structuredClone(augmentedIon); - chargedIon.molecules = [[structuredClone(element), 1]]; - - const chargedTerm: Term = augmentNode(structuredClone(term)); - chargedTerm.value = chargedIon; - - const chargedExpr: Expression = structuredClone(augmentedExpression); - chargedExpr.terms?.push(chargedTerm); - const unbalancedCharges: Statement = augmentNode(structuredClone(statement)); unbalancedCharges.left = structuredClone(chargedExpr); diff --git a/test/models/Nuclear.test.ts b/test/models/Nuclear.test.ts index 1c35c45..73907ec 100644 --- a/test/models/Nuclear.test.ts +++ b/test/models/Nuclear.test.ts @@ -88,9 +88,10 @@ function unaugmentedTestCheck(target: T, test: T): CheckerRes describe("testCheck Particle", () => { it("Returns truthy CheckerResponse when particles match", () => { - // Act + // Arrange const particleCopy: Particle = structuredClone(particle) + // Act const testResponse = testCheck(particleCopy, particle); // Assert @@ -100,12 +101,13 @@ describe("testCheck Particle", () => { ); it("Returns falsy CheckerResponse when particles don't match", () => { - // Act + // Arrange const valueMismatch: Particle = structuredClone(particle); valueMismatch.particle = "betaparticle"; valueMismatch.mass = 0; valueMismatch.atomic = -1; + // Act const elementIncorrect = testCheck(valueMismatch, particle); // Assert @@ -115,10 +117,11 @@ describe("testCheck Particle", () => { ); it("Returns falsy CheckerResponse when atomic number is invalid", () => { - // Act + // Arrange const nucleonMismatch: Particle = structuredClone(particle); nucleonMismatch.particle = "betaparticle"; + // Act const nucleonIncorrect = testCheck(nucleonMismatch, particle); // Assert @@ -131,9 +134,10 @@ describe("testCheck Particle", () => { describe("testCheck Isotope", () => { it("Returns truthy CheckerResponse when isotope match", () => { - // Act + // Arrange const isotopeCopy: Isotope = structuredClone(isotope); + // Act const testResponse = testCheck(isotopeCopy, isotope); // Assert @@ -143,11 +147,13 @@ describe("testCheck Isotope", () => { ); it("Returns falsy CheckerResponse when isotope do not match", () => { + // Arrange const elementMismatch: Isotope = structuredClone(isotope); elementMismatch.element = "Cl"; elementMismatch.mass = 35; elementMismatch.atomic = 17; + // Act const elementIncorrect = testCheck(elementMismatch, isotope); // Assert @@ -157,9 +163,11 @@ describe("testCheck Isotope", () => { ); it("Returns falsy CheckerResponse when atomic number is invalid", () => { + // Arrange const nucleonMismatch: Isotope = structuredClone(isotope); nucleonMismatch.element = "Cl"; + // Act const nucleonIncorrect = testCheck(nucleonMismatch, isotope); // Assert @@ -172,23 +180,32 @@ describe("testCheck Isotope", () => { describe("testCheck Term", () => { it("Returns truthy CheckerResponse when terms match", () => { - let termCopy: Term = structuredClone(term); + // Arrange + const termCopy: Term = structuredClone(term); - let testResponse = testCheck(termCopy, term) + // Act + const testResponse = testCheck(termCopy, term) + + // Assert expect(testResponse.isEqual).toBeTruthy(); } ); it("Returns falsy CheckerResponse when terms don't match", () => { - // Mismatched coefficients - let mismatchTerm: Term = structuredClone(term); + // Arrange + const mismatchTerm: Term = structuredClone(term); mismatchTerm.coeff = 3; - expect(testCheck(mismatchTerm, term).isEqual).toBeFalsy(); - expect(testCheck(mismatchTerm, term).sameCoefficient).toBeFalsy(); + // Act + const termIncorrect = testCheck(mismatchTerm, term); + const typeIncorrect = testCheck(particleTerm, term); + + // Assert + expect(termIncorrect.isEqual).toBeFalsy(); + expect(termIncorrect.sameCoefficient).toBeFalsy(); // Mismatched type - expect(testCheck(particleTerm, term).isEqual).toBeFalsy(); + expect(typeIncorrect.isEqual).toBeFalsy(); } ); it("Retains CheckerResponse properties", @@ -205,6 +222,7 @@ describe("testCheck Term", () => { const particleCopyTerm: Term = structuredClone(particleTerm); particleCopyTerm.value = particleCopy; + // Act const isotopeResponse = testCheck(isotopeError, isotope); const particleResponse = testCheck(particleCopy, particle); @@ -221,32 +239,38 @@ describe("testCheck Term", () => { describe("testCheck Expression", () => { it("Returns truthy CheckerResponse when expressions match", () => { - // Act + // Arrange const permutedExpression: Expression = structuredClone(expression); permutedExpression.terms?.reverse; + // Act const testResponse: CheckerResponse = testCheck(permutedExpression, expression); + // Assert expect(testResponse.isEqual).toBeTruthy(); } ); it("Returns falsy CheckerResponse when expressions do not match", () => { - // Act + // Arrange const lengthMismatch: Expression = structuredClone(expression); lengthMismatch.terms?.push(structuredClone(term)); const termMismatch: Expression = structuredClone(expression); if (termMismatch.terms) termMismatch.terms[1] = structuredClone(term); + // Act + const lengthIncorrect = testCheck(lengthMismatch, expression); + const termIncorrect = testCheck(termMismatch, expression); + // Assert - expect(testCheck(lengthMismatch, expression).isEqual).toBeFalsy(); - expect(testCheck(termMismatch, expression).isEqual).toBeFalsy(); + expect(lengthIncorrect.isEqual).toBeFalsy(); + expect(termIncorrect.isEqual).toBeFalsy(); } ); it("Returns an error if the AST is not augmented", () => { - // Act + // Arrange // This is the same as expression just unaugmented const unaugmentedExpression: Expression = { type: "expr", @@ -254,9 +278,12 @@ describe("testCheck Expression", () => { rest: structuredClone(particleTerm) } + // Act + const testResponse = unaugmentedTestCheck(unaugmentedExpression, expression); + // Assert - expect(unaugmentedTestCheck(unaugmentedExpression, expression).containsError).toBeTruthy(); - expect(unaugmentedTestCheck(unaugmentedExpression, expression).error).toEqual("Received unaugmented AST during checking process."); + expect(testResponse.containsError).toBeTruthy(); + expect(testResponse.error).toEqual("Received unaugmented AST during checking process."); expect(console.error).toHaveBeenCalled(); } @@ -266,9 +293,10 @@ describe("testCheck Expression", () => { describe("testCheck Statement", () => { it("Returns truthy CheckerResponse when expressions match", () => { - // Act + // Arrange const copy: Statement = structuredClone(statement); + // Act const copyResult: CheckerResponse = testCheck(copy, statement); // Assert @@ -277,12 +305,13 @@ describe("testCheck Statement", () => { ); it("Returns falsy CheckerResponse when expressions do not match", () => { - // Act + // Arrange const swappedExpressions: Statement = structuredClone(statement); const tempExpression = swappedExpressions.left; swappedExpressions.left = swappedExpressions.right; swappedExpressions.right = tempExpression; + // Act const swapResult = testCheck(swappedExpressions, statement); // Assert @@ -323,7 +352,7 @@ describe("Check", () => { it("Returns error message when given one", () => { - // Act + // Arrange const error: ParseError = { type: "error", value: "Sphinx of black quartz, judge my vow", @@ -334,7 +363,9 @@ describe("Check", () => { result: error } + // Act const response: CheckerResponse = check(errorAST, ast); + // Assert expect(response.containsError).toBeTruthy(); expect(response.error).toBe("Sphinx of black quartz, judge my vow"); @@ -343,12 +374,14 @@ describe("Check", () => { ); it("Returns type mismatch when appropriate", () => { - // Act + // Arrange const expressionAST: NuclearAST = { result: structuredClone(expression) } + // Act const response: CheckerResponse = check(ast, expressionAST); + // Assert expect(response.typeMismatch).toBeTruthy(); expect(response.expectedType).toBe("expr"); From 61fca955cd844b8e14cb8b605ea6624d27f10fce Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:51:18 +0000 Subject: [PATCH 10/11] Consolidate structuredClone inside testCheck --- src/models/Chemistry.ts | 2 +- src/models/Nuclear.ts | 6 +-- src/routes/Nuclear.ts | 2 +- test/models/Chemistry.test.ts | 56 ++++++++++++------------- test/models/Nuclear.test.ts | 78 +++++++++++++++++++++-------------- 5 files changed, 80 insertions(+), 64 deletions(-) diff --git a/src/models/Chemistry.ts b/src/models/Chemistry.ts index 80e3cda..58c0d38 100644 --- a/src/models/Chemistry.ts +++ b/src/models/Chemistry.ts @@ -580,7 +580,7 @@ export function check(test: ChemAST, target: ChemAST, options: ChemistryOptions) response.expectedType = target.result.type; response.receivedType = test.result.type; - if (isEqual(test.result, target.result) && !options.keepAggregates) { + if (!options.keepAggregates && isEqual(test.result, target.result)) { return response; } diff --git a/src/models/Nuclear.ts b/src/models/Nuclear.ts index 1382070..2c9024b 100644 --- a/src/models/Nuclear.ts +++ b/src/models/Nuclear.ts @@ -309,12 +309,12 @@ function checkNodesEqual(test: ASTNode, target: ASTNode, response: CheckerRespon } } -export function check(test: NuclearAST, target: NuclearAST): CheckerResponse { - const response = STARTING_RESPONSE(); +export function check(test: NuclearAST, target: NuclearAST, options: ChemistryOptions): CheckerResponse { + const response = STARTING_RESPONSE(options); response.expectedType = target.result.type; response.receivedType = test.result.type; - if (isEqual(test.result, target.result)) { + if (!options.keepAggregates && isEqual(test.result, target.result)) { return response; } diff --git a/src/routes/Nuclear.ts b/src/routes/Nuclear.ts index c3f0397..fd164bf 100644 --- a/src/routes/Nuclear.ts +++ b/src/routes/Nuclear.ts @@ -25,7 +25,7 @@ router.post('/check', checkValidationRules, (req: Request, res: Response) => { const target: NuclearAST = augment(parseNuclearExpression(req.body.target)[0]); const test: NuclearAST = augment(parseNuclearExpression(req.body.test)[0]); - const result: CheckerResponse = check(test, target); + const result: CheckerResponse = check(test, target, {}); res.status(201).send(result); diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index d028193..73a44c7 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -1,4 +1,3 @@ -import exp from "constants"; import { Bracket, Compound, Element, exportedForTesting, Ion, Term, Expression, Statement, ParseError, check, ChemAST, augment, STARTING_COEFFICIENT, Result, ASTNode, isExpression } from "../../src/models/Chemistry"; import { CheckerResponse, listComparison, ChemistryOptions } from "../../src/models/common"; const { augmentNode } = exportedForTesting; @@ -139,7 +138,7 @@ function testCheck(target: T, test: T, options?: ChemistryOpt } function unaugmentedTestCheck(target: T, test: T, options?: ChemistryOptions): CheckerResponse { - return check({result: target as unknown as Result}, {result: test as unknown as Result}, options ?? {}); + return check(structuredClone({result: target as unknown as Result}), structuredClone({result: test as unknown as Result}), options ?? {}); } describe("listComparison", () => { @@ -277,7 +276,7 @@ describe("testCheck Compounds", () => { const compoundCopy: Compound = structuredClone(compound); // Act - const testResponse = testCheck(compoundCopy, structuredClone(compound), { keepAggregates: true }); + const testResponse = testCheck(compound, compoundCopy, { keepAggregates: true }); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -297,7 +296,7 @@ describe("testCheck Compounds", () => { const permutationsOptions = { allowPermutations: true }; // Act - const testResponse = testCheck(permutedCompound, structuredClone(compound), permutationsOptions); + const testResponse = testCheck(permutedCompound, compound, permutationsOptions); const hydrocarbonResponse = testCheck(hydrocarbonCompound, permutedHydrocarbonCompound, permutationsOptions); // Assert @@ -314,8 +313,8 @@ describe("testCheck Compounds", () => { lengthMismatch.elements?.push(structuredClone(element)); // Act - const typesIncorrect = unaugmentedTestCheck(typeMismatch, augmentNode(structuredClone(compound))); - const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentNode(structuredClone(compound))); + const typesIncorrect = unaugmentedTestCheck(typeMismatch, augmentedCompound); + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentedCompound); // Assert expect(typesIncorrect.isEqual).toBeFalsy(); @@ -341,8 +340,8 @@ describe("testCheck Compounds", () => { elementMismatch.elements = [elementCoeffMismatch]; // Act - const bracketResponse: CheckerResponse = testCheck(bracketCoeffMismatch, structuredClone(bracket)); - const elementResponse: CheckerResponse = testCheck(elementCoeffMismatch, structuredClone(element)); + const bracketResponse: CheckerResponse = testCheck(bracketCoeffMismatch, bracket); + const elementResponse: CheckerResponse = testCheck(elementCoeffMismatch, element); // Assert expect(testCheck(bracketMismatch, minimalBracketCompound)).toEqual({...bracketResponse, expectedType: "compound", receivedType: "compound"}); @@ -352,7 +351,7 @@ describe("testCheck Compounds", () => { it("Returns an error if the AST is not augmented", () => { // Act - const testResponse = unaugmentedTestCheck(structuredClone(compound), augmentedCompound, { keepAggregates: true }); + const testResponse = unaugmentedTestCheck(compound, augmentedCompound, { keepAggregates: true }); // Assert expect(testResponse.containsError).toBeTruthy(); @@ -370,7 +369,7 @@ describe("testCheck Ions", () => { const ionClone: Ion = structuredClone(ion); // Act - const testResponse = testCheck(structuredClone(ion), ionClone, { keepAggregates: true }); + const testResponse = testCheck(ion, ionClone, { keepAggregates: true }); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -389,7 +388,7 @@ describe("testCheck Ions", () => { const permutationsOptions = { allowPermutations: true, keepAggregates: true }; // Act - const testResponse = testCheck(permutedIon, structuredClone(ion), permutationsOptions); + const testResponse = testCheck(permutedIon, ion, permutationsOptions); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -405,7 +404,7 @@ describe("testCheck Ions", () => { } // Act - const moleculeIncorrect = unaugmentedTestCheck(moleculeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); + const moleculeIncorrect = unaugmentedTestCheck(moleculeMismatch, augmentedIon, { keepAggregates: true }); // Assert expect(moleculeIncorrect.isEqual).toBeFalsy(); @@ -422,7 +421,7 @@ describe("testCheck Ions", () => { } // Act - const chargeIncorrect = unaugmentedTestCheck(chargeMismatch, structuredClone(augmentedIon), { keepAggregates: true }); + const chargeIncorrect = unaugmentedTestCheck(chargeMismatch, augmentedIon, { keepAggregates: true }); // Assert expect(chargeIncorrect.isEqual).toBeFalsy(); @@ -436,7 +435,7 @@ describe("testCheck Ions", () => { lengthMismatch.molecules?.push([structuredClone(element), 1]); // Act - const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, structuredClone(augmentedIon), { keepAggregates: true}); + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentedIon, { keepAggregates: true}); // Assert expect(lengthIncorrect.isEqual).toBeFalsy(); @@ -445,7 +444,7 @@ describe("testCheck Ions", () => { it("Returns an error if the AST is not augmented", () => { // Act - const testResponse = unaugmentedTestCheck(structuredClone(ion), augmentedIon); + const testResponse = unaugmentedTestCheck(ion, augmentedIon); // Assert expect(testResponse.containsError).toBeTruthy(); @@ -511,7 +510,7 @@ describe("testCheck Term", () => { perturbedTerm.coeff = { numerator: 6, denominator: 4 }; // Act - const testResponse = testCheck(perturbedTerm, structuredClone(term), scaledOptions); + const testResponse = testCheck(perturbedTerm, term, scaledOptions); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -525,7 +524,7 @@ describe("testCheck Term", () => { mismatchTerm.coeff = { numerator: 1, denominator: 5 }; // Act - const testResponse = testCheck(mismatchTerm, structuredClone(term)); + const testResponse = testCheck(mismatchTerm, term); // Assert expect(testResponse.isEqual).toBeFalsy(); @@ -576,7 +575,7 @@ describe("testCheck Term", () => { mismatchTerm.value = { type: "electron" }; // Act - const testResponse = testCheck(mismatchTerm, structuredClone(term)); + const testResponse = testCheck(mismatchTerm, term); // Assert expect(testResponse.isEqual).toBeFalsy(); @@ -590,8 +589,8 @@ describe("testCheck Term", () => { complexTerm.value = structuredClone(compound); // Act - const testResponse = testCheck(complexTerm, structuredClone(term)); - const compoundResponse = testCheck(structuredClone(compound), minimalCompound); + const testResponse = testCheck(complexTerm, term); + const compoundResponse = testCheck(compound, minimalCompound); // Assert expect(testResponse).toEqual({...compoundResponse, expectedType: "term", receivedType: "term"}); @@ -627,7 +626,7 @@ describe("testCheck Expression", () => { } // Act - const testResponse = unaugmentedTestCheck(perturbedExpression, structuredClone(augmentedExpression), scaledOptions); + const testResponse = unaugmentedTestCheck(perturbedExpression, augmentedExpression, scaledOptions); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -673,7 +672,7 @@ describe("testCheck Expression", () => { it("Returns an error if the AST is not augmented", () => { // Act - const testResponse = unaugmentedTestCheck(structuredClone(expression), augmentedExpression); + const testResponse = unaugmentedTestCheck(expression, augmentedExpression); // Assert expect(testResponse.containsError).toBeTruthy(); @@ -698,13 +697,12 @@ describe("testCheck Statement", () => { // Arrange const copy: Statement = structuredClone(statement); - const copyResult: CheckerResponse = testCheck(copy, statement); - - copy.arrow = "DArr"; const doubleArrowCopy: Statement = structuredClone(copy); + doubleArrowCopy.arrow = "DArr"; // Act - const arrowResult = testCheck(copy, doubleArrowCopy); + const copyResult: CheckerResponse = testCheck(copy, statement, { keepAggregates: true }); + const arrowResult: CheckerResponse = testCheck(doubleArrowCopy, doubleArrowCopy, { keepAggregates: true }); // Assert expect(copyResult.isEqual).toBeTruthy(); @@ -728,7 +726,7 @@ describe("testCheck Statement", () => { } // Act - const testResponse = unaugmentedTestCheck(perturbedStatement, structuredClone(augmentedStatement), scaledOptions); + const testResponse = unaugmentedTestCheck(perturbedStatement, augmentedStatement, scaledOptions); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -787,7 +785,7 @@ describe("testCheck Statement", () => { it("Returns truthy CheckerResponse when statements charges are balanced", () => { // Arrange - const balancedCharges: Statement = augmentNode(structuredClone(statement)); + const balancedCharges: Statement = structuredClone(augmentedStatement); balancedCharges.left = structuredClone(chargedExpr); balancedCharges.right = structuredClone(chargedExpr); @@ -803,7 +801,7 @@ describe("testCheck Statement", () => { it("Returns falsy CheckerResponse when statements charges are unbalanced", () => { // Arrange - const unbalancedCharges: Statement = augmentNode(structuredClone(statement)); + const unbalancedCharges: Statement = structuredClone(augmentedStatement); unbalancedCharges.left = structuredClone(chargedExpr); // Act diff --git a/test/models/Nuclear.test.ts b/test/models/Nuclear.test.ts index 73907ec..74b5df7 100644 --- a/test/models/Nuclear.test.ts +++ b/test/models/Nuclear.test.ts @@ -1,5 +1,5 @@ import { Particle, Isotope, Term, Expression, Statement, ParseError, check, NuclearAST, exportedForTesting, Result, ASTNode, augmentNode } from "../../src/models/Nuclear"; -import { CheckerResponse } from "../../src/models/common"; +import { CheckerResponse, ChemistryOptions } from "../../src/models/common"; const original = console.error; @@ -67,22 +67,26 @@ const particleTerm: Term = { coeff: 2, isParticle: true }; + const expression: Expression = { type: "expr", term: structuredClone(term), - terms: [structuredClone(term), structuredClone(particleTerm)] + rest: structuredClone(particleTerm) } +const augmentedExpression: Expression = augmentNode(structuredClone(expression)); + const statement: Statement = { type: "statement", left: structuredClone(term), - right: structuredClone(particleTerm), + right: structuredClone(expression), } +const augmentedStatement: Statement = augmentNode(structuredClone(statement)); -function testCheck(target: T, test: T): CheckerResponse { - return check({result: augmentNode(target) as unknown as Result}, {result: augmentNode(test) as unknown as Result}); +function testCheck(target: T, test: T, options?: ChemistryOptions): CheckerResponse { + return check({result: augmentNode(structuredClone(target)) as unknown as Result}, {result: augmentNode(structuredClone(test)) as unknown as Result}, options ?? {}); } -function unaugmentedTestCheck(target: T, test: T): CheckerResponse { - return check({result: target as unknown as Result}, {result: test as unknown as Result}); +function unaugmentedTestCheck(target: T, test: T, options?: ChemistryOptions): CheckerResponse { + return check(structuredClone({result: target as unknown as Result}), structuredClone({result: test as unknown as Result}), options ?? {}); } describe("testCheck Particle", () => { @@ -240,11 +244,11 @@ describe("testCheck Expression", () => { it("Returns truthy CheckerResponse when expressions match", () => { // Arrange - const permutedExpression: Expression = structuredClone(expression); + const permutedExpression: Expression = structuredClone(augmentedExpression); permutedExpression.terms?.reverse; // Act - const testResponse: CheckerResponse = testCheck(permutedExpression, expression); + const testResponse: CheckerResponse = unaugmentedTestCheck(permutedExpression, augmentedExpression); // Assert expect(testResponse.isEqual).toBeTruthy(); @@ -253,15 +257,15 @@ describe("testCheck Expression", () => { it("Returns falsy CheckerResponse when expressions do not match", () => { // Arrange - const lengthMismatch: Expression = structuredClone(expression); - lengthMismatch.terms?.push(structuredClone(term)); + const lengthMismatch: Expression = structuredClone(augmentedExpression); + lengthMismatch.terms?.push(structuredClone(term)) - const termMismatch: Expression = structuredClone(expression); - if (termMismatch.terms) termMismatch.terms[1] = structuredClone(term); + const termMismatch: Expression = structuredClone(augmentedExpression); + if (termMismatch.terms) termMismatch.terms[1] = structuredClone(particleTerm); // Act - const lengthIncorrect = testCheck(lengthMismatch, expression); - const termIncorrect = testCheck(termMismatch, expression); + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentedExpression); + const termIncorrect = unaugmentedTestCheck(termMismatch, augmentedExpression); // Assert expect(lengthIncorrect.isEqual).toBeFalsy(); @@ -270,16 +274,8 @@ describe("testCheck Expression", () => { ); it("Returns an error if the AST is not augmented", () => { - // Arrange - // This is the same as expression just unaugmented - const unaugmentedExpression: Expression = { - type: "expr", - term: structuredClone(term), - rest: structuredClone(particleTerm) - } - // Act - const testResponse = unaugmentedTestCheck(unaugmentedExpression, expression); + const testResponse = unaugmentedTestCheck(expression, expression, { keepAggregates: true }); // Assert expect(testResponse.containsError).toBeTruthy(); @@ -318,26 +314,48 @@ describe("testCheck Statement", () => { expect(swapResult.isEqual).toBeFalsy(); } ); - it("Correctly checks whether statements are balanced", + it("Returns truthy CheckerResponse when expressions are balanced", () => { // Arrange const balancedStatement: Statement = structuredClone(statement); balancedStatement.right = structuredClone(term); // Act - const balancedResponse = testCheck(balancedStatement, balancedStatement); - const unbalancedResponse = testCheck(statement, balancedStatement); + const balancedResponse = testCheck(balancedStatement, balancedStatement, { keepAggregates: true }); // Assert expect(balancedResponse.isBalanced).toBeTruthy(); expect(balancedResponse.balancedAtom).toBeTruthy(); expect(balancedResponse.balancedMass).toBeTruthy(); + } + ) + it("Returns falsy CheckerResponse when expressions are balanced", + () => { + // Arrange + const balancedStatement: Statement = structuredClone(statement); + balancedStatement.right = structuredClone(term); + + // Act + const unbalancedResponse = testCheck(statement, statement, { keepAggregates: true }); + // Assert expect(unbalancedResponse.isBalanced).toBeFalsy(); expect(unbalancedResponse.balancedAtom).toBeFalsy(); expect(unbalancedResponse.balancedMass).toBeFalsy(); } ); + it("Returns an error if the AST is not augmented", + () => { + // Act + const testResponse = unaugmentedTestCheck(statement, statement, { keepAggregates: true }); + + // Assert + expect(testResponse.containsError).toBeTruthy(); + expect(testResponse.error).toEqual("Received unaugmented AST during checking process."); + + expect(console.error).toHaveBeenCalled(); + } + ); }); describe("Check", () => { @@ -364,7 +382,7 @@ describe("Check", () => { } // Act - const response: CheckerResponse = check(errorAST, ast); + const response: CheckerResponse = check(errorAST, ast, {}); // Assert expect(response.containsError).toBeTruthy(); @@ -380,7 +398,7 @@ describe("Check", () => { } // Act - const response: CheckerResponse = check(ast, expressionAST); + const response: CheckerResponse = check(ast, expressionAST, {}); // Assert expect(response.typeMismatch).toBeTruthy(); @@ -390,7 +408,7 @@ describe("Check", () => { it("Returns truthy CheckerResponse when ASTs match", () => { // Act - const response: CheckerResponse = check(ast, ast); + const response: CheckerResponse = check(ast, ast, {}); // Assert expect(response).toEqual(trueResponse); From 40b6a5bac67c7c601a10dc6a69c1a0fe108e635f Mon Sep 17 00:00:00 2001 From: Sol Dubock <94075844+sjd210@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:52:45 +0000 Subject: [PATCH 11/11] Make all tests consistent --- test/models/Chemistry.test.ts | 37 +++++++++++++++++++++++++---------- test/models/Nuclear.test.ts | 20 +++++++++++++------ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/test/models/Chemistry.test.ts b/test/models/Chemistry.test.ts index 73a44c7..e41a27a 100644 --- a/test/models/Chemistry.test.ts +++ b/test/models/Chemistry.test.ts @@ -304,20 +304,29 @@ describe("testCheck Compounds", () => { expect(hydrocarbonResponse.isEqual).toBeTruthy(); } ); - it("Returns falsy CheckerResponse when compounds don't match", + it("Returns falsy CheckerResponse when compounds have mismatched type", () => { // Arrange const typeMismatch: Compound = structuredClone(compound); typeMismatch.elements = [structuredClone(element), structuredClone(element)]; + + // Act + const typesIncorrect = unaugmentedTestCheck(typeMismatch, augmentedCompound); + + // Assert + expect(typesIncorrect.isEqual).toBeFalsy(); + } + ); + it("Returns falsy CheckerResponse when compounds have mismatched length", + () => { + // Arrange const lengthMismatch: Compound = augmentNode(structuredClone(compound)); lengthMismatch.elements?.push(structuredClone(element)); // Act - const typesIncorrect = unaugmentedTestCheck(typeMismatch, augmentedCompound); const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentedCompound); // Assert - expect(typesIncorrect.isEqual).toBeFalsy(); expect(lengthIncorrect.isEqual).toBeFalsy(); } ); @@ -579,7 +588,7 @@ describe("testCheck Term", () => { // Assert expect(testResponse.isEqual).toBeFalsy(); - expect(testResponse.typeMismatch).toBeFalsy(); + expect(testResponse.typeMismatch).toBeTruthy(); } ) it("Retains CheckerResponse properties", @@ -633,24 +642,32 @@ describe("testCheck Expression", () => { expect(testResponse.atomCount?.O).toEqual({"numerator": 4, "denominator": 1}); } ); - it("Returns falsy CheckerResponse when expressions don't match", + it("Returns falsy CheckerResponse when expressions have mismatched terms", () => { // Arrange - const lengthMismatch: Expression = structuredClone(augmentedExpression); - lengthMismatch.terms?.push(structuredClone(hydrate)); - const termMismatch: Expression = structuredClone(augmentedExpression); if (termMismatch.terms) termMismatch.terms[1] = structuredClone(hydrate); // Act - const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentedExpression); const termIncorrect = unaugmentedTestCheck(termMismatch, augmentedExpression); // Assert - expect(lengthIncorrect.isEqual).toBeFalsy(); expect(termIncorrect.isEqual).toBeFalsy(); } ); + it("Returns falsy CheckerResponse when expressions have mismatched length", + () => { + // Arrange + const lengthMismatch: Expression = structuredClone(augmentedExpression); + lengthMismatch.terms?.push(structuredClone(hydrate)); + + // Act + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentedExpression); + + // Assert + expect(lengthIncorrect.isEqual).toBeFalsy(); + } + ); it("Retains CheckerResponse properties", () => { // Arrange diff --git a/test/models/Nuclear.test.ts b/test/models/Nuclear.test.ts index 74b5df7..5759917 100644 --- a/test/models/Nuclear.test.ts +++ b/test/models/Nuclear.test.ts @@ -149,7 +149,7 @@ describe("testCheck Isotope", () => { expect(testResponse.validAtomicNumber).toBeTruthy(); } ); - it("Returns falsy CheckerResponse when isotope do not match", + it("Returns falsy CheckerResponse when isotope don't match", () => { // Arrange const elementMismatch: Isotope = structuredClone(isotope); @@ -254,21 +254,29 @@ describe("testCheck Expression", () => { expect(testResponse.isEqual).toBeTruthy(); } ); - it("Returns falsy CheckerResponse when expressions do not match", + it("Returns falsy CheckerResponse when expressions have mismatched length", () => { // Arrange const lengthMismatch: Expression = structuredClone(augmentedExpression); lengthMismatch.terms?.push(structuredClone(term)) + // Act + const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentedExpression); + + // Assert + expect(lengthIncorrect.isEqual).toBeFalsy(); + } + ); + it("Returns falsy CheckerResponse when expressions have mismatched terms", + () => { + // Arrange const termMismatch: Expression = structuredClone(augmentedExpression); if (termMismatch.terms) termMismatch.terms[1] = structuredClone(particleTerm); // Act - const lengthIncorrect = unaugmentedTestCheck(lengthMismatch, augmentedExpression); const termIncorrect = unaugmentedTestCheck(termMismatch, augmentedExpression); // Assert - expect(lengthIncorrect.isEqual).toBeFalsy(); expect(termIncorrect.isEqual).toBeFalsy(); } ); @@ -299,7 +307,7 @@ describe("testCheck Statement", () => { expect(copyResult.isEqual).toBeTruthy(); } ); - it("Returns falsy CheckerResponse when expressions do not match", + it("Returns falsy CheckerResponse when expressions don't match", () => { // Arrange const swappedExpressions: Statement = structuredClone(statement); @@ -329,7 +337,7 @@ describe("testCheck Statement", () => { expect(balancedResponse.balancedMass).toBeTruthy(); } ) - it("Returns falsy CheckerResponse when expressions are balanced", + it("Returns falsy CheckerResponse when expressions are unbalanced", () => { // Arrange const balancedStatement: Statement = structuredClone(statement);