661 lines
19 KiB
Swift
661 lines
19 KiB
Swift
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<Int>) -> Bool {
|
||
guard !atoms.isEmpty else { return false }
|
||
guard range.lowerBound >= 0, range.upperBound < atoms.count else { return false }
|
||
atoms.removeSubrange(range)
|
||
return true
|
||
}
|
||
}
|