Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/cupertino.dart';
import 'package:win32/win32.dart';

import 'file_selector_dart/dialog_mode.dart';
import 'file_selector_dart/dialog_wrapper.dart';
import 'file_selector_dart/dialog_wrapper_factory.dart';
import 'file_selector_dart/selection_options.dart';

/// File dialog handling for Open and Save operations.
class FileSelectorApi {
/// Creates a new instance of [FileSelectorApi].
/// Allows Dependency Injection of a [DialogWrapperFactory] to handle dialog creation.
FileSelectorApi(this._dialogWrapperFactory)
: _foregroundWindow = GetForegroundWindow();

/// Creates a fake instance of [FileSelectorApi] for testing purpose where the [_foregroundWindow] handle is set
/// from the outside.
@visibleForTesting
FileSelectorApi.useFakeForegroundWindow(
this._dialogWrapperFactory, this._foregroundWindow);

final DialogWrapperFactory _dialogWrapperFactory;

final int _foregroundWindow;

/// Displays a dialog window to open one or more files.
List<String?> showOpenDialog(
SelectionOptions options,
String? initialDirectory,
String? confirmButtonText,
) =>
_showDialog(_foregroundWindow, DialogMode.Open, options, initialDirectory,
null, confirmButtonText);

/// Displays a dialog used to save a file.
List<String?> showSaveDialog(
SelectionOptions options,
String? initialDirectory,
String? suggestedName,
String? confirmButtonText,
) =>
_showDialog(_foregroundWindow, DialogMode.Save, options, initialDirectory,
suggestedName, confirmButtonText);

List<String?> _showDialog(
int parentWindow,
DialogMode mode,
SelectionOptions options,
String? initialDirectory,
String? suggestedName,
String? confirmLabel) {
final DialogWrapper dialogWrapper =
_dialogWrapperFactory.createInstance(mode);
if (!SUCCEEDED(dialogWrapper.lastResult)) {
throw WindowsException(E_FAIL);
}
int dialogOptions = 0;
if (options.selectFolders) {
dialogOptions |= FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS;
}
if (options.allowMultiple) {
dialogOptions |= FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT;
}
if (dialogOptions != 0) {
dialogWrapper.addOptions(dialogOptions);
}

if (initialDirectory != null) {
dialogWrapper.setFolder(initialDirectory);
}
if (suggestedName != null) {
dialogWrapper.setFileName(suggestedName);
}
if (confirmLabel != null) {
dialogWrapper.setOkButtonLabel(confirmLabel);
}

if (options.allowedTypes.isNotEmpty) {
dialogWrapper.setFileTypeFilters(options.allowedTypes);
}

final List<String?>? files = dialogWrapper.show(parentWindow);
if (files != null) {
return files;
}
throw WindowsException(E_FAIL);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dialog_mode.dart';
import 'dialog_wrapper.dart';
import 'ifile_dialog_controller_factory.dart';
import 'ifile_dialog_factory.dart';

/// Implementation of DialogWrapperFactory that provides [DialogWrapper] instances.
class DialogWrapperFactory {
/// Creates a [DialogWrapperFactory] that makes use of [IFileDialogControllerFactory] and [IFileDialogFactory]
/// to create [DialogWrapper] instances.
DialogWrapperFactory(
this._fileDialogControllerFactory, this._fileDialogFactory);

final IFileDialogControllerFactory _fileDialogControllerFactory;

final IFileDialogFactory _fileDialogFactory;

/// Creates a [DialogWrapper] based on [dialogMode].
DialogWrapper createInstance(DialogMode dialogMode) {
return DialogWrapper(
_fileDialogControllerFactory, _fileDialogFactory, dialogMode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';

/// Options for Dialog window
class SelectionOptions {
/// Creates a new [SelectionOptions] instance with the specified values.
/// It defaults [allowMultiple] to false, [selectFolders] to false and no [allowedTypes]
SelectionOptions({
this.allowMultiple = false,
this.selectFolders = false,
this.allowedTypes = const <XTypeGroup>[],
});

/// Indicates whether the user is able to select multiple items at the same time or not.
bool allowMultiple;

/// Indicates whether the user is able to select folders or not.
bool selectFolders;

/// A list of file types that can be selected.
List<XTypeGroup> allowedTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
import 'package:file_selector_windows/src/file_selector_api.dart';
import 'package:file_selector_windows/src/file_selector_dart/dialog_mode.dart';
import 'package:file_selector_windows/src/file_selector_dart/dialog_wrapper.dart';
import 'package:file_selector_windows/src/file_selector_dart/dialog_wrapper_factory.dart';
import 'package:file_selector_windows/src/file_selector_dart/selection_options.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:win32/win32.dart';

import 'file_selector_api_test.mocks.dart';

@GenerateMocks(<Type>[DialogWrapperFactory, DialogWrapper])
void main() {
const int parentWindow = 1;
final MockDialogWrapperFactory mockDialogWrapperFactory =
MockDialogWrapperFactory();
late MockDialogWrapper mockDialogWrapper;
final FileSelectorApi fileSelectorApi =
FileSelectorApi.useFakeForegroundWindow(
mockDialogWrapperFactory, parentWindow);

const List<String> expectedFileList = <String>['fileA', 'fileB'];
final SelectionOptions emptyOptions = SelectionOptions();

setUp(() {
mockDialogWrapper = MockDialogWrapper();
when(mockDialogWrapper.lastResult).thenReturn(S_OK);
when(mockDialogWrapperFactory.createInstance(DialogMode.Save))
.thenReturn(mockDialogWrapper);
when(mockDialogWrapperFactory.createInstance(DialogMode.Open))
.thenReturn(mockDialogWrapper);
when(mockDialogWrapper.show(parentWindow)).thenReturn(expectedFileList);
});

tearDown(() {
reset(mockDialogWrapper);
reset(mockDialogWrapperFactory);
});

test('FileSelectorApi should not be null', () {
expect(fileSelectorApi, isNotNull);
});

group('showSaveDialog', () {
test('should call setFileName if a suggestedName is provided', () {
// Arrange
const String suggestedName = 'suggestedName';

// Act
fileSelectorApi.showSaveDialog(emptyOptions, null, suggestedName, null);

// Assert
verify(mockDialogWrapper.setFileName(suggestedName)).called(1);
});

test('should create a DialogWrapper with DialogMode Save', () {
// Act
fileSelectorApi.showSaveDialog(emptyOptions, null, null, null);

// Assert
verify(mockDialogWrapperFactory.createInstance(DialogMode.Save))
.called(1);
});
});
group('showOpenDialog', () {
test('should create a DialogWrapper with DialogMode Open', () {
// Act
fileSelectorApi.showOpenDialog(emptyOptions, null, null);

// Assert
verify(mockDialogWrapperFactory.createInstance(DialogMode.Open))
.called(1);
});
});
group('Common behavior', () {
test('should throw a WindowsException is DialogWrapper can not be created',
() {
// Arrange
when(mockDialogWrapperFactory.createInstance(DialogMode.Open))
.thenReturn(mockDialogWrapper);
when(mockDialogWrapper.lastResult).thenReturn(E_FAIL);

// Act - Assert
expect(() => fileSelectorApi.showOpenDialog(emptyOptions, null, null),
throwsA(const TypeMatcher<WindowsException>()));
});

test('should not call AddOptions if no options are configured', () {
// Act
fileSelectorApi.showOpenDialog(emptyOptions, null, null);

// Assert
verifyNever(mockDialogWrapper.addOptions(any));
});
test('should call AddOptions with FOS_PICKFOLDERS configured', () {
// Arrange
final SelectionOptions options = SelectionOptions(selectFolders: true);

// Act
fileSelectorApi.showOpenDialog(options, null, null);

// Assert
verify(mockDialogWrapper
.addOptions(FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS))
.called(1);
});

test('should call AddOptions with FOS_ALLOWMULTISELECT configured', () {
// Arrange
final SelectionOptions options = SelectionOptions(allowMultiple: true);

// Act
fileSelectorApi.showOpenDialog(options, null, null);

// Assert
verify(mockDialogWrapper
.addOptions(FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT))
.called(1);
});

test('should call setFolder if an initialDirectory is provided', () {
// Arrange
const String initialDirectory = 'path/to/dir';

// Act
fileSelectorApi.showOpenDialog(emptyOptions, initialDirectory, null);

// Assert
verify(mockDialogWrapper.setFolder(initialDirectory)).called(1);
});

test('should call setOkButtonLabel if confirmButtonText is provided', () {
// Arrange
const String confirmButtonText = 'OK';

// Act
fileSelectorApi.showOpenDialog(emptyOptions, null, confirmButtonText);

// Assert
verify(mockDialogWrapper.setOkButtonLabel(confirmButtonText)).called(1);
});

test('should call setFileTypeFilters with provided allowedTypes',
() {
// Arrange
final SelectionOptions options =
SelectionOptions(allowedTypes: <XTypeGroup>[
const XTypeGroup(extensions: <String>['jpg', 'png'], label: 'Images'),
const XTypeGroup(extensions: <String>['txt', 'json'], label: 'Text'),
]);

// Act
fileSelectorApi.showOpenDialog(options, null, null);

// Assert
verify(mockDialogWrapper.setFileTypeFilters(options.allowedTypes))
.called(1);
});

test('should return the file list on success', () {
// Act
final List<String?> result =
fileSelectorApi.showOpenDialog(emptyOptions, null, null);

// Assert
expect(result.length, expectedFileList.length);
expect(result, expectedFileList);
});

test('should throw an exception if file list is null', () {
// Arrange
when(mockDialogWrapper.show(parentWindow)).thenReturn(null);

// Act - Assert
expect(() => fileSelectorApi.showOpenDialog(emptyOptions, null, null),
throwsA(const TypeMatcher<WindowsException>()));
});
});
}
Loading