From 21754bc63e004649862a5f1110e1e2a3fe5f6981 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 17 Jul 2026 16:03:47 -0400 Subject: [PATCH 1/2] [file_selector] Switch to Swift Testing Updates the iOS and macOS Swift unit tests to use Swift Testing rather than XCTest. Part of https://github.com/flutter/flutter/issues/180787 --- .../ios/RunnerTests/FileSelectorTests.swift | 74 ++- .../macos/RunnerTests/RunnerTests.swift | 593 +++++++++--------- 2 files changed, 332 insertions(+), 335 deletions(-) diff --git a/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift b/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift index 6b6efd8dee43..0eace4cf0bab 100644 --- a/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift +++ b/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift @@ -3,7 +3,7 @@ // found in the LICENSE file. import Flutter -import XCTest +import Testing @testable import file_selector_ios @@ -25,8 +25,8 @@ final class StubViewPresenterProvider: ViewPresenterProvider { } } -class FileSelectorTests: XCTestCase { - func testPickerPresents() throws { +@Suite @MainActor struct FileSelectorTests { + @Test func pickerPresents() throws { let presenter = TestViewPresenter() let plugin = FileSelectorPlugin( viewPresenterProvider: StubViewPresenterProvider(viewPresenter: presenter)) @@ -37,58 +37,56 @@ class FileSelectorTests: XCTestCase { config: FileSelectorConfig(utis: [], allowMultiSelection: false) ) { _ in } - XCTAssertEqual(plugin.pendingCompletions.count, 1) - XCTAssertTrue(picker.delegate === plugin.pendingCompletions.first) - XCTAssertTrue(presenter.presentedController === picker) + #expect(plugin.pendingCompletions.count == 1) + #expect(picker.delegate === plugin.pendingCompletions.first) + #expect(presenter.presentedController === picker) } - func testReturnsPickedFiles() throws { + @Test func returnsPickedFiles() async throws { let plugin = FileSelectorPlugin( viewPresenterProvider: StubViewPresenterProvider(viewPresenter: TestViewPresenter())) let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import) plugin.documentPickerViewControllerOverride = picker - let completionWasCalled = expectation(description: "completion") - plugin.openFile( - config: FileSelectorConfig(utis: [], allowMultiSelection: false) - ) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths, ["/file1.txt", "/file2.txt"]) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { completionWasCalled in + plugin.openFile( + config: FileSelectorConfig(utis: [], allowMultiSelection: false) + ) { result in + switch result { + case .success(let paths): + #expect(paths == ["/file1.txt", "/file2.txt"]) + case .failure(let error): + Issue.record("\(error)") + } + completionWasCalled() } - completionWasCalled.fulfill() + plugin.pendingCompletions.first!.documentPicker( + picker, + didPickDocumentsAt: [URL(string: "file:///file1.txt")!, URL(string: "file:///file2.txt")!]) } - plugin.pendingCompletions.first!.documentPicker( - picker, - didPickDocumentsAt: [URL(string: "file:///file1.txt")!, URL(string: "file:///file2.txt")!]) - - waitForExpectations(timeout: 30.0) - XCTAssertTrue(plugin.pendingCompletions.isEmpty) + #expect(plugin.pendingCompletions.isEmpty) } - func testCancellingPickerReturnsEmptyList() throws { + @Test func cancellingPickerReturnsEmptyList() async throws { let plugin = FileSelectorPlugin( viewPresenterProvider: StubViewPresenterProvider(viewPresenter: TestViewPresenter())) let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import) plugin.documentPickerViewControllerOverride = picker - let completionWasCalled = expectation(description: "completion") - plugin.openFile( - config: FileSelectorConfig(utis: [], allowMultiSelection: false) - ) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths.count, 0) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { completionWasCalled in + plugin.openFile( + config: FileSelectorConfig(utis: [], allowMultiSelection: false) + ) { result in + switch result { + case .success(let paths): + #expect(paths.count == 0) + case .failure(let error): + Issue.record("\(error)") + } + completionWasCalled() } - completionWasCalled.fulfill() + plugin.pendingCompletions.first!.documentPickerWasCancelled(picker) } - plugin.pendingCompletions.first!.documentPickerWasCancelled(picker) - - waitForExpectations(timeout: 30.0) - XCTAssertTrue(plugin.pendingCompletions.isEmpty) + #expect(plugin.pendingCompletions.isEmpty) } } diff --git a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift index b68065387a03..03d21a1e876e 100644 --- a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift +++ b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift @@ -3,8 +3,8 @@ // found in the LICENSE file. import FlutterMacOS +import Testing import UniformTypeIdentifiers -import XCTest @testable import file_selector_macos @@ -40,9 +40,9 @@ class TestViewProvider: NSObject, ViewProvider { var window: NSWindow? = NSWindow() } -class ExampleTests: XCTestCase { +@Suite @MainActor struct ExampleTests { - func testOpenSimple() throws { + @Test func openSimple() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -51,32 +51,32 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: false, - canChooseDirectories: false, - canChooseFiles: true, - baseOptions: SavePanelOptions()) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths[0], returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths[0] == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) if let panel = panelController.openPanel { - XCTAssertTrue(panel.canChooseFiles) + #expect(panel.canChooseFiles) // For consistency across platforms, directory selection is disabled. - XCTAssertFalse(panel.canChooseDirectories) + #expect(!panel.canChooseDirectories) } } - func testOpenWithArguments() throws { + @Test func openWithArguments() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -85,36 +85,36 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: false, - canChooseDirectories: false, - canChooseFiles: true, - baseOptions: SavePanelOptions( - directoryPath: "/some/dir", - nameFieldStringValue: "a name", - prompt: "Open it!")) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths[0], returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions( + directoryPath: "/some/dir", + nameFieldStringValue: "a name", + prompt: "Open it!")) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths[0] == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) if let panel = panelController.openPanel { - XCTAssertEqual(panel.directoryURL?.path, "/some/dir") + #expect(panel.directoryURL?.path == "/some/dir") // nameFieldStringValue is not set for NSOpenPanel, only for NSSavePanel - XCTAssertNotEqual(panel.nameFieldStringValue, "a name") - XCTAssertEqual(panel.prompt, "Open it!") + #expect(panel.nameFieldStringValue != "a name") + #expect(panel.prompt == "Open it!") } } - func testOpenMultiple() throws { + @Test func openMultiple() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -123,29 +123,29 @@ class ExampleTests: XCTestCase { let returnPaths = ["/foo/bar", "/foo/baz"] panelController.openURLs = returnPaths.map({ path in URL(fileURLWithPath: path) }) - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: true, - canChooseDirectories: false, - canChooseFiles: true, - baseOptions: SavePanelOptions()) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths.count, returnPaths.count) - XCTAssertEqual(paths[0], returnPaths[0]) - XCTAssertEqual(paths[1], returnPaths[1]) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths.count == returnPaths.count) + #expect(paths[0] == returnPaths[0]) + #expect(paths[1] == returnPaths[1]) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) } - func testOpenWithFilter() throws { + @Test func openWithFilter() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -154,42 +154,42 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: true, - canChooseDirectories: false, - canChooseFiles: true, - baseOptions: SavePanelOptions( - allowedFileTypes: AllowedTypes( - extensions: ["txt", "json"], - mimeTypes: ["text/html"], - utis: ["public.text", "public.image"]))) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths[0], returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions( + allowedFileTypes: AllowedTypes( + extensions: ["txt", "json"], + mimeTypes: ["text/html"], + utis: ["public.text", "public.image"]))) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths[0] == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) if let panel = panelController.openPanel { if #available(macOS 11.0, *) { - XCTAssertTrue(panel.allowedContentTypes.contains(UTType.plainText)) - XCTAssertTrue(panel.allowedContentTypes.contains(UTType.json)) - XCTAssertTrue(panel.allowedContentTypes.contains(UTType.html)) - XCTAssertTrue(panel.allowedContentTypes.contains(UTType.image)) + #expect(panel.allowedContentTypes.contains(UTType.plainText)) + #expect(panel.allowedContentTypes.contains(UTType.json)) + #expect(panel.allowedContentTypes.contains(UTType.html)) + #expect(panel.allowedContentTypes.contains(UTType.image)) } else { // MIME type is not supported for the legacy codepath, but the rest should be set. - XCTAssertEqual(panel.allowedFileTypes, ["txt", "json", "public.text", "public.image"]) + #expect(panel.allowedFileTypes == ["txt", "json", "public.text", "public.image"]) } } } - func testFilterUnknownFileExtension() throws { + @Test func filterUnknownFileExtension() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -199,42 +199,42 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: true, - canChooseDirectories: false, - canChooseFiles: true, - baseOptions: SavePanelOptions( - allowedFileTypes: AllowedTypes( - extensions: [unknownExtension], - mimeTypes: [], - utis: []))) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths[0], returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions( + allowedFileTypes: AllowedTypes( + extensions: [unknownExtension], + mimeTypes: [], + utis: []))) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths[0] == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) if let panel = panelController.openPanel { if #available(macOS 11.0, *) { - XCTAssertEqual(panel.allowedContentTypes.count, 1) - XCTAssertEqual(panel.allowedContentTypes[0].preferredFilenameExtension, unknownExtension) + #expect(panel.allowedContentTypes.count == 1) + #expect(panel.allowedContentTypes[0].preferredFilenameExtension == unknownExtension) // If this isn't true, the dynamic type created for the extension won't work as a file // extension filter. - XCTAssertTrue(panel.allowedContentTypes[0].conforms(to: UTType.data)) + #expect(panel.allowedContentTypes[0].conforms(to: UTType.data)) } else { - XCTAssertEqual(panel.allowedFileTypes, [unknownExtension]) + #expect(panel.allowedFileTypes == [unknownExtension]) } } } - func testOpenWithFilterLegacy() throws { + @Test func openWithFilterLegacy() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -244,70 +244,70 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: true, - canChooseDirectories: false, - canChooseFiles: true, - baseOptions: SavePanelOptions( - allowedFileTypes: AllowedTypes( - extensions: ["txt", "json"], - mimeTypes: ["text/html"], - utis: ["public.text", "public.image"]))) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths[0], returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions( + allowedFileTypes: AllowedTypes( + extensions: ["txt", "json"], + mimeTypes: ["text/html"], + utis: ["public.text", "public.image"]))) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths[0] == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) if let panel = panelController.openPanel { // On the legacy path, the allowedFileTypes should be set directly. - XCTAssertEqual(panel.allowedFileTypes, ["txt", "json", "public.text", "public.image"]) + #expect(panel.allowedFileTypes == ["txt", "json", "public.text", "public.image"]) // They should also be translated to corresponding allowed content types. if #available(macOS 11.0, *) { - XCTAssertTrue(panel.allowedContentTypes.contains(UTType.plainText)) - XCTAssertTrue(panel.allowedContentTypes.contains(UTType.json)) - XCTAssertTrue(panel.allowedContentTypes.contains(UTType.image)) + #expect(panel.allowedContentTypes.contains(UTType.plainText)) + #expect(panel.allowedContentTypes.contains(UTType.json)) + #expect(panel.allowedContentTypes.contains(UTType.image)) // MIME type is not supported for the legacy codepath. - XCTAssertFalse(panel.allowedContentTypes.contains(UTType.html)) + #expect(!panel.allowedContentTypes.contains(UTType.html)) } } } - func testOpenCancel() throws { + @Test func openCancel() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: false, - canChooseDirectories: false, - canChooseFiles: true, - baseOptions: SavePanelOptions()) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths.count, 0) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: false, + canChooseFiles: true, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths.count == 0) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) } - func testSaveSimple() throws { + @Test func saveSimple() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -316,27 +316,27 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.saveURL = URL(fileURLWithPath: returnPath) - let called = XCTestExpectation() - let options = SavePanelOptions() - plugin.displaySavePanel(options: options) { result in - switch result { - case .success(let path): - XCTAssertEqual(path, returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = SavePanelOptions() + plugin.displaySavePanel(options: options) { result in + switch result { + case .success(let path): + #expect(path == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.savePanel) + #expect(panelController.savePanel != nil) if let panel = panelController.savePanel { // By default, "New Folder" button is visible for Save dialogs - XCTAssertTrue(panel.canCreateDirectories) + #expect(panel.canCreateDirectories) } } - func testSaveWithArguments() throws { + @Test func saveWithArguments() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -345,31 +345,31 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.saveURL = URL(fileURLWithPath: returnPath) - let called = XCTestExpectation() - let options = SavePanelOptions( - directoryPath: "/some/dir", - nameFieldStringValue: "a name", - prompt: "Save it!") - plugin.displaySavePanel(options: options) { result in - switch result { - case .success(let path): - XCTAssertEqual(path, returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = SavePanelOptions( + directoryPath: "/some/dir", + nameFieldStringValue: "a name", + prompt: "Save it!") + plugin.displaySavePanel(options: options) { result in + switch result { + case .success(let path): + #expect(path == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.savePanel) + #expect(panelController.savePanel != nil) if let panel = panelController.savePanel { - XCTAssertEqual(panel.directoryURL?.path, "/some/dir") - XCTAssertEqual(panel.nameFieldStringValue, "a name") - XCTAssertEqual(panel.prompt, "Save it!") + #expect(panel.directoryURL?.path == "/some/dir") + #expect(panel.nameFieldStringValue == "a name") + #expect(panel.prompt == "Save it!") } } - func testSaveNewFolderHidden() throws { + @Test func saveNewFolderHidden() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -378,49 +378,48 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.saveURL = URL(fileURLWithPath: returnPath) - let called = XCTestExpectation() - let options = SavePanelOptions(canCreateDirectories: false) - - plugin.displaySavePanel(options: options) { result in - switch result { - case .success(let path): - XCTAssertEqual(path, returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = SavePanelOptions(canCreateDirectories: false) + plugin.displaySavePanel(options: options) { result in + switch result { + case .success(let path): + #expect(path == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.savePanel) + #expect(panelController.savePanel != nil) if let panel = panelController.savePanel { - XCTAssertFalse(panel.canCreateDirectories) + #expect(!panel.canCreateDirectories) } } - func testSaveCancel() throws { + @Test func saveCancel() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) - let called = XCTestExpectation() - let options = SavePanelOptions() - plugin.displaySavePanel(options: options) { result in - switch result { - case .success(let path): - XCTAssertNil(path) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = SavePanelOptions() + plugin.displaySavePanel(options: options) { result in + switch result { + case .success(let path): + #expect(path == nil) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.savePanel) + #expect(panelController.savePanel != nil) } - func testGetDirectorySimple() throws { + @Test func getDirectorySimple() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -429,63 +428,63 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: false, - canChooseDirectories: true, - canChooseFiles: false, - baseOptions: SavePanelOptions()) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths[0], returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths[0] == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) if let panel = panelController.openPanel { - XCTAssertTrue(panel.canChooseDirectories) + #expect(panel.canChooseDirectories) // For consistency across platforms, file selection is disabled. - XCTAssertFalse(panel.canChooseFiles) + #expect(!panel.canChooseFiles) // The Dart API only allows a single directory to be returned, so users shouldn't be allowed // to select multiple. - XCTAssertFalse(panel.allowsMultipleSelection) + #expect(!panel.allowsMultipleSelection) // By default, "New Folder" button is hidden for Choose Directory dialogs. - XCTAssertFalse(panel.canCreateDirectories) + #expect(!panel.canCreateDirectories) } } - func testGetDirectoryCancel() throws { + @Test func getDirectoryCancel() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: false, - canChooseDirectories: true, - canChooseFiles: false, - baseOptions: SavePanelOptions()) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths.count, 0) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths.count == 0) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) } - func testGetDirectoriesMultiple() throws { + @Test func getDirectoriesMultiple() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -494,61 +493,61 @@ class ExampleTests: XCTestCase { let returnPaths = ["/foo/bar", "/foo/test"] panelController.openURLs = returnPaths.map({ path in URL(fileURLWithPath: path) }) - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: true, - canChooseDirectories: true, - canChooseFiles: false, - baseOptions: SavePanelOptions()) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths, returnPaths) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths == returnPaths) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) if let panel = panelController.openPanel { - XCTAssertTrue(panel.canChooseDirectories) + #expect(panel.canChooseDirectories) // For consistency across platforms, file selection is disabled. - XCTAssertFalse(panel.canChooseFiles) - XCTAssertTrue(panel.allowsMultipleSelection) + #expect(!panel.canChooseFiles) + #expect(panel.allowsMultipleSelection) // By default, "New Folder" button is hidden for Choose Directory dialogs. - XCTAssertFalse(panel.canCreateDirectories) + #expect(!panel.canCreateDirectories) } } - func testGetDirectoryMultipleCancel() throws { + @Test func getDirectoryMultipleCancel() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: true, - canChooseDirectories: true, - canChooseFiles: false, - baseOptions: SavePanelOptions()) - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths.count, 0) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths.count == 0) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) } - func testGetDirectoryNewFolderVisible() throws { + @Test func getDirectoryNewFolderVisible() async throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), @@ -557,27 +556,27 @@ class ExampleTests: XCTestCase { let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] - let called = XCTestExpectation() - let options = OpenPanelOptions( - allowsMultipleSelection: false, - canChooseDirectories: true, - canChooseFiles: false, - baseOptions: SavePanelOptions(canCreateDirectories: true)) - - plugin.displayOpenPanel(options: options) { result in - switch result { - case .success(let paths): - XCTAssertEqual(paths[0], returnPath) - case .failure(let error): - XCTFail("\(error)") + await confirmation("completion") { called in + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions(canCreateDirectories: true)) + + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + #expect(paths[0] == returnPath) + case .failure(let error): + Issue.record("\(error)") + } + called() } - called.fulfill() } - wait(for: [called]) - XCTAssertNotNil(panelController.openPanel) + #expect(panelController.openPanel != nil) if let panel = panelController.openPanel { - XCTAssertTrue(panel.canCreateDirectories) + #expect(panel.canCreateDirectories) } } } From e4c98fc554b02c51813cca864241da4d4f41f8d8 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 17 Jul 2026 16:17:47 -0400 Subject: [PATCH 2/2] GCA review --- .../ios/RunnerTests/FileSelectorTests.swift | 10 +- .../macos/RunnerTests/RunnerTests.swift | 180 ++++++++---------- 2 files changed, 84 insertions(+), 106 deletions(-) diff --git a/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift b/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift index 0eace4cf0bab..78019a5d22ae 100644 --- a/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift +++ b/packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.swift @@ -48,7 +48,7 @@ final class StubViewPresenterProvider: ViewPresenterProvider { let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import) plugin.documentPickerViewControllerOverride = picker - await confirmation("completion") { completionWasCalled in + try await confirmation("completion") { completionWasCalled in plugin.openFile( config: FileSelectorConfig(utis: [], allowMultiSelection: false) ) { result in @@ -60,7 +60,8 @@ final class StubViewPresenterProvider: ViewPresenterProvider { } completionWasCalled() } - plugin.pendingCompletions.first!.documentPicker( + let pendingCompletion = try #require(plugin.pendingCompletions.first) + pendingCompletion.documentPicker( picker, didPickDocumentsAt: [URL(string: "file:///file1.txt")!, URL(string: "file:///file2.txt")!]) } @@ -73,7 +74,7 @@ final class StubViewPresenterProvider: ViewPresenterProvider { let picker = UIDocumentPickerViewController(documentTypes: [], in: UIDocumentPickerMode.import) plugin.documentPickerViewControllerOverride = picker - await confirmation("completion") { completionWasCalled in + try await confirmation("completion") { completionWasCalled in plugin.openFile( config: FileSelectorConfig(utis: [], allowMultiSelection: false) ) { result in @@ -85,7 +86,8 @@ final class StubViewPresenterProvider: ViewPresenterProvider { } completionWasCalled() } - plugin.pendingCompletions.first!.documentPickerWasCancelled(picker) + let pendingCompletion = try #require(plugin.pendingCompletions.first) + pendingCompletion.documentPickerWasCancelled(picker) } #expect(plugin.pendingCompletions.isEmpty) } diff --git a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift index 03d21a1e876e..d8fd85fe5577 100644 --- a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift +++ b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift @@ -60,7 +60,7 @@ class TestViewProvider: NSObject, ViewProvider { plugin.displayOpenPanel(options: options) { result in switch result { case .success(let paths): - #expect(paths[0] == returnPath) + #expect(paths == [returnPath]) case .failure(let error): Issue.record("\(error)") } @@ -68,12 +68,10 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) - if let panel = panelController.openPanel { - #expect(panel.canChooseFiles) - // For consistency across platforms, directory selection is disabled. - #expect(!panel.canChooseDirectories) - } + let panel = try #require(panelController.openPanel) + #expect(panel.canChooseFiles) + // For consistency across platforms, directory selection is disabled. + #expect(!panel.canChooseDirectories) } @Test func openWithArguments() async throws { @@ -97,7 +95,7 @@ class TestViewProvider: NSObject, ViewProvider { plugin.displayOpenPanel(options: options) { result in switch result { case .success(let paths): - #expect(paths[0] == returnPath) + #expect(paths == [returnPath]) case .failure(let error): Issue.record("\(error)") } @@ -105,13 +103,11 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) - if let panel = panelController.openPanel { - #expect(panel.directoryURL?.path == "/some/dir") - // nameFieldStringValue is not set for NSOpenPanel, only for NSSavePanel - #expect(panel.nameFieldStringValue != "a name") - #expect(panel.prompt == "Open it!") - } + let panel = try #require(panelController.openPanel) + #expect(panel.directoryURL?.path == "/some/dir") + // nameFieldStringValue is not set for NSOpenPanel, only for NSSavePanel + #expect(panel.nameFieldStringValue != "a name") + #expect(panel.prompt == "Open it!") } @Test func openMultiple() async throws { @@ -132,9 +128,7 @@ class TestViewProvider: NSObject, ViewProvider { plugin.displayOpenPanel(options: options) { result in switch result { case .success(let paths): - #expect(paths.count == returnPaths.count) - #expect(paths[0] == returnPaths[0]) - #expect(paths[1] == returnPaths[1]) + #expect(paths == returnPaths) case .failure(let error): Issue.record("\(error)") } @@ -142,7 +136,7 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) + _ = try #require(panelController.openPanel) } @Test func openWithFilter() async throws { @@ -167,7 +161,7 @@ class TestViewProvider: NSObject, ViewProvider { plugin.displayOpenPanel(options: options) { result in switch result { case .success(let paths): - #expect(paths[0] == returnPath) + #expect(paths == [returnPath]) case .failure(let error): Issue.record("\(error)") } @@ -175,17 +169,15 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) - if let panel = panelController.openPanel { - if #available(macOS 11.0, *) { - #expect(panel.allowedContentTypes.contains(UTType.plainText)) - #expect(panel.allowedContentTypes.contains(UTType.json)) - #expect(panel.allowedContentTypes.contains(UTType.html)) - #expect(panel.allowedContentTypes.contains(UTType.image)) - } else { - // MIME type is not supported for the legacy codepath, but the rest should be set. - #expect(panel.allowedFileTypes == ["txt", "json", "public.text", "public.image"]) - } + let panel = try #require(panelController.openPanel) + if #available(macOS 11.0, *) { + #expect(panel.allowedContentTypes.contains(UTType.plainText)) + #expect(panel.allowedContentTypes.contains(UTType.json)) + #expect(panel.allowedContentTypes.contains(UTType.html)) + #expect(panel.allowedContentTypes.contains(UTType.image)) + } else { + // MIME type is not supported for the legacy codepath, but the rest should be set. + #expect(panel.allowedFileTypes == ["txt", "json", "public.text", "public.image"]) } } @@ -212,7 +204,7 @@ class TestViewProvider: NSObject, ViewProvider { plugin.displayOpenPanel(options: options) { result in switch result { case .success(let paths): - #expect(paths[0] == returnPath) + #expect(paths == [returnPath]) case .failure(let error): Issue.record("\(error)") } @@ -220,17 +212,15 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) - if let panel = panelController.openPanel { - if #available(macOS 11.0, *) { - #expect(panel.allowedContentTypes.count == 1) - #expect(panel.allowedContentTypes[0].preferredFilenameExtension == unknownExtension) - // If this isn't true, the dynamic type created for the extension won't work as a file - // extension filter. - #expect(panel.allowedContentTypes[0].conforms(to: UTType.data)) - } else { - #expect(panel.allowedFileTypes == [unknownExtension]) - } + let panel = try #require(panelController.openPanel) + if #available(macOS 11.0, *) { + #expect(panel.allowedContentTypes.count == 1) + #expect(panel.allowedContentTypes[0].preferredFilenameExtension == unknownExtension) + // If this isn't true, the dynamic type created for the extension won't work as a file + // extension filter. + #expect(panel.allowedContentTypes[0].conforms(to: UTType.data)) + } else { + #expect(panel.allowedFileTypes == [unknownExtension]) } } @@ -257,7 +247,7 @@ class TestViewProvider: NSObject, ViewProvider { plugin.displayOpenPanel(options: options) { result in switch result { case .success(let paths): - #expect(paths[0] == returnPath) + #expect(paths == [returnPath]) case .failure(let error): Issue.record("\(error)") } @@ -265,19 +255,17 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) - if let panel = panelController.openPanel { - // On the legacy path, the allowedFileTypes should be set directly. - #expect(panel.allowedFileTypes == ["txt", "json", "public.text", "public.image"]) - - // They should also be translated to corresponding allowed content types. - if #available(macOS 11.0, *) { - #expect(panel.allowedContentTypes.contains(UTType.plainText)) - #expect(panel.allowedContentTypes.contains(UTType.json)) - #expect(panel.allowedContentTypes.contains(UTType.image)) - // MIME type is not supported for the legacy codepath. - #expect(!panel.allowedContentTypes.contains(UTType.html)) - } + let panel = try #require(panelController.openPanel) + // On the legacy path, the allowedFileTypes should be set directly. + #expect(panel.allowedFileTypes == ["txt", "json", "public.text", "public.image"]) + + // They should also be translated to corresponding allowed content types. + if #available(macOS 11.0, *) { + #expect(panel.allowedContentTypes.contains(UTType.plainText)) + #expect(panel.allowedContentTypes.contains(UTType.json)) + #expect(panel.allowedContentTypes.contains(UTType.image)) + // MIME type is not supported for the legacy codepath. + #expect(!panel.allowedContentTypes.contains(UTType.html)) } } @@ -304,7 +292,7 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) + _ = try #require(panelController.openPanel) } @Test func saveSimple() async throws { @@ -329,11 +317,9 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.savePanel != nil) - if let panel = panelController.savePanel { - // By default, "New Folder" button is visible for Save dialogs - #expect(panel.canCreateDirectories) - } + let panel = try #require(panelController.savePanel) + // By default, "New Folder" button is visible for Save dialogs + #expect(panel.canCreateDirectories) } @Test func saveWithArguments() async throws { @@ -361,12 +347,10 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.savePanel != nil) - if let panel = panelController.savePanel { - #expect(panel.directoryURL?.path == "/some/dir") - #expect(panel.nameFieldStringValue == "a name") - #expect(panel.prompt == "Save it!") - } + let panel = try #require(panelController.savePanel) + #expect(panel.directoryURL?.path == "/some/dir") + #expect(panel.nameFieldStringValue == "a name") + #expect(panel.prompt == "Save it!") } @Test func saveNewFolderHidden() async throws { @@ -391,10 +375,8 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.savePanel != nil) - if let panel = panelController.savePanel { - #expect(!panel.canCreateDirectories) - } + let panel = try #require(panelController.savePanel) + #expect(!panel.canCreateDirectories) } @Test func saveCancel() async throws { @@ -416,7 +398,7 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.savePanel != nil) + _ = try #require(panelController.savePanel) } @Test func getDirectorySimple() async throws { @@ -437,7 +419,7 @@ class TestViewProvider: NSObject, ViewProvider { plugin.displayOpenPanel(options: options) { result in switch result { case .success(let paths): - #expect(paths[0] == returnPath) + #expect(paths == [returnPath]) case .failure(let error): Issue.record("\(error)") } @@ -445,17 +427,15 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) - if let panel = panelController.openPanel { - #expect(panel.canChooseDirectories) - // For consistency across platforms, file selection is disabled. - #expect(!panel.canChooseFiles) - // The Dart API only allows a single directory to be returned, so users shouldn't be allowed - // to select multiple. - #expect(!panel.allowsMultipleSelection) - // By default, "New Folder" button is hidden for Choose Directory dialogs. - #expect(!panel.canCreateDirectories) - } + let panel = try #require(panelController.openPanel) + #expect(panel.canChooseDirectories) + // For consistency across platforms, file selection is disabled. + #expect(!panel.canChooseFiles) + // The Dart API only allows a single directory to be returned, so users shouldn't be allowed + // to select multiple. + #expect(!panel.allowsMultipleSelection) + // By default, "New Folder" button is hidden for Choose Directory dialogs. + #expect(!panel.canCreateDirectories) } @Test func getDirectoryCancel() async throws { @@ -481,7 +461,7 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) + _ = try #require(panelController.openPanel) } @Test func getDirectoriesMultiple() async throws { @@ -510,15 +490,13 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) - if let panel = panelController.openPanel { - #expect(panel.canChooseDirectories) - // For consistency across platforms, file selection is disabled. - #expect(!panel.canChooseFiles) - #expect(panel.allowsMultipleSelection) - // By default, "New Folder" button is hidden for Choose Directory dialogs. - #expect(!panel.canCreateDirectories) - } + let panel = try #require(panelController.openPanel) + #expect(panel.canChooseDirectories) + // For consistency across platforms, file selection is disabled. + #expect(!panel.canChooseFiles) + #expect(panel.allowsMultipleSelection) + // By default, "New Folder" button is hidden for Choose Directory dialogs. + #expect(!panel.canCreateDirectories) } @Test func getDirectoryMultipleCancel() async throws { @@ -544,7 +522,7 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) + _ = try #require(panelController.openPanel) } @Test func getDirectoryNewFolderVisible() async throws { @@ -566,7 +544,7 @@ class TestViewProvider: NSObject, ViewProvider { plugin.displayOpenPanel(options: options) { result in switch result { case .success(let paths): - #expect(paths[0] == returnPath) + #expect(paths == [returnPath]) case .failure(let error): Issue.record("\(error)") } @@ -574,9 +552,7 @@ class TestViewProvider: NSObject, ViewProvider { } } - #expect(panelController.openPanel != nil) - if let panel = panelController.openPanel { - #expect(panel.canCreateDirectories) - } + let panel = try #require(panelController.openPanel) + #expect(panel.canCreateDirectories) } }