Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ jobs:
- uses: actions/checkout@v4
- name: Build Brisa
run: cd Brisa && ./Scripts/build.sh
- name: Run tests
run: cd Brisa && ./Scripts/test.sh
76 changes: 40 additions & 36 deletions Brisa/Brisa.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@
BA20A0C4DC4B9C4542075BF5 /* AudioBank.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C8760EBC2C89B3ABB1FBD2D /* AudioBank.swift */; };
C4F649AA8A11C5506C952C13 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C833F95CD8DC84FEED2D47F7 /* ContentView.swift */; };
EB048C3B6F73CC64ABD9C808 /* InputSoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 749B533C75C9AC9F23CFB6CF /* InputSoundsView.swift */; };
939398DBB19571CA42D723CF /* PomodoroTimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4941FA5761A0A6F1528FB917 /* PomodoroTimerView.swift */; };
7C1E5A0B3D2F4A6981B0C4D7 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E9B6D4F1A8C3057B6E2A91C /* Theme.swift */; };
B4D80C2E6F1A93577E2D5A08 /* PomodoroWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A19F3C7E5B2D40886C1E7D35 /* PomodoroWidget.swift */; };
D3A61F9B2C8E47501A6B3E90 /* SystemIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F0C82E4A7B1396D0E4A7C21 /* SystemIntegration.swift */; };
8B2E4D7A1C5F60932E8A4B17 /* Confetti.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A91E3B5D2084F6A1B7E5C3 /* Confetti.swift */; };
3E7A5C1D9B4F2086A5D1C7E2 /* YouTubeVideos.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2B6D803A9E41C57B0D8E4A6 /* YouTubeVideos.swift */; };
9C1F4B7E2A6D3058E1B9C4A7 /* MixSharing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D3A8E1F5C2B70496A8E2D15 /* MixSharing.swift */; };
A5E27C19D3B8406F1C7A9E32 /* Routines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B90D6A2E8F153C7D2A6B841 /* Routines.swift */; };
C3D8E1F2A4B5C6D7E8F90A1B /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E9F2A3B5C6D7E8F90A1B2C /* Main.swift */; };
E1B84A6D0C3F27958B4E1A70 /* RoutinesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2C9D5A13E846B0C9D3F5A2 /* RoutinesView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -39,6 +49,16 @@
E05E9C5BA1029142EF4CEA50 /* AppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModel.swift; sourceTree = "<group>"; };
D39E7FD40216F484E4D9166C /* ImportSoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportSoundsView.swift; sourceTree = "<group>"; };
D426557338CA7710B792A969 /* ImportedSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportedSounds.swift; sourceTree = "<group>"; };
4941FA5761A0A6F1528FB917 /* PomodoroTimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PomodoroTimerView.swift; sourceTree = "<group>"; };
2E9B6D4F1A8C3057B6E2A91C /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
A19F3C7E5B2D40886C1E7D35 /* PomodoroWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PomodoroWidget.swift; sourceTree = "<group>"; };
5F0C82E4A7B1396D0E4A7C21 /* SystemIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemIntegration.swift; sourceTree = "<group>"; };
C7A91E3B5D2084F6A1B7E5C3 /* Confetti.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Confetti.swift; sourceTree = "<group>"; };
F2B6D803A9E41C57B0D8E4A6 /* YouTubeVideos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubeVideos.swift; sourceTree = "<group>"; };
6D3A8E1F5C2B70496A8E2D15 /* MixSharing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixSharing.swift; sourceTree = "<group>"; };
4B90D6A2E8F153C7D2A6B841 /* Routines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routines.swift; sourceTree = "<group>"; };
D4E9F2A3B5C6D7E8F90A1B2C /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = "<group>"; };
7F2C9D5A13E846B0C9D3F5A2 /* RoutinesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutinesView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXGroup section */
Expand Down Expand Up @@ -75,6 +95,16 @@
5920DD25C75ABF9D3606CE60 /* MixEditor.swift */,
7F591E43CDC7F5BC3355E174 /* SoundLibrary.swift */,
1A934D2D26B6C834DF712558 /* WelcomeView.swift */,
4941FA5761A0A6F1528FB917 /* PomodoroTimerView.swift */,
2E9B6D4F1A8C3057B6E2A91C /* Theme.swift */,
A19F3C7E5B2D40886C1E7D35 /* PomodoroWidget.swift */,
5F0C82E4A7B1396D0E4A7C21 /* SystemIntegration.swift */,
C7A91E3B5D2084F6A1B7E5C3 /* Confetti.swift */,
F2B6D803A9E41C57B0D8E4A6 /* YouTubeVideos.swift */,
6D3A8E1F5C2B70496A8E2D15 /* MixSharing.swift */,
4B90D6A2E8F153C7D2A6B841 /* Routines.swift */,
D4E9F2A3B5C6D7E8F90A1B2C /* Main.swift */,
7F2C9D5A13E846B0C9D3F5A2 /* RoutinesView.swift */,
);
path = Sources;
sourceTree = "<group>";
Expand Down Expand Up @@ -192,6 +222,16 @@
369937A5DA55F518340F61DF /* MixEditor.swift in Sources */,
18AA2EFF13BAA71C43097A7D /* SoundLibrary.swift in Sources */,
40214DB63F42605020A0CC91 /* WelcomeView.swift in Sources */,
939398DBB19571CA42D723CF /* PomodoroTimerView.swift in Sources */,
7C1E5A0B3D2F4A6981B0C4D7 /* Theme.swift in Sources */,
B4D80C2E6F1A93577E2D5A08 /* PomodoroWidget.swift in Sources */,
D3A61F9B2C8E47501A6B3E90 /* SystemIntegration.swift in Sources */,
8B2E4D7A1C5F60932E8A4B17 /* Confetti.swift in Sources */,
3E7A5C1D9B4F2086A5D1C7E2 /* YouTubeVideos.swift in Sources */,
9C1F4B7E2A6D3058E1B9C4A7 /* MixSharing.swift in Sources */,
A5E27C19D3B8406F1C7A9E32 /* Routines.swift in Sources */,
C3D8E1F2A4B5C6D7E8F90A1B /* Main.swift in Sources */,
E1B84A6D0C3F27958B4E1A70 /* RoutinesView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -376,39 +416,3 @@
};
rootObject = 4A1984492DC2971FB43E60A9 /* Project object */;
}
939398DBB19571CA42D723CF /* PomodoroTimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4941FA5761A0A6F1528FB917 /* PomodoroTimerView.swift */; };
7C1E5A0B3D2F4A6981B0C4D7 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E9B6D4F1A8C3057B6E2A91C /* Theme.swift */; };
B4D80C2E6F1A93577E2D5A08 /* PomodoroWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A19F3C7E5B2D40886C1E7D35 /* PomodoroWidget.swift */; };
D3A61F9B2C8E47501A6B3E90 /* SystemIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F0C82E4A7B1396D0E4A7C21 /* SystemIntegration.swift */; };
8B2E4D7A1C5F60932E8A4B17 /* Confetti.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A91E3B5D2084F6A1B7E5C3 /* Confetti.swift */; };
3E7A5C1D9B4F2086A5D1C7E2 /* YouTubeVideos.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2B6D803A9E41C57B0D8E4A6 /* YouTubeVideos.swift */; };
9C1F4B7E2A6D3058E1B9C4A7 /* MixSharing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D3A8E1F5C2B70496A8E2D15 /* MixSharing.swift */; };
A5E27C19D3B8406F1C7A9E32 /* Routines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B90D6A2E8F153C7D2A6B841 /* Routines.swift */; };
E1B84A6D0C3F27958B4E1A70 /* RoutinesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2C9D5A13E846B0C9D3F5A2 /* RoutinesView.swift */; };
4941FA5761A0A6F1528FB917 /* PomodoroTimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PomodoroTimerView.swift; sourceTree = "<group>"; };
2E9B6D4F1A8C3057B6E2A91C /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
A19F3C7E5B2D40886C1E7D35 /* PomodoroWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PomodoroWidget.swift; sourceTree = "<group>"; };
5F0C82E4A7B1396D0E4A7C21 /* SystemIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemIntegration.swift; sourceTree = "<group>"; };
C7A91E3B5D2084F6A1B7E5C3 /* Confetti.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Confetti.swift; sourceTree = "<group>"; };
F2B6D803A9E41C57B0D8E4A6 /* YouTubeVideos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubeVideos.swift; sourceTree = "<group>"; };
6D3A8E1F5C2B70496A8E2D15 /* MixSharing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixSharing.swift; sourceTree = "<group>"; };
4B90D6A2E8F153C7D2A6B841 /* Routines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routines.swift; sourceTree = "<group>"; };
7F2C9D5A13E846B0C9D3F5A2 /* RoutinesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutinesView.swift; sourceTree = "<group>"; };
4941FA5761A0A6F1528FB917 /* PomodoroTimerView.swift */,
2E9B6D4F1A8C3057B6E2A91C /* Theme.swift */,
A19F3C7E5B2D40886C1E7D35 /* PomodoroWidget.swift */,
5F0C82E4A7B1396D0E4A7C21 /* SystemIntegration.swift */,
C7A91E3B5D2084F6A1B7E5C3 /* Confetti.swift */,
F2B6D803A9E41C57B0D8E4A6 /* YouTubeVideos.swift */,
6D3A8E1F5C2B70496A8E2D15 /* MixSharing.swift */,
4B90D6A2E8F153C7D2A6B841 /* Routines.swift */,
7F2C9D5A13E846B0C9D3F5A2 /* RoutinesView.swift */,
939398DBB19571CA42D723CF /* PomodoroTimerView.swift in Sources */,
7C1E5A0B3D2F4A6981B0C4D7 /* Theme.swift in Sources */,
B4D80C2E6F1A93577E2D5A08 /* PomodoroWidget.swift in Sources */,
D3A61F9B2C8E47501A6B3E90 /* SystemIntegration.swift in Sources */,
8B2E4D7A1C5F60932E8A4B17 /* Confetti.swift in Sources */,
3E7A5C1D9B4F2086A5D1C7E2 /* YouTubeVideos.swift in Sources */,
9C1F4B7E2A6D3058E1B9C4A7 /* MixSharing.swift in Sources */,
A5E27C19D3B8406F1C7A9E32 /* Routines.swift in Sources */,
E1B84A6D0C3F27958B4E1A70 /* RoutinesView.swift in Sources */,
25 changes: 25 additions & 0 deletions Brisa/Scripts/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/zsh
set -euo pipefail

