@@ -586,6 +586,156 @@ final class URLTests : XCTestCase {
586586 XCTAssertEqual ( appended. relativePath, " relative/with:slash " )
587587 }
588588
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+
589739 func testURLFilePathDropsTrailingSlashes( ) throws {
590740 var url = URL ( filePath: " /path/slashes/// " )
591741 XCTAssertEqual ( url. path ( ) , " /path/slashes/// " )
0 commit comments