Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Source/Bridge/Extensions/URL+Utils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

extension URL {

/// A computed property that returns the URL's path, ensuring a trailing slash is preserved if it exists.
///
/// This property addresses the behavioral difference between `URL.path` in iOS 15 (which strips trailing slashes)
/// and `URL.path()` in iOS 16+ (which preserves them), providing a single, consistent behavior across all OS versions.
var pathPreservingSlash: String {
let components = URLComponents(string: self.absoluteString)
return components?.path ?? ""
}
}
4 changes: 2 additions & 2 deletions Source/Turbo/Path Configuration/PathConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ public final class PathConfiguration {
/// Returns a merged dictionary containing all the properties that match this URL.
public func properties(for url: URL) -> PathProperties {
if Hotwire.config.pathConfiguration.matchQueryStrings, let query = url.query {
return properties(for: "\(url.path)?\(query)")
return properties(for: "\(url.pathPreservingSlash)?\(query)")
}
return properties(for: url.path)
return properties(for: url.pathPreservingSlash)
}

/// Returns a merged dictionary containing all the properties
Expand Down
47 changes: 47 additions & 0 deletions Tests/Bridge/Extensions/URL+UtilsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@testable import HotwireNative
import XCTest

class URL_UtilsTests: XCTestCase {

func testCompatiblePath_withoutTrailingSlash() {
let url = URL(string: "https://www.example.com/path/to/resource")!
let expectedPath = "/path/to/resource"
XCTAssertEqual(url.pathPreservingSlash, expectedPath, "Path should not have a trailing slash.")
}

func testCompatiblePath_withTrailingSlash() {
let url = URL(string: "https://www.example.com/path/to/directory/")!
let expectedPath = "/path/to/directory/"
XCTAssertEqual(url.pathPreservingSlash, expectedPath, "Path should preserve the trailing slash.")
}

func testCompatiblePath_rootPathWithSlash() {
let url = URL(string: "https://www.example.com/")!
let expectedPath = "/"
XCTAssertEqual(url.pathPreservingSlash, expectedPath, "Root path should be a single slash.")
}

func testCompatiblePath_domainOnly() {
let url = URL(string: "https://www.example.com")!
let expectedPath = ""
XCTAssertEqual(url.pathPreservingSlash, expectedPath, "Path should be empty for a URL with no path component.")
}

func testCompatiblePath_withTrailingSlash_andQueryParameters() {
let url = URL(string: "https://www.example.com/path/to/directory/?param1=value1&param2=value2")!
let expectedPath = "/path/to/directory/"
XCTAssertEqual(url.pathPreservingSlash, expectedPath, "Path should preserve the trailing slash and ignore query parameters.")
}

func testCompatiblePath_withoutTrailingSlash_andQueryParameters() {
let url = URL(string: "https://www.example.com/path/to/resource?param1=value1")!
let expectedPath = "/path/to/resource"
XCTAssertEqual(url.pathPreservingSlash, expectedPath, "Path should not have a trailing slash and ignore query parameters.")
}

func testCompatiblePath_pathWithMultipleSlashes() {
let url = URL(string: "https://www.example.com/path//to/resource/")!
let expectedPath = "/path//to/resource/"
XCTAssertEqual(url.pathPreservingSlash, expectedPath, "Path should preserve all slashes as they are.")
}
}