diff --git a/SwiftMath/Tests/SwiftMathTests/MTMathListTests.swift b/SwiftMath/Tests/SwiftMathTests/MTMathListTests.swift deleted file mode 100755 index dfa2e9f..0000000 --- a/SwiftMath/Tests/SwiftMathTests/MTMathListTests.swift +++ /dev/null @@ -1,624 +0,0 @@ -import XCTest -@testable import SwiftUIMath - -// -// MathRenderSwiftTests.swift -// MathRenderSwiftTests -// -// Created by Mike Griebling on 2023-01-02. -// - -final class MTMathListTests: XCTestCase { - - func testSubScript() throws { - let str = "-52x^{13+y}_{15-} + (-12.3 *)\\frac{-12}{15.2}" - let list = MTMathListBuilder.build(fromString: str)! - let finalized = list.finalized - try self.checkListContents(finalized) - // refinalizing a finalized list should not cause any more changes - try self.checkListContents(finalized.finalized) - } - - func checkListContents(_ finalized:MTMathList) throws { - // check - XCTAssertEqual((finalized.atoms.count), 10, "Num atoms"); - var atom = finalized.atoms[0]; - XCTAssertEqual(atom.type, .unaryOperator, "Atom 0"); - XCTAssertEqual(atom.nucleus, "−", "Atom 0 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 1)), "Range"); - atom = finalized.atoms[1]; - XCTAssertEqual(atom.type, .number, "Atom 1"); - XCTAssertEqual(atom.nucleus, "52", "Atom 1 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(1, 2)), "Range"); - atom = finalized.atoms[2]; - XCTAssertEqual(atom.type, .variable, "Atom 2"); - XCTAssertEqual(atom.nucleus, "x", "Atom 2 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(3, 1)), "Range"); - - let superScr = atom.superScript! - XCTAssertEqual((superScr.atoms.count), 3, "Super script"); - atom = superScr.atoms[0]; - XCTAssertEqual(atom.type, .number, "Super Atom 0"); - XCTAssertEqual(atom.nucleus, "13", "Super Atom 0 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 2)), "Range"); - atom = superScr.atoms[1]; - XCTAssertEqual(atom.type, .binaryOperator, "Super Atom 1"); - XCTAssertEqual(atom.nucleus, "+", "Super Atom 1 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(2, 1)), "Range"); - atom = superScr.atoms[2]; - XCTAssertEqual(atom.type, .variable, "Super Atom 2"); - XCTAssertEqual(atom.nucleus, "y", "Super Atom 2 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(3, 1)), "Range"); - - atom = finalized.atoms[2]; - let subScr = atom.subScript! - XCTAssertEqual((subScr.atoms.count), 2, "Sub script"); - atom = subScr.atoms[0]; - XCTAssertEqual(atom.type, .number, "Sub Atom 0"); - XCTAssertEqual(atom.nucleus, "15", "Sub Atom 0 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 2)), "Range"); - atom = subScr.atoms[1]; - XCTAssertEqual(atom.type, .unaryOperator, "Sub Atom 1"); - XCTAssertEqual(atom.nucleus, "−", "Sub Atom 1 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(2, 1)), "Range"); - - atom = finalized.atoms[3]; - XCTAssertEqual(atom.type, .binaryOperator, "Atom 3"); - XCTAssertEqual(atom.nucleus, "+", "Atom 3 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(4, 1)), "Range"); - atom = finalized.atoms[4]; - XCTAssertEqual(atom.type, .open, "Atom 4"); - XCTAssertEqual(atom.nucleus, "(", "Atom 4 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(5, 1)), "Range"); - atom = finalized.atoms[5]; - XCTAssertEqual(atom.type, .unaryOperator, "Atom 5"); - XCTAssertEqual(atom.nucleus, "−", "Atom 5 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(6, 1)), "Range"); - atom = finalized.atoms[6]; - XCTAssertEqual(atom.type, .number, "Atom 6"); - XCTAssertEqual(atom.nucleus, "12.3", "Atom 6 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(7, 4)), "Range"); - atom = finalized.atoms[7]; - XCTAssertEqual(atom.type, .unaryOperator, "Atom 7"); - XCTAssertEqual(atom.nucleus, "*", "Atom 7 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(11, 1)), "Range"); - atom = finalized.atoms[8]; - XCTAssertEqual(atom.type, .close, "Atom 8"); - XCTAssertEqual(atom.nucleus, ")", "Atom 8 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(12, 1)), "Range"); - - let frac = finalized.atoms[9] as! MTFraction - XCTAssertEqual(frac.type, .fraction, "Atom 9"); - XCTAssertEqual(frac.nucleus, "", "Atom 9 value"); - XCTAssertTrue(NSEqualRanges(frac.indexRange, NSMakeRange(13, 1)), "Range"); - - let numer = frac.numerator! - XCTAssertNotNil(numer, "Numerator"); - XCTAssertEqual((numer.atoms.count), 2, "Numer script"); - atom = numer.atoms[0]; - XCTAssertEqual(atom.type, .unaryOperator, "Numer Atom 0"); - XCTAssertEqual(atom.nucleus, "−", "Numer Atom 0 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 1)), "Range"); - atom = numer.atoms[1]; - XCTAssertEqual(atom.type, .number, "Numer Atom 1"); - XCTAssertEqual(atom.nucleus, "12", "Numer Atom 1 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(1, 2)), "Range"); - - - let denom = frac.denominator! - XCTAssertNotNil(denom, "Denominator"); - XCTAssertEqual((denom.atoms.count), 1, "Denom script"); - atom = denom.atoms[0]; - XCTAssertEqual(atom.type, .number, "Denom Atom 0"); - XCTAssertEqual(atom.nucleus, "15.2", "Denom Atom 0 value"); - XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 4)), "Range"); - - } - - func testAdd() throws { - let list = MTMathList() - XCTAssertEqual(list.atoms.count, 0); - let atom = MTMathAtomFactory.placeholder() - list.add(atom) - XCTAssertEqual(list.atoms.count, 1); - XCTAssertEqual(list.atoms[0], atom); - let atom2 = MTMathAtomFactory.placeholder() - list.add(atom2); - XCTAssertEqual(list.atoms.count, 2); - XCTAssertEqual(list.atoms[0], atom); - XCTAssertEqual(list.atoms[1], atom2); - } - - private var options : XCTExpectedFailure.Options { - let op = XCTExpectedFailure.Options() - op.isStrict = true - return op - } - - func testAddErrors() throws { - let list = MTMathList() - var atom : MTMathAtom? = nil - list.add(atom) - atom = MTMathAtom(type: .boundary, value: "") - XCTExpectFailure("Test adding an illegal atom", options:options) { - XCTAssertThrowsError(list.add(atom)) - } - } - - func testInsert() throws { - let list = MTMathList() - XCTAssertEqual(list.atoms.count, 0); - let atom = MTMathAtomFactory.placeholder() - list.insert(atom, at: 0) - XCTAssertEqual(list.atoms.count, 1); - XCTAssertEqual(list.atoms[0], atom); - let atom2 = MTMathAtomFactory.placeholder() - list.insert(atom2, at: 0) - XCTAssertEqual(list.atoms.count, 2); - XCTAssertEqual(list.atoms[0], atom2); - XCTAssertEqual(list.atoms[1], atom); - let atom3 = MTMathAtomFactory.placeholder() - list.insert(atom3, at: 2) - XCTAssertEqual(list.atoms.count, 3); - XCTAssertEqual(list.atoms[0], atom2); - XCTAssertEqual(list.atoms[1], atom); - XCTAssertEqual(list.atoms[2], atom3); - } - - func testInsertErrors() throws { - let list = MTMathList() - var atom : MTMathAtom? = nil - list.insert(atom, at: 0) - atom = MTMathAtom(type: .boundary, value:"") - XCTExpectFailure("Test adding an illegal atom", options:options) { - XCTAssertThrowsError(list.insert(atom, at:0)) - } - atom = MTMathAtomFactory.placeholder() - list.insert(atom, at:1) - } - - func testAppend() throws { - let list1 = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.placeholder() - let atom3 = MTMathAtomFactory.placeholder() - list1.add(atom) - list1.add(atom2) - list1.add(atom3) - - let list2 = MTMathList() - let atom5 = MTMathAtomFactory.times() - let atom6 = MTMathAtomFactory.divide() - list2.add(atom5) - list2.add(atom6) - - XCTAssertEqual(list1.atoms.count, 3); - XCTAssertEqual(list2.atoms.count, 2); - - list1.append(list2) - XCTAssertEqual(list1.atoms.count, 5); - XCTAssertEqual(list1.atoms[3], atom5); - XCTAssertEqual(list1.atoms[4], atom6); - } - - func testRemoveLast() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - list.add(atom) - XCTAssertEqual(list.atoms.count, 1); - list.removeLastAtom() - XCTAssertEqual(list.atoms.count, 0); - // Removing from empty list. - list.removeLastAtom() - XCTAssertEqual(list.atoms.count, 0); - let atom2 = MTMathAtomFactory.placeholder() - list.add(atom) - list.add(atom2); - XCTAssertEqual(list.atoms.count, 2); - list.removeLastAtom() - XCTAssertEqual(list.atoms.count, 1); - XCTAssertEqual(list.atoms[0], atom); - } - - func testRemoveAtomAtIndex() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.placeholder() - list.add(atom) - list.add(atom2); - XCTAssertEqual(list.atoms.count, 2); - list.removeAtom(at:0) - XCTAssertEqual(list.atoms.count, 1); - XCTAssertEqual(list.atoms[0], atom2); - - // Index out of range - XCTExpectFailure("Test removing an out-of-index cell", options: options) { - XCTAssertThrowsError(list.removeAtom(at:2)) - } - } - - func testRemoveAtomsInRange() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.placeholder() - let atom3 = MTMathAtomFactory.placeholder() - list.add(atom) - list.add(atom2); - list.add(atom3) - XCTAssertEqual(list.atoms.count, 3) - list.removeAtoms(in: 1...2) - XCTAssertEqual(list.atoms.count, 1); - XCTAssertEqual(list.atoms[0], atom); - - // Index out of range - XCTExpectFailure("Test removing an out-of-bounds range", options: options) { - XCTAssertThrowsError(list.removeAtoms(in: 1...3)) - } - } - -// func MTAssertEqual(test, expression1, expression2, ...) \ -// _XCTPrimitiveAssertEqual(test, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__) -// -// func MTAssertNotEqual(test, expression1, expression2, ...) \ -// _XCTPrimitiveAssertNotEqual(test, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__) - - func checkAtomCopy(_ copy:MTMathAtom?, original:MTMathAtom?, forTest test:String) throws { - guard let copy = copy, let original = original else { return } - XCTAssertEqual(copy.type, original.type, test) - XCTAssertEqual(copy.nucleus, original.nucleus, test) - // Should be different objects with the same content - XCTAssertNotEqual(copy, original, test) - } - - func checkListCopy(_ copy:MTMathList?, original:MTMathList?, forTest test:String) throws { - guard let copy = copy, let original = original else { return } - XCTAssertEqual(copy.atoms.count, original.atoms.count, test) - for (i, copyAtom) in copy.atoms.enumerated() { - let origAtom = original.atoms[i]; - try self.checkAtomCopy(copyAtom, original:origAtom, forTest:test) - } - } - - func testCopy() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom) - list.add(atom2); - list.add(atom3) - - let list2 = MTMathList(list) - try checkListCopy(list2, original:list, forTest:self.description) - } - - func testAtomInit() throws { - var atom = MTMathAtom(type: .open, value: "(") - XCTAssertEqual(atom.nucleus, "(") - XCTAssertEqual(atom.type, .open) - - atom = MTMathAtom(type: .radical, value:"(") - XCTAssertEqual(atom.nucleus, ""); - XCTAssertEqual(atom.type, .radical); - } - - func testAtomScripts() throws { - var atom = MTMathAtom(type: .open, value:"(") - XCTAssertTrue(atom.isScriptAllowed()) - atom.subScript = MTMathList() - XCTAssertNotNil(atom.subScript); - atom.superScript = MTMathList() - XCTAssertNotNil(atom.superScript); - - atom = MTMathAtom(type: .boundary, value:"(") - XCTAssertFalse(atom.isScriptAllowed()); - // Can set to nil - atom.subScript = nil; - XCTAssertNil(atom.subScript); - atom.superScript = nil; - XCTAssertNil(atom.superScript); - // Can't set to value - let list = MTMathList() - - XCTExpectFailure("No sub/super-script on boundary atoms", options: options) { - XCTAssertThrowsError(atom.subScript = list) - XCTAssertThrowsError(atom.superScript = list) - } - } - - func testAtomCopy() throws { - let list = MTMathList() - let atom1 = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom1) - list.add(atom2); - list.add(atom3) - - let list2 = MTMathList() - list2.add(atom3) - list2.add(atom2) - - let atom = MTMathAtom(type: .open, value:"(") - atom.subScript = list; - atom.superScript = list2; - let copy : MTMathAtom = atom.copy() - - try checkAtomCopy(copy, original:atom, forTest:self.description) - try checkListCopy(copy.superScript, original:atom.superScript, forTest:self.description) - try checkListCopy(copy.subScript, original:atom.subScript, forTest:self.description) - } - - func testCopyFraction() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom) - list.add(atom2); - list.add(atom3) - - let list2 = MTMathList() - list2.add(atom3) - list2.add(atom2) - - let frac = MTFraction(hasRule: false) - XCTAssertEqual(frac.type, .fraction); - frac.numerator = list; - frac.denominator = list2; - frac.leftDelimiter = "a"; - frac.rightDelimiter = "b"; - - let copy = MTFraction(frac) - try checkAtomCopy(copy, original:frac, forTest:self.description) - try checkListCopy(copy.numerator, original:frac.numerator, forTest:self.description) - try checkListCopy(copy.denominator, original:frac.denominator, forTest:self.description) - XCTAssertFalse(copy.hasRule) - XCTAssertEqual(copy.leftDelimiter, "a"); - XCTAssertEqual(copy.rightDelimiter, "b"); - } - - func testCopyRadical() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom) - list.add(atom2); - list.add(atom3) - - let list2 = MTMathList() - list2.add(atom3) - list2.add(atom2) - - let rad = MTRadical() - XCTAssertEqual(rad.type, .radical) - rad.radicand = list; - rad.degree = list2; - - let copy = MTRadical(rad) - try checkAtomCopy(copy, original:rad, forTest:self.description) - try checkListCopy(copy.radicand, original:rad.radicand ,forTest:self.description) - try checkListCopy(copy.degree, original:rad.degree, forTest:self.description) - } - - func testCopyLargeOperator() throws { - let lg = MTLargeOperator(value: "lim", limits:true) - XCTAssertEqual(lg.type, .largeOperator); - XCTAssertTrue(lg.limits); - - let copy = MTLargeOperator(lg) - try checkAtomCopy(copy, original:lg, forTest:self.description) - XCTAssertEqual(copy.limits, lg.limits); - } - - func testCopyInner() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom) - list.add(atom2); - list.add(atom3) - - let inner = MTInner() - inner.innerList = list; - inner.leftBoundary = MTMathAtom(type: .boundary, value: "(") - inner.rightBoundary = MTMathAtom(type: .boundary, value:")") - XCTAssertEqual(inner.type, .inner); - - let copy = MTInner(inner) - try checkAtomCopy(copy, original:inner, forTest:self.description) - try checkListCopy(copy.innerList, original:inner.innerList, forTest:self.description) - try checkAtomCopy(copy.leftBoundary!, original:inner.leftBoundary, forTest:self.description) - try checkAtomCopy(copy.rightBoundary, original:inner.rightBoundary, forTest:self.description) - } - - func testSetInnerBoundary() throws { - let inner = MTInner() - - // Can set non-nil - inner.leftBoundary = MTMathAtom(type: .boundary, value:"(") - inner.rightBoundary = MTMathAtom(type: .boundary, value:")") - XCTAssertNotNil(inner.leftBoundary); - XCTAssertNotNil(inner.rightBoundary); - // Can set nil - inner.leftBoundary = nil; - inner.rightBoundary = nil; - XCTAssertNil(inner.leftBoundary); - XCTAssertNil(inner.rightBoundary); - // Can't set non boundary - let atom = MTMathAtomFactory.placeholder() - XCTExpectFailure("Setting illegal boundary atoms", options: options) { - XCTAssertThrowsError(inner.leftBoundary = atom); - XCTAssertThrowsError(inner.rightBoundary = atom); - } - } - - func testCopyOverline() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom) - list.add(atom2); - list.add(atom3) - - let over = MTOverLine() - XCTAssertEqual(over.type, .overline); - over.innerList = list; - - let copy = MTOverLine(over) - try checkAtomCopy(copy, original:over, forTest:self.description) - try checkListCopy(copy.innerList, original:over.innerList, forTest:self.description) - } - - func testCopyUnderline() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom) - list.add(atom2); - list.add(atom3) - - let under = MTUnderLine() - XCTAssertEqual(under.type, .underline); - under.innerList = list; - - let copy = MTUnderLine(under) - try checkAtomCopy(copy, original:under, forTest:self.description) - try checkListCopy(copy.innerList, original:under.innerList, forTest:self.description) - } - - func testCopyAcccent() throws { - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom) - list.add(atom2); - list.add(atom3) - - let accent = MTAccent(value: "^") - XCTAssertEqual(accent.type, .accent); - accent.innerList = list; - - let copy = MTAccent(accent) - try checkAtomCopy(copy, original:accent, forTest:self.description) - try checkListCopy(copy.innerList ,original:accent.innerList, forTest:self.description) - } - - func testCopySpace() throws { - let space = MTMathSpace(space: 3) - XCTAssertEqual(space.type, .space); - - let copy = MTMathSpace(space) - try checkAtomCopy(copy, original:space, forTest:self.description) - XCTAssertEqual(space.space, copy.space); - } - - func testCopyStyle() throws { - let style = MTMathStyle(style: .script) - XCTAssertEqual(style.type, .style); - - let copy = MTMathStyle(style) - try checkAtomCopy(copy, original:style, forTest:self.description) - XCTAssertEqual(style.style, copy.style); - } - - func testCreateMathTable() throws { - let table = MTMathTable() - XCTAssertEqual(table.type, .table); - - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom) - list.add(atom2); - list.add(atom3) - - let list2 = MTMathList() - list2.add(atom3) - list2.add(atom2) - - table.set(cell: list, forRow:3, column:2) - table.set(cell: list2, forRow:1, column:0) - - table.set(alignment: .left, forColumn: 2) - table.set(alignment: .right, forColumn:1) - - // Verify that everything is created correctly - XCTAssertEqual(table.cells.count, 4); // 4 rows - XCTAssertNotNil(table.cells[0]); - XCTAssertEqual(table.cells[0].count, 0); // 0 elements in row 0 - XCTAssertEqual(table.cells[1].count, 1); // 1 element in row 1 - XCTAssertNotNil(table.cells[2]); - XCTAssertEqual(table.cells[2].count, 0); - XCTAssertEqual(table.cells[3].count, 3); - - // Verify the elements in the rows - XCTAssertEqual(table.cells[1][0].atoms.count, 2); - XCTAssertEqual(table.cells[1][0], list2); - XCTAssertNotNil(table.cells[3][0]); - XCTAssertEqual(table.cells[3][0].atoms.count, 0); - - XCTAssertNotNil(table.cells[3][0]); - XCTAssertEqual(table.cells[3][0].atoms.count, 0); - - XCTAssertNotNil(table.cells[3][1]); - XCTAssertEqual(table.cells[3][1].atoms.count, 0); - - XCTAssertEqual(table.cells[3][2], list); - - XCTAssertEqual(table.numRows, 4); - XCTAssertEqual(table.numColumns, 3); - - // Verify the alignments - XCTAssertEqual(table.alignments.count, 3); - XCTAssertEqual(table.alignments[0], .center); - XCTAssertEqual(table.alignments[1], .right); - XCTAssertEqual(table.alignments[2], .left); - } - - func testCopyMathTable() throws { - let table = MTMathTable() - XCTAssertEqual(table.type, .table); - - let list = MTMathList() - let atom = MTMathAtomFactory.placeholder() - let atom2 = MTMathAtomFactory.times() - let atom3 = MTMathAtomFactory.divide() - list.add(atom) - list.add(atom2); - list.add(atom3) - - let list2 = MTMathList() - list2.add(atom3) - list2.add(atom2) - - table.set(cell:list, forRow:0, column:1) - table.set(cell:list2, forRow:0, column:2) - - table.set(alignment: .left, forColumn:2) - table.set(alignment: .right, forColumn:1) - table.interRowAdditionalSpacing = 3; - table.interColumnSpacing = 10; - - let copy = MTMathTable(table) - try checkAtomCopy(copy, original:table, forTest:self.description) - XCTAssertEqual(copy.interColumnSpacing, table.interColumnSpacing); - XCTAssertEqual(copy.interRowAdditionalSpacing, table.interRowAdditionalSpacing); - XCTAssertEqual(copy.alignments, table.alignments) - - XCTAssertNotEqual(copy.cells, table.cells); - XCTAssertNotEqual(copy.cells[0], table.cells[0] ); - XCTAssertEqual(copy.cells[0].count, table.cells[0].count); - XCTAssertEqual(copy.cells[0][0].atoms.count, 0); - XCTAssertNotEqual(copy.cells[0][0], table.cells[0][0]); - try checkListCopy(copy.cells[0][1], original:list, forTest:self.description) - try checkListCopy(copy.cells[0][2], original:list2, forTest:self.description) - } - -} diff --git a/Tests/SwiftUIMathTests/Internal/Model/AtomListTests.swift b/Tests/SwiftUIMathTests/Internal/Model/AtomListTests.swift new file mode 100644 index 0000000..97422f3 --- /dev/null +++ b/Tests/SwiftUIMathTests/Internal/Model/AtomListTests.swift @@ -0,0 +1,612 @@ +import Foundation +import Testing +@testable import SwiftUIMath + +@Suite +struct AtomListTests { + @Test + func parsesScriptsAndFinalizesAtomList() throws { + let input = "-52x^{13+y}_{15-} + (-12.3 *)\\frac{-12}{15.2}" + let list = try parseFinalizedAtomList(from: input) + + try assertFinalizedAtomListContents(list) + // Re-finalizing should be stable. + try assertFinalizedAtomListContents(list.finalized) + } + + @Test + func appendsAtomsInOrder() { + let list = Math.AtomList() + #expect(list.atoms.isEmpty) + + let first = Math.AtomFactory.placeholder() + list.append(first) + #expect(list.atoms.count == 1) + #expect(list.atoms[0] === first) + + let second = Math.AtomFactory.placeholder() + list.append(second) + #expect(list.atoms.count == 2) + #expect(list.atoms[0] === first) + #expect(list.atoms[1] === second) + } + + @Test + func insertsAtomsAtIndices() { + let list = Math.AtomList() + let first = Math.AtomFactory.placeholder() + list.insert(first, at: 0) + #expect(list.atoms.count == 1) + #expect(list.atoms[0] === first) + + let second = Math.AtomFactory.placeholder() + list.insert(second, at: 0) + #expect(list.atoms.count == 2) + #expect(list.atoms[0] === second) + #expect(list.atoms[1] === first) + + let third = Math.AtomFactory.placeholder() + list.insert(third, at: 2) + #expect(list.atoms.count == 3) + #expect(list.atoms[0] === second) + #expect(list.atoms[1] === first) + #expect(list.atoms[2] === third) + } + + @Test + func appendsListContents() { + let list1 = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.placeholder() + let atom3 = Math.AtomFactory.placeholder() + list1.append(atom1) + list1.append(atom2) + list1.append(atom3) + + let list2 = Math.AtomList() + let atom4 = Math.AtomFactory.times() + let atom5 = Math.AtomFactory.divide() + list2.append(atom4) + list2.append(atom5) + + #expect(list1.atoms.count == 3) + #expect(list2.atoms.count == 2) + + list1.append(contentsOf: list2) + #expect(list1.atoms.count == 5) + #expect(list1.atoms[3] === atom4) + #expect(list1.atoms[4] === atom5) + } + + @Test + func removesLastAtom() { + let list = Math.AtomList() + let atom = Math.AtomFactory.placeholder() + list.append(atom) + + #expect(list.atoms.count == 1) + list.removeLastAtomForTesting() + #expect(list.atoms.isEmpty) + + list.removeLastAtomForTesting() + #expect(list.atoms.isEmpty) + + let atom2 = Math.AtomFactory.placeholder() + list.append(atom) + list.append(atom2) + + #expect(list.atoms.count == 2) + list.removeLastAtomForTesting() + #expect(list.atoms.count == 1) + #expect(list.atoms[0] === atom) + } + + @Test + func removesAtomAtIndex() { + let list = Math.AtomList() + let first = Math.AtomFactory.placeholder() + let second = Math.AtomFactory.placeholder() + list.append(first) + list.append(second) + + #expect(list.atoms.count == 2) + #expect(list.removeAtomForTesting(at: 0)) + #expect(list.atoms.count == 1) + #expect(list.atoms[0] === second) + + #expect(!list.removeAtomForTesting(at: 2)) + } + + @Test + func removesAtomsInRange() { + let list = Math.AtomList() + let first = Math.AtomFactory.placeholder() + let second = Math.AtomFactory.placeholder() + let third = Math.AtomFactory.placeholder() + list.append(first) + list.append(second) + list.append(third) + + #expect(list.atoms.count == 3) + #expect(list.removeAtomsForTesting(in: 1...2)) + #expect(list.atoms.count == 1) + #expect(list.atoms[0] === first) + + #expect(!list.removeAtomsForTesting(in: 1...3)) + } + + @Test + func copiesAtomListsWithDistinctAtoms() throws { + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let copy = Math.AtomList(list) + try assertAtomListCopyMatches(copy, original: list, context: "atom list copy") + } + + @Test + func initializesAtomWithCorrectNucleusAndType() { + var atom = Math.Atom(type: .open, value: "(") + #expect(atom.nucleus == "(") + #expect(atom.type == .open) + + atom = Math.Atom(type: .radical, value: "(") + #expect(atom.nucleus.isEmpty) + #expect(atom.type == .radical) + } + + @Test + func supportsScriptsWhenAllowed() { + var atom = Math.Atom(type: .open, value: "(") + #expect(atom.allowsScripts) + + atom.`subscript` = Math.AtomList() + #expect(atom.`subscript` != nil) + + atom.superscript = Math.AtomList() + #expect(atom.superscript != nil) + + atom = Math.Atom(type: .boundary, value: "(") + #expect(!atom.allowsScripts) + #expect(atom.`subscript` == nil) + #expect(atom.superscript == nil) + } + + @Test + func copiesAtomsWithScripts() throws { + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let list2 = Math.AtomList() + list2.append(atom3) + list2.append(atom2) + + let atom = Math.Atom(type: .open, value: "(") + atom.`subscript` = list + atom.superscript = list2 + + let copy = atom.copy() + try assertAtomCopyMatches(copy, original: atom, context: "atom copy") + try assertAtomListCopyMatches( + copy.superscript, original: atom.superscript, context: "superscript copy" + ) + try assertAtomListCopyMatches( + copy.`subscript`, original: atom.`subscript`, context: "subscript copy" + ) + } + + @Test + func copiesFraction() throws { + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let list2 = Math.AtomList() + list2.append(atom3) + list2.append(atom2) + + let fraction = Math.Fraction(hasRule: false) + #expect(fraction.type == .fraction) + fraction.numerator = list + fraction.denominator = list2 + fraction.leftDelimiter = "a" + fraction.rightDelimiter = "b" + + let copy = Math.Fraction(fraction) + try assertAtomCopyMatches(copy, original: fraction, context: "fraction copy") + try assertAtomListCopyMatches( + copy.numerator, original: fraction.numerator, context: "fraction numerator copy" + ) + try assertAtomListCopyMatches( + copy.denominator, original: fraction.denominator, context: "fraction denominator copy" + ) + #expect(!copy.hasRule) + #expect(copy.leftDelimiter == "a") + #expect(copy.rightDelimiter == "b") + } + + @Test + func copiesRadical() throws { + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let list2 = Math.AtomList() + list2.append(atom3) + list2.append(atom2) + + let radical = Math.Radical() + #expect(radical.type == .radical) + radical.radicand = list + radical.degree = list2 + + let copy = Math.Radical(radical) + try assertAtomCopyMatches(copy, original: radical, context: "radical copy") + try assertAtomListCopyMatches(copy.radicand, original: radical.radicand, context: "radicand copy") + try assertAtomListCopyMatches(copy.degree, original: radical.degree, context: "degree copy") + } + + @Test + func copiesLargeOperator() throws { + let largeOperator = Math.LargeOperator(limits: true) + largeOperator.nucleus = "lim" + #expect(largeOperator.type == .largeOperator) + #expect(largeOperator.limits) + + let copy = Math.LargeOperator(largeOperator) + try assertAtomCopyMatches(copy, original: largeOperator, context: "large operator copy") + #expect(copy.limits == largeOperator.limits) + } + + @Test + func copiesInnerAtom() throws { + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let inner = Math.Inner() + inner.innerList = list + inner.leftBoundary = Math.Atom(type: .boundary, value: "(") + inner.rightBoundary = Math.Atom(type: .boundary, value: ")") + #expect(inner.type == .inner) + + let copy = Math.Inner(inner) + try assertAtomCopyMatches(copy, original: inner, context: "inner atom copy") + try assertAtomListCopyMatches(copy.innerList, original: inner.innerList, context: "inner list copy") + try assertAtomCopyMatches(copy.leftBoundary!, original: inner.leftBoundary, context: "left boundary copy") + try assertAtomCopyMatches(copy.rightBoundary!, original: inner.rightBoundary, context: "right boundary copy") + } + + @Test + func setsInnerBoundaries() { + let inner = Math.Inner() + + inner.leftBoundary = Math.Atom(type: .boundary, value: "(") + inner.rightBoundary = Math.Atom(type: .boundary, value: ")") + #expect(inner.leftBoundary != nil) + #expect(inner.rightBoundary != nil) + + inner.leftBoundary = nil + inner.rightBoundary = nil + #expect(inner.leftBoundary == nil) + #expect(inner.rightBoundary == nil) + } + + @Test + func copiesOverline() throws { + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let overline = Math.Overline() + #expect(overline.type == .overline) + overline.innerList = list + + let copy = Math.Overline(overline) + try assertAtomCopyMatches(copy, original: overline, context: "overline copy") + try assertAtomListCopyMatches(copy.innerList, original: overline.innerList, context: "overline list copy") + } + + @Test + func copiesUnderline() throws { + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let underline = Math.Underline() + #expect(underline.type == .underline) + underline.innerList = list + + let copy = Math.Underline(underline) + try assertAtomCopyMatches(copy, original: underline, context: "underline copy") + try assertAtomListCopyMatches(copy.innerList, original: underline.innerList, context: "underline list copy") + } + + @Test + func copiesAccent() throws { + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let accent = Math.Accent(value: "^") + #expect(accent.type == .accent) + accent.innerList = list + + let copy = Math.Accent(accent) + try assertAtomCopyMatches(copy, original: accent, context: "accent copy") + try assertAtomListCopyMatches(copy.innerList, original: accent.innerList, context: "accent list copy") + } + + @Test + func copiesSpace() throws { + let space = Math.Space(amount: 3) + #expect(space.type == .space) + + let copy = Math.Space(space) + try assertAtomCopyMatches(copy, original: space, context: "space copy") + #expect(space.amount == copy.amount) + } + + @Test + func copiesStyle() throws { + let style = Math.Style(level: .script) + #expect(style.type == .style) + + let copy = Math.Style(style) + try assertAtomCopyMatches(copy, original: style, context: "style copy") + #expect(style.level == copy.level) + } + + @Test + func createsTableAtom() { + let table = Math.Table() + #expect(table.type == .table) + + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let list2 = Math.AtomList() + list2.append(atom3) + list2.append(atom2) + + table.setCell(list, forRow: 3, column: 2) + table.setCell(list2, forRow: 1, column: 0) + + table.setAlignment(.left, forColumn: 2) + table.setAlignment(.right, forColumn: 1) + + #expect(table.cells.count == 4) + #expect(table.cells[0].count == 0) + #expect(table.cells[1].count == 1) + #expect(table.cells[2].count == 0) + #expect(table.cells[3].count == 3) + + #expect(table.cells[1][0].atoms.count == 2) + #expect(table.cells[1][0] === list2) + + #expect(table.cells[3][0].atoms.isEmpty) + #expect(table.cells[3][1].atoms.isEmpty) + #expect(table.cells[3][2] === list) + + #expect(table.numberOfRows == 4) + #expect(table.numberOfColumns == 3) + + #expect(table.alignments.count == 3) + #expect(table.alignments[0] == .center) + #expect(table.alignments[1] == .right) + #expect(table.alignments[2] == .left) + } + + @Test + func copiesTableAtom() throws { + let table = Math.Table() + #expect(table.type == .table) + + let list = Math.AtomList() + let atom1 = Math.AtomFactory.placeholder() + let atom2 = Math.AtomFactory.times() + let atom3 = Math.AtomFactory.divide() + list.append(atom1) + list.append(atom2) + list.append(atom3) + + let list2 = Math.AtomList() + list2.append(atom3) + list2.append(atom2) + + table.setCell(list, forRow: 0, column: 1) + table.setCell(list2, forRow: 0, column: 2) + + table.setAlignment(.left, forColumn: 2) + table.setAlignment(.right, forColumn: 1) + table.interRowAdditionalSpacing = 3 + table.interColumnSpacing = 10 + + let copy = Math.Table(table) + try assertAtomCopyMatches(copy, original: table, context: "table copy") + #expect(copy.interColumnSpacing == table.interColumnSpacing) + #expect(copy.interRowAdditionalSpacing == table.interRowAdditionalSpacing) + #expect(copy.alignments == table.alignments) + + #expect(copy.cells[0].count == table.cells[0].count) + #expect(copy.cells[0][0].atoms.isEmpty) + #expect(copy.cells[0][0] !== table.cells[0][0]) + try assertAtomListCopyMatches(copy.cells[0][1], original: list, context: "table list copy") + try assertAtomListCopyMatches(copy.cells[0][2], original: list2, context: "table list copy") + } + + private func parseFinalizedAtomList(from input: String) throws -> Math.AtomList { + let list = try #require(Math.Parser.build(fromString: input)) + return list.finalized + } + + private func assertFinalizedAtomListContents(_ finalized: Math.AtomList) throws { + #expect(finalized.atoms.count == 10, "Num atoms") + + var atom = finalized.atoms[0] + try assertAtom(atom, type: .unaryOperator, nucleus: "−", range: NSRange(location: 0, length: 1)) + + atom = finalized.atoms[1] + try assertAtom(atom, type: .number, nucleus: "52", range: NSRange(location: 1, length: 2)) + + atom = finalized.atoms[2] + try assertAtom(atom, type: .variable, nucleus: "x", range: NSRange(location: 3, length: 1)) + + let superscript = try #require(atom.superscript) + #expect(superscript.atoms.count == 3, "Super script") + + atom = superscript.atoms[0] + try assertAtom(atom, type: .number, nucleus: "13", range: NSRange(location: 0, length: 2)) + + atom = superscript.atoms[1] + try assertAtom(atom, type: .binaryOperator, nucleus: "+", range: NSRange(location: 2, length: 1)) + + atom = superscript.atoms[2] + try assertAtom(atom, type: .variable, nucleus: "y", range: NSRange(location: 3, length: 1)) + + atom = finalized.atoms[2] + let subscriptList = try #require(atom.`subscript`) + #expect(subscriptList.atoms.count == 2, "Sub script") + + atom = subscriptList.atoms[0] + try assertAtom(atom, type: .number, nucleus: "15", range: NSRange(location: 0, length: 2)) + + atom = subscriptList.atoms[1] + try assertAtom(atom, type: .unaryOperator, nucleus: "−", range: NSRange(location: 2, length: 1)) + + atom = finalized.atoms[3] + try assertAtom(atom, type: .binaryOperator, nucleus: "+", range: NSRange(location: 4, length: 1)) + + atom = finalized.atoms[4] + try assertAtom(atom, type: .open, nucleus: "(", range: NSRange(location: 5, length: 1)) + + atom = finalized.atoms[5] + try assertAtom(atom, type: .unaryOperator, nucleus: "−", range: NSRange(location: 6, length: 1)) + + atom = finalized.atoms[6] + try assertAtom(atom, type: .number, nucleus: "12.3", range: NSRange(location: 7, length: 4)) + + atom = finalized.atoms[7] + try assertAtom(atom, type: .unaryOperator, nucleus: "*", range: NSRange(location: 11, length: 1)) + + atom = finalized.atoms[8] + try assertAtom(atom, type: .close, nucleus: ")", range: NSRange(location: 12, length: 1)) + + let fraction = try #require(finalized.atoms[9] as? Math.Fraction) + #expect(fraction.type == .fraction) + #expect(fraction.nucleus.isEmpty) + #expect(fraction.indexRange == NSRange(location: 13, length: 1)) + + let numerator = try #require(fraction.numerator) + #expect(numerator.atoms.count == 2, "Numerator") + + atom = numerator.atoms[0] + try assertAtom(atom, type: .unaryOperator, nucleus: "−", range: NSRange(location: 0, length: 1)) + + atom = numerator.atoms[1] + try assertAtom(atom, type: .number, nucleus: "12", range: NSRange(location: 1, length: 2)) + + let denominator = try #require(fraction.denominator) + #expect(denominator.atoms.count == 1, "Denominator") + + atom = denominator.atoms[0] + try assertAtom(atom, type: .number, nucleus: "15.2", range: NSRange(location: 0, length: 4)) + } + + private func assertAtom( + _ atom: Math.Atom, + type: Math.AtomType, + nucleus: String, + range: NSRange + ) throws { + #expect(atom.type == type) + #expect(atom.nucleus == nucleus) + #expect(atom.indexRange == range) + } + + private func assertAtomCopyMatches( + _ copy: Math.Atom?, + original: Math.Atom?, + context: String + ) throws { + let copy = try #require(copy, "Missing copy for \(context)") + let original = try #require(original, "Missing original for \(context)") + #expect(copy.type == original.type, "\(context) type") + #expect(copy.nucleus == original.nucleus, "\(context) nucleus") + #expect(copy !== original, "\(context) identity") + } + + private func assertAtomListCopyMatches( + _ copy: Math.AtomList?, + original: Math.AtomList?, + context: String + ) throws { + let copy = try #require(copy, "Missing copy for \(context)") + let original = try #require(original, "Missing original for \(context)") + #expect(copy.atoms.count == original.atoms.count, "\(context) count") + + for (index, copyAtom) in copy.atoms.enumerated() { + let originalAtom = original.atoms[index] + try assertAtomCopyMatches(copyAtom, original: originalAtom, context: "\(context) atom \(index)") + } + } +} + +extension Math.AtomList { + func removeLastAtomForTesting() { + guard !atoms.isEmpty else { return } + atoms.removeLast() + } + + func removeAtomForTesting(at index: Int) -> Bool { + guard atoms.indices.contains(index) else { return false } + atoms.remove(at: index) + return true + } + + func removeAtomsForTesting(in range: ClosedRange) -> Bool { + guard !atoms.isEmpty else { return false } + guard range.lowerBound >= 0, range.upperBound < atoms.count else { return false } + atoms.removeSubrange(range) + return true + } +} diff --git a/Tests/SwiftUIMathTests/Untitled.swift b/Tests/SwiftUIMathTests/Untitled.swift deleted file mode 100644 index 15fdc1e..0000000 --- a/Tests/SwiftUIMathTests/Untitled.swift +++ /dev/null @@ -1,6 +0,0 @@ -// -// Untitled.swift -// swiftui-math -// -// Created by Guille Gonzalez on 31/12/25. -//