@@ -586,6 +586,156 @@ final class URLTests : XCTestCase {
586
586
XCTAssertEqual ( appended. relativePath, " relative/with:slash " )
587
587
}
588
588
589
+ func testURLDeletingLastPathComponent( ) throws {
590
+ var absolute = URL ( filePath: " /absolute/path " , directoryHint: . notDirectory)
591
+ // Note: .relativePath strips the trailing slash for compatibility
592
+ XCTAssertEqual ( absolute. relativePath, " /absolute/path " )
593
+ XCTAssertFalse ( absolute. hasDirectoryPath)
594
+
595
+ absolute. deleteLastPathComponent ( )
596
+ XCTAssertEqual ( absolute. relativePath, " /absolute " )
597
+ XCTAssertTrue ( absolute. hasDirectoryPath)
598
+
599
+ absolute. deleteLastPathComponent ( )
600
+ XCTAssertEqual ( absolute. relativePath, " / " )
601
+ XCTAssertTrue ( absolute. hasDirectoryPath)
602
+
603
+ // The old .deleteLastPathComponent() implementation appends ".." to the
604
+ // root directory "/", resulting in "/../". This resolves back to "/".
605
+ // The new implementation simply leaves "/" as-is.
606
+ absolute. deleteLastPathComponent ( )
607
+ checkBehavior ( absolute. relativePath, new: " / " , old: " /.. " )
608
+ XCTAssertTrue ( absolute. hasDirectoryPath)
609
+
610
+ absolute. append ( path: " absolute " , directoryHint: . isDirectory)
611
+ checkBehavior ( absolute. path, new: " /absolute " , old: " /../absolute " )
612
+
613
+ // Reset `var absolute` to "/absolute" to prevent having
614
+ // a "/../" prefix in all the old expectations.
615
+ absolute = URL ( filePath: " /absolute " , directoryHint: . isDirectory)
616
+
617
+ var relative = URL ( filePath: " relative/path " , directoryHint: . notDirectory, relativeTo: absolute)
618
+ XCTAssertEqual ( relative. relativePath, " relative/path " )
619
+ XCTAssertFalse ( relative. hasDirectoryPath)
620
+ XCTAssertEqual ( relative. path, " /absolute/relative/path " )
621
+
622
+ relative. deleteLastPathComponent ( )
623
+ XCTAssertEqual ( relative. relativePath, " relative " )
624
+ XCTAssertTrue ( relative. hasDirectoryPath)
625
+ XCTAssertEqual ( relative. path, " /absolute/relative " )
626
+
627
+ relative. deleteLastPathComponent ( )
628
+ XCTAssertEqual ( relative. relativePath, " . " )
629
+ XCTAssertTrue ( relative. hasDirectoryPath)
630
+ XCTAssertEqual ( relative. path, " /absolute " )
631
+
632
+ relative. deleteLastPathComponent ( )
633
+ XCTAssertEqual ( relative. relativePath, " .. " )
634
+ XCTAssertTrue ( relative. hasDirectoryPath)
635
+ XCTAssertEqual ( relative. path, " / " )
636
+
637
+ relative. deleteLastPathComponent ( )
638
+ XCTAssertEqual ( relative. relativePath, " ../.. " )
639
+ XCTAssertTrue ( relative. hasDirectoryPath)
640
+ checkBehavior ( relative. path, new: " / " , old: " /.. " )
641
+
642
+ relative. append ( path: " path " , directoryHint: . isDirectory)
643
+ XCTAssertEqual ( relative. relativePath, " ../../path " )
644
+ XCTAssertTrue ( relative. hasDirectoryPath)
645
+ checkBehavior ( relative. path, new: " /path " , old: " /../path " )
646
+
647
+ relative. deleteLastPathComponent ( )
648
+ XCTAssertEqual ( relative. relativePath, " ../.. " )
649
+ XCTAssertTrue ( relative. hasDirectoryPath)
650
+ checkBehavior ( relative. path, new: " / " , old: " /.. " )
651
+
652
+ relative = URL ( filePath: " " , relativeTo: absolute)
653
+ checkBehavior ( relative. relativePath, new: " " , old: " . " )
654
+ XCTAssertTrue ( relative. hasDirectoryPath)
655
+ XCTAssertEqual ( relative. path, " /absolute " )
656
+
657
+ relative. deleteLastPathComponent ( )
658
+ XCTAssertEqual ( relative. relativePath, " .. " )
659
+ XCTAssertTrue ( relative. hasDirectoryPath)
660
+ XCTAssertEqual ( relative. path, " / " )
661
+
662
+ relative. deleteLastPathComponent ( )
663
+ XCTAssertEqual ( relative. relativePath, " ../.. " )
664
+ XCTAssertTrue ( relative. hasDirectoryPath)
665
+ checkBehavior ( relative. path, new: " / " , old: " /.. " )
666
+
667
+ relative = URL ( filePath: " relative/./ " , relativeTo: absolute)
668
+ // According to RFC 3986, "." and ".." segments should not be removed
669
+ // until the path is resolved against the base URL (when calling .path)
670
+ checkBehavior ( relative. relativePath, new: " relative/. " , old: " relative " )
671
+ XCTAssertTrue ( relative. hasDirectoryPath)
672
+ XCTAssertEqual ( relative. path, " /absolute/relative " )
673
+
674
+ relative. deleteLastPathComponent ( )
675
+ checkBehavior ( relative. relativePath, new: " relative/.. " , old: " . " )
676
+ XCTAssertTrue ( relative. hasDirectoryPath)
677
+ XCTAssertEqual ( relative. path, " /absolute " )
678
+
679
+ relative = URL ( filePath: " relative/. " , directoryHint: . isDirectory, relativeTo: absolute)
680
+ checkBehavior ( relative. relativePath, new: " relative/. " , old: " relative " )
681
+ XCTAssertTrue ( relative. hasDirectoryPath)
682
+ XCTAssertEqual ( relative. path, " /absolute/relative " )
683
+
684
+ relative. deleteLastPathComponent ( )
685
+ checkBehavior ( relative. relativePath, new: " relative/.. " , old: " . " )
686
+ XCTAssertTrue ( relative. hasDirectoryPath)
687
+ XCTAssertEqual ( relative. path, " /absolute " )
688
+
689
+ relative = URL ( filePath: " relative/.. " , relativeTo: absolute)
690
+ XCTAssertEqual ( relative. relativePath, " relative/.. " )
691
+ checkBehavior ( relative. hasDirectoryPath, new: true , old: false )
692
+ XCTAssertEqual ( relative. path, " /absolute " )
693
+
694
+ relative. deleteLastPathComponent ( )
695
+ XCTAssertEqual ( relative. relativePath, " relative/../.. " )
696
+ XCTAssertTrue ( relative. hasDirectoryPath)
697
+ XCTAssertEqual ( relative. path, " / " )
698
+
699
+ relative = URL ( filePath: " relative/.. " , directoryHint: . isDirectory, relativeTo: absolute)
700
+ XCTAssertEqual ( relative. relativePath, " relative/.. " )
701
+ XCTAssertTrue ( relative. hasDirectoryPath)
702
+ XCTAssertEqual ( relative. path, " /absolute " )
703
+
704
+ relative. deleteLastPathComponent ( )
705
+ XCTAssertEqual ( relative. relativePath, " relative/../.. " )
706
+ XCTAssertTrue ( relative. hasDirectoryPath)
707
+ XCTAssertEqual ( relative. path, " / " )
708
+
709
+ var url = try XCTUnwrap ( URL ( string: " scheme://host.with.no.path " ) )
710
+ XCTAssertTrue ( url. path ( ) . isEmpty)
711
+
712
+ url. deleteLastPathComponent ( )
713
+ XCTAssertEqual ( url. absoluteString, " scheme://host.with.no.path " )
714
+ XCTAssertTrue ( url. path ( ) . isEmpty)
715
+
716
+ let unusedBase = URL ( string: " base://url " )
717
+ url = try XCTUnwrap ( URL ( string: " scheme://host.with.no.path " , relativeTo: unusedBase) )
718
+ XCTAssertEqual ( url. absoluteString, " scheme://host.with.no.path " )
719
+ XCTAssertTrue ( url. path ( ) . isEmpty)
720
+
721
+ url. deleteLastPathComponent ( )
722
+ XCTAssertEqual ( url. absoluteString, " scheme://host.with.no.path " )
723
+ XCTAssertTrue ( url. path ( ) . isEmpty)
724
+
725
+ var schemeRelative = try XCTUnwrap ( URL ( string: " scheme:relative/path " ) )
726
+ // Bug in the old implementation where a relative path is not recognized
727
+ checkBehavior ( schemeRelative. relativePath, new: " relative/path " , old: " " )
728
+
729
+ schemeRelative. deleteLastPathComponent ( )
730
+ checkBehavior ( schemeRelative. relativePath, new: " relative " , old: " " )
731
+
732
+ schemeRelative. deleteLastPathComponent ( )
733
+ XCTAssertEqual ( schemeRelative. relativePath, " " )
734
+
735
+ schemeRelative. deleteLastPathComponent ( )
736
+ XCTAssertEqual ( schemeRelative. relativePath, " " )
737
+ }
738
+
589
739
func testURLFilePathDropsTrailingSlashes( ) throws {
590
740
var url = URL ( filePath: " /path/slashes/// " )
591
741
XCTAssertEqual ( url. path ( ) , " /path/slashes/// " )
0 commit comments