diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e98ff32ad..313f35029 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,4 +14,12 @@ jobs: shell: bash run: | sudo xcode-select --switch /Applications/Xcode_14.2.app - xcodebuild test -scheme LiveViewNative -sdk iphonesimulator16.2 -destination "OS=16.2,name=iPhone 14 Pro" \ No newline at end of file + xcodebuild test -scheme LiveViewNative -sdk iphonesimulator16.2 -destination "OS=16.2,name=iPhone 14 Pro" + - name: Build for macOS + shell: bash + run: | + xcodebuild -scheme LiveViewNative -sdk macosx13.1 -destination "platform=macOS" + - name: Build for watchOS + shell: bash + run: | + xcodebuild -scheme LiveViewNative -sdk watchsimulator9.1 -destination "OS=9.1,name=Apple Watch Series 8 (45mm)" \ No newline at end of file diff --git a/Package.resolved b/Package.resolved index 3dfade513..e05c821cf 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,7 +6,7 @@ "location" : "https://github.com/liveviewnative/liveview-native-core-swift.git", "state" : { "branch" : "main", - "revision" : "144e9e6ef84d5fb37dea201ab06f80f6ae5fc292" + "revision" : "0387adcfead5e4d6b84e3edf2f675aa333648451" } }, { diff --git a/Package.swift b/Package.swift index 8afd2cc1a..4085d4ce3 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,9 @@ import PackageDescription let package = Package( name: "LiveViewNative", platforms: [ - .iOS("16.0") + .iOS("16.0"), + .macOS("13.0"), + .watchOS("9.0"), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -32,7 +34,8 @@ let package = Package( ]), .testTarget( name: "LiveViewNativeTests", - dependencies: ["LiveViewNative"]), + dependencies: ["LiveViewNative"] + ), .testTarget( name: "RenderingTests", dependencies: ["LiveViewNative"] diff --git a/Sources/LiveViewNative/BuiltinRegistry.swift b/Sources/LiveViewNative/BuiltinRegistry.swift index e047f8d30..b4c067881 100644 --- a/Sources/LiveViewNative/BuiltinRegistry.swift +++ b/Sources/LiveViewNative/BuiltinRegistry.swift @@ -58,12 +58,16 @@ struct BuiltinRegistry { ProgressView(element: element, context: context) case "divider": Divider() +#if os(iOS) case "edit-button": EditButton() +#endif case "toggle": Toggle(element: element, context: context) +#if !os(watchOS) case "menu": Menu(element: element, context: context) +#endif case "slider": Slider(element: element, context: context) case "phx-form": diff --git a/Sources/LiveViewNative/Modifiers/ListRowSeparatorModifier.swift b/Sources/LiveViewNative/Modifiers/ListRowSeparatorModifier.swift index 3dbd23104..22e753fad 100644 --- a/Sources/LiveViewNative/Modifiers/ListRowSeparatorModifier.swift +++ b/Sources/LiveViewNative/Modifiers/ListRowSeparatorModifier.swift @@ -55,7 +55,11 @@ struct ListRowSeparatorModifier: ViewModifier, Decodable, Equatable { } func body(content: Content) -> some View { + #if !os(watchOS) content.listRowSeparator(visibility, edges: edges) + #else + content + #endif } private enum CodingKeys: String, CodingKey { diff --git a/Sources/LiveViewNative/Views/Controls and Indicators/Menus/Menu.swift b/Sources/LiveViewNative/Views/Controls and Indicators/Menus/Menu.swift index a944d27e2..b9f923eb8 100644 --- a/Sources/LiveViewNative/Views/Controls and Indicators/Menus/Menu.swift +++ b/Sources/LiveViewNative/Views/Controls and Indicators/Menus/Menu.swift @@ -4,7 +4,7 @@ // // Created by Carson Katri on 1/19/23. // - +#if !os(watchOS) import SwiftUI struct Menu: View { @@ -43,3 +43,4 @@ fileprivate extension View { } } } +#endif diff --git a/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift b/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift index 6f88638ef..f7fafd196 100644 --- a/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift +++ b/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift @@ -45,10 +45,12 @@ private extension SwiftUI.List { switch element.attributeValue(for: "style") { case nil, "plain": self.listStyle(.plain) +#if os(iOS) case "grouped": self.listStyle(.grouped) case "inset-grouped": self.listStyle(.insetGrouped) +#endif default: fatalError("Invalid list style '\(element.attributeValue(for: "name")!)'") } diff --git a/Sources/LiveViewNative/Views/Text Input and Output/SecureField.swift b/Sources/LiveViewNative/Views/Text Input and Output/SecureField.swift index bacd78d82..a665f23ad 100644 --- a/Sources/LiveViewNative/Views/Text Input and Output/SecureField.swift +++ b/Sources/LiveViewNative/Views/Text Input and Output/SecureField.swift @@ -26,8 +26,10 @@ struct SecureField: TextFieldProtocol { .focused($isFocused) .applyTextFieldStyle(textFieldStyle) .applyAutocorrectionDisabled(disableAutocorrection) +#if os(iOS) || os(tvOS) .textInputAutocapitalization(autocapitalization) .applyKeyboardType(keyboard) +#endif .applySubmitLabel(submitLabel) .onChange(of: isFocused, perform: handleFocus) } diff --git a/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift b/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift index 412ca6ec4..ae173956d 100644 --- a/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift +++ b/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift @@ -22,8 +22,12 @@ struct TextField: TextFieldProtocol { .focused($isFocused) .applyTextFieldStyle(textFieldStyle) .applyAutocorrectionDisabled(disableAutocorrection) +#if !os(macOS) .textInputAutocapitalization(autocapitalization) +#endif +#if os(iOS) || os(tvOS) .applyKeyboardType(keyboard) +#endif .applySubmitLabel(submitLabel) .onChange(of: isFocused, perform: handleFocus) } diff --git a/Sources/LiveViewNative/Views/Text Input and Output/TextFieldProtocol.swift b/Sources/LiveViewNative/Views/Text Input and Output/TextFieldProtocol.swift index aba46b04b..01377e7df 100644 --- a/Sources/LiveViewNative/Views/Text Input and Output/TextFieldProtocol.swift +++ b/Sources/LiveViewNative/Views/Text Input and Output/TextFieldProtocol.swift @@ -91,6 +91,7 @@ extension TextFieldProtocol { } } +#if !os(macOS) var autocapitalization: TextInputAutocapitalization? { switch element.attributeValue(for: "autocapitalization") { case "sentences": @@ -105,7 +106,9 @@ extension TextFieldProtocol { return nil } } +#endif +#if os(iOS) || os(tvOS) var keyboard: UIKeyboardType? { switch element.attributeValue(for: "keyboard") { case "ascii-capable": @@ -134,6 +137,7 @@ extension TextFieldProtocol { return nil } } +#endif var submitLabel: SubmitLabel? { switch element.attributeValue(for: "submit-label") { @@ -164,8 +168,12 @@ extension TextFieldProtocol { enum TextFieldStyle: String { case automatic case plain +#if !os(watchOS) case roundedBorder = "rounded-border" +#endif +#if os(macOS) case squareBorder = "square-border" +#endif } extension View { @@ -176,14 +184,14 @@ extension View { self.textFieldStyle(.automatic) case .plain: self.textFieldStyle(.plain) +#if !os(watchOS) case .roundedBorder: self.textFieldStyle(.roundedBorder) +#endif +#if os(macOS) case .squareBorder: - #if os(macOS) self.textFieldStyle(.squareBorder) - #else - self - #endif +#endif } } @@ -196,6 +204,7 @@ extension View { } } +#if os(iOS) || os(tvOS) @ViewBuilder func applyKeyboardType(_ keyboardType: UIKeyboardType?) -> some View { if let keyboardType { @@ -204,6 +213,7 @@ extension View { self } } +#endif @ViewBuilder func applySubmitLabel(_ submitLabel: SubmitLabel?) -> some View { diff --git a/Tests/RenderingTests/ProgressViewTests.swift b/Tests/RenderingTests/ProgressViewTests.swift index 024f4bb4b..1f5e56fde 100644 --- a/Tests/RenderingTests/ProgressViewTests.swift +++ b/Tests/RenderingTests/ProgressViewTests.swift @@ -12,14 +12,14 @@ import SwiftUI @MainActor final class ProgressViewTests: XCTestCase { func testValue() throws { - try assertMatch(#""#) { + try assertMatch(#""#, size: .init(width: 200, height: 200)) { ProgressView(value: 0.5) } } func testTotal() throws { - try assertMatch(#""#) { - ProgressView(value: 0.5, total: 5) + try assertMatch(#""#, size: .init(width: 200, height: 200)) { + ProgressView(value: 2.5, total: 5) } } } diff --git a/Tests/RenderingTests/assertMatch.swift b/Tests/RenderingTests/assertMatch.swift index c3ebe192d..821e9d94f 100644 --- a/Tests/RenderingTests/assertMatch.swift +++ b/Tests/RenderingTests/assertMatch.swift @@ -11,56 +11,71 @@ import Foundation @testable import LiveViewNative import LiveViewNativeCore -@MainActor -func assertMatch( - _ markup: String, - _ file: String = #file, - _ line: Int = #line, - _ function: StaticString = #function, - environment: @escaping (inout EnvironmentValues) -> () = { _ in }, - size: CGSize? = nil, - @ViewBuilder _ view: () -> some View -) throws { - try assertMatch(name: "\(URL(filePath: file).lastPathComponent)-\(line)-\(function)", markup, environment: environment, size: size, view) -} - -@MainActor -func assertMatch( - name: String, - _ markup: String, - environment: @escaping (inout EnvironmentValues) -> () = { _ in }, - size: CGSize? = nil, - @ViewBuilder _ view: () -> some View -) throws { - let session = LiveSessionCoordinator(URL(string: "http://localhost")!) - let document = try LiveViewNativeCore.Document.parse(markup) - let viewTree = session.rootCoordinator.builder.fromNodes( - document[document.root()].children(), - context: LiveContext(coordinator: session.rootCoordinator, url: session.url) - ).environment(\.coordinatorEnvironment, CoordinatorEnvironment(session.rootCoordinator, document: document)) - - let markupImage = snapshot( - viewTree - .transformEnvironment(\.self, transform: environment), - size: size - )?.pngData() - let viewImage = snapshot( - view() - .transformEnvironment(\.self, transform: environment), - size: size - )?.pngData() +extension XCTestCase { + @MainActor + func assertMatch( + _ markup: String, + _ file: String = #file, + _ line: Int = #line, + _ function: StaticString = #function, + environment: @escaping (inout EnvironmentValues) -> () = { _ in }, + size: CGSize? = nil, + @ViewBuilder _ view: () -> some View + ) throws { + try assertMatch(name: "\(URL(filePath: file).lastPathComponent)-\(line)-\(function)", markup, environment: environment, size: size, view) + } - if markupImage == viewImage { - XCTAssert(true) - } else { - let markupURL = URL.temporaryDirectory.appendingPathComponent("\(name)_markup", conformingTo: .png) - let viewURL = URL.temporaryDirectory.appendingPathComponent("\(name)_view", conformingTo: .png) - try markupImage?.write(to: markupURL) - try viewImage?.write(to: viewURL) - XCTAssert(false, "Rendered views did not match. Outputs saved to \(markupURL.path()) and \(viewURL.path())") + @MainActor + func assertMatch( + name: String, + _ markup: String, + environment: @escaping (inout EnvironmentValues) -> () = { _ in }, + size: CGSize? = nil, + @ViewBuilder _ view: () -> some View + ) throws { + #if !os(iOS) + fatalError("Rendering tests not supported on platforms other than iOS at this time") + #else + let session = LiveSessionCoordinator(URL(string: "http://localhost")!) + let document = try LiveViewNativeCore.Document.parse(markup) + let viewTree = session.rootCoordinator.builder.fromNodes( + document[document.root()].children(), + context: LiveContext(coordinator: session.rootCoordinator, url: session.url) + ).environment(\.coordinatorEnvironment, CoordinatorEnvironment(session.rootCoordinator, document: document)) + + guard let markupImage = snapshot( + viewTree + .transformEnvironment(\.self, transform: environment), + size: size + ) + else { + return XCTAssert(false, "Markup failed to render an image") + } + guard let viewImage = snapshot( + view() + .transformEnvironment(\.self, transform: environment), + size: size + ) + else { + return XCTAssert(false, "View failed to render an image") + } + + self.add(XCTAttachment(image: markupImage)) + self.add(XCTAttachment(image: viewImage)) + + let markupData = markupImage.pngData() + let viewData = viewImage.pngData() + + if markupData == viewData { + XCTAssert(true) + } else { + XCTAssert(false, "Rendered views did not match. Attachments can be viewed in the Report navigator.") + } + #endif } } +#if os(iOS) private class SnapshotWindow: UIWindow { override var safeAreaInsets: UIEdgeInsets { .zero @@ -86,3 +101,4 @@ private func snapshot(_ view: some View, size: CGSize?) -> UIImage? { uiView.layer.render(in: context.cgContext) } } +#endif