diff --git a/redwood-widget-shared-test/build.gradle b/redwood-widget-shared-test/build.gradle new file mode 100644 index 0000000000..fad8ab84bd --- /dev/null +++ b/redwood-widget-shared-test/build.gradle @@ -0,0 +1,31 @@ +import static app.cash.redwood.buildsupport.TargetGroup.ToolkitAllWithoutAndroid + +redwoodBuild { + targets(ToolkitAllWithoutAndroid) +} + +kotlin { + sourceSets { + commonMain { + dependencies { + api projects.redwoodLayoutApi + api projects.redwoodLayoutModifiers + api projects.redwoodLayoutWidget + api projects.redwoodRuntime + api projects.redwoodSnapshotTesting + api projects.redwoodTesting + api projects.redwoodWidget + api projects.redwoodYoga + api libs.kotlin.test + } + } + jvmMain { + dependencies { + // The kotlin.test library provides JVM variants for multiple testing frameworks. When used + // as a test dependency this selection is transparent. But since we are publishing a library + // we need to select one ourselves at compilation time. + api libs.kotlin.test.junit + } + } + } +} diff --git a/redwood-widget-shared-test/src/commonMain/kotlin/app/cash/redwood/widget/AbstractRedwoodViewTest.kt b/redwood-widget-shared-test/src/commonMain/kotlin/app/cash/redwood/widget/AbstractRedwoodViewTest.kt new file mode 100644 index 0000000000..2aee8fe1ee --- /dev/null +++ b/redwood-widget-shared-test/src/commonMain/kotlin/app/cash/redwood/widget/AbstractRedwoodViewTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.redwood.widget + +import app.cash.redwood.snapshot.testing.Snapshotter +import app.cash.redwood.snapshot.testing.TestWidgetFactory +import app.cash.redwood.snapshot.testing.text +import kotlin.test.Test + +abstract class AbstractRedwoodViewTest> { + + abstract val widgetFactory: TestWidgetFactory + + abstract fun redwoodView(): R + + abstract fun snapshotter(redwoodView: R): Snapshotter + + /** + * This test uses a string that wraps to confirm the root view's dimensions aren't unbounded. + * https://github.com/cashapp/redwood/issues/2128 + */ + @Test + fun testSingleChildElement() { + val redwoodView = redwoodView() + redwoodView.children.insert(0, widgetFactory.text("Hello ".repeat(50))) + snapshotter(redwoodView).snapshot() + } +} diff --git a/redwood-widget-uiview-test/RedwoodWidgetUIViewTests.xcodeproj/project.pbxproj b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..f39f5b731a --- /dev/null +++ b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests.xcodeproj/project.pbxproj @@ -0,0 +1,420 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 108501E82CA4B0C3008AAC12 /* UIViewRedwoodViewTestHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 108501E72CA4B0C3008AAC12 /* UIViewRedwoodViewTestHost.swift */; }; + CB408CED2AC6549F00E1BA00 /* KotlinHostingXCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB408CEC2AC6549F00E1BA00 /* KotlinHostingXCTestCase.swift */; }; + CB408CF72AC657EF00E1BA00 /* KotlinHostingXCTestCaseHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = CB408CF62AC657EF00E1BA00 /* KotlinHostingXCTestCaseHelper.m */; }; + CB8A21EF2AC6647F00C104C2 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = CB8A21EE2AC6647F00C104C2 /* SnapshotTesting */; }; + CB9729D32AD82D0C00804E94 /* SnapshotTestingCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9729D22AD82D0C00804E94 /* SnapshotTestingCallback.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 108501E72CA4B0C3008AAC12 /* UIViewRedwoodViewTestHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewRedwoodViewTestHost.swift; sourceTree = ""; }; + 63E90CF521FEBBB700449E04 /* RedwoodWidgetUIViewTestKt.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RedwoodWidgetUIViewTestKt.framework; path = "build/xcode-frameworks/RedwoodWidgetUIViewTestKt.framework"; sourceTree = ""; }; + CB408CE52AC64CEF00E1BA00 /* RedwoodWidgetUIViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RedwoodWidgetUIViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + CB408CEC2AC6549F00E1BA00 /* KotlinHostingXCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KotlinHostingXCTestCase.swift; sourceTree = ""; }; + CB408CF52AC657EF00E1BA00 /* RedwoodWidgetUIViewTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RedwoodWidgetUIViewTests-Bridging-Header.h"; sourceTree = ""; }; + CB408CF62AC657EF00E1BA00 /* KotlinHostingXCTestCaseHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KotlinHostingXCTestCaseHelper.m; sourceTree = ""; }; + CB408CF82AC6581100E1BA00 /* KotlinHostingXCTestCaseHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KotlinHostingXCTestCaseHelper.h; sourceTree = ""; }; + CB9729D22AD82D0C00804E94 /* SnapshotTestingCallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotTestingCallback.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + CB408CE22AC64CEF00E1BA00 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CB8A21EF2AC6647F00C104C2 /* SnapshotTesting in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00D5E68C2AAF3EBD00692213 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 635661C821F12B7D00DD7240 = { + isa = PBXGroup; + children = ( + CB408CE62AC64CEF00E1BA00 /* RedwoodWidgetUIViewTests */, + 635661D221F12B7E00DD7240 /* Products */, + 00D5E68C2AAF3EBD00692213 /* Frameworks */, + 63E90CF521FEBBB700449E04 /* RedwoodWidgetUIViewTestKt.framework */, + ); + sourceTree = ""; + }; + 635661D221F12B7E00DD7240 /* Products */ = { + isa = PBXGroup; + children = ( + CB408CE52AC64CEF00E1BA00 /* RedwoodWidgetUIViewTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + CB408CE62AC64CEF00E1BA00 /* RedwoodWidgetUIViewTests */ = { + isa = PBXGroup; + children = ( + CB9729D22AD82D0C00804E94 /* SnapshotTestingCallback.swift */, + CB408CEC2AC6549F00E1BA00 /* KotlinHostingXCTestCase.swift */, + CB408CF62AC657EF00E1BA00 /* KotlinHostingXCTestCaseHelper.m */, + CB408CF52AC657EF00E1BA00 /* RedwoodWidgetUIViewTests-Bridging-Header.h */, + CB408CF82AC6581100E1BA00 /* KotlinHostingXCTestCaseHelper.h */, + 108501E72CA4B0C3008AAC12 /* UIViewRedwoodViewTestHost.swift */, + ); + path = RedwoodWidgetUIViewTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + CB408CE42AC64CEF00E1BA00 /* RedwoodWidgetUIViewTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = CB408CE92AC64CEF00E1BA00 /* Build configuration list for PBXNativeTarget "RedwoodWidgetUIViewTests" */; + buildPhases = ( + 00DD12E22AAF46C500CA3FD3 /* ShellScript */, + CB408CE12AC64CEF00E1BA00 /* Sources */, + CB408CE22AC64CEF00E1BA00 /* Frameworks */, + CB408CE32AC64CEF00E1BA00 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RedwoodWidgetUIViewTests; + packageProductDependencies = ( + CB8A21EE2AC6647F00C104C2 /* SnapshotTesting */, + ); + productName = RedwoodWidgetUIViewTests; + productReference = CB408CE52AC64CEF00E1BA00 /* RedwoodWidgetUIViewTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 635661C921F12B7D00DD7240 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1420; + LastUpgradeCheck = 1220; + ORGANIZATIONNAME = "Square Inc"; + TargetAttributes = { + CB408CE42AC64CEF00E1BA00 = { + CreatedOnToolsVersion = 14.2; + LastSwiftMigration = 1420; + }; + }; + }; + buildConfigurationList = 635661CC21F12B7D00DD7240 /* Build configuration list for PBXProject "RedwoodWidgetUIViewTests" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 635661C821F12B7D00DD7240; + packageReferences = ( + CB8A21ED2AC6647F00C104C2 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, + ); + productRefGroup = 635661D221F12B7E00DD7240 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CB408CE42AC64CEF00E1BA00 /* RedwoodWidgetUIViewTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CB408CE32AC64CEF00E1BA00 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD12E22AAF46C500CA3FD3 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"$SRCROOT/..\"\n./gradlew :redwood-widget-uiview-test:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + CB408CE12AC64CEF00E1BA00 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CB408CED2AC6549F00E1BA00 /* KotlinHostingXCTestCase.swift in Sources */, + CB408CF72AC657EF00E1BA00 /* KotlinHostingXCTestCaseHelper.m in Sources */, + CB9729D32AD82D0C00804E94 /* SnapshotTestingCallback.swift in Sources */, + 108501E82CA4B0C3008AAC12 /* UIViewRedwoodViewTestHost.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 635661E121F12B8000DD7240 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGNING_ALLOWED = NO; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 635661E221F12B8000DD7240 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGNING_ALLOWED = NO; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + CB408CEA2AC64CEF00E1BA00 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + FRAMEWORK_SEARCH_PATHS = ( + "$(SRCROOT)/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(PROJECT_DIR)/build/xcode-frameworks", + ); + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-framework", + "\"RedwoodWidgetUIViewTestKt\"", + "-ObjC", + "-framework", + RedwoodWidgetUIViewTestKt, + ); + PRODUCT_BUNDLE_IDENTIFIER = app.cash.redwood.widget.uiview.testing; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "RedwoodWidgetUIViewTests/RedwoodWidgetUIViewTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + CB408CEB2AC64CEF00E1BA00 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + FRAMEWORK_SEARCH_PATHS = ( + "$(SRCROOT)/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(PROJECT_DIR)/build/xcode-frameworks", + ); + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.2; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-l\"c++\"", + "-framework", + "\"RedwoodWidgetUIViewTestKt\"", + "-ObjC", + "-framework", + RedwoodWidgetUIViewTestKt, + ); + PRODUCT_BUNDLE_IDENTIFIER = app.cash.redwood.widget.uiview.testing; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OBJC_BRIDGING_HEADER = "RedwoodWidgetUIViewTests/RedwoodWidgetUIViewTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 635661CC21F12B7D00DD7240 /* Build configuration list for PBXProject "RedwoodWidgetUIViewTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 635661E121F12B8000DD7240 /* Debug */, + 635661E221F12B8000DD7240 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CB408CE92AC64CEF00E1BA00 /* Build configuration list for PBXNativeTarget "RedwoodWidgetUIViewTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CB408CEA2AC64CEF00E1BA00 /* Debug */, + CB408CEB2AC64CEF00E1BA00 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + CB8A21ED2AC6647F00C104C2 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + CB8A21EE2AC6647F00C104C2 /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = CB8A21ED2AC6647F00C104C2 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 635661C921F12B7D00DD7240 /* Project object */; +} diff --git a/redwood-widget-uiview-test/RedwoodWidgetUIViewTests.xcodeproj/xcshareddata/xcschemes/RedwoodWidgetUIViewTests.xcscheme b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests.xcodeproj/xcshareddata/xcschemes/RedwoodWidgetUIViewTests.xcscheme new file mode 100644 index 0000000000..c29a557216 --- /dev/null +++ b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests.xcodeproj/xcshareddata/xcschemes/RedwoodWidgetUIViewTests.xcscheme @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/KotlinHostingXCTestCase.swift b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/KotlinHostingXCTestCase.swift new file mode 100644 index 0000000000..c0382c8a5c --- /dev/null +++ b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/KotlinHostingXCTestCase.swift @@ -0,0 +1,63 @@ +import Foundation +import XCTest + +/** + * An abstract base class for hosting Kotlin test classes in Swift projects. + * + * Subclasses must override `initTest(name:)` to create instances of `KotlinType`. + */ +open class KotlinHostingXCTestCase: KotlinHostingXCTestCaseHelper where KotlinTest: NSObject { + /** + * Create an instance of `KotlinTest` for invoking test method `name`. + */ + open class func initTest(name: String) -> KotlinTest { + fatalError("Missing override for initTest(name:)") + } + + public override class func createTestMethods() { + var targetClass: AnyClass = KotlinTest.self + while (targetClass != NSObject.self) { + var methodCount: UInt32 = 0 + let methodList = class_copyMethodList(targetClass, &methodCount) + if let methodList = methodList { + defer { free(methodList) } + for i in 0.. Void = { + if target.responds(to: #selector(setUp)) { + target.perform(#selector(setUp)) + } + defer { + if target.responds(to: #selector(tearDown)) { + target.perform(#selector(tearDown)) + } + } + + target.perform(selector) + } + let implementation = imp_implementationWithBlock(block) + class_addMethod(self, selector, implementation, "v@:") + } + + public override class var defaultTestSuite: XCTestSuite { + createTestMethods() + return super.defaultTestSuite + } +} diff --git a/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/KotlinHostingXCTestCaseHelper.h b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/KotlinHostingXCTestCaseHelper.h new file mode 100644 index 0000000000..7e4622d582 --- /dev/null +++ b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/KotlinHostingXCTestCaseHelper.h @@ -0,0 +1,20 @@ +// Created by Michał Laskowski on 10/02/2020. +// Copyright © 2020 Michał Laskowski. All rights reserved. +// Derived from https://github.com/michallaskowski/kuiks/blob/c8500df2a55fe031a1bcf546c771c3d9f30dbf90/NativeTestBase/ObjC/TestBaseForSelector.h +// Licensed as Apache-2.0. + +#ifndef TestBase_h +#define TestBase_h +@import XCTest; + +NS_ASSUME_NONNULL_BEGIN + +@interface KotlinHostingXCTestCaseHelper : XCTestCase + ++(void)createTestMethods; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* TestBase_h */ diff --git a/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/KotlinHostingXCTestCaseHelper.m b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/KotlinHostingXCTestCaseHelper.m new file mode 100644 index 0000000000..f7991ed221 --- /dev/null +++ b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/KotlinHostingXCTestCaseHelper.m @@ -0,0 +1,25 @@ +// Created by Michał Laskowski on 10/02/2020. +// Copyright © 2020 Michał Laskowski. All rights reserved. +// Derived from https://github.com/michallaskowski/kuiks/blob/c8500df2a55fe031a1bcf546c771c3d9f30dbf90/NativeTestBase/ObjC/TestBaseForSelector.m +// Licensed as Apache-2.0. + +#import +#include "KotlinHostingXCTestCaseHelper.h" +#import "objc/runtime.h" + +@implementation KotlinHostingXCTestCaseHelper + ++(void)createTestMethods { + [NSException raise:@"NotImplemented" format:@"Subclasses must implement a valid createTestMethods method"]; +} + +/** + This is overriden in order to be able to trigger tests from Test Navigator panel. Method can not be overriden in Swift, because it needs also + to override `testCaseWithInvocation:`. And using NSInvocation is not possible in Swift. Hence this Obj-C TestBase class, and this method. + */ ++(instancetype)testCaseWithSelector:(SEL)selector { + [self createTestMethods]; + return [super testCaseWithSelector:selector]; +} + +@end diff --git a/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/RedwoodWidgetUIViewTests-Bridging-Header.h b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/RedwoodWidgetUIViewTests-Bridging-Header.h new file mode 100644 index 0000000000..832bf76ca4 --- /dev/null +++ b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/RedwoodWidgetUIViewTests-Bridging-Header.h @@ -0,0 +1 @@ +#include "KotlinHostingXCTestCaseHelper.h" diff --git a/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/SnapshotTestingCallback.swift b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/SnapshotTestingCallback.swift new file mode 100644 index 0000000000..879e557d77 --- /dev/null +++ b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/SnapshotTestingCallback.swift @@ -0,0 +1,19 @@ +import RedwoodWidgetUIViewTestKt +import SnapshotTesting +import UIKit + +final class SnapshotTestingCallback : Redwood_snapshot_testingUIViewSnapshotCallback { + private let testName: String + private let fileName: StaticString + + init(named testName: String, _ fileName: StaticString = #file) { + self.testName = testName + self.fileName = fileName + } + + func verifySnapshot(view: UIView, name: String?, delay: TimeInterval = 0.0) { + // Set `record` to true to generate new snapshots. Be sure to revert that before committing! + // Note that tests always fail when `record` is true. + assertSnapshot(of: view, as: .wait(for: delay, on: .image), named: name, record: false, file: fileName, testName: testName) + } +} diff --git a/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/UIViewRedwoodViewTestHost.swift b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/UIViewRedwoodViewTestHost.swift new file mode 100644 index 0000000000..50f6ed2159 --- /dev/null +++ b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/UIViewRedwoodViewTestHost.swift @@ -0,0 +1,8 @@ +import RedwoodWidgetUIViewTestKt +import UIKit + +final class UIViewRedwoodViewTestHost: KotlinHostingXCTestCase { + override class func initTest(name: String) -> UIViewRedwoodViewTest { + return UIViewRedwoodViewTest(callback: SnapshotTestingCallback(named: name)) + } +} diff --git a/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/__Snapshots__/UIViewRedwoodViewTestHost/testSingleChildElement.1.png b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/__Snapshots__/UIViewRedwoodViewTestHost/testSingleChildElement.1.png new file mode 100644 index 0000000000..92e676f843 --- /dev/null +++ b/redwood-widget-uiview-test/RedwoodWidgetUIViewTests/__Snapshots__/UIViewRedwoodViewTestHost/testSingleChildElement.1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef3a33e208e9dcacf181558420b4b02cdb8e1d2259201b5524deb18bf0cf1c20 +size 87900 diff --git a/redwood-widget-uiview-test/build.gradle b/redwood-widget-uiview-test/build.gradle new file mode 100644 index 0000000000..a86c8f0bac --- /dev/null +++ b/redwood-widget-uiview-test/build.gradle @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import static app.cash.redwood.buildsupport.TargetGroup.ToolkitIos +import static org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.common + +redwoodBuild { + targets(ToolkitIos) +} + +kotlin { + targets.configureEach { target -> + if (target.platformType != common) { + target.binaries.framework { + compilation = target.compilations.test + baseName = 'RedwoodWidgetUIViewTestKt' + } + } + } + + sourceSets { + commonMain { + dependencies { + api projects.redwoodLayoutWidget + implementation projects.redwoodWidgetCompose + implementation projects.redwoodWidgetSharedTest + implementation projects.redwoodYoga + } + } + commonTest { + dependencies { + api projects.redwoodLayoutSharedTest + api projects.redwoodSnapshotTesting + api libs.kotlin.test + implementation libs.assertk + } + } + } +} diff --git a/redwood-widget-uiview-test/src/commonTest/kotlin/app/cash/redwood/widget/uiview/UIViewRedwoodViewTest.kt b/redwood-widget-uiview-test/src/commonTest/kotlin/app/cash/redwood/widget/uiview/UIViewRedwoodViewTest.kt new file mode 100644 index 0000000000..bca269c7d2 --- /dev/null +++ b/redwood-widget-uiview-test/src/commonTest/kotlin/app/cash/redwood/widget/uiview/UIViewRedwoodViewTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.redwood.widget.uiview + +import app.cash.redwood.snapshot.testing.UIViewSnapshotCallback +import app.cash.redwood.snapshot.testing.UIViewSnapshotter +import app.cash.redwood.snapshot.testing.UIViewTestWidgetFactory +import app.cash.redwood.widget.AbstractRedwoodViewTest +import app.cash.redwood.widget.RedwoodUIView +import kotlinx.cinterop.cValue +import platform.CoreGraphics.CGRectZero +import platform.UIKit.UIStackView +import platform.UIKit.UIView + +class UIViewRedwoodViewTest( + private val callback: UIViewSnapshotCallback, +) : AbstractRedwoodViewTest() { + override val widgetFactory = UIViewTestWidgetFactory + + override fun redwoodView() = RedwoodUIView(UIStackView(cValue { CGRectZero })) + + override fun snapshotter(redwoodView: RedwoodUIView) = + UIViewSnapshotter.framed(callback, redwoodView.view) +} diff --git a/redwood-widget-view-test/build.gradle b/redwood-widget-view-test/build.gradle new file mode 100644 index 0000000000..365453a4ea --- /dev/null +++ b/redwood-widget-view-test/build.gradle @@ -0,0 +1,23 @@ +import static app.cash.redwood.buildsupport.TargetGroup.ToolkitAndroid + +redwoodBuild { + targets(ToolkitAndroid) +} + +apply plugin: 'app.cash.paparazzi' + +dependencies { + testImplementation projects.redwoodSnapshotTesting + testImplementation projects.redwoodWidgetSharedTest + testImplementation libs.androidx.activity + testImplementation libs.testParameterInjector +} + +android { + namespace 'app.cash.redwood.widget.view' + + buildFeatures { + // For Paparazzi, which internally uses resources to set itself up. + androidResources = true + } +} diff --git a/redwood-widget-view-test/src/test/kotlin/app/cash/redwood/widget/view/ViewRedwoodViewTest.kt b/redwood-widget-view-test/src/test/kotlin/app/cash/redwood/widget/view/ViewRedwoodViewTest.kt new file mode 100644 index 0000000000..42a5c60ac3 --- /dev/null +++ b/redwood-widget-view-test/src/test/kotlin/app/cash/redwood/widget/view/ViewRedwoodViewTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package app.cash.redwood.widget.view + +import android.view.View +import androidx.activity.OnBackPressedDispatcher +import app.cash.paparazzi.DeviceConfig +import app.cash.paparazzi.Paparazzi +import app.cash.redwood.snapshot.testing.TestWidgetFactory +import app.cash.redwood.snapshot.testing.ViewSnapshotter +import app.cash.redwood.snapshot.testing.ViewTestWidgetFactory +import app.cash.redwood.widget.AbstractRedwoodViewTest +import app.cash.redwood.widget.RedwoodLayout +import com.android.resources.LayoutDirection +import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import org.junit.Rule +import org.junit.runner.RunWith + +@RunWith(TestParameterInjector::class) +class ViewRedwoodViewTest( + @TestParameter layoutDirection: LayoutDirection, +) : AbstractRedwoodViewTest() { + @get:Rule + val paparazzi = Paparazzi( + deviceConfig = DeviceConfig.PIXEL_6.copy(layoutDirection = layoutDirection), + theme = "android:Theme.Material.Light.NoActionBar", + supportsRtl = true, + ) + + override val widgetFactory: TestWidgetFactory + get() = ViewTestWidgetFactory(paparazzi.context) + + override fun redwoodView() = RedwoodLayout(paparazzi.context, OnBackPressedDispatcher()) + + override fun snapshotter(redwoodView: RedwoodLayout) = ViewSnapshotter(paparazzi, redwoodView) +} diff --git a/redwood-widget-view-test/src/test/snapshots/images/app.cash.redwood.widget.view_ViewRedwoodViewTest_testSingleChildElement[LTR].png b/redwood-widget-view-test/src/test/snapshots/images/app.cash.redwood.widget.view_ViewRedwoodViewTest_testSingleChildElement[LTR].png new file mode 100644 index 0000000000..33d4ba26dc --- /dev/null +++ b/redwood-widget-view-test/src/test/snapshots/images/app.cash.redwood.widget.view_ViewRedwoodViewTest_testSingleChildElement[LTR].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae6ff7047674dc2cbaaddc7cf71ddddad4b3eb8533f1a5bebd21bddc29f533de +size 8861 diff --git a/redwood-widget-view-test/src/test/snapshots/images/app.cash.redwood.widget.view_ViewRedwoodViewTest_testSingleChildElement[RTL].png b/redwood-widget-view-test/src/test/snapshots/images/app.cash.redwood.widget.view_ViewRedwoodViewTest_testSingleChildElement[RTL].png new file mode 100644 index 0000000000..ca2a582934 --- /dev/null +++ b/redwood-widget-view-test/src/test/snapshots/images/app.cash.redwood.widget.view_ViewRedwoodViewTest_testSingleChildElement[RTL].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:261a473c45bbb250764fc282c2a0c075f455915d0cc9b9fa1f5cce9b665c7fcf +size 8889 diff --git a/redwood-widget/build.gradle b/redwood-widget/build.gradle index a1f5dc7c24..ba150ce82d 100644 --- a/redwood-widget/build.gradle +++ b/redwood-widget/build.gradle @@ -20,6 +20,7 @@ kotlin { dependencies { implementation libs.kotlin.test implementation libs.assertk + implementation projects.redwoodSnapshotTesting implementation projects.redwoodWidgetTesting } } diff --git a/settings.gradle b/settings.gradle index da269deec6..05d666ffc1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -87,7 +87,10 @@ include ':redwood-treehouse-host' include ':redwood-treehouse-host-composeui' include ':redwood-widget' include ':redwood-widget-compose' +include ':redwood-widget-shared-test' include ':redwood-widget-testing' +include ':redwood-widget-view-test' +include ':redwood-widget-uiview-test' include ':redwood-yoga' include ':test-app:schema'