CyborgTests/DrawCommandTests.swift (171 lines of code) (raw):
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
@testable import Cyborg
import XCTest
class DrawingCommandTests: XCTestCase {
func test_move() {
let (move, buffer) = XMLString.create(from: "M300,70")
defer {
buffer.deallocate()
}
let result = parseMoveAbsolute()(move, 0)
let expected = CGMutablePath()
let distance = CGPoint(x: 300, y: 70)
let movement: DrawingCommand = .moveAbsolute(distance)
expected.move(to: distance)
switch result {
case .ok(let pathSegment, _):
let path = createPath(from: pathSegment)
XCTAssertEqual(path, expected)
XCTAssertEqual(movement, pathSegment[0])
case .error(let error):
XCTFail(error.message)
}
}
func test_closePath() {
let (close, buffer) = XMLString.create(from: " z")
defer {
buffer.deallocate()
}
let result = consumeTrivia(before: parseClosePath())(close, 0)
let expected = CGMutablePath()
expected.closeSubpath()
switch result {
case .ok(let wrapped, let index):
let path = createPath(from: wrapped)
XCTAssertEqual(index, close.count)
XCTAssertEqual(path, expected)
case .error(let error):
XCTFail(error.message)
}
}
func test_line() {
"l 1,0 2,1 3,4".withXMLString { lineData in
let expected = CGMutablePath()
let points = [(1, 0), (2, 1), (3, 4)].map(CGPoint.init)
var last: CGPoint = .zero
for point in points {
let point = point.add(last)
last = point
expected.addLine(to: point)
}
switch parseLine()(lineData, 0) {
case .ok(let result, _):
let path = createPath(from: result)
XCTAssertEqual(path, expected)
case .error(let error):
XCTFail(error.message)
}
}
}
func test_parse_curve() {
let (curve, buffer) = XMLString.create(from: "c2,2 3,2 8,2")
defer {
buffer.deallocate()
}
let start = CGPoint(x: 6, y: 2)
let expected = CGMutablePath()
expected.move(to: start)
expected.addCurve(to: CGPoint(x: 8, y: 2).add(start),
control1: CGPoint(x: 2, y: 2).add(start),
control2: CGPoint(x: 3, y: 2).add(start))
switch parseCurve()(curve, 0) {
case .ok(let wrapped, let index):
let result = CGMutablePath()
result.move(to: start)
_ = createPath(from: wrapped, start: start.asPriorContext, path: result)
XCTAssertEqual(result, expected)
XCTAssertEqual(index, curve.count)
case .error(let error):
XCTFail(error.message)
}
}
func test_parse_vertical() {
"v 1 4 5"
.withXMLString { string in
switch parseVertical()(string, 0) {
case .ok(let wrapped, let index):
XCTAssertEqual(index, string.count)
let expected = CGMutablePath()
expected.move(to: .zero)
expected.addLine(to: .init(x: 0, y: 1))
expected.addLine(to: .init(x: 0, y: 5))
expected.addLine(to: .init(x: 0, y: 10))
let result = CGMutablePath()
result.move(to: .zero)
_ = createPath(from: wrapped, path: result)
XCTAssertEqual(result, expected)
case .error(let error):
XCTFail(error.message)
}
}
}
func test_parse_absolute_vertical() {
"V 1 4 5"
.withXMLString { string in
switch parseVerticalAbsolute()(string, 0) {
case .ok(let wrapped, let index):
XCTAssertEqual(index, string.count)
let expected = CGMutablePath()
expected.move(to: .zero)
expected.addLine(to: .init(x: 0, y: 1))
expected.addLine(to: .init(x: 0, y: 4))
expected.addLine(to: .init(x: 0, y: 5))
let result = CGMutablePath()
result.move(to: .zero)
_ = createPath(from: wrapped, path: result)
XCTAssertEqual(result, expected)
case .error(let error):
XCTFail(error.message)
}
}
}
func test_Matrix2x2d_multiplication() {
let point = CGPoint(x: 1, y: 0)
assertAlmostEqual(rotation(angle: 90 * .pi / 180).times(point), CGPoint(x: 0, y: 1))
assertAlmostEqual(rotation(angle: 360 * .pi / 180).times(point), point)
assertAlmostEqual(rotation(angle: 180 * .pi / 180).times(point), CGPoint(x: -1, y: 0))
}
func test_sphericalArc() {
let arc = EllipticArc(center: .zero,
radius: .init(x: 5, y: 5),
xAngle: 0)
XCTAssertEqual(arc.point(for: 0), CGPoint(x: 5, y: 0))
assertAlmostEqual(arc.point(for: 90 * .pi / 180), CGPoint(x: 0, y: 5))
assertAlmostEqual(arc.point(for: 180 * .pi / 180), CGPoint(x: -5, y: 0))
}
func test_ellipticArc() {
let arc = EllipticArc(center: .zero,
radius: .init(x: 10, y: 5),
xAngle: 0)
XCTAssertEqual(arc.point(for: 0), CGPoint(x: 10, y: 0))
}
func test_ellipticArcXAngle() {
let arc = EllipticArc(center: .zero,
radius: .init(x: 10, y: 5),
xAngle: .pi)
assertAlmostEqual(arc.point(for: 0), CGPoint(x: -10, y: 0))
assertAlmostEqual(arc.point(for: .pi), CGPoint(x: 10, y: 0))
}
func rotation(angle: CGFloat) -> Matrix2x2 {
return .init(m00: cos(angle),
m01: sin(angle),
m10: -sin(angle),
m11: cos(angle))
}
}
func assertAlmostEqual(_ lhs: CGPoint,
_ rhs: CGPoint,
line: UInt = #line) {
let error: CGFloat = 0.001 // TODO: I just picked this number arbitrarily
XCTAssert(abs(lhs.x - rhs.x) < error && abs(lhs.y - rhs.y) < error,
"\(lhs), \(rhs) are not close to equal.", line: line)
}
func consumeTrivia<T>(before: @escaping Parser<T>) -> Parser<T> {
return { stream, index in
var next = index
while next != stream.count,
stream[next] == .whitespace || stream[next] == .newline {
next += 1
}
return before(stream, next)
}
}