diff --git a/CHANGELOG.md b/CHANGELOG.md index 271da0f1..9551785d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,15 @@ ## Master +### Enhancements + +- Added support for resolving superclass properties for not-NSObject subclasses + +### Bug Fixes + - Fixed rendering `{{ block.super }}` with several levels of inheritance + ## 0.10.1 ### Enhancements diff --git a/Sources/Variable.swift b/Sources/Variable.swift index 239f2521..afc5dbd5 100644 --- a/Sources/Variable.swift +++ b/Sources/Variable.swift @@ -100,15 +100,9 @@ public struct Variable : Equatable, Resolvable { current = object.value(forKey: bit) #endif } else if let value = current { - let mirror = Mirror(reflecting: value) - current = mirror.descendant(bit) - + current = Mirror(reflecting: value).getValue(for: bit) if current == nil { return nil - // mirror returns non-nil value even for nil-containing properties - // so we have to check if its value is actually nil or not - } else if let current = current, String(describing: current) == "nil" { - return nil } } else { return nil @@ -179,3 +173,18 @@ func parseFilterComponents(token: String) -> (String, [Variable]) { .map { Variable($0) } return (name, variables) } + +extension Mirror { + func getValue(for key: String) -> Any? { + let result = descendant(key) + if result == nil { + // go through inheritance chain to reach superclass properties + return superclassMirror?.getValue(for: key) + } else if let result = result, String(describing: result) == "nil" { + // mirror returns non-nil value even for nil-containing properties + // so we have to check if its value is actually nil or not + return nil + } + return result + } +} diff --git a/Tests/StencilTests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift index ac532a31..44af2cc2 100644 --- a/Tests/StencilTests/VariableSpec.swift +++ b/Tests/StencilTests/VariableSpec.swift @@ -4,7 +4,10 @@ import Spectre #if os(OSX) -@objc class Object : NSObject { +@objc class Superclass: NSObject { + let name = "Foo" +} +@objc class Object : Superclass { let title = "Hello World" } #endif @@ -17,6 +20,13 @@ fileprivate struct Article { let author: Person } +fileprivate class WebSite { + let url: String = "blog.com" +} + +fileprivate class Blog: WebSite { + let articles: [Article] = [Article(author: Person(name: "Kyle"))] +} func testVariable() { describe("Variable") { @@ -35,6 +45,7 @@ func testVariable() { #if os(OSX) context["object"] = Object() #endif + context["blog"] = Blog() $0.it("can resolve a string literal with double quotes") { let variable = Variable("\"name\"") @@ -122,6 +133,25 @@ func testVariable() { let result = try variable.resolve(context) as? String try expect(result) == "Hello World" } + + $0.it("can resolve a superclass value via KVO") { + let variable = Variable("object.name") + let result = try variable.resolve(context) as? String + try expect(result) == "Foo" + } #endif + + $0.it("can resolve a value via reflection") { + let variable = Variable("blog.articles.0.author.name") + let result = try variable.resolve(context) as? String + try expect(result) == "Kyle" + } + + $0.it("can resolve a superclass value via reflection") { + let variable = Variable("blog.url") + let result = try variable.resolve(context) as? String + try expect(result) == "blog.com" + } + } }