diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..a5c62b6 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,19 @@ +# This workflow will build a Swift project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift + +name: Tests + +on: + pull_request: + branches: [ "main" ] + +jobs: + tests: + + runs-on: macos-13 + + steps: + - uses: actions/checkout@v3 + - name: Run tests + run: xcodebuild test -scheme 'Compound' -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' + diff --git a/Sources/Compound/Colors/CompoundColors.swift b/Sources/Compound/Colors/CompoundColors.swift index 2946b8e..4e81769 100644 --- a/Sources/Compound/Colors/CompoundColors.swift +++ b/Sources/Compound/Colors/CompoundColors.swift @@ -92,6 +92,26 @@ public struct CompoundColors { public let bgSubtleSecondaryLevel0 = Color(UIColor { $0.isLight ? UIColor(compound.colorGray300) : UIColor(compound.colorThemeBg) }) public let bgCanvasDefaultLevel1 = Color(UIColor { $0.isLight ? UIColor(compound.colorThemeBg) : UIColor(compound.colorGray300) }) + // MARK: - Avatar Colors + // Used to determine the background color and the foreground color of an avatar. + + // Order matches the one from web + // https://github.com/vector-im/compound-web/blob/5dda11aa9733462fb8422968181275bc3e9b35e3/src/components/Avatar/Avatar.module.css#L64 + internal let avatarColors: [AvatarColor] = [ + .init(background: compound.colorBlue300, foreground: compound.colorBlue1200), + .init(background: compound.colorFuchsia300, foreground: compound.colorFuchsia1200), + .init(background: compound.colorGreen300, foreground: compound.colorGreen1200), + .init(background: compound.colorPink300, foreground: compound.colorPink1200), + .init(background: compound.colorOrange300, foreground: compound.colorOrange1200), + .init(background: compound.colorCyan300, foreground: compound.colorCyan1200), + .init(background: compound.colorPurple300, foreground: compound.colorPurple1200), + .init(background: compound.colorLime300, foreground: compound.colorLime1200) + ] + + public func avatarColor(for contentID: String) -> AvatarColor { + avatarColors[contentID.hashCode] + } + // MARK: - Awaiting Semantic Tokens /// This token is a placeholder and hasn't been finalised. @@ -125,3 +145,19 @@ private extension UITraitCollection { /// Whether or not the trait collection contains a `userInterfaceStyle` of `.light`. var isLight: Bool { userInterfaceStyle == .light } } + +public struct AvatarColor: Equatable { + let background: Color + let foreground: Color +} + +private extension String { + /// Calculates a numeric hash same as Element Web + /// See original function here https://github.com/matrix-org/matrix-react-sdk/blob/321dd49db4fbe360fc2ff109ac117305c955b061/src/utils/FormattingUtils.js#L47 + var hashCode: Int { + let characterCodeSum = self.reduce(0) { sum, character in + sum + Int(character.unicodeScalars.first?.value ?? 0) + } + return (characterCodeSum % Color.compound.avatarColors.count) + } +} diff --git a/Tests/CompoundTests/AvatarColorsTests.swift b/Tests/CompoundTests/AvatarColorsTests.swift new file mode 100644 index 0000000..d82ef3a --- /dev/null +++ b/Tests/CompoundTests/AvatarColorsTests.swift @@ -0,0 +1,46 @@ +// +// File.swift +// +// +// Created by Mauro Romito on 31/08/23. +// + +import Foundation + +@testable import Compound +import SwiftUI +import XCTest + +final class AvatarColorsTests: XCTestCase { + struct TestCase { + let input: String + private let webOutput: Int + + // remember that web starts the index from 1 while we start from 0 + var output: Int { + webOutput - 1 + } + + init(input: String, webOutput: Int) { + self.input = input + self.webOutput = webOutput + } + } + + func testAvatarColorHash() { + // Match the tests with the web ones for consistency between the two platforms + // https://github.com/vector-im/compound-web/blob/5dda11aa9733462fb8422968181275bc3e9b35e3/src/components/Avatar/Avatar.test.tsx#L62 + let testCases: [TestCase] = [ + .init(input: "@bob:example.org", webOutput: 8), + .init(input: "@alice:example.org", webOutput: 3), + .init(input: "@charlie:example.org", webOutput: 5), + .init(input: "@dan:example.org", webOutput: 8), + .init(input: "@elena:example.org", webOutput: 2), + .init(input: "@fanny:example.org", webOutput: 1) + ] + + for testCase in testCases { + XCTAssertEqual(Color.compound.avatarColor(for: testCase.input), Color.compound.avatarColors[testCase.output]) + } + } +}