Skip to content

Commit

Permalink
Add unit tests for new @available behaviour
Browse files Browse the repository at this point in the history
- Adds unit tests for verifying that the `introduced` field is parsed with the correct formatting rules.
- Fixes a bug where a leading or trailing full stop would be considered a valid format.
- Updates FIXMEs to contain the most up-to-date information
- Refactors MetadataAvailabilityTests slightly

Resolves rdar://129355087.
  • Loading branch information
anferbui committed Jun 21, 2024
1 parent 859044b commit e0f5775
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public struct AvailabilityRenderItem: Codable, Hashable, Equatable {

init?(_ availability: Metadata.Availability, current: PlatformVersion?) {
// FIXME: Deprecated/Beta markings need platform versions to display properly in Swift-DocC-Render (rdar://56897597)
// Fill in the appropriate values here when that's fixed (https://github.com/apple/swift-docc/issues/441)
// FIXME: Initalize `deprecated` here once implemented in @Available (https://github.com/apple/swift-docc/issues/441)

let platformName = PlatformName(metadataPlatform: availability.platform)
name = platformName?.displayName
Expand Down
11 changes: 9 additions & 2 deletions Sources/SwiftDocC/Semantics/Metadata/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ extension Metadata {
/// The platform that this argument's information applies to.
@DirectiveArgumentWrapped(name: .unnamed)
public var platform: Platform

/// The platform version that this page applies to.
@DirectiveArgumentWrapped(name: .custom("introduced"))
public var introducedVersion: VersionTriplet
Expand All @@ -122,9 +122,16 @@ extension Metadata {
}

extension VersionTriplet: DirectiveArgumentValueConvertible {
static let separator = "."

init?(rawDirectiveArgumentValue: String) {
guard !rawDirectiveArgumentValue.hasSuffix(VersionTriplet.separator),
!rawDirectiveArgumentValue.hasPrefix(VersionTriplet.separator) else {
return nil
}

// Split the string into major, minor and patch components
let availabilityComponents = rawDirectiveArgumentValue.split(separator: ".", maxSplits: 2)
let availabilityComponents = rawDirectiveArgumentValue.split(separator: .init(VersionTriplet.separator), maxSplits: 2)
guard !availabilityComponents.isEmpty else {
return nil
}
Expand Down
143 changes: 92 additions & 51 deletions Tests/SwiftDocCTests/Semantics/MetadataAvailabilityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,86 +17,121 @@ import Markdown
class MetadataAvailabilityTests: XCTestCase {
func testInvalidWithNoArguments() throws {
let source = "@Available"
let document = Document(parsing: source, options: .parseBlockDirectives)
let directive = document.child(at: 0) as? BlockDirective
XCTAssertNotNil(directive)

let (bundle, context) = try testBundleAndContext(named: "AvailabilityBundle")

directive.map { directive in
var problems = [Problem]()
XCTAssertEqual(Metadata.Availability.directiveName, directive.name)
let availability = Metadata.Availability(from: directive, source: nil, for: bundle, in: context, problems: &problems)
XCTAssertNil(availability)

try assertDirective(Metadata.Availability.self, source: source) { directive, problems in
XCTAssertNil(directive)

XCTAssertEqual(2, problems.count)
let diagnosticIdentifiers = Set(problems.map { $0.diagnostic.identifier })
XCTAssertEqual(diagnosticIdentifiers, ["org.swift.docc.HasArgument.unlabeled", "org.swift.docc.HasArgument.introduced"])
}
}

func testInvalidDuplicateIntroduced() throws {
func assertInvalidDirective(source: String) throws {
let document = Document(parsing: source, options: .parseBlockDirectives)
let directive = document.child(at: 0) as? BlockDirective
XCTAssertNotNil(directive)

let (bundle, context) = try testBundleAndContext(named: "AvailabilityBundle")

directive.map { directive in
var problems = [Problem]()
XCTAssertEqual(Metadata.directiveName, directive.name)
let _ = Metadata(from: directive, source: nil, for: bundle, in: context, problems: &problems)
XCTAssertEqual(2, problems.count)
let diagnosticIdentifiers = Set(problems.map { $0.diagnostic.identifier })
XCTAssertEqual(diagnosticIdentifiers, ["org.swift.docc.\(Metadata.Availability.self).DuplicateIntroduced"])
}
}

for platform in Metadata.Availability.Platform.defaultCases {
let source = """
@Metadata {
@Available(\(platform.rawValue), introduced: \"1.0\")
@Available(\(platform.rawValue), introduced: \"2.0\")
}
"""
try assertInvalidDirective(source: source)
try assertDirective(Metadata.self, source: source) { directive, problems in
XCTAssertEqual(2, problems.count)
let diagnosticIdentifiers = Set(problems.map { $0.diagnostic.identifier })
XCTAssertEqual(diagnosticIdentifiers, ["org.swift.docc.\(Metadata.Availability.self).DuplicateIntroduced"])
}
}
}

func testInvalidIntroducedFormat() throws {
let source = """
@Metadata {
@Available(Package, introduced: \"\")
@Available(Package, introduced: \".\")
@Available(Package, introduced: \"1.\")
@Available(Package, introduced: \".1\")
@Available(Package, introduced: \"test\")
@Available(Package, introduced: \"test.1.2\")
@Available(Package, introduced: \"2.1.test\")
@Available(Package, introduced: \"test.test.test\")
}
"""

func testValidDirective() throws {
// assemble all the combinations of arguments you could give
try assertDirective(Metadata.self, source: source) { directive, problems in
XCTAssertEqual(9, problems.count)
let diagnosticIdentifiers = Set(problems.map { $0.diagnostic.identifier })
XCTAssertEqual(diagnosticIdentifiers, ["org.swift.docc.HasArgument.introduced.ConversionFailed", "org.swift.docc.Metadata.NoConfiguration"])
}
}

func testValidIntroducedFormat() throws {
let source = """
@Metadata {
@Available(iOS, introduced: \"3.5.2\")
@Available(macOS, introduced: \"3.5\")
@Available(Package, introduced: \"3\")
}
"""

try assertDirective(Metadata.self, source: source) { directive, problems in
let directive = try XCTUnwrap(directive)
let platforms = directive.availability.map { $0.platform }
let introducedVersions = directive.availability.map { $0.introducedVersion }



XCTAssertEqual(3, directive.availability.count)
XCTAssertEqual(platforms, [
.iOS,
.macOS,
.other("Package")
])
XCTAssertEqual(introducedVersions, [
VersionTriplet(3, 5, 2),
VersionTriplet(3, 5, 0),
VersionTriplet(3, 0, 0)
])

XCTAssertEqual(0, problems.count)
}
}

func testValidIntroducedDirective() throws {
// Assemble all the combinations of arguments you could give
let validArguments: [String] = [
// FIXME: isBeta and isDeprecated are unused (https://github.com/apple/swift-docc/issues/441)
// "isBeta: true",
// "isDeprecated: true",
// "isBeta: true, isDeprecated: true",
// FIXME: Uncomment once `deprecated` is implemented in @Available (https://github.com/apple/swift-docc/issues/441)
// "deprecated: \"1.0\"",
]
// separate those that give a version so we can test the `*` platform separately
var validArgumentsWithVersion = ["introduced: \"1.0\""]
for arg in validArguments {
validArgumentsWithVersion.append("introduced: \"1.0\", \(arg)")
validArgumentsWithVersion.append("\(arg), introduced: \"1.0\"")
}

var checkPlatforms = Metadata.Availability.Platform.defaultCases.map({ $0.rawValue })
checkPlatforms.append("Package")

checkPlatforms += [
"Package",
"\"My Package\"", // Also check a platform with spaces in the name
// FIXME: Test validArguments with the `*` platform once that's introduced (https://github.com/apple/swift-docc/issues/441)
// "*",
]

for platform in checkPlatforms {
// FIXME: Test validArguments with the `*` platform once that's introduced
// cf. https://github.com/apple/swift-docc/issues/441
for args in validArgumentsWithVersion {
try assertValidAvailability(source: "@Available(\(platform), \(args))")
}
}

// also check a platform with spaces in the name
for args in validArgumentsWithVersion {
try assertValidAvailability(source: "@Available(\"My Package\", \(args))")
}

// also test for giving no platform
for args in validArguments {
try assertValidAvailability(source: "@Available(\(args))")
}

// basic validity test for giving several directives
// FIXME: re-add isBeta after that is implemented (https://github.com/apple/swift-docc/issues/441)
}

/// Basic validity test for giving several directives.
func testMultipleAvailabilityDirectives() throws {
// FIXME: Add a `deprecated` argument here once implemented in @Available (https://github.com/apple/swift-docc/issues/441)
let source = """
@Metadata {
@Available(macOS, introduced: "11.0")
Expand All @@ -105,19 +140,25 @@ class MetadataAvailabilityTests: XCTestCase {
"""
try assertValidMetadata(source: source)
}

func assertValidDirective<Directive: AutomaticDirectiveConvertible>(_ type: Directive.Type, source: String) throws {
func assertDirective<Directive: AutomaticDirectiveConvertible>(_ type: Directive.Type, source: String, assertion assert: (Directive?, [Problem]) throws -> Void) throws {
let document = Document(parsing: source, options: .parseBlockDirectives)
let directive = document.child(at: 0) as? BlockDirective
XCTAssertNotNil(directive)

let (bundle, context) = try testBundleAndContext(named: "AvailabilityBundle")

directive.map { directive in
try directive.map { directive in
var problems = [Problem]()
XCTAssertEqual(Directive.directiveName, directive.name)
let converted = Directive(from: directive, source: nil, for: bundle, in: context, problems: &problems)
XCTAssertNotNil(converted)
try assert(converted, problems)
}
}

func assertValidDirective<Directive: AutomaticDirectiveConvertible>(_ type: Directive.Type, source: String) throws {
try assertDirective(type, source: source) { directive, problems in
XCTAssertNotNil(directive)
XCTAssert(problems.isEmpty)
}
}
Expand Down

0 comments on commit e0f5775

Please sign in to comment.