From a1783074a015eec287333ce40fe6c0b60ee3c90b Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Tue, 24 Jan 2023 10:27:57 -0500 Subject: [PATCH 1/4] Fix macOS build --- Package.swift | 3 ++- Sources/LiveViewNative/BuiltinRegistry.swift | 2 ++ .../Layout Containers/Collection Containers/List.swift | 2 ++ .../Views/Text Input and Output/SecureField.swift | 2 ++ .../Views/Text Input and Output/TextField.swift | 2 ++ .../Views/Text Input and Output/TextFieldProtocol.swift | 6 ++++++ 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 8afd2cc1a..6b5fe115f 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,8 @@ import PackageDescription let package = Package( name: "LiveViewNative", platforms: [ - .iOS("16.0") + .iOS("16.0"), + .macOS("13.0"), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. diff --git a/Sources/LiveViewNative/BuiltinRegistry.swift b/Sources/LiveViewNative/BuiltinRegistry.swift index 18fed28d1..02c7a0624 100644 --- a/Sources/LiveViewNative/BuiltinRegistry.swift +++ b/Sources/LiveViewNative/BuiltinRegistry.swift @@ -56,8 +56,10 @@ struct BuiltinRegistry { Link(element: element, context: context) case "divider": Divider() +#if !os(macOS) case "edit-button": EditButton() +#endif case "toggle": Toggle(element: element, context: context) case "phx-form": diff --git a/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift b/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift index 6f88638ef..caf13cfbd 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(macOS) 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..313ae15eb 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(macOS) .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..6626cc7d8 100644 --- a/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift +++ b/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift @@ -22,8 +22,10 @@ struct TextField: TextFieldProtocol { .focused($isFocused) .applyTextFieldStyle(textFieldStyle) .applyAutocorrectionDisabled(disableAutocorrection) +#if !os(macOS) .textInputAutocapitalization(autocapitalization) .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..23409d529 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(macOS) 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") { @@ -196,6 +200,7 @@ extension View { } } +#if !os(macOS) @ViewBuilder func applyKeyboardType(_ keyboardType: UIKeyboardType?) -> some View { if let keyboardType { @@ -204,6 +209,7 @@ extension View { self } } +#endif @ViewBuilder func applySubmitLabel(_ submitLabel: SubmitLabel?) -> some View { From 1ae3fb7ddd4252df584cad353ffb975a97a16d2e Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Tue, 24 Jan 2023 14:34:57 -0500 Subject: [PATCH 2/4] Add watchOS support --- Package.swift | 1 + Sources/LiveViewNative/BuiltinRegistry.swift | 2 +- .../Modifiers/ListRowSeparatorModifier.swift | 4 ++++ .../Collection Containers/List.swift | 2 +- .../Text Input and Output/SecureField.swift | 2 +- .../Views/Text Input and Output/TextField.swift | 2 ++ .../TextFieldProtocol.swift | 16 ++++++++++------ 7 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 6b5fe115f..45843ca6a 100644 --- a/Package.swift +++ b/Package.swift @@ -8,6 +8,7 @@ let package = Package( platforms: [ .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. diff --git a/Sources/LiveViewNative/BuiltinRegistry.swift b/Sources/LiveViewNative/BuiltinRegistry.swift index 02c7a0624..a60b8d6ce 100644 --- a/Sources/LiveViewNative/BuiltinRegistry.swift +++ b/Sources/LiveViewNative/BuiltinRegistry.swift @@ -56,7 +56,7 @@ struct BuiltinRegistry { Link(element: element, context: context) case "divider": Divider() -#if !os(macOS) +#if os(iOS) case "edit-button": EditButton() #endif 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/Layout Containers/Collection Containers/List.swift b/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift index caf13cfbd..f7fafd196 100644 --- a/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift +++ b/Sources/LiveViewNative/Views/Layout Containers/Collection Containers/List.swift @@ -45,7 +45,7 @@ private extension SwiftUI.List { switch element.attributeValue(for: "style") { case nil, "plain": self.listStyle(.plain) -#if !os(macOS) +#if os(iOS) case "grouped": self.listStyle(.grouped) case "inset-grouped": diff --git a/Sources/LiveViewNative/Views/Text Input and Output/SecureField.swift b/Sources/LiveViewNative/Views/Text Input and Output/SecureField.swift index 313ae15eb..a665f23ad 100644 --- a/Sources/LiveViewNative/Views/Text Input and Output/SecureField.swift +++ b/Sources/LiveViewNative/Views/Text Input and Output/SecureField.swift @@ -26,7 +26,7 @@ struct SecureField: TextFieldProtocol { .focused($isFocused) .applyTextFieldStyle(textFieldStyle) .applyAutocorrectionDisabled(disableAutocorrection) -#if !os(macOS) +#if os(iOS) || os(tvOS) .textInputAutocapitalization(autocapitalization) .applyKeyboardType(keyboard) #endif diff --git a/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift b/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift index 6626cc7d8..ae173956d 100644 --- a/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift +++ b/Sources/LiveViewNative/Views/Text Input and Output/TextField.swift @@ -24,6 +24,8 @@ struct TextField: TextFieldProtocol { .applyAutocorrectionDisabled(disableAutocorrection) #if !os(macOS) .textInputAutocapitalization(autocapitalization) +#endif +#if os(iOS) || os(tvOS) .applyKeyboardType(keyboard) #endif .applySubmitLabel(submitLabel) diff --git a/Sources/LiveViewNative/Views/Text Input and Output/TextFieldProtocol.swift b/Sources/LiveViewNative/Views/Text Input and Output/TextFieldProtocol.swift index 23409d529..01377e7df 100644 --- a/Sources/LiveViewNative/Views/Text Input and Output/TextFieldProtocol.swift +++ b/Sources/LiveViewNative/Views/Text Input and Output/TextFieldProtocol.swift @@ -108,7 +108,7 @@ extension TextFieldProtocol { } #endif -#if !os(macOS) +#if os(iOS) || os(tvOS) var keyboard: UIKeyboardType? { switch element.attributeValue(for: "keyboard") { case "ascii-capable": @@ -168,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 { @@ -180,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 } } @@ -200,7 +204,7 @@ extension View { } } -#if !os(macOS) +#if os(iOS) || os(tvOS) @ViewBuilder func applyKeyboardType(_ keyboardType: UIKeyboardType?) -> some View { if let keyboardType { From 57ad3aaf00d38c019ed8939f583c6168933e0f06 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Wed, 25 Jan 2023 17:25:37 -0500 Subject: [PATCH 3/4] Fix watchOS build RE Menu --- Sources/LiveViewNative/BuiltinRegistry.swift | 2 ++ .../Views/Controls and Indicators/Menus/Menu.swift | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/LiveViewNative/BuiltinRegistry.swift b/Sources/LiveViewNative/BuiltinRegistry.swift index d48d43b26..b4c067881 100644 --- a/Sources/LiveViewNative/BuiltinRegistry.swift +++ b/Sources/LiveViewNative/BuiltinRegistry.swift @@ -64,8 +64,10 @@ struct BuiltinRegistry { #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/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 From 5124af6786d02a9928b9da44b983922ff699ed4b Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Wed, 25 Jan 2023 18:21:09 -0500 Subject: [PATCH 4/4] Add platform builds to CI --- .github/workflows/ci.yml | 10 +- Package.resolved | 2 +- Package.swift | 3 +- Tests/RenderingTests/ProgressViewTests.swift | 6 +- Tests/RenderingTests/assertMatch.swift | 108 +++++++++++-------- 5 files changed, 77 insertions(+), 52 deletions(-) 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 45843ca6a..4085d4ce3 100644 --- a/Package.swift +++ b/Package.swift @@ -34,7 +34,8 @@ let package = Package( ]), .testTarget( name: "LiveViewNativeTests", - dependencies: ["LiveViewNative"]), + dependencies: ["LiveViewNative"] + ), .testTarget( name: "RenderingTests", dependencies: ["LiveViewNative"] 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