diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-04 17:06:28 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-04 17:06:28 +0200 |
| commit | c843d34f56c207abcf4b93e424125eea2dc601e0 (patch) | |
| tree | 415dc2d9be4be31e290e1dd1dedb0b630c5e8fc6 /MapTests | |
| parent | ed10ac191df473c92c4fec495aafa7f569d108c8 (diff) | |
Upgrade to Swift 6
Diffstat (limited to 'MapTests')
| -rw-r--r-- | MapTests/DebouncerTests.swift | 71 | ||||
| -rw-r--r-- | MapTests/MapDocumentTests.swift | 81 | ||||
| -rw-r--r-- | MapTests/MapParserStrategyTests.swift | 163 | ||||
| -rw-r--r-- | MapTests/MapTests.swift | 162 | ||||
| -rw-r--r-- | MapTests/StageTests.swift | 119 |
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) + } + } + +} |