script_dir="${0:A:h}"
project_dir="${script_dir:h}"
build_dir="${project_dir}/build/tests"
mkdir -p "${build_dir}"

# Every app source except the entry point, plus the tests and their runner.
source_files=("${project_dir}/Sources/"*.swift(N))
source_files=(${source_files:#*/Main.swift})
test_files=("${project_dir}/Tests/"*.swift)

swiftc -parse-as-library "${source_files[@]}" "${test_files[@]}" \
-o "${build_dir}/BrisaTests" \
-module-cache-path "${project_dir}/build/ModuleCache" \
-framework SwiftUI \
-framework AVFoundation \
-framework AppKit \
-framework Carbon \
-framework UserNotifications \
-target arm64-apple-macosx14.0 \
-suppress-warnings

"${build_dir}/BrisaTests"
1 change: 0 additions & 1 deletion Brisa/Sources/AppEntry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ final class BrisaAppDelegate: NSObject, NSApplicationDelegate {
}
}

@main
struct BrisaApp: App {
@NSApplicationDelegateAdaptor(BrisaAppDelegate.self) private var delegate
@StateObject private var model = AppModel.shared
Expand Down
7 changes: 7 additions & 0 deletions Brisa/Sources/Main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import SwiftUI

/// The entry point lives apart from `BrisaApp` so `Scripts/test.sh` can compile every other source file into the test runner.
@main
enum BrisaMain {
static func main() { BrisaApp.main() }
}
73 changes: 73 additions & 0 deletions Brisa/Tests/MixSharingTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Foundation

enum MixSharingTests {
static let all: [TestCase] = [
TestCase(name: "link round-trips a mix") {
let mix = SharedMix(name: "Rainy study", levels: ["rain": 0.4, "brown": 0.25])
let link = try unwrap(MixSharing.link(mix))
try expectEqual(link.scheme, "brisa")
try expectEqual(MixSharing.decode(link: link), mix)
},
TestCase(name: "file data round-trips a mix") {
let mix = SharedMix(name: "Night", levels: ["pink": 0.3])
try expectEqual(MixSharing.decode(data: try unwrap(MixSharing.fileData(mix))), mix)
},
TestCase(name: "shared(from:) drops imported and silent sounds") {
let mix = Mix(name: "Mine", levels: ["rain": 0.5, "imported-123": 0.5, "white": 0])
let result = try unwrap(MixSharing.shared(from: mix))
try expectEqual(result.mix.levels, ["rain": 0.5])
try expectEqual(result.omitted, 2)
},
TestCase(name: "shared(from:) clamps and rounds levels") {
let result = try unwrap(MixSharing.shared(from: Mix(name: "Loud", levels: ["rain": 3, "pink": 0.12345])))
try expectEqual(result.mix.levels, ["rain": 1, "pink": 0.123])
},
TestCase(name: "shared(from:) returns nil when nothing is portable") {
try expectNil(MixSharing.shared(from: Mix(name: "Imported only", levels: ["imported-1": 0.5])))
},
TestCase(name: "shared(from:) falls back to a default name") {
try expectEqual(try unwrap(MixSharing.shared(from: Mix(name: " \n ", levels: ["rain": 0.5]))).mix.name, "Shared mix")
},
TestCase(name: "validated keeps only known sounds and clamps levels") {
let raw = SharedMix(name: "Mixed", levels: ["rain": 2, "pink": -1, "unknown": 0.5, "brown": .nan, "white": 0.2])
try expectEqual(MixSharing.validated(raw), SharedMix(name: "Mixed", levels: ["rain": 1, "white": 0.2]))
},
TestCase(name: "validated rejects unknown versions") {
var raw = SharedMix(name: "Future", levels: ["rain": 0.5])
raw.v = 2
try expectNil(MixSharing.validated(raw))
},
TestCase(name: "validated rejects mixes without playable sounds") {
try expectNil(MixSharing.validated(SharedMix(name: "Empty", levels: ["unknown": 0.5, "rain": 0])))
},
TestCase(name: "validated rejects oversized level lists") {
var levels: [String: Double] = ["rain": 0.5]
for index in 0..<(MixSharing.maxSounds * 2) { levels["fake-\(index)"] = 0.5 }
try expectNil(MixSharing.validated(SharedMix(name: "Spam", levels: levels)))
},
TestCase(name: "cleanName strips control characters and limits length") {
try expectEqual(MixSharing.cleanName(" Deep\nfocus\u{0007} "), "Deepfocus")
try expectEqual(MixSharing.cleanName(String(repeating: "a", count: 100))?.count, MixSharing.maxNameLength)
try expectNil(MixSharing.cleanName(" \t "))
},
TestCase(name: "decode(link:) rejects other schemes and hosts") {
let link = try unwrap(MixSharing.link(SharedMix(name: "Rain", levels: ["rain": 0.5])))
var components = try unwrap(URLComponents(url: link, resolvingAgainstBaseURL: false))
components.scheme = "https"
try expectNil(MixSharing.decode(link: try unwrap(components.url)))
components.scheme = "brisa"
components.host = "other"
try expectNil(MixSharing.decode(link: try unwrap(components.url)))
},
TestCase(name: "decode rejects garbage and oversized payloads") {
try expectNil(MixSharing.decode(link: try unwrap(URL(string: "brisa://mix?d=not-json"))))
try expectNil(MixSharing.decode(data: Data(repeating: 32, count: MixSharing.maxBytes + 1)))
},
TestCase(name: "base64URL uses URL-safe characters without padding") {
let data = Data([0xfb, 0xff, 0xfe])
let text = MixSharing.base64URL(data)
try expect(!text.contains("+") && !text.contains("/") && !text.contains("="), text)
try expectEqual(MixSharing.dataFromBase64URL(text), data)
}
]
}
Loading
Loading