aboutsummaryrefslogtreecommitdiff
path: root/MapTests
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2025-07-04 17:06:28 +0200
committerRuben Beltran del Rio <git@r.bdr.sh>2025-07-04 17:06:28 +0200
commitc843d34f56c207abcf4b93e424125eea2dc601e0 (patch)
tree415dc2d9be4be31e290e1dd1dedb0b630c5e8fc6 /MapTests
parented10ac191df473c92c4fec495aafa7f569d108c8 (diff)
Upgrade to Swift 6
Diffstat (limited to 'MapTests')
-rw-r--r--MapTests/DebouncerTests.swift71
-rw-r--r--MapTests/MapDocumentTests.swift81
-rw-r--r--MapTests/MapParserStrategyTests.swift163
-rw-r--r--MapTests/MapTests.swift162
-rw-r--r--MapTests/StageTests.swift119
5 files changed, 594 insertions, 2 deletions
diff --git a/MapTests/DebouncerTests.swift b/MapTests/DebouncerTests.swift
new file mode 100644
index 0000000..9ac36cd
--- /dev/null
+++ b/MapTests/DebouncerTests.swift
@@ -0,0 +1,71 @@
+// Copyright (C) 2024 Rubén Beltrán del Río
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import Foundation
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see https://map.tranquil.systems.
+import Testing
+
+@testable import Map
+
+struct DebouncerTests {
+
+ @Test func testDebouncerExecutesAfterDelay() async throws {
+ let debouncer = Debouncer(seconds: 0.1)
+ var executed = false
+
+ debouncer.debounce {
+ executed = true
+ }
+
+ #expect(executed == false)
+
+ try await Task.sleep(nanoseconds: 150_000_000) // 0.15 seconds
+
+ #expect(executed == true)
+ }
+
+ @Test func testDebouncerCancelsReplacement() async throws {
+ let debouncer = Debouncer(seconds: 0.1)
+ var firstExecuted = false
+ var secondExecuted = false
+
+ debouncer.debounce {
+ firstExecuted = true
+ }
+
+ debouncer.debounce {
+ secondExecuted = true
+ }
+
+ try await Task.sleep(nanoseconds: 150_000_000) // 0.15 seconds
+
+ #expect(firstExecuted == false)
+ #expect(secondExecuted == true)
+ }
+
+ @Test func testDebouncerMultipleReplacements() async throws {
+ let debouncer = Debouncer(seconds: 0.1)
+ var counter = 0
+
+ for i in 1...5 {
+ debouncer.debounce {
+ counter += i
+ }
+ }
+
+ try await Task.sleep(nanoseconds: 150_000_000) // 0.15 seconds
+
+ #expect(counter == 5)
+ }
+
+}
diff --git a/MapTests/MapDocumentTests.swift b/MapTests/MapDocumentTests.swift
new file mode 100644
index 0000000..7ae93fc
--- /dev/null
+++ b/MapTests/MapDocumentTests.swift
@@ -0,0 +1,81 @@
+// Copyright (C) 2024 Rubén Beltrán del Río
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import SwiftUI
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see https://map.tranquil.systems.
+import Testing
+import UniformTypeIdentifiers
+
+@testable import Map
+
+struct MapDocumentTests {
+
+ @Test func testMapDocumentInitialization() async throws {
+ let document = MapDocument()
+ #expect(
+ document.text == """
+ Anchor (60, 5) [x]
+ Node A (78, 23)
+ Node B (40, 30) [square]
+ Node C (65, 70)
+
+ Anchor -> Node A
+ Anchor -> Node B
+ Node A -> Node C
+ Node B -> Node C
+ """)
+ }
+
+ @Test func testMapDocumentInitializationWithText() async throws {
+ let testText = "Test map content"
+ let document = MapDocument(text: testText)
+ #expect(document.text == testText)
+ }
+
+ @Test func testMapDocumentReadableContentTypes() async throws {
+ let contentTypes = MapDocument.readableContentTypes
+ #expect(contentTypes.count == 1)
+ #expect(contentTypes.first == UTType.wmap)
+ }
+
+ @Test func testMapDocumentTextProperty() async throws {
+ let document = MapDocument(text: "Test content")
+ #expect(document.text == "Test content")
+
+ // Test that the document can handle different text content
+ let mapContent = "Node A (10, 20)\nNode B (30, 40)\nNode A -> Node B"
+ let mapDocument = MapDocument(text: mapContent)
+ #expect(mapDocument.text == mapContent)
+ }
+
+ @Test func testMapDocumentWithEmptyContent() async throws {
+ let document = MapDocument(text: "")
+ #expect(document.text == "")
+ }
+
+ @Test func testMapDocumentExportAsImage() async throws {
+ let document = MapDocument(text: "Node A (10, 20)\nNode B (30, 40)")
+
+ await MainActor.run {
+ let image = document.exportAsImage(withEvolution: .general)
+ #expect(image != nil)
+ #expect(image!.size.width > 0)
+ #expect(image!.size.height > 0)
+ }
+ }
+
+ @Test func testUTTypeExampleText() async throws {
+ let utType = UTType.wmap
+ #expect(utType.identifier == "systems.tranquil.map.wmap")
+ }
+}
diff --git a/MapTests/MapParserStrategyTests.swift b/MapTests/MapParserStrategyTests.swift
new file mode 100644
index 0000000..c9f92e8
--- /dev/null
+++ b/MapTests/MapParserStrategyTests.swift
@@ -0,0 +1,163 @@
+// Copyright (C) 2024 Rubén Beltrán del Río
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import CoreGraphics
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see https://map.tranquil.systems.
+import Testing
+
+@testable import Map
+
+struct MapParserStrategyTests {
+
+ @Test func testVertexParserStrategyCanHandle() async throws {
+ let strategy = VertexParserStrategy()
+
+ #expect(strategy.canHandle(line: "Node (10, 20)"))
+ #expect(strategy.canHandle(line: "NodeWithDecimals (30.5, 40.2)"))
+ #expect(strategy.canHandle(line: "Multi Word Node (0, 100)"))
+ #expect(strategy.canHandle(line: "Node C (50, 60) [square]"))
+
+ #expect(!strategy.canHandle(line: "Node A -> Node B"))
+ #expect(!strategy.canHandle(line: "[Note] (10, 20) Test"))
+ #expect(!strategy.canHandle(line: "Invalid line"))
+ }
+
+ @Test func testVertexParserStrategyHandle() async throws {
+ let strategy = VertexParserStrategy()
+ let vertices: [String: Vertex] = [:]
+
+ let (type, object) = strategy.handle(index: 0, line: "Test Node (25, 75)", vertices: vertices)
+
+ #expect(String(describing: type) == String(describing: Vertex.self))
+ let vertex = object as! Vertex
+ #expect(vertex.id == 0)
+ #expect(vertex.label == "Test Node")
+ #expect(vertex.position.x == 25.0)
+ #expect(vertex.position.y == 75.0)
+ #expect(vertex.shape == .circle)
+ }
+
+ @Test func testVertexParserStrategyHandleWithShape() async throws {
+ let strategy = VertexParserStrategy()
+ let vertices: [String: Vertex] = [:]
+
+ let (type, object) = strategy.handle(
+ index: 1, line: "Shaped Node (10, 20) [triangle]", vertices: vertices)
+
+ #expect(String(describing: type) == String(describing: Vertex.self))
+ let vertex = object as! Vertex
+ #expect(vertex.shape == .triangle)
+ }
+
+ @Test func testEdgeParserStrategyCanHandle() async throws {
+ let strategy = EdgeParserStrategy()
+
+ #expect(strategy.canHandle(line: "Node A -- Node B"))
+ #expect(strategy.canHandle(line: "Node A -> Node B"))
+ #expect(strategy.canHandle(line: "Multi Word A -> Multi Word B"))
+
+ #expect(!strategy.canHandle(line: "Node A (10, 20)"))
+ #expect(!strategy.canHandle(line: "[Note] (10, 20) Test"))
+ #expect(!strategy.canHandle(line: "Invalid line"))
+ }
+
+ @Test func testEdgeParserStrategyHandle() async throws {
+ let strategy = EdgeParserStrategy()
+ let vertices: [String: Vertex] = [
+ "Node A": Vertex(id: 0, label: "Node A", position: CGPoint(x: 10, y: 20)),
+ "Node B": Vertex(id: 1, label: "Node B", position: CGPoint(x: 30, y: 40)),
+ ]
+
+ let (type, object) = strategy.handle(index: 2, line: "Node A -> Node B", vertices: vertices)
+
+ #expect(String(describing: type) == String(describing: MapEdge.self))
+ let edge = object as! MapEdge
+ #expect(edge.id == 2)
+ #expect(edge.origin.x == 10.0)
+ #expect(edge.origin.y == 20.0)
+ #expect(edge.destination.x == 30.0)
+ #expect(edge.destination.y == 40.0)
+ #expect(edge.arrowhead == true)
+ }
+
+ @Test func testEdgeParserStrategyHandleWithMissingVertex() async throws {
+ let strategy = EdgeParserStrategy()
+ let vertices: [String: Vertex] = [
+ "Node A": Vertex(id: 0, label: "Node A", position: CGPoint(x: 10, y: 20))
+ ]
+
+ let (type, object) = strategy.handle(index: 2, line: "Node A -> Node B", vertices: vertices)
+
+ #expect(String(describing: type) == String(describing: NSObject.self))
+ #expect(object is NSObject)
+ }
+
+ @Test func testNoteParserStrategyCanHandle() async throws {
+ let strategy = NoteParserStrategy()
+
+ #expect(strategy.canHandle(line: "[Note] (10, 20) Test note"))
+ #expect(strategy.canHandle(line: "[Note] (50.5, 60.2) Multi word note"))
+
+ #expect(!strategy.canHandle(line: "Node A (10, 20)"))
+ #expect(!strategy.canHandle(line: "Node A -> Node B"))
+ #expect(!strategy.canHandle(line: "Invalid line"))
+ }
+
+ @Test func testBlockerParserStrategyCanHandle() async throws {
+ let strategy = BlockerParserStrategy()
+
+ #expect(strategy.canHandle(line: "[Blocker] Node A"))
+ #expect(strategy.canHandle(line: "[Blocker] Multi Word Node"))
+
+ #expect(!strategy.canHandle(line: "Node A (10, 20)"))
+ #expect(!strategy.canHandle(line: "[Note] (10, 20) Test"))
+ #expect(!strategy.canHandle(line: "Invalid line"))
+ }
+
+ @Test func testOpportunityParserStrategyCanHandle() async throws {
+ let strategy = OpportunityParserStrategy()
+
+ #expect(strategy.canHandle(line: "[Evolution] Node A +15"))
+ #expect(strategy.canHandle(line: "[Evolution] Node B -10"))
+ #expect(strategy.canHandle(line: "[Evolution] Multi Word Node +5.5"))
+
+ #expect(!strategy.canHandle(line: "Node A (10, 20)"))
+ #expect(!strategy.canHandle(line: "[Blocker] Node A"))
+ #expect(!strategy.canHandle(line: "Invalid line"))
+ }
+
+ @Test func testStageParserStrategyCanHandle() async throws {
+ let strategy = StageParserStrategy()
+
+ #expect(strategy.canHandle(line: "[I] 25"))
+ #expect(strategy.canHandle(line: "[II] 50.5"))
+ #expect(strategy.canHandle(line: "[III] 75"))
+
+ #expect(!strategy.canHandle(line: "[IV] 100"))
+ #expect(!strategy.canHandle(line: "Node A (10, 20)"))
+ #expect(!strategy.canHandle(line: "Invalid line"))
+ }
+
+ @Test func testGroupParserStrategyCanHandle() async throws {
+ let strategy = GroupParserStrategy()
+
+ #expect(strategy.canHandle(line: "[Group] Node A, Node B"))
+ #expect(strategy.canHandle(line: "[Group] Node A, Node B, Node C"))
+ #expect(strategy.canHandle(line: "[Group] Multi Word A, Multi Word B"))
+
+ #expect(!strategy.canHandle(line: "Node A (10, 20)"))
+ #expect(!strategy.canHandle(line: "[Note] (10, 20) Test"))
+ #expect(!strategy.canHandle(line: "Invalid line"))
+ }
+
+}
diff --git a/MapTests/MapTests.swift b/MapTests/MapTests.swift
index 5a8f781..0dbf63b 100644
--- a/MapTests/MapTests.swift
+++ b/MapTests/MapTests.swift
@@ -1,11 +1,169 @@
+import CoreGraphics
import Testing
@testable import Map
struct MapTests {
- @Test func testExample() async throws {
- // Write your test here and use APIs like `#expect(...)` to check expected conditions.
+ @Test func testMapParserWithEmptyInput() async throws {
+ let result = MapParser.parse(content: "")
+ #expect(result.vertices.isEmpty)
+ #expect(result.edges.isEmpty)
+ #expect(result.notes.isEmpty)
+ #expect(result.blockers.isEmpty)
+ #expect(result.opportunities.isEmpty)
+ #expect(result.groups.isEmpty)
+ }
+
+ @Test func testMapParserWithSingleVertex() async throws {
+ let content = "Node A (10, 20)"
+ let result = MapParser.parse(content: content)
+
+ #expect(result.vertices.count == 1)
+ let vertex = result.vertices.first!
+ #expect(vertex.label == "Node A")
+ #expect(vertex.position.x == 10.0)
+ #expect(vertex.position.y == 20.0)
+ #expect(vertex.shape == .circle)
+ }
+
+ @Test func testMapParserWithVertexWithShape() async throws {
+ let content = "Node B (30, 40) [square]"
+ let result = MapParser.parse(content: content)
+
+ #expect(result.vertices.count == 1)
+ let vertex = result.vertices.first!
+ #expect(vertex.label == "Node B")
+ #expect(vertex.position.x == 30.0)
+ #expect(vertex.position.y == 40.0)
+ #expect(vertex.shape == .square)
+ }
+
+ @Test func testMapParserWithEdge() async throws {
+ let content = """
+ Node A (10, 20)
+ Node B (30, 40)
+ Node A -- Node B
+ """
+ let result = MapParser.parse(content: content)
+
+ #expect(result.vertices.count == 2)
+ #expect(result.edges.count == 1)
+ let edge = result.edges.first!
+ #expect(edge.origin.x == 10.0)
+ #expect(edge.origin.y == 20.0)
+ #expect(edge.destination.x == 30.0)
+ #expect(edge.destination.y == 40.0)
+ #expect(edge.arrowhead == false)
+ }
+
+ @Test func testMapParserWithArrowEdge() async throws {
+ let content = """
+ Node A (10, 20)
+ Node B (30, 40)
+ Node A -> Node B
+ """
+ let result = MapParser.parse(content: content)
+
+ #expect(result.edges.count == 1)
+ let edge = result.edges.first!
+ #expect(edge.arrowhead == true)
+ }
+
+ @Test func testMapParserWithNote() async throws {
+ let content = "[Note] (50, 60) This is a test note"
+ let result = MapParser.parse(content: content)
+
+ #expect(result.notes.count == 1)
+ let note = result.notes.first!
+ #expect(note.position.x == 50.0)
+ #expect(note.position.y == 60.0)
+ #expect(note.text == "This is a test note")
+ }
+
+ @Test func testMapParserWithBlocker() async throws {
+ let content = """
+ Node A (10, 20)
+ [Blocker] Node A
+ """
+ let result = MapParser.parse(content: content)
+
+ #expect(result.blockers.count == 1)
+ let blocker = result.blockers.first!
+ #expect(blocker.position.x == 10.0)
+ #expect(blocker.position.y == 20.0)
+ }
+
+ @Test func testMapParserWithOpportunity() async throws {
+ let content = """
+ Node A (10, 20)
+ [Evolution] Node A +15
+ """
+ let result = MapParser.parse(content: content)
+
+ #expect(result.opportunities.count == 1)
+ let opportunity = result.opportunities.first!
+ #expect(opportunity.origin.x == 10.0)
+ #expect(opportunity.origin.y == 20.0)
+ #expect(opportunity.destination.x == 25.0)
+ #expect(opportunity.destination.y == 20.0)
+ }
+
+ @Test func testMapParserWithGroup() async throws {
+ let content = """
+ Node A (10, 20)
+ Node B (30, 40)
+ Node C (50, 60)
+ [Group] Node A, Node B, Node C
+ """
+ let result = MapParser.parse(content: content)
+
+ #expect(result.groups.count == 1)
+ let group = result.groups.first!
+ #expect(group.count == 3)
+ #expect(group.map { $0.label }.contains("Node A"))
+ #expect(group.map { $0.label }.contains("Node B"))
+ #expect(group.map { $0.label }.contains("Node C"))
+ }
+
+ @Test func testMapParserWithStages() async throws {
+ let content = """
+ [I] 15
+ [II] 35
+ [III] 80
+ """
+ let result = MapParser.parse(content: content)
+
+ #expect(result.stages.count == 3)
+ #expect(result.stages[0] == 15.0)
+ #expect(result.stages[1] == 35.0)
+ #expect(result.stages[2] == 80.0)
+ }
+
+ @Test func testMapParserWithComplexMap() async throws {
+ let content = """
+ Node A (10, 20)
+ Node B (30, 40) [square]
+ Node C (50, 60)
+ Node A -> Node B
+ Node B -- Node C
+ [Note] (70, 80) Complex map example
+ [Blocker] Node A
+ [Evolution] Node B +10
+ [Group] Node A, Node C
+ [I] 25
+ [II] 75
+ """
+ let result = MapParser.parse(content: content)
+
+ #expect(result.vertices.count == 3)
+ #expect(result.edges.count == 2)
+ #expect(result.notes.count == 1)
+ #expect(result.blockers.count == 1)
+ #expect(result.opportunities.count == 1)
+ #expect(result.groups.count == 1)
+ #expect(result.stages[0] == 25.0)
+ #expect(result.stages[1] == 75.0)
}
}
diff --git a/MapTests/StageTests.swift b/MapTests/StageTests.swift
new file mode 100644
index 0000000..05812fe
--- /dev/null
+++ b/MapTests/StageTests.swift
@@ -0,0 +1,119 @@
+// Copyright (C) 2024 Rubén Beltrán del Río
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see https://map.tranquil.systems.
+import Testing
+
+@testable import Map
+
+struct StageTests {
+
+ @Test func testStageTypesGrouping() async throws {
+ #expect(StageType.types.count == 4)
+ #expect(StageType.types.contains(.general))
+ #expect(StageType.types.contains(.practice))
+ #expect(StageType.types.contains(.data))
+ #expect(StageType.types.contains(.knowledge))
+
+ #expect(StageType.characteristics.count == 3)
+ #expect(StageType.characteristics.contains(.ubiquity))
+ #expect(StageType.characteristics.contains(.certainty))
+ #expect(StageType.characteristics.contains(.publicationTypes))
+
+ #expect(StageType.properties.count == 12)
+ #expect(StageType.properties.contains(.market))
+ #expect(StageType.properties.contains(.efficiency))
+
+ #expect(StageType.custom.count == 1)
+ #expect(StageType.custom.contains(.behavior))
+ }
+
+ @Test func testStageTypeIdentifiable() async throws {
+ #expect(StageType.general.id == "general")
+ #expect(StageType.practice.id == "practice")
+ #expect(StageType.behavior.id == "behavior")
+ }
+
+ @Test func testStageCreationGeneral() async throws {
+ let stage = Stage.stages(.general)
+ #expect(stage.i == "Genesis")
+ #expect(stage.ii == "Custom")
+ #expect(stage.iii == "Product (+rental)")
+ #expect(stage.iv == "Commodity (+utility)")
+ }
+
+ @Test func testStageCreationPractice() async throws {
+ let stage = Stage.stages(.practice)
+ #expect(stage.i == "Novel")
+ #expect(stage.ii == "Emerging")
+ #expect(stage.iii == "Good")
+ #expect(stage.iv == "Best")
+ }
+
+ @Test func testStageCreationData() async throws {
+ let stage = Stage.stages(.data)
+ #expect(stage.i == "Unmodelled")
+ #expect(stage.ii == "Divergent")
+ #expect(stage.iii == "Convergent")
+ #expect(stage.iv == "Modelled")
+ }
+
+ @Test func testStageCreationKnowledge() async throws {
+ let stage = Stage.stages(.knowledge)
+ #expect(stage.i == "Concept")
+ #expect(stage.ii == "Hypothesis")
+ #expect(stage.iii == "Theory")
+ #expect(stage.iv == "Accepted")
+ }
+
+ @Test func testStageCreationBehavior() async throws {
+ let stage = Stage.stages(.behavior)
+ #expect(stage.i == "Uncertain when to use")
+ #expect(stage.ii == "Learning when to use")
+ #expect(stage.iii == "Learning through use")
+ #expect(stage.iv == "Known / common usage")
+ }
+
+ @Test func testStageTitleGeneral() async throws {
+ let title = Stage.title(.general)
+ #expect(title == "Activities")
+ }
+
+ @Test func testStageTitlePractice() async throws {
+ let title = Stage.title(.practice)
+ #expect(title == "Practice")
+ }
+
+ @Test func testStageTitleBehavior() async throws {
+ let title = Stage.title(.behavior)
+ #expect(title == "Behavior")
+ }
+
+ @Test func testAllStageTypesHaveTitles() async throws {
+ for stageType in StageType.allCases {
+ let title = Stage.title(stageType)
+ #expect(!title.isEmpty)
+ }
+ }
+
+ @Test func testAllStageTypesHaveStages() async throws {
+ for stageType in StageType.allCases {
+ let stage = Stage.stages(stageType)
+ #expect(!stage.i.isEmpty)
+ #expect(!stage.ii.isEmpty)
+ #expect(!stage.iii.isEmpty)
+ #expect(!stage.iv.isEmpty)
+ }
+ }
+
+}